Symfony versus Flat PHP

Why is Symfony better than just opening up a file and writing flat PHP?

If you've never used a PHP framework, aren't familiar with the
Model-View-Controller (MVC) philosophy, or just wonder what all the hype
is around Symfony, this chapter is for you. Instead of telling you that
Symfony allows you to develop faster and better software than with flat PHP,
you'll see for yourself.

In this chapter, you'll write a simple application in flat PHP, and then
refactor it to be more organized. You'll travel through time, seeing the
decisions behind why web development has evolved over the past several years
to where it is now.

By the end, you'll see how Symfony can rescue you from mundane tasks and
let you take back control of your code.

In this chapter, you'll build the token blog application using only flat PHP.
To begin, create a single page that displays blog entries that have been
persisted to the database. Writing in flat PHP is quick and dirty:

<?php// index.php$link=newPDO("mysql:host=localhost;dbname=blog_db",'myuser','mypassword');$result=$link->query('SELECT id, title FROM post');?><!DOCTYPE html><html><head><title>List of Posts</title></head><body><h1>List of Posts</h1><ul><?phpwhile($row=$result->fetch(PDO::FETCH_ASSOC)):?><li><ahref="/show.php?id=<?=$row['id']?>"><?=$row['title']?></a></li><?phpendwhile?></ul></body></html><?php$link=null;?>

That's quick to write, fast to execute, and, as your app grows, impossible
to maintain. There are several problems that need to be addressed:

No error-checking: What if the connection to the database fails?

Poor organization: If the application grows, this single file will become
increasingly unmaintainable. Where should you put code to handle a form
submission? How can you validate data? Where should code go for sending
emails?

Difficult to reuse code: Since everything is in one file, there's no
way to reuse any part of the application for other "pages" of the blog.

Note

Another problem not mentioned here is the fact that the database is
tied to MySQL. Though not covered here, Symfony fully integrates Doctrine,
a library dedicated to database abstraction and mapping.

The code can immediately gain from separating the application "logic" from
the code that prepares the HTML "presentation":

1
2
3
4
5
6
7
8
9
10
11
12
13
14

// index.php$link=newPDO("mysql:host=localhost;dbname=blog_db",'myuser','mypassword');$result=$link->query('SELECT id, title FROM post');$posts=array();while($row=$result->fetch(PDO::FETCH_ASSOC)){$posts[]=$row;}$link=null;// include the HTML presentation coderequire'templates/list.php';

The HTML code is now stored in a separate file templates/list.php, which
is primarily an HTML file that uses a template-like PHP syntax:

By convention, the file that contains all the application logic - index.php -
is known as a "controller". The term controller is a word you'll hear a lot,
regardless of the language or framework you use. It refers simply to the area
of your code that processes user input and prepares the response.

In this case, the controller prepares data from the database and then includes
a template to present that data. With the controller isolated, you could
easily change just the template file if you needed to render the blog
entries in some other format (e.g. list.json.php for JSON format).

So far the application contains only one page. But what if a second page
needed to use the same database connection, or even the same array of blog
posts? Refactor the code so that the core behavior and data-access functions
of the application are isolated in a new file called model.php:

// model.phpfunctionopen_database_connection(){$link=newPDO("mysql:host=localhost;dbname=blog_db",'myuser','mypassword');return$link;}functionclose_database_connection(&$link){$link=null;}functionget_all_posts(){$link=open_database_connection();$result=$link->query('SELECT id, title FROM post');$posts=array();while($row=$result->fetch(PDO::FETCH_ASSOC)){$posts[]=$row;}close_database_connection($link);return$posts;}

Tip

The filename model.php is used because the logic and data access of
an application is traditionally known as the "model" layer. In a well-organized
application, the majority of the code representing your "business logic"
should live in the model (as opposed to living in a controller). And unlike
in this example, only a portion (or none) of the model is actually concerned
with accessing a database.

Now, the sole task of the controller is to get data from the model layer of
the application (the model) and to call a template to render that data.
This is a very simple example of the model-view-controller pattern.

