We already have controller annotations such as "Method", "Route" so on. In this example we're going to create a custom one for a specific reason. We're going to allow only specific football players to access all the action methods in PlayerController. If I don't want you to access it then I won't list your name in this custom annotation. Our example is as simple as that!


Config.yml


parameters:
player_names:
- Pele
- Zico
- Maradona
player_annotation_namespace: "Application\\FrontendBundle\\Annotation\\Player"

Player annotation


namespace Application\FrontendBundle\Annotation;

/**
* @Annotation
*/
class Player
{
/**
* @var array
*/
public $names = [];

/**
* @var string
*/
public $code = 403;

/**
* @var string
*/
public $message = 'Unauthorised player.';
}

PlayerController


Based on the @Player annotation settings below, I only want "Pele" and "Zico" to access this controller. I also override the default error message of "Player" class above Unauthorised player. by setting "message" property to Maradona is unauthorised player. but I keep the error "code" intact.


namespace Application\FrontendBundle\Controller;

use Application\FrontendBundle\Annotation\Player;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
* @Route("player", service="application_frontend.controller.player")
*
* @Player(names={"Pele", "Zico"}, message="Maradona is unauthorised player.")
*/
class PlayerController extends Controller
{
/**
* @@param Request $request
* @@param string $name
*
* @Method({"GET"})
*
* @Route("/{name}", requirements={"name" = "\w+"})
*
* @return Response
*/
public function indexAction(Request $request, $name)
{
$country = $request->query->get('country', 'none');

return new Response(sprintf('%s is from %s national football team.', $name, $country));
}
}

AnnotationListener


namespace Application\FrontendBundle\Listener;

use Application\FrontendBundle\Annotation\Player;
use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;

class AnnotationListener
{
private $reader;
private $playerNames;
private $playerAnnotationNamespace;

public function __construct(
Reader $reader,
array $playerNames,
$playerAnnotationNamespace
) {
$this->reader = $reader;
$this->playerNames = $playerNames;
$this->playerAnnotationNamespace = $playerAnnotationNamespace;
}

public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller) || !$event->isMasterRequest()) {
return;
}

$player = $this->getPlayer($event->getRequest());

$this->validatePlayer($controller, $player);
}

private function getPlayer(Request $request)
{
$routeParams = $request->attributes->get('_route_params');
if (!is_array($routeParams)) {
throw new BadRequestHttpException('Invalid request.');
}

if (!isset($routeParams['name'])) {
throw new MissingMandatoryParametersException('Missing name parameter.');
}

return $routeParams['name'];
}

private function validatePlayer(array $controller, $player)
{
/** @var Player $playerAnnotation */
$playerAnnotation = $this->reader->getClassAnnotation(
new ReflectionClass($controller[0]),
$this->playerAnnotationNamespace
);

if (!in_array($player, $playerAnnotation->names)) {
throw new BadRequestHttpException($playerAnnotation->message, null, $playerAnnotation->code);
}
}
}

Listeners.yml


services:
application_frontend.listener.annotation:
class: Application\FrontendBundle\Listener\AnnotationListener
arguments:
- @annotation_reader
- %player_names%
- %player_annotation_namespace%
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }

Tests


Valid


# Request
GET http://football.dev/app_dev.php/player/Pele?country=Brazil

# Response
200 OK
Pele is from Brazil national football team.

Valid


# Request
GET http://football.dev/app_dev.php/player/Zico?country=Brazil

# Response
200 OK
Zico is from Brazil national football team.

Invalid


# Request
GET http://football.dev/app_dev.php/player/Maradona?country=Argentina

# Response
400 Bad Request
Maradona is unauthorised player.
400 Bad Request - BadRequestHttpException