Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commitdb0ad14

Browse files
committed
added part 5
1 parentbd3ca8e commitdb0ad14

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

‎book/part5.rst

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
Create your own framework... on top of the Symfony2 Components (part 5)
2+
=======================================================================
3+
4+
The astute reader has noticed that our framework hardcodes the way specific
5+
"code" (the templates) is run. For simple pages like the ones we have created
6+
so far, that's not a problem, but if you want to add more logic, you would be
7+
forced to put the logic into the template itself, which is probably not a good
8+
idea, especially if you still have the separation of concerns principle in
9+
mind.
10+
11+
Let's separate the template code from the logic by adding a new layer: the
12+
controller: *The controller mission is to generate a Response based on the
13+
information conveyed by the client Request.*
14+
15+
Change the template rendering part of the framework to read as follows::
16+
17+
<?php
18+
19+
// example.com/web/front.php
20+
21+
// ...
22+
23+
try {
24+
$request->attributes->add($matcher->match($request->getPathInfo()));
25+
$response = call_user_func('render_template', $request);
26+
} catch (Routing\Exception\ResourceNotFoundException $e) {
27+
$response = new Response('Not Found', 404);
28+
} catch (Exception $e) {
29+
$response = new Response('An error occurred', 500);
30+
}
31+
32+
As the rendering is now done by an external function (``render_template()``
33+
here), we need to pass to it the attributes extracted from the URL. We could
34+
have passed them as an additional argument to ``render_template()``, but
35+
instead, let's use another feature of the ``Request`` class called
36+
*attributes*: Request attributes lets you attach additional information about
37+
the Request that is not directly related to the HTTP Request data.
38+
39+
You can now create the ``render_template()`` function, a generic controller
40+
that renders a template when there is no specific logic. To keep the same
41+
template as before, request attributes are extracted before the template is
42+
rendered::
43+
44+
function render_template($request)
45+
{
46+
extract($request->attributes->all(), EXTR_SKIP);
47+
ob_start();
48+
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
49+
50+
return new Response(ob_get_clean());
51+
}
52+
53+
As ``render_template`` is used as an argument to the PHP ``call_user_func()``
54+
function, we can replace it with any valid PHP `callbacks`_. This allows us to
55+
use a function, an anonymous function, or a method of a class as a
56+
controller... your choice.
57+
58+
As a convention, for each route, the associated controller is configured via
59+
the ``_controller`` route attribute::
60+
61+
$routes->add('hello', new Routing\Route('/hello/{name}', array(
62+
'name' => 'World',
63+
'_controller' => 'render_template',
64+
)));
65+
66+
try {
67+
$request->attributes->add($matcher->match($request->getPathInfo()));
68+
$response = call_user_func($request->attributes->get('_controller'), $request);
69+
} catch (Routing\Exception\ResourceNotFoundException $e) {
70+
$response = new Response('Not Found', 404);
71+
} catch (Exception $e) {
72+
$response = new Response('An error occurred', 500);
73+
}
74+
75+
A route can now be associated with any controller and of course, within a
76+
controller, you can still use the ``render_template()`` to render a template::
77+
78+
$routes->add('hello', new Routing\Route('/hello/{name}', array(
79+
'name' => 'World',
80+
'_controller' => function ($request) {
81+
return render_template($request);
82+
}
83+
)));
84+
85+
This is rather flexible as you can change the Response object afterwards and
86+
you can even pass additional arguments to the template::
87+
88+
$routes->add('hello', new Routing\Route('/hello/{name}', array(
89+
'name' => 'World',
90+
'_controller' => function ($request) {
91+
// $foo will be available in the template
92+
$request->attributes->set('foo', 'bar');
93+
94+
$response = render_template($request);
95+
96+
// change some header
97+
$response->headers->set('Content-Type', 'text/plain');
98+
99+
return $response;
100+
}
101+
)));
102+
103+
Here is the updated and improved version of our framework::
104+
105+
<?php
106+
107+
// example.com/web/front.php
108+
109+
require_once __DIR__.'/../vendor/.composer/autoload.php';
110+
111+
use Symfony\Component\HttpFoundation\Request;
112+
use Symfony\Component\HttpFoundation\Response;
113+
use Symfony\Component\Routing;
114+
115+
function render_template($request)
116+
{
117+
extract($request->attributes->all(), EXTR_SKIP);
118+
ob_start();
119+
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
120+
121+
return new Response(ob_get_clean());
122+
}
123+
124+
$request = Request::createFromGlobals();
125+
$routes = include __DIR__.'/../src/app.php';
126+
127+
$context = new Routing\RequestContext();
128+
$context->fromRequest($request);
129+
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
130+
131+
try {
132+
$request->attributes->add($matcher->match($request->getPathInfo()));
133+
$response = call_user_func($request->attributes->get('_controller'), $request);
134+
} catch (Routing\Exception\ResourceNotFoundException $e) {
135+
$response = new Response('Not Found', 404);
136+
} catch (Exception $e) {
137+
$response = new Response('An error occurred', 500);
138+
}
139+
140+
$response->send();
141+
142+
To celebrate the birth of our new framework, let's create a brand new
143+
application that needs some simple logic. Our application has one page that
144+
says whether a given year is a leap year or not. When calling
145+
``/is_leap_year``, you get the answer for the current year, but the you can
146+
also specify a year like in ``/is_leap_year/2009``. Being generic, the
147+
framework does not need to be modified in any way, just create a new
148+
``app.php`` file::
149+
150+
<?php
151+
152+
// example.com/src/app.php
153+
154+
use Symfony\Component\Routing;
155+
use Symfony\Component\HttpFoundation\Response;
156+
157+
function is_leap_year($year = null) {
158+
if (null === $year) {
159+
$year = date('Y');
160+
}
161+
162+
return 0 == $year % 400 || (0 == $year % 4 && 0 != $year % 100);
163+
}
164+
165+
$routes = new Routing\RouteCollection();
166+
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
167+
'year' => null,
168+
'_controller' => function ($request) {
169+
if (is_leap_year($request->attributes->get('year'))) {
170+
return new Response('Yep, this is a leap year!');
171+
}
172+
173+
return new Response('Nope, this is not a leap year.');
174+
}
175+
)));
176+
177+
return $routes;
178+
179+
The ``is_leap_year()`` function returns ``true`` when the given year is a leap
180+
year, ``false`` otherwise. If the year is null, the current year is tested.
181+
The controller is simple: it gets the year from the request attributes, pass
182+
it to the `is_leap_year()`` function, and according to the return value it
183+
creates a new Response object.
184+
185+
As always, you can decide to stop here and use the framework as is; it's
186+
probably all you need to create simple websites like those fancy one-page
187+
`websites`_ and hopefully a few others.
188+
189+
.. _`callbacks`:http://php.net/callback#language.types.callback
190+
.. _`websites`:http://kottke.org/08/02/single-serving-sites

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp