Table of contents
Part 1Part 2
Part 3Part 4Part 5Before we dive into refactoring the code, I first want to take a step back and take a look at why you would like to use the framework instead of writing your application in pure PHP. Why using a framework is actually a good idea, even for the simplest code snippet, and why creating your own framework based on Symfony2 components is better than creating a framework from scratch.
- I will not talk about the obvious advantages of using frameworks when working with large applications and with more than a few developers; There are already a lot of good resources on this topic.
Although the “application” that we wrote last time was quite simple, it has several drawbacks:
<?php
First, if the
name variable is not set in the request parameter, a PHP warning will be issued. Fix this:
<?php
')
Believe it or not, even this small piece of code is vulnerable to one of the most common vulnerabilities - XSS (Cross-Site Scripting). Here is a more secure version:
<?php $input = isset($_GET['name']) ? $_GET['name'] : 'World'; header('Content-Type: text/html; charset=utf-8'); printf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));
- As you can see, protecting the code with htmlspecialchars () is very tedious and can easily lead to a typo. This is one reason to use a templating engine such as Twig , where auto-shielding is enabled by default.
As you can see, initially simple code becomes more complicated when adding security checks and avoiding PHP warnings / notifications.
In addition to security, this code is also not easily testable. Even if there is not so much to test, it seems to me that writing unit tests for the simplest piece of PHP code is not natural and will look ugly. Here is an example of the PHPUnit unit test for the above code:
<?php
At this point, if you are not sure that security and testing are really two very good reasons for not writing code in the old way, but adapting it to your framework, you can stop reading this series now and return to code, on which you worked earlier.
- Of course, using the framework should give you more than just security and testing. But more importantly, you need to keep in mind that the framework should allow you to write better code faster.
We move to OOP using the HttpFoundation component. (Going OOP with the HttpFoundation Component)
Writing web-based code is essentially working with the HTTP protocol. So the basic principles of our framework should be based on
the HTTP specification .
The HTTP specification describes how a client (for example, a browser) interacts with a server (our application through a web server). The dialogue between the client and the server is clearly defined messages, requests and responses: the client sends a request to the server and based on this request, the server returns a response.
In PHP, the request is represented as global variables (
$ _GET, $ _POST, $ _FILE, $ _COOKIE, $ _SESSION ...), and the response is generated using functions (echo, header, setcookie, ...).
The first step to better code is to use an object-oriented approach. The main purpose of the HttpFoundation component is to replace PHP global variables with an object-oriented layer.
To use this component, add it to the project dependencies in the
composer.json
file after this, run the command
composer update
Finally, at the bottom of the autoload.php file, add the code needed to automatically download the component:
<?php
Now, we rewrite our application using the "
Request " and "
Response " classes:
<php
The
createFromGlobals () method creates an instance of the "
Request " class based on the current values ​​of the PHP global variables.
The
send () method sends the data of the "
Response " class object back to the client (first go the HTTP headers followed by the content).
- Hint: Before calling the send () method, we must call the prepare () method ( $ response-> prepare ($ request); ) to make sure that the " Response " is compatible with the HTTP specification. For example, if we get a page using the “HEAD” method, then we need to remove the content from the response body.
The main difference from the previous code is that you have full control over HTTP messages. You can create any request you want, and you are responsible for sending a reply whenever you see fit.
- We did not set the “Content-Type” header in the rewritten code, since in the Response object the default encoding is UTF-8.
With the class "
Request ", all the requested information is always at your fingertips, thanks to a nice and simple API:
<?php
You can also simulate a query:
$request = Request::create('/index.php?name=Fabien');
With the "
Response " class, you can easily customize the answer:
<?php $response = new Response(); $response->setContent('Hello world!'); $response->setStatusCode(200); $response->headers->set('Content-Type', 'text/html');
- Hint: in order to debug " Response ", cast the object to a string type; this will return HTTP response submissions (headers and content)
Last but not least, these classes, like any other class in symfony, have been tested for security by an independent company. And, the Open-Source project also means that many other developers around the world are reading the code and have already fixed potential security issues. When was the last time you ordered a professional security audit for your self-made framework?
Even such a simple operation as returning a client's IP address may not be secure:
<?php if ($myIp == $_SERVER['REMOTE_ADDR']) {
This works fine until you add a reverse proxy in front of the production server, for now you have to change the code so that it works on both of your machines (if you do not have a reverse proxy on the development server):
<?php if ($myIp == $_SERVER['HTTP_X_FORWARDED_FOR'] || $myIp == $_SERVER['REMOTE_ADDR']) {
Using the
Request :: getClientIp () method, you will have a working code from day one, regardless of the presence of a proxy.
<?php $request = Request::createFromGlobals(); if ($myIp == $request->getClientIp()) {
There is also an added advantage: it is safe by default. What do I mean by security? The end user can manipulate the value of
$ _SERVER ['HTTP_X_FORWARDED_FOR'] and cannot be trusted. So, if you use this code in production without a proxy server, it becomes quite easy to abuse your system. This does not happen with the
getClientIp () method, since you must explicitly indicate that you trust this header by calling
trustProxyData () :
<?php Request::trustProxyData(); if ($myIp == $request->getClientIp(true)) {
Thus,
getClientIp () works reliably in all circumstances. You can use it in all your projects, regardless of the configuration, it will behave properly and safely. This is one of the goals of using the framework. If you were writing a framework from scratch, you would have to think about all these cases yourself. So why not use technology that already works?
- If you want to learn more about the HttpFoundation component, you can take a look at the API or read the documentation on the symfony website.
Believe it or not, now you have the first framework. You can stop at this, if you want of course. Using the Symfony2 component HttpFoundation already allows you to write better and more testable code. It also allows you to write code faster, as most daily problems are already solved for you.
In fact, projects such as Drupal have adopted (for upcoming version 8) the HttpFoundation component; if it works for them, it will probably work for you. Do not reinvent the wheel.
I almost forgot to mention one additional advantage: using the HttpFoundation component, you can achieve better interaction between frameworks and applications that use it (today
Symfony2 ,
Drupal 8 ,
PhpBB 4 ,
Silex ,
Midgard CMS ,
Zikula ...).