Posted

in

Request Resources: the missing Laravel component?

Laravel - Request transformer missing

Recently I posted this image on Twitter, made from scratch. It has always been an issue to me, and I will try to explain why and the potential solutions.

The conceptual issue

The typical flow of a web app consists in receiving requests and sending back the right data. In Laravel, incoming requests are passed first to a “FormRequest” created with:

php artisan make:request

The role of this component is to authorize/validate the incoming data. Then the Controller take cares of the validated data, by creating models or by doing whatever is needed.

The response from the Controller to the frontend involves usually some database queries. To format correctly the response, Laravel implemented an entity called “Eloquent Resources”, which acts as a transformation layer between models and the JSON response sent back to the frontend.

Eloquent Resources are very useful as it gives the right flexibility to transform the data from the model before sending it back.

However, it seems we don’t have any native component to do this for incoming requests.

In Laravel, both flows (incoming and outgoing flows) are asymmetrical in their concept.

The consequence of the lack of “Request Transformers” is that usually, people do the data transformation of incoming data either in their controller or in their “Form Request”.

I think it’s not an optimal approach, and I believe we need a formal structure to do this.

The workarounds

We have many choices here to tackle this issue. We’ll review some of them.

Solution 1: Transform Request data in the controller

This is ugly. It makes your Controller quickly fat and unreadable. At a conceptual level, this is not the role of the controller to do this. I see more the controller as the coordinator for incoming requests, while we are looking for a data layer.

Solution 2: Using Form Request

We could use Form Request to “format” incoming data.

This is suggested in this post.

There are two issues with this code:

  • It’s ugly as we are dealing with untyped data stored under the form of an array.
  • The use of the prepareForValidation method opens the door to bugs and vulnerabilities. It’s always a good security measure to reduce the amount of code executed before the validation of incoming data.

There is also the after() method, which is executed after the validation, but it seems not well-fitted to formalize and transform the data.

Solution 3: Using Jobs, actions or services

Here we start to explore solutions that are not “native” to Laravel (except Jobs). Actions or Services are not Laravel components (eg you don’t have an artisan command to create them for instance).

All these conceptual components – in their name and common usage – do not represent a data layer, but are more part of a workflow. An Action is doing something. A Service might be seen as a collection of actions. And jobs are also performing something.

So, here again, it seems not appropriate.

Solution 4: Using Repositories

The repository design pattern might be used for our problem, but the interpretation and the way repositories are used is far to be a standard. Repositories are usually bi-directional: they act as a layer from the model to the response sent back, but also as a layer between the request to the models/database.

Furthermore, they usually look like more a intermediate controller rather than a data layer.

Therefore, this solution is dismissed.

Solution 5 : Using DTO

DTOs – or Data Transfer Objects – are a good concept and the name seems appropriate to our need to transform request data to a structured data layer.

This short article explains the concept quickly and nicely.

Example of a Laravel DTO

Honestly, this code is good. We have a clear dissociation between the data layer and the “actions” made on the data (provided by the Service class).

By opening the DTO you know immediately:

  • What fields are expected from your incoming request
  • How they are modified (if needed)

Pushing the DTO workaround further…

We could even push the concept further by linking more strongly the Request to it’s data layer.

WoW! This is a beautiful code, and everything seems to be to its right place!

  • The controller acts as a coordinator
  • The Service provides features
  • The DTO is the exact reflect of the validated Request data, and can transform them as needed.

This is almost a perfect solution.

So, for each complex incoming Request, we would have:

  • A FormRequest to authorize and validate the data
  • A DTO taking as input the validated data and transforming them if needed
  • A Service to implement proper actions on this DTO, like saving, updating, etc.

Sounds good.

Wait… There’s a Spatie module for that

Spatie has released a module to implement DTOs: Laravel Data.

I must admit that I tried it quickly, then delete it. It’s a though module, difficult to understand at a first sight. Then, I took a long time to read again about DTOs to understand the module.

With the Laravel Data module, you can easily map requests AND models to their underlying DTO.

Let’s say your DTO is as follows:

You can easily instantiate your DTO from a model, like this:

For Requests, this is the same:

This is ultra-powerful, and I have many other things to explore in this module.

But based on my first assessment, I feel there is something wrong with it.

The issue with DTOs

I like the concept of DTOs. It answers to our question: DTO is a perfect fit to be the formal representation of a Request. Furthermore, it solves one additional problem that Laravel has natively: where should I put a model that is not related to the database?

The Laravel association between models and the database is huge. A model is the representation of a table in the database, period. This is not exactly what the M means in the MVC architecture, a Model is any kind of data structure (not storage… structure!)

So, if I have a data structure to implement, I am doomed: I will not put in my Models directory because I want to keep a clear view of the representation of the database.

DTOs solve perfectly this issue. However, it leads to an increased complexity because of their flexibility.

