So far, we have been programming in PHP, discovering how server-side programming works, and we have run into a few problems:
This is why frameworks exist, and using one becomes essential for large projects. For example:
But first, let's go through the features these software tools provide!
Consider the following PHP page:
<ul>
<?php if ($_GET['page'] == 'show') {
$sql = 'SELECT title FROM series ORDER BY title ASC';
$query = $pdo->query($sql);
foreach ($query as $row) { ?>
<li><?= $row['title']; ?></li>
<? }
}
?>
</ul>
Many things are mixed together here, which makes the code compact, hard to read and hardly reusable.
We can actually list the problems:
JSON API in addition to the website (we would like to reuse
the query)<?php makes the code hard to read when
there is logic around itThe MVC pattern, for Model, View, Controller, proposes a separation of concerns:
For instance, to rewrite this code following this separation, we could first
write in a Model class:
// Model.php, Model class
protected function getSeries(): array
{
$sql = 'SELECT title FROM series ORDER BY title ASC';
$query = $this->pdo->query($sql);
return $query->fetchAll();
}
Then, retrieve these values in the controller, if the GET parameter has the right value:
if ($_GET['page'] == 'show') {
$series = $model->getSeries();
}
And finally display them in the view:
<ul>
<?php foreach ($series as $show) { ?>
<li><?= $show['title']; ?></li>
<?php } ?>
</ul>
However, a framework is not just an organizational philosophy, it is also a set of software tools that assist you in each of these tasks. We will now present some of these components.
So far, this is how we have been doing things:
Here, the routing is implicitly done by the web server, since the PHP scripts are different resources.
The problems with this approach:
.php file, you have to include the various parts of the code again (boilerplate), which makes the application hard to maintainOne way to get nice URLs is to use the web server's URL rewriting mechanism:
This solution gives us arbitrary URLs, but requires maintaining a URL rewriting file, which is moreover specific to the web server in use.
Furthermore, it does not solve the problem of having multiple PHP files.
When using a framework, URL rewriting is used, but towards a single front controller:
This way, routing is done in PHP within the application.
Routing is the problem of associating addresses (URLs) with the controller (the piece of PHP code) that will be invoked.
The router is therefore a set of matching rules.
For example:
/home →
DefaultController::home()/login →
SecurityController::login()/product/{id} →
ProductController::show($id)<?php
class DefaultController
{
/**
* @Route("/hello/{name}", name="hello")
*/
public function hello($name)
{
return new Response('Hello '.$name);
}
}
Here:
@Route("/hello/{name}") annotation is a way to map
the /hello/{name} route to the hello($name) functionhello)Routes map an address to a controller, but they can also be used to produce URLs.
For example, from the route name, you can ask the router to generate the matching address:
<?php
$homeUrl = $this->generateUrl('home');
$helloBobUrl = $this->generateUrl('hello', ['name' => 'Bob']);
This way, the external names visible to everyone (/hello/Bob) are decoupled from the internal name of the route (hello)
/product/{id})/admin/*)It is entirely possible to write PHP pages as a template system.
However, there are a few drawbacks:
<?php...)
An example of a template engine is Twig.
This system makes it easier to write views, that is the content of the HTML pages that will be rendered.
Twig supports inheritance, escaping by default and many syntactic shortcuts to make writing templates easier.
Here is an example of a template:
<html>
<head>
<title>
{% block title %}My title{% endblock %}
</title>
</head>
<body>
<h1>{{ block('title') }}</h1>
{% block content %}
Hello {{ name }}!
{% endblock %}
</body>
</html>
As you can see, Twig lets you write documents directly in HTML, except for some tags that add structure, much like PHP.
In this example:
{% block content %} is a block that can be overridden in child templates{{ block('title') }} displays again the content of the title block used earlier{{ name }} displays a variableThe previous template can be extended like this:
{% extends 'index.html.twig' %}
{% block title %}
{{ parent() }} - My page
{% endblock %}
{% block content %}
Welcome to this page!
{% endblock %}
The extends keyword states that this page inherits from index.html.twig. Just like
class inheritance, your template will then be based on this parent template and can redefine its
behavior.
Blocks can then be overridden, that is modified by redefining them. You can also use
parent() to call the parent template and use its content, as in the case of the title,
which here becomes "My title - My page"
You can also use conditions and loops with Twig:
{% if not users|length %}
<i>No users</i>
{% else %}
<ul>
{% for user in users %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>
{% endif %}
For more comprehensive documentation, you can check the official Twig documentation.
user.name can be $user['name'] or $user->getName())An ORM, for Object Relational Mapping, performs a mapping, that is an association between the relational world (tables, rows, fields...) and the object world (classes, instances, attributes...).
This mapping is usually done with configuration files or annotations.
| Relational | Object |
| Table | Class (or entity) |
| Row | Instance |
| Column | Attribute |
| Foreign key | Reference |
This correspondence becomes clear when comparing an entity-relationship diagram with a UML diagram.
There are two paradigms when it comes to ORMs:
One example is Doctrine, the ORM that comes with the Symfony framework.
In Database First, it can for instance generate, from the shows database, a set of classes matching the tables of the database.
You can then run queries using Doctrine:
public function indexAction(SeriesRepository $repository)
{
$series = $repository->findBy(['year_start' => '2011']);
return $this->render('series/index.html.twig', [
'series' => $series
]);
}
And then display them in the view:
<ul>
{% for show in series %}
<li>{{ show.title }}</li>
{% endfor %}
</ul>
Note that series here is a collection of instances
of the Series class.
You can also create entries using persist():
<?php
$entityManager = $this->getDoctrine()->getManager();
$user = new User();
$user
->setName('Toto')
->setEmail('toto@toto.com')
->setPassword('totopass')
;
$entityManager->persist($user);
$entityManager->flush();
Most simple operations can be done without writing any SQL query!
In general, an ORM provides an API for querying through methods, as well as a dedicated language.
For example, Doctrine provides the DQL language to query the database:
<?php
// Here we don't query the tables but the ORM "classes"
// The language is independent of the actual DBMS
$query = $entityManager->createQuery('SELECT s
FROM App:Series s
WHERE s.year_start = "2011"');
$series = $query->getResult();