Create Custom Value Resolver in Symfony 8

Create Custom Value Resolver in Symfony 8

Symfony provides a flexible mechanism for mapping controller arguments to concrete values. This mechanism is powered by value resolvers, which inspect controller method signatures and supply appropriate objects at runtime. While the framework provides built-in resolvers, some applications require a custom one. This tutorial shows how to create custom value resolver in Symfony 8 application.

Assume that controller actions frequently require access to request-related tracking details such as the client IP address and the User-Agent header. Instead of extracting this data manually inside each controller, a dedicated model object can encapsulate this information.

src/Model/TrackingData.php

<?php

namespace App\Model;

class TrackingData
{
    public function __construct(private ?string $ip, private ?string $userAgent)
    {
    }

    public function getIp(): ?string { return $this->ip; }

    public function getUserAgent(): ?string { return $this->userAgent; }
}

Custom value resolvers must implement ValueResolverInterface, which defines a single method called resolve. This method receives the current Request object and an ArgumentMetadata instance that describes the controller argument being processed. Based on this information, the resolver decides whether it can handle the argument. If not, it must return an empty array. If it supports the argument, it must return an array containing the resolved value (or multiple values for variadic arguments).

src/ValueResolver/TrackingDataValueResolver.php

<?php

namespace App\ValueResolver;

use App\Model\TrackingData;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

class TrackingDataValueResolver implements ValueResolverInterface
{
    public function resolve(Request $request, ArgumentMetadata $argument): iterable
    {
        if ($argument->getType() !== TrackingData::class) {
            return [];
        }

        $trackingData = new TrackingData(
            $request->getClientIp(),
            $request->headers->get('User-Agent'),
        );

        return [$trackingData];
    }
}

With a custom value resolver in place, the model can be injected directly into controller methods.

src/Controller/TestController.php

<?php

namespace App\Controller;

use App\Model\TrackingData;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class TestController
{
    #[Route('/')]
    public function index(TrackingData $trackingData): Response
    {
        return new Response($trackingData->getIp().' '.$trackingData->getUserAgent());
    }
}

Leave a Comment

Cancel reply

Your email address will not be published.