Différences entre framework et library sur Stack Overflow ou artima developper.
http://app.host.tld/controller/action[/key/val]
http://app.host.tld/controller/action[/key/val]
Ce schéma est clair mais pas tout à fait juste : dans Laravel, le contrôleur récupère la page générée à partir de la vue, et c’est lui qui renvoie le HTML (objet Response) au client.
L’explication donnée par Joe Gregorio pour le langage Python est : « parce que c’est facile. »
Dans les faits, cela montre également une maturité de la plateforme.
There are people who actually like programming. I don’t understand why they like programming. Rasmus Lerdorf 💬
Il y a plus de vingt ans, Rasmus Lerdorf bricola un outil pour savoir qui consultait son CV.
Zend, c’est à dire ZEev et aNDi, ont réécrit PHP et qui allait devenir PHP 3 le précurseur du langage de prédilection pour créer sur le web.
PHP a évolué depuis pour devenir ce qu’il est aujourd’hui. Sa popularité est liée au fait qu’il est simple à mettre en œuvre, gratuit et libre. Tout un tas de modules est fourni avec pour faire de l’imagerie, des bases de données, du XML, etc.
Et plus encore sur la page History of PHP et Wikipedia: PHP.
Les différentes moutures de PHP 7 offrent ceci, entre autres.
void
L’évolution de PHP a fait que les usagers du langage, créateur de frameworks, d’outils (comme Composer), ont senti le besoin d’émettre des recommendations afin d’aller vers un plus interopérable.
Durant ce cours, nous allons vous embêter avec PSR-1, PSR-2 et PSR-4.
Qui est qui?
oOops, ceci n’a rien à voir avec le cours.
(1)
Donc, ce ne sont pas Gandalf (sans sa barbe) et Saruman mais bien Sir Tim Berners-Lee et Vinton Cerf, responsables du (World Wide) Web et de l’Internet.
https://github.com/HE-Arc/php-intro-framework/
$ sudo systemctl start httpd
$ cd /var/www/html
$ git clone \
> https://github.com/\
> HE-Arc/php-intro-framework
$ cd php-intro-framework
$ open http://localhost/php-intro-framework
Les exemples suivant travaillent sur le code disponible dans le dépôt HE-Arc/php-intro-framework.
$ curl -v "http://he-arc.ch/?id=25"
> GET /?id=25 HTTP/1.1
> Host: he-arc.ch
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
<
HTTP est un protocole texte plutôt simple, jugez plutôt:
Ce que nous voyons est une connexion TCP/IP au serveur
he-arc.ch
. Une fois la connexion établie, il envoie en
texte ASCII les entêtes HTTP puis deux retours à la ligne (ce qui
correspond à une ligne vide). La requête HTTP commencent toujours par la
demande, ici GET /index.php?page=equipe&id=25 HTTP/1.1
puis les entêtes, ici: Host: www.he-arc.ch
. La réponse du
serveur est du même type, le code de réponse
(HTTP/1.1 200 OK
), les entêtes, une ligne vide puis le
contenu.
La demande et les entêtes sont en US-ASCII mais le corps peut être
encodé autrement, ici c’est dit dans l’entête
Content-Type: text/html; charset=utf-8
.
PHP parle HTTP.
Le fichier index.php
est le code PHP le plus simple qui
soit. Simple au sens du niveau de compréhension de PHP et d’une forme de
complexité.
<?php // 00-base
// Lecture de la query string `page=<XX>&id=<YY>`.
$page = $_GET["page"] ?? null;
$id = (int) ($_GET["id"] ?? 0);
// Connexion à la base de données.
$db = new PDO("sqlite:../users.db");
// Page HTML
?>
<!DOCTYPE html>
<meta charset=utf-8>
<title>HE-Arc</title>
<?php
// Contenu
if ("equipe" === $page):
$query = $db->query("SELECT * FROM `personnes` WHERE `id` = :id;");
$query->execute(compact('id'));
$personne = $query->fetch(PDO::FETCH_OBJ);
?>
<p><a href="<?php echo $_SERVER["PHP_SELF"] ?>">retour</a>
<h1>Équipe</h1>
<h2>
<?php echo $personne->prenom ?>
<?php echo $personne->nom ?>
</h2>
<p>
<img src="//www.gravatar.com/avatar/<?php
echo md5(strtolower($personne->email));
?>" alt="avatar">
<?php
else:
?>
<h1>Accueil</h1>
<ul>
<li><a href="?page=equipe&id=1">Yoan Blanc</a>
<li><a href="?page=equipe&id=2">Yoan Blanc</a>
</ul>
<?php
endif
PHP est un langage de template.
Pour preuve, il faut ouvrir une balise <?php
pour
commencer la partie code.
Avec la pratique, on a réalisé que mélanger la logique métier et celle d’affichage n’est pas optimal car difficile à lire et maintenir.
Quel est le problème avec cette solution?
Dans ce le cas présent rien ne nous empêche de mettre de la logique métier dans nos fichiers de template, car ils sont faits de PHP eux aussi.
{# 02-twig/templates/collaborateur.html #}
{%- extends "base.html" -%}
{% block corps -%}
<p><a href="?">retour</a>
<h1>Équipe</h1>
<h2>
{{- personne.prenom -}}
{{ personne.nom -}}
</h2>
<p><img
src="//www.gravatar.com/avatar/
{{- personne.email | strtolower | md5 }}"
alt="avatar">
{% endblock -%}
La page est réalisée avec Twig <2.0. À partir de la version 2.0, il faut utiliser un autoloader externe, comme celui de composer (voir ci-dessous).
Le code est un poil plus propre du côté de nos templates qui
ne peuvent plus exécuter de PHP sauf ce qu’on leur autorise, ici
md5
et strtolower
. Voir 02-twig/index.php
.
<?php // 02-twig
require_once 'Twig/lib/Twig/Autoloader.php';
Twig_Autoloader::register();
// ...
// Configuration de Twig
$loader = new Twig_Loader_FileSystem("templates");
$twig = new Twig_Environment($loader);
// Ajout des filtres md5 et strtolower qui sont les fonctions PHP du même nom.
$twig->addFilter(new Twig_SimpleFilter('strtolower', 'strtolower'));
$twig->addFilter(new Twig_SimpleFilter('md5', 'md5'));
// variable globale
$titre = "HE-Arc";
// Contenu
if ("equipe" === $page) {
// ...
$personne = // ...
echo $twig->render("equipe.html", compact("titre", "personne"));
} else {
$personnes = // ...
echo $twig->render("accueil.html", compact("titre", "personnes"));
}
Problème d’injection SQL.
Effectuer des requêtes MySQL à la main ou devoir connaitre tous les champs crée beaucoup de redondance et de failles de sécurité potentielles.
Une solution est d’ajouter une couche d’abstraction qui va cacher la structure réelle de notre base de données et offrir une interface orientée objet. Un Object-Relational Mapping ou ORM(3) dans le jargon.
Une bibliothèque qui va créer ce lien entre les mondes objet et relationnel ou document (généralement MongoDB). Il en existe toute une foule.
<?php // 03-redbean/index.php
require 'RedBean/rb.php';
R::setup("sqlite:../users.db");
// ...
if ("equipe" === $page) {
$personne = R::load("personnes", $id);
echo $twig->render(
"equipe.html",
compact("titre", "personne")
);
} else {
$personnes = R::find("personnes");
echo $twig->render(
"accueil.html",
compact("titre", "personnes")
);
}
Pensez à Wikipedia.
Les adresses des pages font partie de l’expérience utilisateur. Un utilisateur doit être capable d’imaginer le contenu de la page en lisant l’URI. Certainement, ce que vous faites avant de cliquer sur un lien.
/index.php?page=equipe&id=42
La personne avec l’identifiant 42
aura également un
slug unique créé à partir de son nom, ici
jean-bon
.
La solution à notre problème est de demander au serveur web de réécrire les URL pour nous.
# 04-routes/.htaccess
# mod_rewrite
RewriteEngine on
RewriteBase /php-intro-framework/04-routes/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
Apache le fait via mod_rewrite
et Nginx try_files
.
// 04-routes/index.php
$uri = $_SERVER['REQUEST_URI'],
$matches = [];
preg_match(
"#^/(?P<page>[^/]+)/(?P<slug>[^/]+)/?#",
$uri,
$matches
) or die('Arrrrrgh');
echo call_user_func_array(
$matches['page'],
[$matches['slug']]
);
Le code complet va nettoyer l’URI et définir les fonction correspondant aux pages possibles.
Lien entre les adresses (URI) et des actions dans le code.
a.k.a. the Front Controller.
En pratique, les actions ne sont pas des fonctions mises à plat mais
sont encapsulées dans une classe qu’on nomme un contrôleur. Faire ainsi
permet de regrouper logiquement les fonctions et éviter d’utiliser
d’affreux éléments tel que global
.
MVC(4) vient des applications bureau et ne représente pas toujours le fonctionnement dans le monde du web. Par exemple, Django, un framework Python, se décrit comme étant Modèle - Template - Vue(5).
Les frameworks web en PHP (ou d’autres langages) reposent majoritairement sur ce paradigme.
Gestionnaire de paquets pour PHP: getcomposer.org
Nos dépendances sont ainsi matérialisées dans le projet et peuvent être installée, ou mises à jour simplement.
En principe les numéros de version respectent le SemVer (Semantic Versioning) et les différents signes permettent de sélection une ou plusieurs versions (voir [Version and constraints][https://getcomposer.org/doc/articles/versions.md]).
$ composer install
puis
Utilisation de FastRoute
(voir 06-fastroute/index.php).
$ composer require nikic/fast-route
FastRoute
repose sur un système proche de celui que nous
avons utilisé jusqu’ici. D’autres systèmes, tels que
Aura.Router
pour ne citer que lui, reposent sur la
spécification PSR-7.
Cette dernière décrit l’interface objet d’un message HTTP, tant au
niveau de la requête que de la réponse.
Si ça ajoute, une bonne couche de complexité, l’énorme avantage
offert par cette idée là est de déléguer le rendu d’une page, ni
echo
, ni header
, Donc il est envisageable de
pouvoir tester (au sens de test unitaire), notre
FrontController.
D’autre part, le call_user_func_array
d’avant n’était
pas très solide,
Une collection de bibliothèques avec un peu de glue.
Un framework web vous propose une structure de base pour construire selon une méthode jugée bonne par ses concepteurs. Il est possible de remplacer un composant par un autre, par le sien. Et même de créer sa glue ou même ses outils propres.
Illuminate\Routing
)Illuminate\*
)Je vous invite à aller lire le code généré pour vous par Laravel. Vous allez retrouver ces éléments. Symfony, CakePHP, etc. auront les mêmes idées.
00-base
.Questions?