Setup HATEOAS hal+json API responses

I’m busy setting up HAL on our API (see the spec here, which we’re building in Symfony 3. We have used the FOSRestBundle, and JMS Serialiser to set up the API to begin with, and the incredible Swagger for documenting it.

If you aren’t using Symfony, you can integrate this package yourself:

composer require willdurand/hateoas

And if you do use Symfony, there’s a bundle.

composer require willdurand/hateoas-bundle

Register the bundle in app/AppKernel.php.

// .. in array of bundles
new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),

Self Link

Open your entity, add the use statement,  and edit the class DocBlock:

use Hateoas\Configuration\Annotation as Hateoas;
@Hateoas\Relation("self", href = "expr('/employees/' ~ object.getId())")

This annotation says that the REST link to an employee is /employees/:id.

Collections with pagination links

Here’s a testAction I made in my EmployeeController:

 * @Rest\Post("/employees/test/test")
 * @SWG\Post(
 *     path="/employees/test/test",
 *     description="Fake call for testing purposes",
 *     operationId="test",
 *     produces={"application/hal+json"},
 *     tags={"employees"},
 *     @SWG\Parameter(
 *         name="page",
 *         in="query",
 *         type="integer",
 *         description="the page you want",
 *         required=false,
 *         default=1
 *     ),
 *     @SWG\Parameter(
 *         name="limit",
 *         in="query",
 *         type="integer",
 *         description="the number of results per page",
 *         required=false,
 *         default=1
 *     ),
 *     @SWG\Response(
 *         response=200,
 *         description="Returned when successful",
 *         @SWG\Schema(ref="#/definitions/listEmployee")
 *     ),
 *     @SWG\Response(
 *         response="default",
 *         description="unexpected error",
 *         @SWG\Schema(
 *             ref="#/definitions/ErrorModel"
 *         )
 *     )
 * )
 * @return PaginatedRepresentation
