Skip to content

Commit 358b4c8

Browse files
committed
added part 4
1 parent 255577f commit 358b4c8

File tree

1 file changed

+253
-0
lines changed

1 file changed

+253
-0
lines changed

book/part4.rst

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
Create your own framework... on top of the Symfony2 Components (part 4)
2+
=======================================================================
3+
4+
Before we start with today's topic, let's refactor our current framework just
5+
a little to make templates even more readable::
6+
7+
<?php
8+
9+
// example.com/web/front.php
10+
11+
require_once __DIR__.'/../src/autoload.php';
12+
13+
use Symfony\Component\HttpFoundation\Request;
14+
use Symfony\Component\HttpFoundation\Response;
15+
16+
$request = Request::createFromGlobals();
17+
18+
$map = array(
19+
'/hello' => 'hello',
20+
'/bye' => 'bye',
21+
);
22+
23+
$path = $request->getPathInfo();
24+
if (isset($map[$path])) {
25+
ob_start();
26+
extract($request->query->all());
27+
include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]);
28+
$response = new Response(ob_get_clean());
29+
} else {
30+
$response = new Response('Not Found', 404);
31+
}
32+
33+
$response->send();
34+
35+
As we now extract the request query parameters, simplify the ``hello.php``
36+
template as follows::
37+
38+
<!-- example.com/src/pages/hello.php -->
39+
40+
Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
41+
42+
Now, we are in good shape to add new features.
43+
44+
One very important aspect of any website is the form of its URLs. Thanks to
45+
the URL map, we have decoupled the URL from the code that generates the
46+
associated response, but it is not yet flexible enough. For instance, we might
47+
want to support dynamic paths to allow embedding data directly into the URL
48+
instead of relying on a query string:
49+
50+
# Before
51+
/hello?name=Fabien
52+
53+
# After
54+
/hello/Fabien
55+
56+
To support this feature, we are going to use the Symfony2 Routing component.
57+
As always, add it to ``composer.json`` and run the ``php composer.phar
58+
update`` command to install it::
59+
60+
.. code-block:: json
61+
62+
{
63+
"require": {
64+
"symfony/class-loader": "2.1.*",
65+
"symfony/http-foundation": "2.1.*",
66+
"symfony/routing": "2.1.*"
67+
}
68+
}
69+
70+
From now on, we are going to use the generated Composer autoloader instead of
71+
our own ``autoload.php``. Remove the ``autoload.php`` file and replace its
72+
reference in ``front.php``::
73+
74+
<?php
75+
76+
// example.com/web/front.php
77+
78+
require_once __DIR__.'/../vendor/.composer/autoload.php';
79+
80+
// ...
81+
82+
Instead of an array for the URL map, the Routing component relies on a
83+
``RouteCollection`` instance::
84+
85+
use Symfony\Component\Routing\RouteCollection;
86+
87+
$routes = new RouteCollection();
88+
89+
Let's add a route that describe the ``/hello/SOMETHING`` URL and add another
90+
one for the simple ``/bye`` one::
91+
92+
use Symfony\Component\Routing\Route;
93+
94+
$routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));
95+
$routes->add('bye', new Route('/bye'));
96+
97+
Each entry in the collection is defined by a name (``hello``) and a ``Route``
98+
instance, which is defined by a route pattern (``/hello/{name}``) and an array
99+
of default values for route attributes (``array('name' => 'World')``).
100+
101+
.. note::
102+
103+
Read the official `documentation`_ for the Routing component to learn more
104+
about its many features like URL generation, attribute requirements, HTTP
105+
method enforcements, loaders for YAML or XML files, dumpers to PHP or
106+
Apache rewrite rules for enhanced performance, and much more.
107+
108+
Based on the information stored in the ``RouteCollection`` instance, a
109+
``UrlMatcher`` instance can match URL paths::
110+
111+
use Symfony\Component\Routing\RequestContext;
112+
use Symfony\Component\Routing\Matcher\UrlMatcher;
113+
114+
$context = new RequestContext();
115+
$context->fromRequest($request);
116+
$matcher = new UrlMatcher($routes, $context);
117+
118+
$attributes = $matcher->match($request->getPathInfo());
119+
120+
The ``match()`` method takes a request path and returns an array of attributes
121+
(notice that the matched route is automatically stored under the special
122+
``_route`` attribute)::
123+
124+
print_r($matcher->match('/bye'));
125+
array (
126+
'_route' => 'bye',
127+
);
128+
129+
print_r($matcher->match('/hello/Fabien'));
130+
array (
131+
'name' => 'Fabien',
132+
'_route' => 'hello',
133+
);
134+
135+
print_r($matcher->match('/hello'));
136+
array (
137+
'name' => 'World',
138+
'_route' => 'hello',
139+
);
140+
141+
.. note::
142+
143+
Even if we don't strictly need the request context in our examples, it is
144+
used in real-world applications to enforce method requirements and more.
145+
146+
The URL matcher throws an exception when none of the routes match::
147+
148+
$matcher->match('/not-found');
149+
150+
// throws a Symfony\Component\Routing\Exception\ResourceNotFoundException
151+
152+
With this knowledge in mind, let's write the new version of our framework::
153+
154+
<?php
155+
156+
// example.com/web/front.php
157+
158+
require_once __DIR__.'/../vendor/.composer/autoload.php';
159+
160+
use Symfony\Component\HttpFoundation\Request;
161+
use Symfony\Component\HttpFoundation\Response;
162+
use Symfony\Component\Routing;
163+
164+
$request = Request::createFromGlobals();
165+
$routes = include __DIR__.'/../src/app.php';
166+
167+
$context = new Routing\RequestContext();
168+
$context->fromRequest($request);
169+
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
170+
171+
try {
172+
extract($matcher->match($request->getPathInfo()));
173+
ob_start();
174+
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
175+
176+
$response = new Response(ob_get_clean());
177+
} catch (Routing\Exception\ResourceNotFoundException $e) {
178+
$response = new Response('Not Found', 404);
179+
} catch (Exception $e) {
180+
$response = new Response('An error occurred', 500);
181+
}
182+
183+
$response->send();
184+
185+
There are a few new things in the code::
186+
187+
* Route names are used for template names;
188+
189+
* ``500`` errors are now managed correctly;
190+
191+
* Request attributes are extracted to keep our templates simple::
192+
193+
<!-- example.com/src/pages/hello.php -->
194+
195+
Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
196+
197+
* Routes configuration has been moved to its own file:
198+
199+
.. code-block:: php
200+
201+
<?php
202+
203+
// example.com/src/app.php
204+
205+
use Symfony\Component\Routing;
206+
207+
$routes = new Routing\RouteCollection();
208+
$routes->add('hello', new Routing\Route('/hello/{name}', array('name' => 'World')));
209+
$routes->add('bye', new Routing\Route('/bye'));
210+
211+
We now have a clear separation between the configuration (everything
212+
specific to our application in ``app.php``) and the framework (the generic
213+
code that powers our application in ``front.php``).
214+
215+
With less than 30 lines of code, we have a new framework, more powerful and
216+
more flexible than the previous one. Enjoy!
217+
218+
Using the Routing component has one big additional benefit: the ability to
219+
generate URLs based on Route definitions. When using both URL matching and URL
220+
generation in your code, changing the URL patterns should have no other
221+
impact. Want to know how to use the generator? Insanely easy::
222+
223+
use Symfony\Component\Routing;
224+
225+
$generator = new Routing\Generator\UrlGenerator($routes, $context);
226+
227+
echo $generator->generate('hello', array('name' => 'Fabien));
228+
// outputs /hello/Fabien
229+
230+
The code should be self-explanatory; and thanks to the context, you can even
231+
generate absolute URLs::
232+
233+
echo $generator->generate('hello', array('name' => 'Fabien), true);
234+
// outputs something like http://example.com/somewhere/hello/Fabien
235+
236+
.. tip::
237+
238+
Concerned about performance? Based on your route definitions, create a
239+
highly optimized URL matcher class that can replace the default
240+
``UrlMatcher``::
241+
242+
$dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes);
243+
244+
echo $dumper->dump();
245+
246+
Want even more performance? Dump your routes as a set of Apache rewrite
247+
rules::
248+
249+
$dumper = new Routing\Matcher\Dumper\ApacheMatcherDumper($routes);
250+
251+
echo $dumper->dump();
252+
253+
.. _`documentation`: http://symfony.com/doc/current/components/routing.html

0 commit comments

Comments
 (0)