Skip to content

Commit

Permalink
feature #5424 Integrate the "Create your own framework" tutorial (fab…
Browse files Browse the repository at this point in the history
…pot, lyrixx, jdreesen, catchamonkey, gnugat, andreia, Arnaud Kleinpeter, willdurand, amitayh, nanocom, hrbonz, Pedro Gimenez, ubick, dirkaholic, bamarni, revollat, javiereguiluz)

This PR was merged into the 2.3 branch.

Discussion
----------

Integrate the "Create your own framework" tutorial

This is a follow-up of the stalled #4455 PR. I've made most, but not all, of the changes proposed by reviewers.

Specifically, I haven't changed the occurrences of `we` by `you`. The reason is that it's too much work. The original PR (submitted 8 months ago) died for that reason. So here it is my question: could we bypass the `we -> you` changes to merge it as soon as possible and fix that in further pull requests?

Commits
-------

bca9bae Updated doc references in the map.rst.inc file
108f109 Removed the numbers from the file names
db9fa43 Made a lot of improvements suggested by reviewers
126bcef removed external references alignement
13a7170 made some changes to better integrate the tutorial into the current documentation
bf9c871 fixed markup
d44e4a2 added the new tutorial in the main index
73cd243 added the "Create your own framework" tutorial
409dba5 move things around
f303b20 removed versions when adding Symfony component with Composer
55f5c12 updated the whole book (changes mainly related to Composer)
842e4d1 bug #25 Update part06.rst (revollat)
a1336e0 Update part06.rst
d802d42 updated code for Symfony 2.3 and made minor tweaks to the text
f3c151c reworded slightly some sentence to convert the text from a series of articles to a book
ca9d5d8 removed unused references
249b704 added missing links
f6656e4 removed a note that is not relevant anymore
10e2732 added an index file
c88c20a removed usage of the ClassLoader component in favor of Composer
2ddd8b9 merged branch bamarni/master (PR #24)
799e963 updated composer autoload path
91e46f6 removed the paragraph about CS as we now have standards
43e2e35 Merge pull request #23 from dirkaholic/master
6e01599 Fix little typo
e8c19f7 Merge pull request #22 from ubick/master
8e7106d Fixed a typo in part02.rst (<php changed to <?php)
c087780 merged branch amitayh/master (PR #17)
0d6beb1 merged branch pedrotgimenez/part11 (PR #21)
60617d7 Fixed one typo.
aae0705 fixed CS
9533f9a merged branch hrbonz/part12_fixes (PR #19)
d0ff8bc updated titles
400c087 add framework code as people would probably modify it following first code example (putting object creations in src/Simplex/Framework.php)
de69a87 HttpKernel name can't be imported twice, if importing only Symfony\Component\HttpKernel\HttpKernel there will be problems later with HttpKernel subclasses (HttpKernel\Controller\ControllerResolver first and the others following). Could use 'use ... as ...' but I don't like it.
006b1e2 fixed markup
1f43dbf removed conflict merge
0941f47 merged branch Nanocom/master (PR #18)
d907d46 Fixed typos
db59374 fixed typo
11886e6 Merge branch 'master' of https://github.com/amitayh/Create-Your-Framework
a8a2da0 Fixed part 12 typos
26bef8d Fixed typos
9bc692f added part 12
0b7581d merged branch willdurand/patch-1 (PR #16)
16b5b09 Fixed typo
831adec merged branch Nanocom/master (PR #15)
a3f0b31 Corrected few typos
ae1171a merged branch jdreesen/patch-3 (PR #10)
7a8e449 merged branch Nanocom/master (PR #14)
09969d9 added part 11
76e45f9 fixed markup
fda9900 fixed pygments code name for json
02aab54 Corrected some english errors
8399581 moved the Context::fromRequest() code to the Framework class
111cac0 removed some use statement to be more consistent with previous parts
a635d89 added part 10
bde64c8 made small tweaks
54e1b08 added a note in part 8
f00401d added part 8
5674395 renamed part as we now know that we are going to have more than 10 parts
479a85e fixed typos
a0b1d42 merged branch gnugat/part7-typo-simple (PR #12)
9ffd186 [part7] Fixing typo: adding 'x' to Simple/Framework class
97743fb added part 7
fdb195c added part 6
0ce2a83 fixed some typos
db0ad14 added part 5
bd3ca8e fixed extract() calls
7e8da09 fixed typo (closes #8)
1473eec fixed typo
358b4c8 added part 4
255577f went back to 2.1.* in composer.json files
3d46b1d merged branch jdreesen/patch-2 (PR #7)
a18f827 fixed typos
18d376e updated composer.json
26be81c added part 3
4b39fc0 removed the path to composer.json as JSON doe snot support comments but some people copy/paste this line too
00e524e fixed ambiguity (closes #6)
6fe23c0 merged branch andreia/master (PR #5)
95edc8d Add missing colon to code-block
7e5cd41 merged branch gnugat/part2-typo-where-where (PR #4)
4c2e4f9 [part2] Fixinng typo: removing one of the double where
ee67eee fixed markup
94db3cb merged branch catchamonkey/patch-1 (PR #3)
a4f52d9 added links to projects
fcaf268 Fixes grammatical error
2c79d42 added part 2
13ba87c added a tip about the Composer autoloader
b93c118 merged branch jdreesen/patch-1 (PR #2)
269c6ce merged branch lyrixx/patch-1 (PR #1)
b239305 Fixed typo
8ed4076 Fixed typo
99c5c08 added a LICENSE file
0ef3f8e added part 1
  • Loading branch information
weaverryan committed Jul 2, 2015
2 parents 9cf3b1e + bca9bae commit e8d0d8e
Show file tree
Hide file tree
Showing 15 changed files with 2,629 additions and 0 deletions.
243 changes: 243 additions & 0 deletions create_framework/dependency-injection.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
The DependencyInjection Component
=================================

In the previous chapter, we emptied the ``Simplex\Framework`` class by
extending the ``HttpKernel`` class from the eponymous component. Seeing this
empty class, you might be tempted to move some code from the front controller
to it::

// example.com/src/Simplex/Framework.php

namespace Simplex;

use Symfony\Component\Routing;
use Symfony\Component\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;

class Framework extends HttpKernel\HttpKernel
{
public function __construct($routes)
{
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
$resolver = new HttpKernel\Controller\ControllerResolver();

$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher));
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));

parent::__construct($dispatcher, $resolver);
}
}

The front controller code would become more concise::

// example.com/web/front.php

require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';

$framework = new Simplex\Framework($routes);

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

Having a concise front controller allows you to have several front controllers
for a single application. Why would it be useful? To allow having different
configuration for the development environment and the production one for
instance. In the development environment, you might want to have error
reporting turned on and errors displayed in the browser to ease debugging::

ini_set('display_errors', 1);
error_reporting(-1);

... but you certainly won't want that same configuration on the production
environment. Having two different front controllers gives you the opportunity
to have a slightly different configuration for each of them.

So, moving code from the front controller to the framework class makes our
framework more configurable, but at the same time, it introduces a lot of
issues:

* We are not able to register custom listeners anymore as the dispatcher is
not available outside the Framework class (an easy workaround could be the
adding of a ``Framework::getEventDispatcher()`` method);

* We have lost the flexibility we had before; you cannot change the
implementation of the ``UrlMatcher`` or of the ``ControllerResolver``
anymore;

* Related to the previous point, we cannot test our framework easily anymore
as it's impossible to mock internal objects;

* We cannot change the charset passed to ``ResponseListener`` anymore (a
workaround could be to pass it as a constructor argument).

The previous code did not exhibit the same issues because we used dependency
injection; all dependencies of our objects were injected into their
constructors (for instance, the event dispatchers were injected into the
framework so that we had total control of its creation and configuration).

Does it mean that we have to make a choice between flexibility, customization,
ease of testing and not to copy and paste the same code into each application
front controller? As you might expect, there is a solution. We can solve all
these issues and some more by using the Symfony dependency injection
container:

.. code-block:: bash
$ composer require symfony/dependency-injection
Create a new file to host the dependency injection container configuration::

// example.com/src/container.php

use Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Reference;

$sc = new DependencyInjection\ContainerBuilder();
$sc->register('context', 'Symfony\Component\Routing\RequestContext');
$sc->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher')
->setArguments(array($routes, new Reference('context')))
;
$sc->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');

$sc->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener')
->setArguments(array(new Reference('matcher')))
;
$sc->register('listener.response', 'Symfony\Component\HttpKernel\EventListener\ResponseListener')
->setArguments(array('UTF-8'))
;
$sc->register('listener.exception', 'Symfony\Component\HttpKernel\EventListener\ExceptionListener')
->setArguments(array('Calendar\\Controller\\ErrorController::exceptionAction'))
;
$sc->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
->addMethodCall('addSubscriber', array(new Reference('listener.router')))
->addMethodCall('addSubscriber', array(new Reference('listener.response')))
->addMethodCall('addSubscriber', array(new Reference('listener.exception')))
;
$sc->register('framework', 'Simplex\Framework')
->setArguments(array(new Reference('dispatcher'), new Reference('resolver')))
;

return $sc;

The goal of this file is to configure your objects and their dependencies.
Nothing is instantiated during this configuration step. This is purely a
static description of the objects you need to manipulate and how to create
them. Objects will be created on-demand when you access them from the
container or when the container needs them to create other objects.

For instance, to create the router listener, we tell Symfony that its class
name is ``Symfony\Component\HttpKernel\EventListener\RouterListener``, and
that its constructor takes a matcher object (``new Reference('matcher')``). As
you can see, each object is referenced by a name, a string that uniquely
identifies each object. The name allows us to get an object and to reference
it in other object definitions.

.. note::

By default, every time you get an object from the container, it returns
the exact same instance. That's because a container manages your "global"
objects.

The front controller is now only about wiring everything together::

// example.com/web/front.php

require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

$routes = include __DIR__.'/../src/app.php';
$sc = include __DIR__.'/../src/container.php';

$request = Request::createFromGlobals();

$response = $sc->get('framework')->handle($request);

$response->send();

As all the objects are now created in the dependency injection container, the
framework code should be the previous simple version::

// example.com/src/Simplex/Framework.php

namespace Simplex;

use Symfony\Component\HttpKernel\HttpKernel;

class Framework extends HttpKernel
{
}

.. note::

If you want a light alternative for your container, consider `Pimple`_, a
simple dependency injection container in about 60 lines of PHP code.

Now, here is how you can register a custom listener in the front controller::

$sc->register('listener.string_response', 'Simplex\StringResponseListener');
$sc->getDefinition('dispatcher')
->addMethodCall('addSubscriber', array(new Reference('listener.string_response')))
;

Beside describing your objects, the dependency injection container can also be
configured via parameters. Let's create one that defines if we are in debug
mode or not::

$sc->setParameter('debug', true);

echo $sc->getParameter('debug');

These parameters can be used when defining object definitions. Let's make the
charset configurable::

$sc->register('listener.response', 'Symfony\Component\HttpKernel\EventListener\ResponseListener')
->setArguments(array('%charset%'))
;

After this change, you must set the charset before using the response listener
object::

$sc->setParameter('charset', 'UTF-8');

Instead of relying on the convention that the routes are defined by the
``$routes`` variables, let's use a parameter again::

$sc->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher')
->setArguments(array('%routes%', new Reference('context')))
;

And the related change in the front controller::

$sc->setParameter('routes', include __DIR__.'/../src/app.php');

We have obviously barely scratched the surface of what you can do with the
container: from class names as parameters, to overriding existing object
definitions, from scope support to dumping a container to a plain PHP class,
and much more. The Symfony dependency injection container is really powerful
and is able to manage any kind of PHP class.

Don't yell at me if you don't want to use a dependency injection container in
your framework. If you don't like it, don't use it. It's your framework, not
mine.

This is (already) the last chapter of this book on creating a framework on top
of the Symfony components. I'm aware that many topics have not been covered
in great details, but hopefully it gives you enough information to get started
on your own and to better understand how the Symfony framework works
internally.

If you want to learn more, read the source code of the `Silex`_
micro-framework, and especially its `Application`_ class.

Have fun!

.. _`Pimple`: https://github.com/fabpot/Pimple
.. _`Silex`: https://silex.sensiolabs.org/
.. _`Application`: https://github.com/fabpot/Silex/blob/master/src/Silex/Application.php
Loading

0 comments on commit e8d0d8e

Please sign in to comment.