Skip to content

Commit 09969d9

Browse files
committed
added part 11
1 parent 76e45f9 commit 09969d9

File tree

1 file changed

+212
-0
lines changed

1 file changed

+212
-0
lines changed

book/part11.rst

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
Create your own framework... on top of the Symfony2 Components (part 11)
2+
========================================================================
3+
4+
If you were to use our framework right now, you would probably have to add
5+
support for custom error messages. Right now, we have 404 and 500 error
6+
support but the responses are hardcoded in the framework itself. Making them
7+
customizable is easy enough though: dispatch a new event and listen to it.
8+
Doing it right means that the listener have to call a regular controller. But
9+
what if the error controller throws an exception? You will end up in an
10+
infinite loop. There should be an easier way, right?
11+
12+
Enter the ``HttpKernel`` class. Instead of solving the same problem over and
13+
over again and instead of reinventing the wheel each time, the ``HttpKernel``
14+
class is a generic, extensible, and flexible implementation of
15+
``HttpKernelInterface``.
16+
17+
This class is very similar to the framework class we have written so far: it
18+
dispatches events at some strategic points during the handling of the request,
19+
it uses a controller resolver to choose the controller to dispatch the request
20+
to, and as an added bonus, it takes care of edge cases and provides great
21+
feedback when a problem arises.
22+
23+
Here is the new framework code::
24+
25+
<?php
26+
27+
// example.com/src/Simplex/Framework.php
28+
29+
namespace Simplex;
30+
31+
use Symfony\Component\HttpKernel\HttpKernel;
32+
33+
class Framework extends HttpKernel
34+
{
35+
}
36+
37+
And the new front controller::
38+
39+
<?php
40+
41+
// example.com/web/front.php
42+
43+
require_once __DIR__.'/../vendor/.composer/autoload.php';
44+
45+
use Symfony\Component\HttpFoundation\Request;
46+
use Symfony\Component\HttpFoundation\Response;
47+
use Symfony\Component\Routing;
48+
use Symfony\Component\HttpKernel;
49+
use Symfony\Component\EventDispatcher\EventDispatcher;
50+
51+
$request = Request::createFromGlobals();
52+
$routes = include __DIR__.'/../src/app.php';
53+
54+
$context = new Routing\RequestContext();
55+
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
56+
$resolver = new HttpKernel\Controller\ControllerResolver();
57+
58+
$dispatcher = new EventDispatcher();
59+
$dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher));
60+
61+
$framework = new Simplex\Framework($dispatcher, $resolver);
62+
63+
$response = $framework->handle($request);
64+
$response->send();
65+
66+
``RouterListener`` is an implementation of the same logic we had in our
67+
framework: it matches the incoming request and populates the request
68+
attributes with route parameters.
69+
70+
Our code is now much more concise and surprisingly more robust and more
71+
powerful than ever. For instance, use the built-in ``ExceptionListener`` to
72+
make your error management configurable::
73+
74+
$errorHandler = function (HttpKernel\Exception\FlattenException $exception) {
75+
$msg = 'Something went wrong! ('.$exception->getMessage().')';
76+
77+
return new Response($msg, $exception->getStatusCode());
78+
});
79+
$dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler);
80+
81+
``ExceptionListener`` gives you a ``FlattenException`` instance instead of the
82+
thrown ``Exception`` instance to ease exception manipulation and display. It
83+
can take any valid controller as an exception handler, so you can create an
84+
ErrorController class instead of using a Closure::
85+
86+
$listener = new HttpKernel\EventListener\ExceptionListener('Calendar\\Controller\\ErrorController::exceptionAction');
87+
$dispatcher->addSubscriber($listener);
88+
89+
The error controller reads as follows::
90+
91+
<?php
92+
93+
// example.com/src/Calendar/Controller/ErrorController.php
94+
95+
namespace Calendar\Controller;
96+
97+
use Symfony\Component\HttpFoundation\Response;
98+
use Symfony\Component\HttpKernel\Exception\FlattenException;
99+
100+
class ErrorController
101+
{
102+
public function exceptionAction(FlattenException $exception)
103+
{
104+
$msg = 'Something went wrong! ('.$exception->getMessage().')';
105+
106+
return new Response($msg, $exception->getStatusCode());
107+
}
108+
}
109+
110+
Voilà! Clean and customizable error management without efforts. And of course,
111+
of your controller throws an exception, HttpKernel will handle it nicely.
112+
113+
In part 2, we have talked about the ``Response::prepare()`` method, which
114+
ensures that a Response is compliant with the HTTP specification. It is
115+
probably a good idea to always call it just before sending the Response to the
116+
client; that's what the ``ResponseListener`` does::
117+
118+
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
119+
120+
This one was easy too! Let's take another one: do you want out of the box
121+
support for streamed responses? Just subscribe to
122+
``StreamedResponseListener``::
123+
124+
$dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener());
125+
126+
And in your controller, return a ``StreamedResponse`` instance instead of a
127+
``Response`` instance.
128+
129+
.. tip::
130+
131+
Read the `Internals`_ chapter of the Symfony2 documentation to learn more
132+
about the events dispatched by HttpKernel and how they allow you to change
133+
the flow of a request.
134+
135+
Now, let's create a listener, one that allows a controller to return a string
136+
instead of a full Response object::
137+
138+
class LeapYearController
139+
{
140+
public function indexAction(Request $request, $year)
141+
{
142+
$leapyear = new LeapYear();
143+
if ($leapyear->isLeapYear($year)) {
144+
return 'Yep, this is a leap year! ';
145+
}
146+
147+
return 'Nope, this is not a leap year.';
148+
}
149+
}
150+
151+
To implement this feature, we are going to listen to the ``kernel.view``
152+
event, which is triggered just after the controller has been called. Its goal
153+
is to convert the controller return value to a proper Response instance, but
154+
only if needed::
155+
156+
<?php
157+
158+
// example.com/src/Simplex/StringResponseListener.php
159+
160+
namespace Simplex;
161+
162+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
163+
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
164+
use Symfony\Component\HttpFoundation\Response;
165+
166+
class StringResponseListener implements EventSubscriberInterface
167+
{
168+
public function onView(GetResponseForControllerResultEvent $event)
169+
{
170+
$response = $event->getControllerResult();
171+
172+
if (is_string($response)) {
173+
$event->setResponse(new Response($response));
174+
}
175+
}
176+
177+
public static function getSubscribedEvents()
178+
{
179+
return array('kernel.view' => 'onView');
180+
}
181+
}
182+
183+
The code is simple because the ``kernel.view`` event is only triggered when
184+
the controller return value is not a Response and because setting the response
185+
on the event stops the event propagation (our listener cannot interfere with
186+
other view listeners).
187+
188+
Don't forget to register it in the front controller::
189+
190+
$dispatcher->addSubscriber(new Simplex\StringResponseListener());
191+
192+
.. note::
193+
194+
If you forget to register the subscriber, HttpKernel will throws an
195+
exception with a nice message: ``The controller must return a response
196+
(Nope, this is not a leap year. given).``.
197+
198+
At this point, our whole framework code is as compact as possible and it is
199+
mainly composed of an assembling of existing libraries. Extending is a matter
200+
of registering event listeners/subscribers.
201+
202+
Hopefully, you now have a better understanding of why the simple looking
203+
``HttpKernelInterface`` is so powerful. Its default implementation,
204+
``HttpKernel``, gives you access to a lot of cool features, ready to be used
205+
out of the box, with no efforts. And because HttpKernel is actually the code
206+
that powers the Symfony2 and Silex frameworks, you have the best of both
207+
worlds: a custom framework, tailored to your needs, but based on a rock-solid
208+
and well maintained low-level architecture that has been proven to work for
209+
many websites; a code that has been audited for security issues and that has
210+
proven to scale well.
211+
212+
.. _`Internals`: http://symfony.com/doc/current/book/internals.html#events

0 commit comments

Comments
 (0)