Replies: 10 comments 16 replies
-
|
That’s an interesting behavior! Will you rely on PSRs to describe a “Request”? |
Beta Was this translation helpful? Give feedback.
-
|
We have been doing something similar lately using DTOs defined alongside the controller but we are using a helper class that performs the mapping any error handling. This has the advantage that we are able to implement them as PSR-7 controllers on the other hand it requires boiler-plate code in every controller. That said, I’m not entirely convinced that boiler-plate code is always a bad thing, after all this trade-off allows us to stick to PSR-7 while also being able to enforce the call signature of the controller on an interface level. The drawback of parameter arguments is that interfaces are pretty much out of the window immediately. For reference, we currently have two iterations of this principle in action:
The second iteration is much closer to this proposal and is meant to illustrate how these things could look in practice. During the implementation we tried out your approach of using attributes on arguments but the lack of interface support was a major let down. I do have to add that there is one thing lurking in the shadows and it’s the In the end I’m a bit torn between your proposals because none of them really tick all the boxes. Do you think you can come up with an approach that is built on top of PSR-7 to increase compatibility with existing routing implementations? |
Beta Was this translation helpful? Give feedback.
-
|
Thinking about your approach, I see a few difficulties in practice. You’ve set yourself the goal to provide a flexible solution that fits a wide range of use cases but at the same time you cannot make assumptions about how the data gets there. At the same time, offering any sort of support for PSR-7 controllers (which are not that exotic) is somewhat reasonable but means that you have to maintain two (similar) code paths for the same feature. Having attributes like It also has the problem that these attributes can be misused since they rely on a mutable global state. Calling them in multiple places within the same request can possibly yield different results. I’m not sure if this is even a thing and if you should even consider this in the first place, but it struck my mind more than once. At least with third party libraries there is always a risk that they mutate superglobals in an unpredictable way. Have you considered introducing a component that needs to be initialized and that offers convenient methods to fetch (and implicitly validate) the values? Something like That approach would be a lot less fancy but offers multiple entry points for the parameters with everything that follows after being completely uniform. The state is encapsulated and immutable, avoiding a whole class of headaches, you don’t have to maintain multiple code paths and you don’t have to deal with any routing (or make any assumptions in that direction). Perhaps I am overthinking this … |
Beta Was this translation helpful? Give feedback.
-
|
This sounds very promising! I have been following your work on this library for some time now (from afar), thinking that one day I would use it to replace Symfony's Serializer component, which is a little painful to work with when it comes to deserializing into strongly typed data structures. I like the option A because of the explicitness it provides: you have to tag an attribute if you want the data coming from the query parameters or request body. Maybe we can consider adding some attributes to option A, something like
Show code example
|
Beta Was this translation helpful? Give feedback.
-
|
Thank you all for your answers! There's also another option I had in mind ~2 months ago and I kind of forgot about it, so I added option E to the thread, would you mind checking it out? I'd really love to have feedback on your own usage of query parameters. Do you use them exclusively for |
Beta Was this translation helpful? Give feedback.
-
|
The new E option seems the most practical. As a reference at work out of 171 endpoints in a single app we only have one that use query parameters. It's a As for the So even only mapping body parameters would be enough in our case. |
Beta Was this translation helpful? Give feedback.
-
|
This comment might be a bit off topic but looking at your example I will have to comment the query parameters bit. Since this is not yet set in stone I believe this is the one chance to handle thing differently in PHP land which would be beneficial for everyone 🤞🏾. In your example you have done this: query: [
'optionA' => 'foo',
'optionB' => 'bar',
],I presume that at some point you did use If you look for instance at the only valid RFC around query parameters ie the URL Living Standard you will see that query parameters are stored as query pairs. which means that your example would instead yield the following : query: [
['optionA', 'foo'],
['optionB', 'bar'],
],While this may seem weird or original at first glance, there are several advantages in parsing the query string (or POST body) this way:
In contrary: The "new" storage format DO guarantee that:
Which means that it makes your Request more interoperable with other language and improve developer DX in cases where position and value are important. Again sorry for the slightly of topic critics but I believe that if Valinor wants to enter the realm of Request -> DTO handling it should also try to add new/innovative ways to handle the request and I believe this is an important topic to raise at this point otherwise the ship will have sail. I hope you will at least consider the proposition. |
Beta Was this translation helpful? Give feedback.
-
|
We went with option A /**
* @param int $instanceId
* @param array{foo: string} $query
* @param array{bar: int} $payload
* @return void
*/
#[Route('/system/execute_task/{instanceId}')]
public function executeTask(
#[MapRouteParameter] int $instanceId,
#[MapQueryString] array $query,
#[MapRequestPayload] array $payload)
{}Solved by looking at the signature of the controller method, and passing to valinor if To be able to use the For I like when I can control everything from the controller and not have to modify something at root-level. |
Beta Was this translation helpful? Give feedback.
-
|
Hi everyone! I've just submitted two pull requests to try out the HTTP request mapping feature. The development is not completely over yet, but most of it has been done.
I'd really like to have some feedback on this — not necessarily a code-review (although it is always welcome 😊) — if you could try it out in your projects, see what's wrong and get back to me, that would be awesome. Having these feedback will help taking a decision on which approach will be included in the library's core. Thank you in advance. 😊 |
Beta Was this translation helpful? Give feedback.
-
|
Hi everyone, thanks a lot for the very interesting discussions we had here. The HTTP request mapping feature has just been released in 2.4.0. Hope you'll enjoy it! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I've lately been working on native HTTP request mapping, provided out of the box by this library. The goal is to ease the validation and mapping of the HTTP request's parts — query parameters, (route) parameters and body — to ensure everything is typed properly before being used inside the controller.
This system would be used in a HTTP application, somewhere between the route resolving and the controller call.
I've got a POC working well, and now I'm reflecting on the best way of structuring the different HTTP parts in the controller parameters, I'd love to have some input about the possible options.
Let's imagine the following HTTP request (it does not particularly makes sense, just to have an idea of what could be done):
The goal is to be able to map this request to either a DTO or a controller's arguments signature:
Now I'm considering several options on how to define the structure where the data should be mapped:
Option A
ℹ️ See this option live in Pull Request #748
#[MapQuery]attribute — the parameter can be either an array shape or a DTO#[MapBody]attribute — the parameter can be either an array shape or a DTOPros: clear distinction between the three sources of data
Cons: an attribute must be used; an array shape (or a DTO) must be used even for simple cases
Show code example⤵️
Option B
#[MapBody]attribute — the parameter can be either an array shape or a DTOPros: it gets easier to map query string as we don't need an array shape or a DTO
Cons: inconsistency on the way of declaring the parameters; collision between route attributes and query parameters can happen
Show code example⤵️
Option C
Pros: consistency; easy to declare any parameter; no need for attribute
Cons: collision between route attributes, query parameters or body can happen; signature can get heavy quite fast when a lot of information is passed
Show code example⤵️
Option D
#[MapQuery]attribute is found on a parameter, all query parameters are mapped on it, otherwise they are mapped at the root level#[MapBody]attribute is found on a parameter, all body values are mapped on it, otherwise they are mapped at the root levelPros: very flexible; allows to choose which option is the best for a given situation
Cons: can lead to inconsistency between different controllers
Option E
ℹ️ See this option live in Pull Request #747
This option aims to bypass the two main issues with other options above:
The idea is that in most cases, safe methods like
GET,HEAD& others rely on query parameters only, whereasPOST,PUT& others rely on body values only/mostly. If we acknowledge that, we can simplify things in almost all cases.To do this, everything is mapped at the root level, but:
GET,HEAD, …) only the query parameters are mappedPOST,PUT, …) only the body values are mappedRoute parameters are always mapped.
When there's a case where a request
POST/PUT/ other, needs to access query parameters, we can always give access to the original request object to access them (we need to define more precisely how to do it).Pros: no risk of collision, no usage of attributes
Cons: if a controller needs to access further data, it must use the request object (see below)
Show code example⤵️
On a side note, Symfony has a similar built-in feature:
#[MapQueryParameter]attribute, or flattened to a single parameter using the#[MapQueryString]attribute#[MapRequestPayload]attributeUpdated on 20th October 2025: added option E which resets the poll so here are the results at this time:
Show poll results
8 votes ·
Beta Was this translation helpful? Give feedback.
All reactions