Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9bc692f

Browse filesBrowse files
committed
added part 12
1 parent 0b7581d commit 9bc692f
Copy full SHA for 9bc692f

File tree

Expand file treeCollapse file tree

1 file changed

+256
-0
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+256
-0
lines changed

‎book/part12.rst

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

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.