PHP attributes let you define structured metadata directly in your code, right next to the classes, methods, or properties they configure. Although using them in Symfony is optional, they greatly improve developer experience and productivity. That's why every new Symfony version adds new attributes and improves the existing ones.
Union Types in #[CurrentUser]
The #[CurrentUser] attribute can be used in controller arguments to get the currently authenticated user. In Symfony 7.4, we've improved it so you can use a union type, allowing your controller to handle multiple possible user types:
1 2 3 4 5 6 7 8 9 10 11 12
use App\Entity\AdminUser;
use App\Entity\Customer;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
class ApiLoginController extends AbstractController
{
#[Route('/api/login', name: 'api_login', methods: ['POST'])]
public function index(#[CurrentUser] AdminUser|Customer $user): Response
{
// ...
}
}
Multiple Environments in #[Route]
The #[Route] attribute lets you define routes directly in controller methods
instead of separate configuration files. The env option restricts a route
to certain configuration environments.
In Symfony 7.4, this option now supports multiple environments:
1 2 3 4 5 6 7 8 9 10 11
#[Route('/_debug/mail-preview', name: 'debug_mail_preview', env: ['dev', 'test'])]
public function previewEmail(MailerInterface $mailer): Response
{
// ...
}
#[Route('/healthcheck', name: 'healthcheck', env: ['staging', 'prod'])]
public function healthCheck(): JsonResponse
{
// ...
}
Repeatable #[AsDecorator]
The #[AsDecorator] attribute helps configure how a service decorates another service using the decorator pattern. In Symfony 7.4, this attribute is now repeatable, allowing a single class/service to decorate multiple others:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
// ...
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
#[AsDecorator('api1.client')]
#[AsDecorator('api2.client')]
#[AsDecorator('api3.client')]
class LoggableService implements HttpClientInterface
{
public function __construct(
private HttpClientInterface $client,
private readonly LoggerInterface $logger,
) {
}
public function request(string $method, string $url, array $options = []): ResponseInterface
{
try {
$response = $this->client->request($method, $url, $options);
$this->logger->info('API call: {method} {url}.', ['method'= > $method, 'url' => $url]);
return $response;
} catch (\Throwable $e) {
$this->logger->error('API call failed: {method} {url}.', ['method'= > $method, 'url' => $url, 'exception' => $e]);
throw $exception;
}
}
}
Union Types in #[AsEventListener]
The #[AsEventListener] attribute configures event listeners directly on their
classes. When the related listener method type-hints the expected event object,
you can omit the event argument in the attribute.
In Symfony 7.4, union types are now supported in these method signatures:
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
final class SomeListener
{
#[AsEventListener]
public function doSomething(CustomEvent|AnotherCustomEvent $event): void
{
// ...
}
}
#[IsGranted] Methods
The #[IsGranted] attribute performs access control checks before running
certain code, such as controller actions. In Symfony 7.4, it now supports a
new methods option to restrict checks to specific HTTP methods.
If the current request method matches one of the configured methods, the access check runs; otherwise, the attribute is ignored:
1 2 3 4 5 6 7 8 9 10 11
#[IsGranted('ROLE_ADMIN', methods: ['GET', 'POST'])]
public function someAction()
{
// ...
}
#[IsGranted('ROLE_ADMIN', methods: 'POST')]
public function otherAction()
{
// ...
}
Route Attribute Auto Registration
Symfony applications use by default this route configuration to load all the
#[Route] attributes (type: attribute) from your default controller
directory (path: ../src/Controller/):
1 2 3 4 5 6
# config/routes.yaml
controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute
In Symfony 7.4, we're improving this feature so you can now use #[Route]
attributes in any directory of your application. It works like this:
- Symfony applies a service tag called
routing.controllerto any class that uses the#[Route]attribute. - A compiler pass collects those tagged services and automatically registers their routes.
With this, your config/routes.yaml file can be simplified to:
1 2
controllers:
resource: routing.controllers
When using the default Symfony recipes, this configuration becomes optional,
so the config/routes.yaml file will be empty by default.
New #[IsSignatureValid] Attribute
Symfony provides utilities to sign and verify URIs in services and controllers.
In Symfony 7.4, a new #[IsSignatureValid] attribute simplifies this feature
by performing signature validation automatically:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// ...
use App\Security\Attribute\IsSignatureValid;
class SomeController extends AbstractController
{
#[IsSignatureValid]
public function someAction(): Response
{
// ...
}
// you can also check signatures for specific HTTP methods
#[IsSignatureValid(methods: ['POST', 'PUT'])]
public function updateItem(): Response
{
// ...
}
}
You can also apply this attribute at the class level to validate signatures across all controller methods.
Love the Route Attribute Auto Registration!
The env property in #[Route] will be great for not leaking endpoints.
This is great, Javier! Thanks for sharing!