1

I'm refactoring a common web app (not spa but traditional server side html rendering). The design/code organization follows more or the less the Clean architecture concepts (ie: controller calls a use case layer then pass the UC response to a presentation layer). The technical environment is PHP/Symfony 6.

about vocabulary: in my environment uses cases are called services and presenter are called view but they covers the same responsibilities than their clean arch. equivalent.

Basically my controllers actions looks like this one:


    /**
     * @throws InternalErrorException
     */
    #[Route('/', name: 'item.front.new')]
    public function newItem(): Response
    {
        $response = $this->service->newItem();
        return $this->view->newItem($response);
    }

A service method is called then the service response object is passed to a View object (really its the presenter object which is called view here, the real view rendering is handled with Twig templates).

Based on this, i'm struggling to find were the handling of HTTP request inputs must be placed, in case of a form data submission for example. I've cases where i need to make some formating, data normalization etc. of the HTTP request inputs and build a Request object that the service expect as input, in other words: DTO stuff from HTTP Request to Service Request.

As of now i place this logic in the controller directly but something is saying to me this logic should not belong to the controller but in the presenter layer..

Here is what i do today (example of handling input from form submission before calling the service):


    /**
     * @throws Exception
     */
    #[Route('/item-update', name: 'item.front.update', methods: 'POST')]
    public function updateItem(Request $request): Response
    {
        $values = $request->request->all('item_form');
        if (empty($values)) {
            throw new BadRequestException('item_form values not found in request');
        }

        $updateItemRequest = new UpdateItemRequest();
        $updateItemRequest->title = $values['title'] ?? null;
        $updateItemRequest->qty = intval($values['qty']);
        [ ... more mapping stuff ... ]

        $response = $this->service->updateItem($updateItemRequest);
        return $this->view->updateItem($response);
    }

From what i understand of the clean arch., I/Os from the "outside" should be handled by the presentation layer.. but because the presenter has to be called after the service it has no chances to take care of this responsibility.

Any advices ?

3
  • What problem do you have with the current code? I don't see any major problems, unless you find this hard to change or maintain. Any indirection will only make reading the code harder, so unless you have a specific need, then I don't think you should follow any kind of "Clean" architecture advice.
    – ahoka
    Dec 27, 2023 at 13:04
  • 1
    "Any indirection will only make reading the code harder" - I disagree :) Why not write the entire program in main then? A huge amount of developer time is wasted in the name of such "readability". Once you and your team are on the same page conceptually, use the tools that the language gives you to your advantage, and write at a level suitable for the audience - that is, express the code in terms that make sense to the people who are working on the project. This may make the code harder to understand for the general developer population out there, but that's not your target audience anyway. Dec 27, 2023 at 16:13
  • @ahoka, i don't have code problem as it works but its more of a pure design question/issue and would like to have some feedbacks and reasoning about the subject. I think "Software Engineering" is a good place for these kind of question.. if i had a specific code/framework issue i would have priviledged Stack Overflow. Again i agree with Filip Milovanović in his comment.. my project is kind of old school server side rendered web app, but is pretty big, its important to layer correctly the code. Dec 27, 2023 at 19:33

2 Answers 2

2

From what i understand of the clean arch., I/Os from the "outside" should be handled by the presentation layer.. but because the presenter has to be called after the service it has no chances to take care of this responsibility.

Any advices ?

Well, nobody said that the presentation layer must consist of a single class, or that you can only call a single method from a class in the presentation layer.

Keep the code at the same level of abstraction. If you want to handle inputs in the presentation layer, then transform this

    /**
     * @throws Exception
     */
    #[Route('/item-update', name: 'item.front.update', methods: 'POST')]
    public function updateItem(Request $request): Response
    {
        $values = $request->request->all('item_form');
        if (empty($values)) {
            throw new BadRequestException('item_form values not found in request');
        }

        $updateItemRequest = new UpdateItemRequest();
        $updateItemRequest->title = $values['title'] ?? null;
        $updateItemRequest->qty = intval($values['qty']);
        [ ... more mapping stuff ... ]

        $response = $this->service->updateItem($updateItemRequest);
        return $this->view->updateItem($response);
    }

into this

    /**
     * @throws Exception
     */
    #[Route('/item-update', name: 'item.front.update', methods: 'POST')]
    public function updateItem(Request $request): Response
    {
        $updateItemParams = $this->whatever->interpretUpdateItemRequest($request);
        $response = $this->service->updateItem($updateItemParams);
        return $this->view->updateItem($response);
    }

or some such thing, where whatever in the first line is your class in the presentation layer (might be view, might be something else).

Note that it now reads almost like a list of bullet points of tasks that are at the same "level":

  • interpret the UpdateItem request to obtain update parameters
  • ask the service to update the item
  • pass the response to the view

In the original code, the last two bullet points were preceded by a whole bunch of fine-grained operations. The updateItem endpoint doesn't need to know details like where to get the title from, or what to do if it is null, or that qty needs to be converted from a string, etc.

What I've described here is basically the "Extract Method" refactoring (you take a block of related code and extract it into a new method, turning the block into a named higher-level operation with well-defined inputs and outputs), followed by moving that method to a (new or existing) class.

You can also wrap this into a try-catch block, or alternatively return a more complex type from interpretUpdateItemRequest that indicates some sort of a status, then make a decision based on that.

1
  • Thank you for taking time to make this great answer. You're right, like i said in my question i'm not really comfortable with all this input handling in the controller directly. Based on your reasoning i think i'll relocate this input dto/mapping stuff in dedicated methods on the view class, something like $this->view->handle<action-name>Input(HttpRequest): ServiceRequest. Seems clean as it is on the same Presentation layer and does not pollute the controller. Dec 27, 2023 at 19:27
1

Yes, If you had a more complex presentation layer like in a SPA, you could imagine that it would probably consume a client library which would handle the serialisation to HTTP and back when making calls to the backend.

The client and request/response models would be on an 'inner' circle and thus fine for the UI to reference and consume.

However, With your 'old skool' render html on server style its hard to cleanly separate out this serialisation code. Its built into the frameworks you are using, which expose the HTTP objects in the controllers.

I think its fine to put this logic in the controllers. I would try to minimise these request DTOs as much as possible and just send form or query data, which can be passed as primitives to your service layer. It cuts down on a lot of single use objects.

3
  • Yeah its WEB 2.0 old school server style style haha. By the way i like the reasoning of @Filip Milovanović in its response that things has to go in their layer. Thanks for taking time to respond. really appreciated Dec 27, 2023 at 19:23
  • Filip makes some good points, but i wonder if his solution follows clean architecture. If you move that mapping out of the controller completely it will need to reference stuff like the http object and the response object. You might end up having to add interfaces to follow the rules to the letter
    – Ewan
    Dec 28, 2023 at 10:46
  • hmm, at least for the http side, the controller can pass "raw" request parameters as a key<->values, something like this: $this->view->handle<action-name>Input($httpRequest->request->all()). In this case the view method as no knowledge about http request stuff and doesn't require it. Dec 28, 2023 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.