Installing and Configuring Symfony2 – Screencast

In this short video you’ll learn everything you need to know to get you up and running, so you can start developing amazing Web-Applications using Symfony2 Framework.

First, we’ll take a look at Composer – incredibly sophisticated but easy to use dependency manager for your PHP projects. Then, we will use Composer to install the Standard Edition of Symfony2 Framework on our local machine. By that time we will be done with the installation part of the tutorial and we will move on to the configuration.

I will show you how I usually configure my Apache server in order for it to be as close to the production environment as possible. We’ll then take a look at the most common issue with the new Symfony2 installation – permission problems. I will teach you how to solve this issue using two different techniques.

Roadmap

  • Install Composer
  • Install Symfony2 Standard Edition
  • Fix permission issues

Installing Composer

$ curl -s https://getcomposer.org/installer | php

Install Symfony2 Standard Edition

$ php composer.phar create-project symfony/framework-standard-edition nullapp 2.1.x-dev

Permissions issues

The warning you’ll get all the time is that the app/cache and app/logs directories must be writable both by the web server and the command line user. There are basically two ways to fix this – the good way and the quick and dirty way.

The good way

Symfony2 documentation recomends using ACL (Access Control List) to fix the problem. If you’re on the Mac, ACL is already preinstalled. For Ubuntu server you’ll need to follow the official documentation to install ACL.

First thing you need to do is to delete any files that might be in the app/logs and app/cache directories:

