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

Commit26be81c

Browse files
committed
added part 3
1 parent4b39fc0 commit26be81c

File tree

1 file changed

+248
-0
lines changed

1 file changed

+248
-0
lines changed

‎book/part3.rst

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
Create your own framework... on top of the Symfony2 Components (part 3)
2+
=======================================================================
3+
4+
Up until now, our application is simplistic as there is only one page. To
5+
spice things up a little bit, let's go crazy and add another page that says
6+
goodbye::
7+
8+
<?php
9+
10+
// framework/bye.php
11+
12+
require_once __DIR__.'/autoload.php';
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\Response;
16+
17+
$request = Request::createFromGlobals();
18+
19+
$response = new Response('Goodbye!');
20+
$response->send();
21+
22+
As you can see for yourself, much of the code is exactly the same as the one
23+
we have written for the first page. Let's extract the common code that we can
24+
share between all our pages. Code sharing sounds like a good plan to create
25+
our first "real" framework!
26+
27+
The PHP way of doing the refactoring would probably be the creation of an
28+
include file::
29+
30+
<?php
31+
32+
// framework/init.php
33+
34+
require_once __DIR__.'/autoload.php';
35+
36+
use Symfony\Component\HttpFoundation\Request;
37+
use Symfony\Component\HttpFoundation\Response;
38+
39+
$request = Request::createFromGlobals();
40+
$response = new Response();
41+
42+
Let's see it in action::
43+
44+
<?php
45+
46+
// framework/index.php
47+
48+
require_once __DIR__.'/init.php';
49+
50+
$input = $request->get('name', 'World');
51+
52+
$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
53+
$response->send();
54+
55+
And for the "Goodbye" page::
56+
57+
<?php
58+
59+
// framework/bye.php
60+
61+
require_once __DIR__.'/init.php';
62+
63+
$response->setContent('Goodbye!');
64+
$response->send();
65+
66+
We have indeed moved most of the shared code into a central place, but it does
67+
not feel like a good abstraction, doesn't it? First, we still have the
68+
``send()`` method in all pages, then our pages does not look like templates,
69+
and we are still not able to test this code properly.
70+
71+
Moreover, adding a new page means that we need to create a new PHP script,
72+
which name is exposed to the end user via the URL
73+
(``http://example.com/goodbye.php``): there is a direct mapping between the PHP
74+
script name and the client URL. This is because the dispatching of the request
75+
is done by the web server directly. It might be a good idea to move this
76+
dispatching to our code for better flexibility. This can be easily achieved by
77+
routing all client requests to a single PHP script.
78+
79+
..tip::
80+
81+
Exposing a single PHP script to the end user is a design pattern called
82+
the "`front controller`_".
83+
84+
Such a script might look like the following::
85+
86+
<?php
87+
88+
// framework/front.php
89+
90+
require_once __DIR__.'/autoload.php';
91+
92+
use Symfony\Component\HttpFoundation\Request;
93+
use Symfony\Component\HttpFoundation\Response;
94+
95+
$request = Request::createFromGlobals();
96+
$response = new Response();
97+
98+
$map = array(
99+
'/hello' => __DIR__.'/hello.php',
100+
'/bye' => __DIR__.'/bye.php',
101+
);
102+
103+
$path = $request->getPathInfo();
104+
if (isset($map[$path])) {
105+
require $map[$path];
106+
} else {
107+
$response->setStatusCode(404);
108+
$response->setContent('Not Found');
109+
}
110+
111+
$response->send();
112+
113+
And here is for instance the new ``hello.php`` script::
114+
115+
<?php
116+
117+
// framework/hello.php
118+
119+
$input = $request->get('name', 'World');
120+
$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
121+
122+
In the ``front.php`` script, ``$map`` associates URL paths with their
123+
corresponding PHP script paths.
124+
125+
As a bonus, if the client ask for a path that is not defined in the URL map,
126+
we return a custom 404 page; you are now in control of your website.
127+
128+
To access a page, you must now use the ``front.php`` script:
129+
130+
* ``http://example.com/front.php/hello?name=Fabien``
131+
132+
* ``http://example.com/front.php/bye``
133+
134+
``/hello`` and ``/bye`` are the page *path*s.
135+
136+
..tip::
137+
138+
Most web servers like Apache or nginx are able to rewrite the incoming
139+
URLs and remove the front controller script so that your users will be
140+
able to type ``http://example.com/hello?name=Fabien``, which looks much
141+
better.
142+
143+
So, the trick is the usage of the ``Request::getPathInfo()`` method which
144+
returns the path of the Request by removing the front controller script name
145+
including its sub-directories (only if needed -- see above tip).
146+
147+
..tip::
148+
149+
You don't even need to setup a web server to test the code. Instead,
150+
replace the ``$request = Request::createFromGlobals();`` call to something
151+
like ``$request = Request::create('/hello?name=Fabien');`` where the
152+
argument is the URL path you want to simulate.
153+
154+
Now that the web server always access the same script (``front.php``) for all
155+
our pages, we can secure our code further by moving all other PHP files
156+
outside the web root directory:
157+
158+
example.com
159+
├── composer.json
160+
│ src
161+
│ ├── autoload.php
162+
│ └── pages
163+
│ ├── hello.php
164+
│ └── bye.php
165+
├── vendor
166+
└── web
167+
└── front.php
168+
169+
Now, configure your web server root directory to point to ``web/`` and all
170+
other files won't be accessible from the client anymore.
171+
172+
..note::
173+
174+
For this new structure to work, you will have to adjust some paths in
175+
various PHP files; the changes are left as an exercise for the reader.
176+
177+
The last thing that is repeated in each page is the call to ``setContent()``.
178+
We can convert all pages to "templates" by just echoing the content and
179+
calling the ``setContent()`` directly from the front controller script::
180+
181+
<?php
182+
183+
// example.com/web/front.php
184+
185+
// ...
186+
187+
$path = $request->getPathInfo();
188+
if (isset($map[$path])) {
189+
ob_start();
190+
include $map[$path];
191+
$response->setContent(ob_get_clean());
192+
} else {
193+
$response->setStatusCode(404);
194+
$response->setContent('Not Found');
195+
}
196+
197+
// ...
198+
199+
And the ``hello.php`` script can now be converted to a template::
200+
201+
<!-- example.com/src/pages/hello.php -->
202+
203+
<?php $name = $request->get('name', 'World') ?>
204+
205+
Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
206+
207+
We have our framework for today::
208+
209+
<?php
210+
211+
// example.com/web/front.php
212+
213+
require_once __DIR__.'/../src/autoload.php';
214+
215+
use Symfony\Component\HttpFoundation\Request;
216+
use Symfony\Component\HttpFoundation\Response;
217+
218+
$request = Request::createFromGlobals();
219+
$response = new Response();
220+
221+
$map = array(
222+
'/hello' => __DIR__.'/../src/pages/hello.php',
223+
'/bye' => __DIR__.'/../src/pages/bye.php',
224+
);
225+
226+
$path = $request->getPathInfo();
227+
if (isset($map[$path])) {
228+
ob_start();
229+
include $map[$path];
230+
$response->setContent(ob_get_clean());
231+
} else {
232+
$response->setStatusCode(404);
233+
$response->setContent('Not Found');
234+
}
235+
236+
$response->send();
237+
238+
Adding a new page is a two step process: add an entry in the map and create a
239+
PHP template in ``src/pages/``. From a template, get the Request data via the
240+
``$request`` variable and tweak the Response headers via the ``$response``
241+
variable.
242+
243+
..note::
244+
245+
If you decide to stop here, you can probably enhance your framework by
246+
extracting the URL map to a configuration file.
247+
248+
.. _`front controller`:http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html#a-front-controller-to-the-rescue

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp