A pragmatic architecture — Argument Value Resolver 3/5

Michael Zangerle
Fusonic
Published in
4 min readMar 3, 2021

In the previous part I explained our file structure, routing with the REST endpoints and how our controllers work. In this part we are going to have a look at how to pass those automatically created command and query objects to the controller actions.

In Symfony there exists a concept called “argument value resolvers” which is used to inject parameters like the request into a controller action. In our case we are less interested in the request object itself, but more on how to get the data from the request object into our command and query objects e.g. GetCustomerQuery $query and ActivateCustomerCommand $command in our controller.

Let’s have a look how they work and add our custom resolver.

How do they work?

As described in the documentation we need to create a class that implements the Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface and register this class as a service with a controller.argument_value_resolver.

The interface forces us to implement the two methods supports and resolve. When the argument resolver is registered and a controller action with a parameter is called, Symfony will go through all argument resolvers and check which one supports the parameter in question. If a parameter (of this type) is supported the resolve method will be called and the expected value will be passed on to the called action as parameter.

Implementation and usage

At the time of this writing there exists no official argument value resolver for what we wanted to achieve so we implemented our own. It can be found here and you are welcome to make use of it.

Basically it will take the request payload and map it onto an object by using the Symfony Serializer. Routing and query parameters will also be taken into account, depending on the request method. Afterwards the created object will be validated with the Symfony Validator. If it’s valid it will be injected into the controller action. If it’s not valid an exception will be thrown.

That’s basically what this class is doing and all we need to know to get started. We will get into the details in a separate blog post.

Query and command objects

Before we can try the whole thing out, we need to create some commands and queries. For our example we will get back to the GetCustomerQuery and ActivateCustomerCommand.

As we are already very specific with the name of our ActivateCustomerCommand we don’t need much information inside this class to know what to do with it in our business logic. To be more precise we only need the id of the customer. That’s why it looks like this:

<?phpnamespace App\Customer\Message\Command;// ...final class ActivateCustomerCommand implements RequestDto
{
/**
* @Assert\NotNull(message="Id should not be null.")
* @Assert\Positive(message="Id should be a positive integer.")
*/
private int $id;
public function getId(): int
{
return $this->id;
}
public function setId(int $id): void
{
$this->id = $id;
}
}

Aside from that we just need to add the RequestDto marker interface which tells the argument resolver to handle this argument. This would be a perfect use case for PHP 8 attributes which we are going to add in the future and deprecate this marker interface.

Pretty much the same works for the GetCustomerQuery. As long as the route parameter name matches the property inside this class, the serializer will do the rest. The same applies to query parameters and the request body as well. Validation annotations are in place too so our basic requirements for these requests and their objects should be covered.

To have a bit more complex example let’s have a look at another case with nested objects and let the serializer do it’s magic. Basically, we want to filter our customer list and therefore we have our command

<?phpnamespace App\Customer\Message\Query;use Fusonic\HttpKernelExtensions\Dto\RequestDto;final class GetCustomersQuery implements RequestDto
{
private CustomerFilter $filters;
public function __construct(?CustomerFilter $filters = null)
{
$this->filters = $filters ?? new CustomerFilter();
}
public function getFilter(): CustomerFilter
{
return $this->filters;
}
public function setFilter(CustomerFilter $filters): void
{
$this->filters = $filters;
}
}

and this command has some optional filter options.

<?phpnamespace App\Customer\Message\Query;final class CustomerFilter
{
private ?string $firstName = null;
private ?string $lastName = null;
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(string $firstName): void
{
$this->firstName = $firstName;
}
public function getLastName(): ?string
{
return $this->lastName;
}
public function setLastName(string $lastName): void
{
$this->lastName = $lastName;
}
}

Integrate it in the project

To make it work in our project the argument resolver has to be registered as service and tagged. That’s it. Then you have automatically created and validated objects in your controller actions.

Fusonic\HttpKernelExtensions\Controller\RequestDtoResolver:
tags:
- { name: controller.argument_value_resolver, priority: 50 }

Next step

In the next step we are going to take a look at the whole messaging infrastructure and configure it to connect all the commands, queries and events with their handlers and have a fully working example.

--

--

Michael Zangerle
Fusonic
Writer for

backend developer from Austria mostly working with PHP and Symfony // software architecture // continuous improvements // legacy code // self improvement