Master Symfony2 - part 6: Annotations 10/09/2014
Deprecated: This series has been re-written - see The Ultimate Developer Guide to Symfony
This is the sixth article of the series on mastering the Symfony2 framework. Have a look at the four first ones:
In the previous articles we created an API allowing us to submit and list quotes:
.
├── app
│ ├── AppKernel.php
│ ├── cache
│ │ └── .gitkeep
│ ├── config
│ │ ├── config_prod.yml
│ │ ├── config_test.yml
│ │ ├── config.yml
│ │ ├── doctrine.yml
│ │ ├── parameters.yml
│ │ ├── parameters.yml.dist
│ │ └── routing.yml
│ ├── console
│ ├── logs
│ │ └── .gitkeep
│ └── phpunit.xml.dist
├── composer.json
├── composer.lock
├── src
│ └── Fortune
│ └── ApplicationBundle
│ ├── Controller
│ │ └── QuoteController.php
│ ├── DependencyInjection
│ │ └── FortuneApplicationExtension.php
│ ├── Entity
│ │ ├── QuoteFactory.php
│ │ ├── QuoteGateway.php
│ │ ├── Quote.php
│ │ └── QuoteRepository.php
│ ├── EventListener
│ │ └── SubmitJsonListener.php
│ ├── FortuneApplicationBundle.php
│ ├── Resources
│ │ └── config
│ │ ├── doctrine
│ │ │ └── Quote.orm.yml
│ │ └── services.xml
│ └── Tests
│ ├── Controller
│ │ └── QuoteControllerTest.php
│ └── Entity
│ └── QuoteRepositoryTest.php
└── web
└── app.php
Here's the repository where you can find the actual code.
In this article, we'll discover annotations.
Doctrine Annotations
Inspired by Python Decorators and Java Annotations, the Doctrine Project created a convenient library allowing to put in the same file: information (like configuration) and source code.
In concrete terms, Annotations
are comments which are read by AnnotationReader
and can then be cached in any
format (generally PHP) to make things faster afterwards.
It's main strength is the possibility to avoid having a configuration file in a
path too far from the source code which uses it. For example intead of having
the schema definition in src/Fortune/ApplicationBundle/Resources/config/doctrine/Quote.orm.yml
we could have it directly in the QuoteEntity
.
Installing Sensio FrameworkExtra Bundle
The Sensio FrameworkExtra Bundle
provides controller annotations, amongst them lies @Route
allowing us to move
the routing configuration from app/config/routing.yml
directly to the actions.
Let's download the bundle:
composer require sensio/framework-extra-bundle:~3.0
Then register it:
<?php
// File: app/AppKernel.php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
class AppKernel extends Kernel
{
public function registerBundles()
{
return array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Fortune\ApplicationBundle\FortuneApplicationBundle(),
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
);
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}
}
Finally, we need to tell Doctrine's Annotation library where to find the classes by registering Composer's autoloader:
<?php
// File: app/autoload.php
use Doctrine\Common\Annotations\AnnotationRegistry;
$loader = require __DIR__.'/../vendor/autoload.php';
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
return $loader;
This file should be used in our front controller:
<?php
use Symfony\Component\HttpFoundation\Request;
require_once __DIR__.'/app/autoload.php';
require_once __DIR__.'/../app/AppKernel.php';
$kernel = new AppKernel('prod', false);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
But also in our test suite:
<?xml version="1.0" encoding="UTF-8"?>
<!-- http://phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit
backupGlobals="false"
colors="true"
syntaxCheck="false"
bootstrap="autoload.php">
<testsuites>
<testsuite name="Functional Test Suite">
<directory>../src/*/*/Tests</directory>
</testsuite>
</testsuites>
</phpunit>
Using the @Route annotation
We can now empty the routing.yml
file and tell it to import the configuration
from the QuoteController
using its annotations:
# File: app/config/routing.yml
fortune_application:
resource: @FortuneApplicationBundle/Controller
type: annotation
The controller itself will look like this:
<?php
// File: src/Fortune/ApplicationBundle/Controller/QuoteController.php
namespace Fortune\ApplicationBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class QuoteController extends Controller
{
/**
* @Route("/api/quotes", methods={"POST"})
*/
public function submitAction(Request $request)
{
$postedValues = $request->request->all();
if (empty($postedValues['content'])) {
$answer = array('message' => 'Missing required parameter: content');
return new JsonResponse($answer, Response::HTTP_UNPROCESSABLE_ENTITY);
}
$quoteRepository = $this->container->get('fortune_application.quote_repository');
$quote = $quoteRepository->insert($postedValues['content']);
return new JsonResponse($quote, Response::HTTP_CREATED);
}
/**
* @Route("/api/quotes", methods={"GET"})
*/
public function listAction(Request $request)
{
$quoteRepository = $this->container->get('fortune_application.quote_repository');
$quotes = $quoteRepository->findAll();
return new JsonResponse($quotes, Response::HTTP_OK);
}
}
And now annotations are ready to be used, as the tests prove it:
./vendor/bin/phpunit -c app
That's green enough for us to commit:
git add -A
git commit -m 'Used annotations'
Conclusion
Annotations allow us to remove the distance between configuration and code.
Note: You should know that annotations can raise concerns about tight coupling, but it doesn't seem to be relevant when used as configuration.
The best thing to do is to minimize their use to the classes which are already coupled to our tools (for example the controllers) and do some research on the subject to make your own opinion.
If the concept seduced you, have a look a ControllerExtraBundle.
The next artile will be the conclusion, I hope you enjoy this series!