You now have a setup that will allow you to reuse the layout.
Unfortunately, to accomplish this, you're forced to use a few ugly
PHP functions (ob_start(), ob_get_clean()) in the template. Symfony
uses a Templating component
that allows this to be accomplished cleanly and easily. You'll see it in
action shortly.

The blog "list" page has now been refactored so that the code is better-organized
and reusable. To prove it, add a blog "show" page, which displays an individual
blog post identified by an id query parameter.

To begin, create a new function in the model.php file that retrieves
an individual blog result based on a given id:

Creating the second page is now very easy and no code is duplicated. Still,
this page introduces even more lingering problems that a framework can solve
for you. For example, a missing or invalid id query parameter will cause
the page to crash. It would be better if this caused a 404 page to be rendered,
but this can't really be done easily yet.

Another major problem is that each individual controller file must include
the model.php file. What if each controller file suddenly needed to include
an additional file or perform some other global task (e.g. enforce security)?
As it stands now, that code would need to be added to every controller file.
If you forget to include something in one file, hopefully it doesn't relate
to security...

The solution is to use a front controller: a single PHP file through which
all requests are processed. With a front controller, the URIs for the
application change slightly, but start to become more flexible:

By using rewrite rules in your
web server configuration,
the index.php won't be needed and you will have beautiful, clean URLs
(e.g. /show).

When using a front controller, a single PHP file (index.php in this case)
renders every request. For the blog post show page, /index.php/show will
actually execute the index.php file, which is now responsible for routing
requests internally based on the full URI. As you'll see, a front controller
is a very powerful tool.

You're about to take a big step with the application. With one file handling
all requests, you can centralize things such as security handling, configuration
loading, and routing. In this application, index.php must now be smart
enough to render the blog post list page or the blog post show page based
on the requested URI:

As a front controller, index.php has taken on an entirely new role, one
that includes loading the core libraries and routing the application so that
one of the two controllers (the list_action() and show_action()
functions) is called. In reality, the front controller is beginning to look and
act a lot like how Symfony handles and routes requests.

But be careful not to confuse the terms front controller and controller. Your
app will usually have just one front controller, which boots your code. You will
have many controller functions: one for each page.

Tip

Another advantage of a front controller is flexible URLs. Notice that
the URL to the blog post show page could be changed from /show to /read
by changing code in only one location. Before, an entire file needed to
be renamed. In Symfony, URLs are even more flexible.

By now, the application has evolved from a single PHP file into a structure
that is organized and allows for code reuse. You should be happier, but far
from satisfied. For example, the routing system is fickle, and wouldn't
recognize that the list page - /index.php - should be accessible also via /
(if Apache rewrite rules were added). Also, instead of developing the blog,
a lot of time is being spent working on the "architecture" of the code (e.g.
routing, calling controllers, templates, etc.). More time will need to be
spent to handle form submissions, input validation, logging and security.
Why should you have to reinvent solutions to all these routine problems?

Symfony to the rescue. Before actually using Symfony, you need to download
it. This can be done by using Composer, which takes care of downloading the
correct version and all its dependencies and provides an autoloader. An
autoloader is a tool that makes it possible to start using PHP classes
without explicitly including the file containing the class.

In your root directory, create a composer.json file with the following
content:

Next, download Composer and then run the following command, which will download Symfony
into a vendor/ directory:

1

$ composer install

Beside downloading your dependencies, Composer generates a vendor/autoload.php file,
which takes care of autoloading for all the files in the Symfony Framework as well as
the files mentioned in the autoload section of your composer.json.

Core to Symfony's philosophy is the idea that an application's main job is
to interpret each request and return a response. To this end, Symfony provides
both a Request and a
Response class. These classes are
object-oriented representations of the raw HTTP request being processed and
the HTTP response being returned. Use them to improve the blog:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// index.phprequire_once'vendor/autoload.php';useSymfony\Component\HttpFoundation\Request;useSymfony\Component\HttpFoundation\Response;$request=Request::createFromGlobals();$uri=$request->getPathInfo();if('/'===$uri){$response=list_action();}elseif('/show'===$uri&&$request->query->has('id')){$response=show_action($request->query->get('id'));}else{$html='<html><body><h1>Page Not Found</h1></body></html>';$response=newResponse($html,Response::HTTP_NOT_FOUND);}// echo the headers and send the response$response->send();