DTOs are not “flow-opinionated”

Basically with the Spatie Laravel Data module, you can create any data representation you want, and construct it with any kind of object: a model, a request, strings, JSON objects, arrays…

Soon, in big applications with a lot of DTOs, we will probably see some anomalies due to this specificity: how to organize them as they become the center of the application?

Indeed, if we have the need to use a DTO, it means that we need a data structure either far from a model, or far from a request structure.

If a DTO fits perfectly the Model AND the Request, there is no need to use a DTO: just create the model from the request, and that’s it. The opposite is true: to send back the right data to the frontend, just send back the model. Don’t bother.

It seems to me that the DTO has a “center of gravity” in most use-cases, either the Model or the Request. What would be the point to build a data structure totally decorellated from the incoming data *AND* from the outgoing data?

This is why I would not be surprised to see “Model DTO” and “Request DTO” in the Data folder of applications using DTOs, or similar conceptual “anomalies” that would enter into collision with Laravel’s structure itself.

Another way to explain this issue is to ask the following question: how do I know if my DTO is a representation of data sent to the frontend or data going to the database?

This can make the code very hard to understand, because the root of the problem is that a data layer should be clearly linked to a flow: You don’t encapsulate/transform data in memory for the pleasure, but to go for a point A to a point B.

Laravel is organized by flow, not by data. We have a router folder, a database folder, a “http” folder, controllers, models, requests, exceptions, middleware. This organization allows the developers to have a clear view on which part of the HTTP flow they are working on. MVC in itself is the description of a flow.

This is why I believe that DTO should be “plugged” clearly to the Laravel flow, and therefore, current implementations of DTOs may not be viable on the long term.

A Request Resource?

Another option would be an improvement on Laravel’s side.

Why do we have Eloquent Resources and not… Request Resources?

Eloquent Resources are good in themselves as we can do anything with that to transform a model to an array/JSON response. Even conditions based on the Request are supported.

They are perfect to fill the gap between models and responses when the data needs to be transformed.

A Request Resource would have the same objective. It would be directly plugged to a Form Request, a bit like this example from the Spatie module:

With the artisan command: “php artisan make:request-resource –request=SongRequest” (this is an example), it would:

  • Create a FormRequestResource file in the appropriate folder,
  • Add automatically the method to access it from the Form Request ($request->getResource())
  • transforming all validation parameters of the FormRequest to the corresponding arguments for the constructor of the Request Resource automatically.

I like this idea because there would be a clear separation between outgoing data layers (Eloquent resources) and incoming data layers (Request resources)

Laravel naming issues

I dislike several naming conventions in Laravel:

  • Eloquent Resource is not clear enough. Eloquent is the ORM, while an Eloquent Resource is more a Model Resource. Renaming Eloquent Resource to Model Resource might be clearer.
  • Eloquent Resource name induces confusions with the name Resource controllers. “Resource controllers” is a bad name I think, particularly because it exists already a well-known and clearer “word” for that: CRUD Controllers.

Furthermore, when you create a Resource Controller, the class created does not contain any mention of a Resource (or model). I mean, you can’t even pass the class of your resource in parameter to fill parts of the stub. It’s just a basic CRUD class totally independent from any resource/model. This is why I think this Artisan command would be better:

php artisan make:controller PhotoController --crud

  • Form Request is not a good name too, in my opinion. These classes are also used by APIs which are not forms. Furthermore, their main role is to authorize and validate incoming data. Why not call them Validation Request or similar?

An overall architecture

Taking into account this new terminology, we would have the following architecture:

With:

  • Validation Request = actual Form Request
  • Request Resource = to be developed
  • Model Resource = Eloquent Resource

A corresponding controller code:

Where $request->getResource() is a UserRequestResource.

For the Artisan commands:

  • php artisan make:model-resource UserResource
  • php artisan make:request-resource UserRequestResource

It would create the two files:

  • app/Http/Resources/Models/UserResource.php
  • app/Http/Resources/Requests/UserRequestResource.php

Properties of Request Resources

Some properties of Request Resources:

  • They would be strongly tied to the validated request. One parameter missing in the constructor? Error. One type error compared to the Request rules? Error.
  • They would include potentially a save() function which would be executed into a database transaction. Indeed, the usage of a Request Resource means that the saving operation may not be trivial, and involves several models.
  • The usage of a save() function would allow this:

It’s neat, no?

Conclusion

I hope you enjoyed this blog post. I probably missed a LOT OF THINGS, details, potential workarounds, trivial things, english grammar error, etc. It contains surely some mistakes as I am far to be a Laravel expert.

This blog post started with a simple question: “hey, how do I transform cleanly my Request data” and everything else came progressively.

I am still not sure which path I will choose for further projects. Probably the Spatie Laravel data module even if it seems overkill for my needs.

Feel free to comment or to provide your feedback!

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *