I am developing an API with Symfony 7.1
I decided to use DTO objects for processing and validating the parameters of my requests.
To detect and report if a mandatory parameter is not present in the request, I created a custom resolver.
POST request
curl --location 'http://dev.myproject/api/v1/authentication/user' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ••••••' \
--data-raw '{
"username" : "username",
"password": "myPassword"
}'
The Controller
#[Route('/user', name: 'auth_user', methods: ['POST'], format: 'json')]
public function authUser(
#[MapRequestPayload(
resolver: ApiAuthUserResolver::class
)] ApiAuthUserDto $apiAuthUserDto,
): JsonResponse
{
[...]
}
The DTO object
namespace App\Dto\Api\Authentication;
use Symfony\Component\Validator\Constraints as Assert;
readonly class ApiAuthUserDto
{
public function __construct(
#[Assert\Type(
type : 'string',
message: 'Le champ username doit être du type string'
)]
#[Assert\NotBlank(message: 'Le champ username ne peut pas être vide')]
public string $username,
#[Assert\Type(
type : 'string',
message: 'Le champ password doit être du type string'
)]
#[Assert\NotBlank(message: 'Le champ password ne peut pas être vide')]
public string $password,
)
{
}
}
The Custom Resolver
namespace App\Resolver\Api;
use App\Dto\Api\Authentication\ApiAuthUserDto;
use App\Utils\Api\ApiParametersParser;
use App\Utils\Api\ApiParametersRef;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Validator\Validator\ValidatorInterface;
readonly class ApiAuthUserResolver implements ValueResolverInterface
{
public function __construct(
private ApiParametersParser $apiParametersParser,
private ValidatorInterface $validator
) {
}
/**
* @param Request $request
* @param ArgumentMetadata $argument
* @return iterable
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$data = json_decode($request->getContent(), true);
$return = $this->apiParametersParser->parse(ApiParametersRef::PARAMS_REF_AUTH_USER, $data);
if (!empty($return)) {
throw new HttpException(Response::HTTP_FORBIDDEN,implode(',', $return));
}
$dto = new ApiAuthUserDto(
$data['username'],
$data['password']
);
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
$nb = $errors->count();
$msg = [];
for ($i = 0; $i < $nb; $i++) {
$msg[] = $errors->get($i)->getMessage() . ' ';
}
throw new HttpException(Response::HTTP_FORBIDDEN,implode(',', $msg));
}
return [$dto];
}
}
I set the priority so as not to have problems with other resolvers
App\Resolver\Api\ApiAuthUserResolver:
tags:
- controller.argument_value_resolver:
priority: 50
The code works well and does its job correctly.
My problem is this:
Since I implemented this custom resolver for my API, all the routes in my applications are broken because my custom resolver is systematically called for a reason I don't know.
For example this code, a very simple route from my project which calls 2 objects
#[Route('/dashboard/index', name: 'index')]
#[Route('/dashboard', 'index_3')]
#[Route('/', name: 'index_2')]
public function index(DashboardTranslate $dashboardTranslate, UserDataService $userDataService): Response
{[...]}
Now gives me the following error:
App\Utils\Api\ApiParametersParser::parse(): Argument #2 ($apiParameters) must be of type array, null given, called in \src\ValueResolver\Api\ApiAuthUserResolver.php on line 36
I don't understand why it is systematically my custom resolver that is called even if it has a lower priority and I define it just for an action via the resolver property of MapRequestPayload
What I want to do is that this custom resolver is only used in this specific case and that for classic cases it is the Symfony resolvers that work as it was the case before
Did I forget something, misconfigure my custom resolver?