The controllers are now responsible for returning a Response object.
To make this easier, you can add a new render_template() function, which,
incidentally, acts quite a bit like the Symfony templating engine:

By bringing in a small part of Symfony, the application is more flexible and
reliable. The Request provides a dependable way to access information
about the HTTP request. Specifically, the
getPathInfo() method returns
a cleaned URI (always returning /show and never /index.php/show).
So, even if the user goes to /index.php/show, the application is intelligent
enough to route the request through show_action().

The Response object gives flexibility when constructing the HTTP response,
allowing HTTP headers and content to be added via an object-oriented interface.
And while the responses in this application are simple, this flexibility
will pay dividends as your application grows.

The blog has come a long way, but it still contains a lot of code for such
a simple application. Along the way, you've made a simple routing
system and a method using ob_start() and ob_get_clean() to render
templates. If, for some reason, you needed to continue building this "framework"
from scratch, you could at least use Symfony's standalone
Routing and
Templating components, which already
solve these problems.

Instead of re-solving common problems, you can let Symfony take care of
them for you. Here's the same sample application, now built in Symfony:

// src/AppBundle/Controller/BlogController.phpnamespaceAppBundle\Controller;useSymfony\Bundle\FrameworkBundle\Controller\Controller;classBlogControllerextendsController{publicfunctionlistAction(){$posts=$this->get('doctrine')->getManager()->createQuery('SELECT p FROM AppBundle:Post p')->execute();return$this->render('Blog/list.html.php',array('posts'=>$posts));}publicfunctionshowAction($id){$post=$this->get('doctrine')->getManager()->getRepository('AppBundle:Post')->find($id);if(!$post){// cause the 404 page not found to be displayedthrow$this->createNotFoundException();}return$this->render('Blog/show.html.php',array('post'=>$post));}}

Notice, both controller functions now live inside a "controller class". This is a
nice way to group related pages. The controller functions are also sometimes called
actions.

The two controllers (or actions) are still lightweight. Each uses the
Doctrine ORM library to retrieve objects from the
database and the Templating component to render a template and return a
Response object. The list list.php template is now quite a bit simpler:

The show show.php template is left as an exercise: updating it should be
really similar to updating the list.php template.

When Symfony's engine (called the Kernel) boots up, it needs a map so
that it knows which controllers to execute based on the request information.
A routing configuration map - app/config/routing.yml - provides this information
in a readable format:

The front controller's only job is to initialize Symfony's engine (called the
Kernel) and pass it a Request object to handle. The Symfony core
asks the router to inspect the request. The router matches the incoming URL
to a specific route and returns information about the route, including the
controller that should be executed. The correct controller from the matched
route is executed and your code inside the controller creates and returns the
appropriate Response object. The HTTP headers and content of the Response
object are sent back to the client.

If you choose to use it, Symfony comes standard with a templating engine
called Twig that makes templates faster to write and easier to read.
It means that the sample application could contain even less code! Take,
for example, rewriting list.html.php template in Twig would look like
this:

Twig is well-supported in Symfony. And while PHP templates will always
be supported in Symfony, the many advantages of Twig will continue to
be discussed. For more information, see the templating chapter.

In the upcoming chapters, you'll learn more about how each piece of Symfony
works and how you can organize your project. For now, celebrate at how migrating
the blog from flat PHP to Symfony has improved life:

Your application now has clear and consistently organized code (though
Symfony doesn't force you into this). This promotes reusability and
allows for new developers to be productive in your project more quickly;

100% of the code you write is for your application. You don't need
to develop or maintain low-level utilities such as autoloading,
routing, or rendering controllers;

The application now enjoys fully-flexible URLs thanks to the Routing
component;

Symfony's HTTP-centric architecture gives you access to powerful tools
such as HTTP caching powered by Symfony's internal HTTP cache or
more powerful tools such as Varnish. This is covered in a later chapter
all about caching.

And perhaps best of all, by using Symfony, you now have access to a whole
set of high-quality open source tools developed by the Symfony community!
A good selection of Symfony community tools can be found on KnpBundles.com.