Skip to content

Commit 0436ee7

Browse files
committed
Merge branch '2.7' into 2.8
* 2.7: (74 commits) Updated doc references in the map.rst.inc file Removed the numbers from the file names Made a lot of improvements suggested by reviewers removed external references alignement made some changes to better integrate the tutorial into the current documentation fixed markup added the new tutorial in the main index move things around removed versions when adding Symfony component with Composer updated the whole book (changes mainly related to Composer) Update part06.rst updated code for Symfony 2.3 and made minor tweaks to the text reworded slightly some sentence to convert the text from a series of articles to a book removed unused references added missing links removed a note that is not relevant anymore added an index file removed usage of the ClassLoader component in favor of Composer updated composer autoload path removed the paragraph about CS as we now have standards ...
2 parents bef92ec + b658ad4 commit 0436ee7

15 files changed

+2629
-0
lines changed

Diff for: create_framework/dependency-injection.rst

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
The DependencyInjection Component
2+
=================================
3+
4+
In the previous chapter, we emptied the ``Simplex\Framework`` class by
5+
extending the ``HttpKernel`` class from the eponymous component. Seeing this
6+
empty class, you might be tempted to move some code from the front controller
7+
to it::
8+
9+
// example.com/src/Simplex/Framework.php
10+
11+
namespace Simplex;
12+
13+
use Symfony\Component\Routing;
14+
use Symfony\Component\HttpKernel;
15+
use Symfony\Component\EventDispatcher\EventDispatcher;
16+
17+
class Framework extends HttpKernel\HttpKernel
18+
{
19+
public function __construct($routes)
20+
{
21+
$context = new Routing\RequestContext();
22+
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
23+
$resolver = new HttpKernel\Controller\ControllerResolver();
24+
25+
$dispatcher = new EventDispatcher();
26+
$dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher));
27+
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
28+
29+
parent::__construct($dispatcher, $resolver);
30+
}
31+
}
32+
33+
The front controller code would become more concise::
34+
35+
// example.com/web/front.php
36+
37+
require_once __DIR__.'/../vendor/autoload.php';
38+
39+
use Symfony\Component\HttpFoundation\Request;
40+
41+
$request = Request::createFromGlobals();
42+
$routes = include __DIR__.'/../src/app.php';
43+
44+
$framework = new Simplex\Framework($routes);
45+
46+
$framework->handle($request)->send();
47+
48+
Having a concise front controller allows you to have several front controllers
49+
for a single application. Why would it be useful? To allow having different
50+
configuration for the development environment and the production one for
51+
instance. In the development environment, you might want to have error
52+
reporting turned on and errors displayed in the browser to ease debugging::
53+
54+
ini_set('display_errors', 1);
55+
error_reporting(-1);
56+
57+
... but you certainly won't want that same configuration on the production
58+
environment. Having two different front controllers gives you the opportunity
59+
to have a slightly different configuration for each of them.
60+
61+
So, moving code from the front controller to the framework class makes our
62+
framework more configurable, but at the same time, it introduces a lot of
63+
issues:
64+
65+
* We are not able to register custom listeners anymore as the dispatcher is
66+
not available outside the Framework class (an easy workaround could be the
67+
adding of a ``Framework::getEventDispatcher()`` method);
68+
69+
* We have lost the flexibility we had before; you cannot change the
70+
implementation of the ``UrlMatcher`` or of the ``ControllerResolver``
71+
anymore;
72+
73+
* Related to the previous point, we cannot test our framework easily anymore
74+
as it's impossible to mock internal objects;
75+
76+
* We cannot change the charset passed to ``ResponseListener`` anymore (a
77+
workaround could be to pass it as a constructor argument).
78+
79+
The previous code did not exhibit the same issues because we used dependency
80+
injection; all dependencies of our objects were injected into their
81+
constructors (for instance, the event dispatchers were injected into the
82+
framework so that we had total control of its creation and configuration).
83+
84+
Does it mean that we have to make a choice between flexibility, customization,
85+
ease of testing and not to copy and paste the same code into each application
86+
front controller? As you might expect, there is a solution. We can solve all
87+
these issues and some more by using the Symfony dependency injection
88+
container:
89+
90+
.. code-block:: bash
91+
92+
$ composer require symfony/dependency-injection
93+
94+
Create a new file to host the dependency injection container configuration::
95+
96+
// example.com/src/container.php
97+
98+
use Symfony\Component\DependencyInjection;
99+
use Symfony\Component\DependencyInjection\Reference;
100+
101+
$sc = new DependencyInjection\ContainerBuilder();
102+
$sc->register('context', 'Symfony\Component\Routing\RequestContext');
103+
$sc->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher')
104+
->setArguments(array($routes, new Reference('context')))
105+
;
106+
$sc->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');
107+
108+
$sc->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener')
109+
->setArguments(array(new Reference('matcher')))
110+
;
111+
$sc->register('listener.response', 'Symfony\Component\HttpKernel\EventListener\ResponseListener')
112+
->setArguments(array('UTF-8'))
113+
;
114+
$sc->register('listener.exception', 'Symfony\Component\HttpKernel\EventListener\ExceptionListener')
115+
->setArguments(array('Calendar\\Controller\\ErrorController::exceptionAction'))
116+
;
117+
$sc->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
118+
->addMethodCall('addSubscriber', array(new Reference('listener.router')))
119+
->addMethodCall('addSubscriber', array(new Reference('listener.response')))
120+
->addMethodCall('addSubscriber', array(new Reference('listener.exception')))
121+
;
122+
$sc->register('framework', 'Simplex\Framework')
123+
->setArguments(array(new Reference('dispatcher'), new Reference('resolver')))
124+
;
125+
126+
return $sc;
127+
128+
The goal of this file is to configure your objects and their dependencies.
129+
Nothing is instantiated during this configuration step. This is purely a
130+
static description of the objects you need to manipulate and how to create
131+
them. Objects will be created on-demand when you access them from the
132+
container or when the container needs them to create other objects.
133+
134+
For instance, to create the router listener, we tell Symfony that its class
135+
name is ``Symfony\Component\HttpKernel\EventListener\RouterListener``, and
136+
that its constructor takes a matcher object (``new Reference('matcher')``). As
137+
you can see, each object is referenced by a name, a string that uniquely
138+
identifies each object. The name allows us to get an object and to reference
139+
it in other object definitions.
140+
141+
.. note::
142+
143+
By default, every time you get an object from the container, it returns
144+
the exact same instance. That's because a container manages your "global"
145+
objects.
146+
147+
The front controller is now only about wiring everything together::
148+
149+
// example.com/web/front.php
150+
151+
require_once __DIR__.'/../vendor/autoload.php';
152+
153+
use Symfony\Component\HttpFoundation\Request;
154+
155+
$routes = include __DIR__.'/../src/app.php';
156+
$sc = include __DIR__.'/../src/container.php';
157+
158+
$request = Request::createFromGlobals();
159+
160+
$response = $sc->get('framework')->handle($request);
161+
162+
$response->send();
163+
164+
As all the objects are now created in the dependency injection container, the
165+
framework code should be the previous simple version::
166+
167+
// example.com/src/Simplex/Framework.php
168+
169+
namespace Simplex;
170+
171+
use Symfony\Component\HttpKernel\HttpKernel;
172+
173+
class Framework extends HttpKernel
174+
{
175+
}
176+
177+
.. note::
178+
179+
If you want a light alternative for your container, consider `Pimple`_, a
180+
simple dependency injection container in about 60 lines of PHP code.
181+
182+
Now, here is how you can register a custom listener in the front controller::
183+
184+
$sc->register('listener.string_response', 'Simplex\StringResponseListener');
185+
$sc->getDefinition('dispatcher')
186+
->addMethodCall('addSubscriber', array(new Reference('listener.string_response')))
187+
;
188+
189+
Beside describing your objects, the dependency injection container can also be
190+
configured via parameters. Let's create one that defines if we are in debug
191+
mode or not::
192+
193+
$sc->setParameter('debug', true);
194+
195+
echo $sc->getParameter('debug');
196+
197+
These parameters can be used when defining object definitions. Let's make the
198+
charset configurable::
199+
200+
$sc->register('listener.response', 'Symfony\Component\HttpKernel\EventListener\ResponseListener')
201+
->setArguments(array('%charset%'))
202+
;
203+
204+
After this change, you must set the charset before using the response listener
205+
object::
206+
207+
$sc->setParameter('charset', 'UTF-8');
208+
209+
Instead of relying on the convention that the routes are defined by the
210+
``$routes`` variables, let's use a parameter again::
211+
212+
$sc->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher')
213+
->setArguments(array('%routes%', new Reference('context')))
214+
;
215+
216+
And the related change in the front controller::
217+
218+
$sc->setParameter('routes', include __DIR__.'/../src/app.php');
219+
220+
We have obviously barely scratched the surface of what you can do with the
221+
container: from class names as parameters, to overriding existing object
222+
definitions, from scope support to dumping a container to a plain PHP class,
223+
and much more. The Symfony dependency injection container is really powerful
224+
and is able to manage any kind of PHP class.
225+
226+
Don't yell at me if you don't want to use a dependency injection container in
227+
your framework. If you don't like it, don't use it. It's your framework, not
228+
mine.
229+
230+
This is (already) the last chapter of this book on creating a framework on top
231+
of the Symfony components. I'm aware that many topics have not been covered
232+
in great details, but hopefully it gives you enough information to get started
233+
on your own and to better understand how the Symfony framework works
234+
internally.
235+
236+
If you want to learn more, read the source code of the `Silex`_
237+
micro-framework, and especially its `Application`_ class.
238+
239+
Have fun!
240+
241+
.. _`Pimple`: https://github.com/fabpot/Pimple
242+
.. _`Silex`: https://silex.sensiolabs.org/
243+
.. _`Application`: https://github.com/fabpot/Silex/blob/master/src/Silex/Application.php

0 commit comments

Comments
 (0)