public function testAction(Request $request)
    $page = $request->get('page') ?? 1;
    $numPerPage = $request->get('limit') ?? 1;

    $co = new Company();
    $co->setName('KGB Inc.')
    $employee = new Employee();
        ->setName('Vladimir Putin');

    $collection = new EntityCollection();

    $co = new Company();
    $co->setName('MAGA Inc.')
    $employee = new Employee();
        ->setName('Donald J Trump');


    $numPages = ceil (count($collection) / $numPerPage);

    $filter = new CollectionFilter();

    $collection = $filter->filterArrayResults($collection->toArray());

    $halCollection = new CollectionRepresentation(

    $pager = new PaginatedRepresentation(

    return $pager;

There’s quite a bit here, but let me explain.

The @Rest\Post(“/employees/test/test”) annotation is the FOSRestBundle. When we call thae URL, it ensures we send POST otherwise we get a 405 response.

The @SWG stuff is Swagger documentation annotation, which hopefully should be self explanatory. The only part you might question is the Schema, which you docblock annotate in the entity class itself. See full Swagger docs for that, but here’s our annotation for a single employee:

* @SWG\Definition(
*   definition="singleEmployee",
*   type="object",
*   allOf={
*       @SWG\Schema(
*           @SWG\Property(property="id", type="integer"),
*           @SWG\Property(property="employeeId", type="integer"),
*           @SWG\Property(property="name", type="string"),
*           @SWG\Property(property="firstName", type="string"),
*           @SWG\Property(property="company",type="object", ref="#/definitions/singleCompany"),
*           @SWG\Property(property="functionDescription", type="string"),
*           @SWG\Property(property="nationalRegisterNumber", type="string"),
*       ),
*   }
* )

@return PaginatedRepresentation returns our collection, wrapped in HAL stuff now.

In the method itself, I create two fake employees (Trump & Putin), add them to the EntityCollection, then I run CollectionFilter on it (, which I made last week as a filter/paginator system for working with full resultsets). So the interesting bit is $halCollection & $pager. We pass our $collection into CollectionRepresentation, and pass that into PaginatedRepresentation.

So now, we go to, try our endpoint, and you should see…

Screen Shot 2017-10-30 at 4.43.34 PM

Have fun! 😀


Creating custom annotations in Symfony 3

I’m currently building a backend API for a site which requires requests to be signed with an access token header. My problem is, the default @Security annotation feature doesn’t fit with our requirements, because we don’t actually have any users on our site! We actually consume our clients SOAP service, which is where all the important data is stored.

Currently, in a typical controller action, I have the following code:

public function changePasswordAction(Request $request)
 $tokenId = $paramFetcher->get('token');
 $tokenSvc = $this->get('Our\SuperbBundle\Service\TokenService');

 // Check the token exists and is valid
 // throws exception if not found or expired
 // actual change password logic here

We are going to have several controllers with multiple calls, so we don’t want to add this in every single controller action, so we will create our own custom annotation.


namespace Our\CustomerZoneBundle\Annotation;

 * Class SecureToken
 * @package Our\CustomerZoneBundle\Annotation
 * @Annotation
class SecureToken

The next thing we do is create an Annotation listener, which I’ll explain below:


namespace Our\CustomerZoneBundle\EventListener;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Util\ClassUtils;
use Our\CustomerZoneBundle\Annotation\SecureToken;
use Our\CustomerZoneBundle\Model\Exception\TokenException;
use Our\CustomerZoneBundle\Service\TokenService;
use ReflectionClass;
use ReflectionObject;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

class SecureTokenAnnotationListener
    /** @var AnnotationReader $reader */
    protected $reader;

    /** @var RequestStack $requestStack */
    protected $requestStack;

    /** @var TokenService $tokenService */
    protected $tokenService;

     * SecureTokenAnnotationListener constructor.
     * @param AnnotationReader $reader
     * @param RequestStack $stack
     * @param TokenService $tokenService
    public function __construct(AnnotationReader $reader, RequestStack $stack, TokenService $tokenService)
        $this->reader = $reader;
        $this->requestStack = $stack;
        $this->tokenService = $tokenService;

     * @param FilterControllerEvent $event
    public function onKernelController(FilterControllerEvent $event)
        $controller = $event->getController();
         * $controller passed can be either a class or a Closure.
         * This is not usual in Symfony2 but it may happen.
         * If it is a class, it comes in array format
        if (!is_array($controller)) {

        /** @var Controller $controllerObject */
        list($controllerObject, $methodName) = $controller;

        $request = $this->requestStack->getCurrentRequest();
        $cookies = $request->cookies;
        $mkzCookie = $cookies->get('mkz');

        // Override the response only if the annotation is used for method or class
        if ($this->hasSecureTokenAnnotation($controllerObject, $methodName)) {
            try {
                if (!$mkzCookie) {
                    throw new TokenException(TokenException::ERROR_NOT_FOUND, 404);
            } catch (TokenException $e) {
                throw new AccessDeniedHttpException($e->getMessage(), $e);

     * @param Controller $controllerObject
     * @param string $methodName
     * @return bool
    private function hasSecureTokenAnnotation(Controller $controllerObject, string $methodName) : bool
        $tokenAnnotation = SecureToken::class;

        $hasAnnotation = false;

        // Get class annotation
        // Using ClassUtils::getClass in case the controller is an proxy
        $classAnnotation = $this->reader->getClassAnnotation(
            new ReflectionClass(ClassUtils::getClass($controllerObject)), $tokenAnnotation

        if ($classAnnotation) {
            $hasAnnotation = true;

        // Get method annotation
        $controllerReflectionObject = new ReflectionObject($controllerObject);
        $reflectionMethod = $controllerReflectionObject->getMethod($methodName);
        $methodAnnotation = $this->reader->getMethodAnnotation($reflectionMethod, $tokenAnnotation);

        if ($methodAnnotation) {
            $hasAnnotation = true;

        return $hasAnnotation;

Our event listener uses the annotation reader class, and takes the request object. When the event triggers, the onKernelController() method is called, where we get the Access Token cookie from the request. Then it checks for the @SecureToken annotation, which can either cover an entire class, or can be set in individual method docblocks.

Next, we hook up the service autowiring in the config services.yml:

    class: Our\CustomerZoneBundle\EventListener\SecureTokenAnnotationListener
    autowire: true
        - { name: kernel.event_listener, event: kernel.controller }

Symfony autowiring is pretty cool. the autowire true key allows Symfony to take care of object instantiation, so we don’t even need to fetch the annotation reader or request for the constructor!

Finally, we add our annotation to our method, and remove our check from inside the method:

 * @Rest\Put("/auth/change-password")
 * @SecureToken
public function changePasswordAction(Request $request)
    // actual change password logic here

Now using whatever HTTP client you like (POSTman 😉 !) when we add our Access Token cookie header, the check happens automatically! Removing the cookie now gives us a 403 response! Success! Have fun! 😀

Codeception Acceptance tests with Javascript

I had an issue on this old legacy site in work where I was writing a basic acceptance test where it clicks all the links in the top section of the home page. The problem was that one of the links opened another window using JavaScript. So I had to reconfigure Codeception to get it running.

There are various different drivers that codeception uses, PhpBrowser which doesn’t do JS, Selenium WebDriver does, and you have several options; you could install Selenium, chrome headless browser, or phantomjs. I chose phantomjs, as it was easiest (for me) to get up and running on a non X Server.

First up, you’ll need phantomjs. Go download it, unpack the zip, move the folder somewhere, and then symlink the bin/phantomjs to /usr/bin/phantomjs.

Next, launch phantomjs like so:

phantomjs --webdriver=4444 --ignore-ssl-errors=true --ssl-protocol=any

Now, in your YAML:

# Codeception Test Suite Configuration

# suite for acceptance tests.
# Run the following command FIRST:
# phantomjs --webdriver=4444 --ignore-ssl-errors=true --ssl-protocol=any


class_name: WebGuy
         - WebDriver
         - WebHelper
             url: 'https://USER:PASS@YOUR_URL_HERE'
             browser: phantomjs
                 acceptSslCerts: true

If you have a site using HTTP Basic Auth, put USER:PASSWORD@ in yopur URL, if not, remove it.

Now in your acceptance test, you can write:

$i->click('Nouvel abonnement');

Note that, in your Javascript, when you run the open window function, you specify a name. This is the name you use, and not the title from the HTML <head> section!

And there you have it! We can now test with javascript functionality!

Debugging a Twig View

Just a quick note here to save future hunting.

Ever seen this crap error message?

An exception has been thrown during the rendering of a templat

It totally sucks, right? And then you try debugging with XDebug, only to find you can’t most of the time, since Twig uses eval().

Anyway, to save you looking, if you set a breakpoint where that message is thrown you can get to see your own Exception:


Look at line 401 (this may have changed in later versions):

} catch (Exception $e) {
    throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getTemplateName(), $e);

You can now see your Exception!

Creating DOMElements from HTML strings

DOMDocuments are cool, and a really nice way of dealing with HTML in an OO fashion. However, sometimes, we have an HTML string element which we need to add to our Document. Here’s how you do it:

    function createNodesFromHTML(DOMDocument $doc,$str) 
        $nodes = array();
        $d = new DOMDocument();
        $child = $d->documentElement->firstChild->firstChild;
        while($child) {
            $nodes[] = $doc->importNode($child,true);
            $child = $child->nextSibling;
        return $nodes;

        $dom = new DOMDocument();
        $icon = '<i class="fa fa-remove"></i>'; // This is our string

        $button = $dom->createElement('a');
        $button->setAttribute('href', '/whatever/delete/12345');
        $button->setAttribute('class', 'btn btn-sm btn-danger');
        // This is us turning the string(s) into nodes we can add
        $nodes = createNodesFromHTML($dom, $icon);
        echo $dom->saveHTML();

Disable register_globals on a legacy PHP app

As any decent developer knows, register_globals was a terrible idea, a security risk, and turned ON by default in old versions of PHP!

Thankfully it was removed in PHP 5.4. However, if you are stuck developing on a site that used register_globals, you may find yourself in a situation where seemingly you can’t upgrade beyond PHP 5.3.

However, it’s not all bad news, we can put a piece of code in place which emulates register_globals. This will let us turn it off. It still means your code is less than secure, but of course that’ll be fixed in time as you upgrade and refactor the site, right?

To emulate register_globals, just add the following code to one of your initialisation/bootstrap scripts:

// Emulate register_globals on
if (!ini_get('register_globals')) {
    $superglobals = array($_SERVER, $_ENV,
        $_FILES, $_COOKIE, $_POST, $_GET);
    if (isset($_SESSION)) {
        array_unshift($superglobals, $_SESSION);
    foreach ($superglobals as $superglobal) {
        extract($superglobal, EXTR_SKIP);

Now you can turn it off in php.ini. Why is it so bad though? Well, have a look at this:


Looks like nothing should happen on that page, right? nothing has been defined.

WRONG! try adding ?loggedIn=anything to the end of the URL: