Skip to content

Commit 97743fb

Browse files
committed
added part 7
1 parent fdb195c commit 97743fb

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed

book/part7.rst

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
Create your own framework... on top of the Symfony2 Components (part 7)
2+
=======================================================================
3+
4+
One down-side of our framework right now is that we need to copy and paste the
5+
code in ``front.php`` each time we create a new website. 40 lines of code is
6+
not that much, but it would be nice if we could wrap this code into a proper
7+
class. It would bring us better *reusability* and easier testing to name just
8+
a few benefits.
9+
10+
If you have a closer look at the code, ``front.php`` has one input, the
11+
Request, and one output, the Response. Our framework class will follow this
12+
simple principle: the logic is about creating the Response associated with a
13+
Request.
14+
15+
As the Symfony2 components requires PHP 5.3, let's create our very own
16+
namespace for our framework: ``Simplex``.
17+
18+
Move the request handling logic into its own ``Simple\\Framework`` class::
19+
20+
<?php
21+
22+
// example.com/src/Simplex/Framework.php
23+
24+
namespace Simplex;
25+
26+
use Symfony\Component\HttpFoundation\Request;
27+
use Symfony\Component\HttpFoundation\Response;
28+
use Symfony\Component\Routing\Matcher\UrlMatcher;
29+
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
30+
31+
class Framework
32+
{
33+
protected $matcher;
34+
protected $resolver;
35+
36+
public function __construct(UrlMatcher $matcher, ControllerResolver $resolver)
37+
{
38+
$this->matcher = $matcher;
39+
$this->resolver = $resolver;
40+
}
41+
42+
public function handle(Request $request)
43+
{
44+
try {
45+
$request->attributes->add($this->matcher->match($request->getPathInfo()));
46+
47+
$controller = $this->resolver->getController($request);
48+
$arguments = $this->resolver->getArguments($request, $controller);
49+
50+
return call_user_func_array($controller, $arguments);
51+
} catch (Routing\Exception\ResourceNotFoundException $e) {
52+
return new Response('Not Found', 404);
53+
} catch (Exception $e) {
54+
return new Response('An error occurred', 500);
55+
}
56+
}
57+
}
58+
59+
And update ``example.com/web/front.php`` accordingly::
60+
61+
<?php
62+
63+
// example.com/web/front.php
64+
65+
// ...
66+
67+
$request = Request::createFromGlobals();
68+
$routes = include __DIR__.'/../src/app.php';
69+
70+
$context = new Routing\RequestContext();
71+
$context->fromRequest($request);
72+
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
73+
$resolver = new HttpKernel\Controller\ControllerResolver();
74+
75+
$framework = new Simplex\Framework($matcher, $resolver);
76+
$response = $framework->handle($request);
77+
78+
$response->send();
79+
80+
To wrap up the refactoring, let's move everything but routes definition from
81+
``example.com/src/app.php`` into yet another namespace: ``Calendar``.
82+
83+
For the classes defined under the ``Simplex`` and ``Calendar`` namespaces to
84+
be autoloaded, update the ``composer.json`` file:
85+
86+
.. code-block:: json
87+
88+
{
89+
"require": {
90+
"symfony/class-loader": "2.1.*",
91+
"symfony/http-foundation": "2.1.*",
92+
"symfony/routing": "2.1.*",
93+
"symfony/http-kernel": "2.1.*"
94+
},
95+
"autoload": {
96+
"psr-0": { "Simplex": "src/", "Calendar": "src/" }
97+
}
98+
}
99+
100+
.. note::
101+
102+
For the autoloader to be updated, run ``php composer.phar update``.
103+
104+
Move the controller to ``Calendar\\Controller\\LeapYearController``::
105+
106+
<?php
107+
108+
// example.com/src/Calendar/Controller/LeapYearController.php
109+
110+
namespace Calendar\Controller;
111+
112+
use Symfony\Component\HttpFoundation\Request;
113+
use Symfony\Component\HttpFoundation\Response;
114+
use Calendar\Model\LeapYear;
115+
116+
class LeapYearController
117+
{
118+
public function indexAction(Request $request, $year)
119+
{
120+
$leapyear = new LeapYear();
121+
if ($leapyear->isLeapYear($year)) {
122+
return new Response('Yep, this is a leap year!');
123+
}
124+
125+
return new Response('Nope, this is not a leap year.');
126+
}
127+
}
128+
129+
And move the ``is_leap_year()`` function to its own class too::
130+
131+
<?php
132+
133+
// example.com/src/Calendar/Model/LeapYear.php
134+
135+
namespace Calendar\Model;
136+
137+
class LeapYear
138+
{
139+
public function isLeapYear($year = null)
140+
{
141+
if (null === $year) {
142+
$year = date('Y');
143+
}
144+
145+
return 0 == $year % 400 || (0 == $year % 4 && 0 != $year % 100);
146+
}
147+
}
148+
149+
Don't forget to update the ``example.com/src/app.php`` file accordingly::
150+
151+
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
152+
'year' => null,
153+
'_controller' => 'Calendar\\Controller\\LeapYearController::indexAction',
154+
)));
155+
156+
To sum up, here is the new file layout:
157+
158+
example.com
159+
├── composer.json
160+
│ src
161+
│ ├── app.php
162+
│ └── Simplex
163+
│ └── Framework.php
164+
│ └── Calendar
165+
│ └── Controller
166+
│ │ └── LeapYearController.php
167+
│ └── Model
168+
│ └── LeapYear.php
169+
├── vendor
170+
└── web
171+
└── front.php
172+
173+
That's it! Our application has now four different layers and each of them has
174+
a well defined goal:
175+
176+
* ``web/front.php``: The front controller; the only exposed PHP code that
177+
makes the interface with the client (it gets the Request and sends the
178+
Response) and provides the boiler-plate code to initialize the framework and
179+
our application;
180+
181+
* ``src/Simplex``: The reusable framework code that abstracts the handling of
182+
incoming Requests (by the way, it makes your controllers/templates easily
183+
testable -- more about that later on);
184+
185+
* ``src/Calendar``: Our application specific code (the controllers and the
186+
model);
187+
188+
* ``src/app.php``: The application configuration/framework customization.

0 commit comments

Comments
 (0)