$ rm -rf app/cache/*
$ rm -rf app/logs/*

Then you need to find out under which user Apache is running – you’ll be needing it in order to set the permissions. Use one of the following commands:

$ ps aux | grep httpd
$ ps aux | grep apache

In my case (Mac OS X 10.8) the Apache user is _www so I’ll execute the following line setting the permissions for this user:

$ sudo chmod +a "_www allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs

And then finally to set permission for myself:

$ sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs

Quick and dirty way

If you don’t have access to ACL, you could uncomment or put the following umask function in three files: app/cosole, web/app_dev.php and web/app.php.

...
umask(0000); 
...

In the end just set the permissions on the two directories:

$ chmod -R 777 app/cache/ app/logs/

Now you can go to your localhost to see the results

The_Context Podcast – Episode 22 – Symfony Live in Berlin

Andrey is talking about his experience during the Symfony Live 2012 Event in Berlin. What is Symfony? How does it work? What do you need to get started?
This leads to the lively discussion about the different blogging clients for iOS – WordPress, JetPack, Note, Drafts. During the discussion we try to define the look of the “modern” mobile application – skeuomorphic or clean?
We end up bashing Windows 8. Well it had it coming, don’t you think?

https://thecontext.de/podcast/2012/12/23/episode-22-symfony-life-2012-in-berlin

Screencast Series – Creating Own PHP Framework Using Symfony2 Components – Episode 11

In this part we’ll be exploring the powers of wonderfull HttpKernelInterface even further by adding even more listeners to the request. This is generally a very good way to add a new functionality, because it lets you to concentrate on the single feature, test this feature, and then integrate in the flow of the request – wherever it fits.

Roadmap

So, that is what we’re going to do:

  • manage 404 and 500 errors with the custom event listener
  • return a string instead of a full Response object by hooking into the kernel.view event

Git setup

Our framework is (almost) a piece of art by now, so that would be shame to destroy it if something goes insanely wrong by introduction of new features. So we’d better create a brach, where we’ll introduce our changes:

    $ git checkout -B episode_11
    $ cp -r episode_10/ episode_11
    $ git add .
    $ git commit -am "Episode 11 Starting Point"</code></pre>

ExceptionListener

To let our framework handle errors gracefully we’ll get rid of the old code and let HttpKernel handle the error handling instead:

   // /src/Simplex/Framework.php

    namespace Simplex;

    use Symfony\Component\HttpKernel\HttpKernel;

    class Framework extends HttpKernel 
    {
    }

We can have a look at HttpKernel class and notice that it’s very similar to what we’ve been trying to build with our Framework class. HttpKernel also has the handle() method with some add-ons.

Let’s edit the Front Controller so it represents the HttpKernel adequately:

use Symfony\Component\HttpKernel\EventListener\RouterListener;

    ...

    // Subscribe to a couple of events with the EventDispatcher Component
   $dispatcher = new EventDispatcher(); 
   $dispatcher->addSubscriber(new RouterListener($matcher));

    // Load our framework to handle Requests
    $framework = new Simplex\Framework($dispatcher, $resolver);
    $framework = new HttpCache($framework, new Store(__DIR__ . '/../cache'));
    $response = $framework->handle($request);

Besides the changes in __constructor() of our Framework which now has one less attribute, we are using the RouterListener. The job of RouterListener, hence the name, is to “listen” to the different routes (represented with URLs) and populate the request accordingly.

So if we now go to the /is_leap_year we still see the correct page. Everything works. One thing that does not – is the exception handling. The nice 404 response we used to incorporate in the handle() method is gone.

But no worries, the whole idea was to decouple the exceptions from the framework. We’ll introduce the ExceptionListers to do just that:

    // Introduce ExceptionListener to handle 404s and 500s
    $errorHandler = function (FlattenException $exception) {
        $msg = 'Something went terribly wrong! We\'re all doomed! Why would you break my site?! [DEBUG: ' . $exception->getMessage() . ']';

        return new Response($msg, $exception->getStatusCode());
    };

    $dispatcher->addSubscriber( new ExceptionListener($errorHandler));

We’ve introduced a component called FlattenException here, which job is NOT no though the exception in place, but rather get the whole Exception information (message, status code), so the application can do something with this, instead of just breaking.

ErrorController

With the FlattenException component we’ve introduced, we could do all kinds of crazy things. For instance, we could write the errors in the logs, send out E-Mails that would wake our developers in the middle of the night (PLEASE DO NOT DO THAT) etc…

To do it easily, ExceptionListener can take the object-oriented notation as an argument, meaning we could refactor our code into a class.

    // /src/Calendar/Controller/ErrorController.php

    namespace Calendar\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\FlattenException;

class ErrorController
{
    public function exceptionAction(FlattenException $exception)
    {
        $msg = "Something went terribly wrong! You broke everything! [DEBUG: ]" . $exception->getMessage() . "\n<br />";

        if ($exception->getStatusCode() == 404) {
            $msg = "Whoops, no such page";
        }

        return new Response($msg, $exception->getStatusCode());
    }
}

StringResponseListener

We could hook into the internal events, such as kervel.view, which is triggered just after ther controller has been called. So we can catch it there and do something cool with it – like, say, convert a Response into a simple string.

First we need to edit the LeapYearController to return simple string:

   // /src/Calendar/Controller/Leap

   class LeapYearController
{
    public function indexAction(Request $request, $year)
    {
        $leapyear = new LeapYear();
        if ($leapyear->isLeapYear($year)) {
            return 'Yes, ' . $year . ' is a leap year. If this number stays the same for 10 seconds caching works: ' . rand();
        } else {
           return 'No, ' . $year . ' is not a leap year.';
        }

    }
}

Then we create a StringResponseListener which will catch the controller Result – in our case it is a string – and send it to the user

// /src/Simplex/StringResponseListener.php

namespace Simplex;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;

class StringResponseListener implements EventSubscriberInterface
{
    public function onView(GetResponseForControllerResultEvent  $event)
    {
        $responseSting = $event->getControllerResult();

        $response = new Response();
        // Play with the Response a bit
        $response->headers->set('Content-Type', 'text/plain');
        $response->setContent($responseSting);

        if (is_string($responseSting)) {
            $event->setResponse($response);
        }
    }

    public static function getSubscribedEvents()
    {
        return array('kernel.view' => 'onView');
    }
}

And the last thing we need to remember is to add the register our newly created listener in the front controller:

// Register the plain text response listener
$dispatcher->addSubscriber(new Simplex\StringResponseListener());

You can checkout the results of this tutorial from the github repository for this episode: Build PHP Framework Screencast – Episode 11

Apple, Amstrad, Symfony – Fabien Potencier’s Keynote on Symfony Live 2012

These are my quick notes of the Fabien Potencier’s keynote at Symfony Live 2012 Berlin.

Intro

We (the developers) are lucky, because we know how web works.

Parable

The artile of the ReadWriteWeb about Facebook wanting to be your one and only – became one of the most popular article on the web! Because people did not enter the www.facebook.com into address bar, but searched in Google for “Facebook Login”. So the ReadWriteWeb was confused for Facebook by millions.

People did not know the difference between browser and search engine.

Questions

How many people understand MVC, object oriented programming, dependecny injection?

Not so many.

What is Symfony’s Target Audience?

Those who do know the answer to those question. And want to work this way. We are not alone – Drupal, eZ.

There is no one true framework and diversity is good – you should think about the context. It’s all about the right tools for the job.

Your way vs. Right way

Quick triva – Fabien used the mouse upside down. With the chord turned into the wrong direction. He moved the mouse left – and the cursor moved to the right… After a while he found out the “right way”.

And it took some time to adjust to this right way. It was just different to what he’s been used to.

If something seems logical to you, this something may be wrong! Examples:
– symfony 1.x vs. Symfony2 coding standards.
– InputOption::PARAMETER_Optional vs. InputOptiojn::VALUE_Option

Standardize

Do NOT reinvent the wheel – by adapting the common set of libraries the PHP community is comming together. And this is a good thing. The composer is the perfect example of standartization.

Fabien asks for Feedback – because even he can be wrong. Many features can be made easier to understand, simplier to implement and more powerful then we know them today.

Good Feedback is also the part of contributing!

A Story

It’s Christmas 1984, Fabien is 11 years old and he has a dream to get a personal computer. And he god Amstrad CPC664 with the colored monitor. It even had an internal floppy disk drive, not the usual casette-deck.

So Fabien unpacked the box, plugged his computer to the monitor… And got the command line. So straight away he was ready to write in Basic 1.1. It took him 15 minutes to read the manual and he could code in basic.

This how easy was it to start coding.

What about today? You buy a new computer. How many software packages you have to install before you actuall start coding?

Making Coding Fun Again

Example of KhanAcademy – a non-profit organization specializing in e-learning, which started with the couple of videos published on YouTube.

I want programming computers to be like coloring with crayons and playing with dulo blocks (– Ryan Dahl, Node.js)

The problem with the current technology – it’s way to complex.

The Symfony Way

Symfony tries to help you to find the problem by inroducing the better exception handling messages. The same happens in the command line tool or in web developer tools.

That is the reasoning behind the preconfigured bundles, as a predefined archive – to make the introduction into Symfony easier.

Composer

It’s one of the best things that happened to the PHP community in the recent years.

But in comparison to PEAR it’s a bit more involving – you have to edit your json file, then run then the composer command to update your dependencies.

SensioLabsDesktop

It’s a desktop application that you can download to you machine and this is the great add-on to you IDE and all the tools you’re used to.

You connect to your SensioLabs account, you can setup, manage and edit the symfony project. And in the future you could even deploy Symfony application from there.

There is an UI to add dependecies to the Composer, bootstrap your application, run the PHPUnit Tests – all these features are built into the SensioLabsDesktop.

3 Steps to Forms Mastery in Symfony2

These are my quick notes of the talk given by Bernhard Schussek on the Symfony Live 2012 Berlin. The slides are available on the SpeakerDeck: https://speakerdeck.com/bschussek/3-steps-to-symfony2-form-mastery

Outline

  • Example
  • Step1: Understand Data Formats
  • Step2: Become Friends with Data Mappers
  • Step3: Get Hot with Events

Example

Form
– Name
– Salary
– Date of Birth

Forms have

  • filed name
  • field type
  • lifecycle

Step 1. Understand Data Formats

  • Model data
  • View data
  • Normalized data

Example

  • Date of Birth
    • Model data – DateTime
    • View data – String
    • Normalized data – DateTime object

Tip 1

Whenever you create a custom type, decide:
– Model format?
– Normalized format?
– View format?

Data Transformation is handled by
Symfony\Component\Form\DataTransormerInterface
So, to write your own transformer, you simply implement this interface.

Tip 2

The normalized data should contain as much information as possible

Tip 3

Do not let transformer to change the information, rather convert it to some other format. For instance, from Date string into DateTime object.

Step2: Become Friends with Data Mappers

Data Mapping is the exchange of the information between the fields and data model.

Mappers

  • Symfony
    • PropertyPathMapper (getter/setters, array)
  • You can write your own

Problems

  • There is a field you don’t want to have your model, but be displayed

    • Example: Terms and Conditions

      $builder->add(‘terms’, ‘checkbox’, array(‘mapped’) => false,
      ‘data’ => true);

  • The name of the fields do not correspond to the names of the model properties

    • Use ‘property_path’ in you $builder->add() method
  • Disabling By-Reference Handling

Step3: Get Hot with Events

Example

We sant to convert all the text in the field all to lower case

Solution

We attach the lower case convesion to the BIND event of the Form, using addEventListener() method.

From Types vs. Instances

Form Type

  • exists once in you app
  • like “text” or “datetime”

Form Instance

  • will change during the life of your app

Practical REST – Hypermedia Application Development with Symfony

These are my quick notes on the talk held by Benjamin Eberlei & Lukas Smith on the Symfony Live 2012 Berlin

Intro

REST are the set of conventions. After all – it will just make your application design better.

Standard Symfony2

Just a simple CRUD. Considering that the Entity will be displayed on the web page, the form for editing the entity will be either redesplayed (invalid) or forwarded to another view.

Standard Controller Workflow

  • Convert Request to Model Request
  • Delegate action to Model
  • Handle failures
  • Pick view
  • Pass model to vew

HTTP Controller Responsibilities

  • Request Body Deserialization
    • application/x-www-form-urlencoded
    • application/json or text/xml
  • View picked up by Accept Heacher/{_format}
  • View requires different logic
  • HTTP Status code
  • HTTP Heaaders (Cache, Allow, …)

HTTP Controller

  • User can pick the Input / Output format (=AE just like in Rails)
    • html
    • xml
    • json
  • Request Body Deserialization depending on the user specified Input format
    • check the request object and

      • json_decode
      • xml decode
  • Multiple Views depending on the user specified Output format
    • html
    • json
    • xml

    • Use Serializer Bundle for this

The standard way to handle REST in Symfony2 is way to complex – which leads to way to big controllers.

Alternatives to Standard Symfony2

FOSRestBundle

View Handler

public function indexAction(Request $request)
    {
        $em - $this-&gt;get('doctrine.orm.default_entity_manager');
        $entities = $em-&gt;getRepository('AcmeBundle:Message')-&gt;findAll();

        return View($entities)
    }

Potential pitfall is that all of the code above is largely based on convetions – be clear with your team about those.

Before / After Hooks

  • ‘kernel.controller’ Event
    • Before Hook
  • ‘kernel.view’ Event
    • Convention based Template Names
  • ‘kernel.response’ Event
    • After Hook
    • Convention based Caching

Parameter Converting

The idea is to minimize fetching code.

  • SensioFrameworkExtraBundle
  • ParamFetching in FOSRestBundle
  • KnpLabs RAD Bundle

Controller Helper

  • Inrodcue your own base class or trait to handle general quick helpers
  • Better: iIntroduce ControllerUtils service
  • Remove code duplication by moving code into service
  • Inject ControllerUtils into controllers

    class ControllerUtils 
    {
        public function redirectRoute($name, $params)
        {
            return $this->redirect($this->generateUrl(...))
        }
    }

Dependency Injection & Controller

  • $this->get() repetition
  • Possible Solutions

    • Controller as a Service
    • DIExtraBundle
    • PublicProperty Injection

      class MessageController
      {
      public $entityManager;
      public $messageRepository;
      }

      inject_services:
      entityManager: doctrine.orm.default_enitity_manager
      messageRepository: acme_rad.repository.message

Routing

  • REST is resource based
  • Non-REST apps are controller based
  • Route helper methods can learn conventions

    class UserController
    {
        // "get_users" [GET] /users
        public function getUsersAction() {}
    
    
    
    // POST /users
    public function postUsersAction() {}

    }

Conventions simplifz Testing

  • Non-functional controller tests
  • Build mocks based on conventions

Forms

Way too much glue code magnet code to handle forms in the controller

Why using Form with REST?
– Powerful Request to Object mapper
– Associations
– Embedded resources
– Property Paths
– Converter, normalizer, and filter included

Ideas for Forms usage
– Convention based FormType detection
– Move more code into Form mapping

Security And AOP in Symfony2

These are my quick notes of the talk that was given by (Johannes Schmitt)[https://github.com/schmittjoh] on the Symfony Live Berlin 2012

Authentication

The user is the one who he claims to be.

Mechanisms

  • HTTP-basic/digest
  • x.509 client certificate
  • Fomr-based login
  • Remember-me cookie
  • Your own …

Authentication system

FirewallListener -> FirewallMap -> Listeners -> Token <-> AuthenticationProvider -> UserProvider -> Encoder -> UserChecker

AccountInterfce

  • getRoles()
  • getPassword()
  • getSalt()
  • eraseCredentials()
  • equals(AccountInterface)

Encoders

  • MessageDigestPasswordEncoder
    • any algorithm supported by the hash() function
    • can automatically encode passwords using base64
  • PlaintextPasswordEncoder

AuthFailureHandler

SessionAuthStrategy

  • What happens – the process

AuthSeuccessHandler

  • Redirects to the Homepage / Userpage

RememberMe

LogoutHandler

  • Does not create response – just deletes the session

LogoutHandlerStrategy

  • Redirection

Authentication Levels

  • Anonymous
  • Remember-Me
  • Full-Fledged

Authorization

Does the user have the permissions?

Mechanisms

  • Request Authoriyation
  • Controller Actions/Methods Authoriyation
  • Class-/Object-based authorization (ACL)

AccessListener

AccessDecisionManager (Voters: AclVoter, RoleVoter, AuthenticatedVoter)
  • Affirmative
  • Unanimous
  • Consensus

SecurityContext

MethodSecurity

ACL

  • You model is decoupled from the ACL
  • Object-/Class-based access
  • Fileds-based access
  • Acess Controll Entry
    • mask

      • permissions are stored as bitmasks (zeros and ones)
      • up to 31 permissions per class
    • granting strategy
      • Any Strategy: $expected & $actual !== 0
      • All Strategy: $expected & $actual === $actual
      • Same Strategy: $expeted === $actual
    • granting
      • Allow or deny access?

        • Granting Entry for the Class as a whole
        • Deny Entry for several specific objects
    • can be inherited
      • Posts < Thread < Forum

Modernisation of legacy PHP application using Symfony 2

This talk was given by Fabrice Bernhard on the Symfony Live 2012 Berlin

  • The need fo progressive rewrite
  • The technical challenges and the solutions
  • The future

Spagetti-age

What we lack is well-desined decoupled and tested architecture that would be more flexible – allowing FAST rewrites.

Why not rewrite from scratch?

  • Not agile – you need to do so much, untill the version is finished
  • Doubles the number of developers – for supporting the old version
  • Probabilyt to forget the hidden features is high

Steps

  • Preventing regressions
    • By definition, spaghetti code is deeply coupled. Touching one part breaks something at the other end
    • Functional Testing – Mink + ZombieJS: https//github.com/Behat/Mink
    • The best functional testing – put it in production
    • Develop the FAST deploying (Capibara etc.)
  • Upgrading the system
    • Symfony2 requires:

      • PHP 5.3.3
      • Sqlite3, JSON, ctype
      • date.timezone set in php.ini
    • Phpcs CodeSniffs fro 5.3 and 5.4 https://github.com/wimg/PHPCompability
  • Routing
    • Extend the Controller with your LegacyController to replace {filename}.php with Routing
  • Sharing the layout
    • The same template – so you can make the changes on the place
    • Use
  • Sharing the session/auth
    • There is a Bundle for that
  • Decoupling the code
    • Use Facade Pattern to introduce API which connects the old pieces of code
  • Migrating the data
    • Copy the DB
    • Provide the sync between two DBs
      • ETL – Kettle
      • Write only into one DB not to currupt the data
    • Probably switch from MySQL to MongoDB?
      • Data in different versions
      • Use @mongoDB\Preload – “just in time migration”

Screencast Series – Creating Own PHP Framework Using Symfony2 Components – Episode 10


Caching

It is time to take a look at the “most important piece of code in the HttpKernel component”, as Fabien Potencier himself puts it – HttpKernelInterface. Among other things this interface supports native HTTP-Caching, the feature we’ll be exporing in this episode.

Implement the Interface

First off we’ll refactor our Framework class to implement the HttpKernelInterface – this way we get all the useful feautres transparently added

// /src/Framework.php

use Symfony\Component\HttpKernle\HttpKernelInterface;

class Framework implements HttpKernelInterface
{
	...
	
	public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
}

Change the front controller

We need to tell our front controller that it needs to use HttpCache component before handling the request

// /web/front.php

use Symofny\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Coponent\HttpKernel\HttpCache\Store;

... 
$framework = new Simplex\Framework($dispatcher, $matcher, $resolver);
$framework = new HttpCache($framework, new Store(__DIR__.'/../cache'));
...

$framework->handle($request)->send();

Time to Live

Even if our framework is extremely fast, we can always use some help in the performace-optimization front. That is how we cache our respone for 10 seconds.

// /src/Calendar/Controller/LeapYearController.php
...
if ($leapyear->isLeapYear($year[0])) {
            $response = new Response('Yep, this is a leap year! Have fun!');
        } else {
            $response = new Response('Nope, this is not a leap year. Wait for it.');
        }

        $response->setTtl(10);

        return $response;
...

We can easily see the results, if we modify our front controller to get some additional information about response. To do that we just fire up the Request statically in code

// /web/front.php
date_default_timezone_set('Europe/Berlin');
... 
$request = Request::create('/is_leap_year/2012');
...
echo $response . "\n";

and test it out in out from our command line directly

php web/front.php

And call the front controller from the browser directly. As you can see we get the full stack of HTTP information – with the activated cache control set to 10 and with the changing Age value:

Screencast Series – Creating Own PHP Framework Using Symfony2 Components – Episode 9


Building Hooks Drupal-Style

We’ve gone a long way to build a nicely decoupled and object-oriented Framework. One thing is missing though – to add the possibility to extend our Framework with the arbitrary code modules.
In this episode we are going to build a “module” that will hook into the live cycle of the Framework – just like Drupal modules do!

EventDispatcher Component

We’re going to use a handy Symfony2 Component that provides a realisation of the Observer pattern – EventDispatcher. Let’s add it to our Composer configuration file:

// composer.json
{
    "require": {
        "symfony/class-loader": "2.1.*",
         "symfony/http-foundation": "2.1.*",
         "symfony/routing": "2.1.*",
         "symfony/http-kernel": "2.1.*",
         "symfony/event-dispatcher": "2.1.*"
},
    "autoload": {
        "psr-0": { "Simplex": "src/", "Calendar": "src/" }
}
}

Introducing the Dispatcher to Simplex Framework

Let’s refactor the Framework code to register a new event called “response” event

<?php
// /src/Simplex/Framework.php

namespace Simplex;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\EventDispatcher\EventDispatcher;

class Framework
{
    protected $matcher;
    protected $resolver;
    protected $dispatcher;

    public function __construct($dispatcher, $matcher, $resolver)
    {
        $this->matcher = $matcher;
        $this->resolver = $resolver;
        $this->dispatcher = $dispatcher;
    }

    public function handle(Request $request)
    {
        try {
            $request->attributes->add($this->matcher->match($request->getPathInfo()));

            $controller = $this->resolver->getController($request);
            $arguments = $this->resolver->getArguments($request, $controller);

            $response =  call_user_func($controller, $arguments);
        } catch (ResourceNotFoundException $e) {
            $response =  new Response('Oooops, we did not find this page!', 404);
        } catch (\Exception $e) {
            $response =  new Response('Wow, something is really broken here!', 500);
        }

        // A new event - Response
        $this->dispatcher->dispatch('response', new ResponseEvent($response, $request));

        return $response;
    }
}

ResponseEvent class

As you’ve noticed we’re using the class ResponseEvent. That is how it looks like

<?php
// /src/Simple/ResponseEvent.php

namespace Simplex;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\Event;

class ResponseEvent extends Event
{

    private $response;
    private $request;

    public function __construct($response, $request)
    {
        $this->request = $request;
        $this->response = $response;
    }

    public function getResponse()
    {
        return $this->response;
    }

    public function getRequest()
    {
        return $this->request;
    }

}

Updating the Front Controller

Now it is time to actually include our dispatcher in the fron controller and add an Event Listener to it

<?php
// /web/front.php

...
use Symfony\Component\EventDispatcher\EventDispatcher;
...

$dispatcher = new EventDispatcher();
$dispatcher->addListener('response', function(Simplex\ResponseEvent $event) {
   $response = $event->getResponse();

   if ($response->isRedirection()
       || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
       || 'html' !== $event->getRequest()->getRequestFormat()
   ) {
       return;
   }

    $response->setContent($response->getContent() . 'GA CODE GOES HERE');
});

$framework = new Simplex\Framework($dispatcher, $matcher, $resolver);
$response = $framework->handle($request);

$response->send();

Refactoring Time!

So far, so good. But as always there is a room for perfection. So we move the code that does not belong in the Front Controller to a separate class

<?php
// /src/Simplex/GoogleListener.php

namespace Simplex;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class GoogleListener implements EventSubscriberInterface
{
    public function onResponse(ResponseEvent $event)
    {
        $response = $event->getResponse();

        if ($response->isRedirection()
            || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
            || 'html' !== $event->getRequest()->getRequestFormat()
        ) {
            return;
        }

        $response->setContent($response->getContent() . 'GA CODE GOES HERE');
    }

    public static function getSubscribedEvents()
    {
        return array('response' => 'onResponse');
    }


}

And since we have implemented EventSubscriberInterface we’ll be making a minor change in the Front Controller to make our Framework even more decoupled – no need to provide the event and method names there, it’s all taken care of in the Listener class

// /web/front.php

...

$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new Simplex\GoogleListener());

...