Flight PHP¶
Cette page à pour but de découvrir le langage PHP au travers de micro framework et le design pattern MVC.
Composer¶
Composer est un gestionnaire de dépendances écrit en PHP pour PHP.
Il permet à ses utilisateurs de déclarer et d’installer les bibliothèques dont le projet principal a besoin.
Note
Composer utilise un fichier composer.json qui contient plusieurs informations sur le projet dont la liste des librairies utilisées. Il est ensuite capable de télécharger automatiquement ces librairies (et les dépendances associées) et de générer un autoloader pour les utiliser simplement dans vos projets PHP.
Installation de composer¶
L’installation de composer est très simple (dans les 2 cas il faut avoir la commande php disponible dans le terminal) :
Sous Windows, il vous suivre les instructions suivantes
Sous unix utiliser votre gestionnaire de package
apt-get install composer,dnf install composer, …
Utilisation de composer¶
Composer est un logiciel qui fonctionne en ligne de commande, ainsi il vous suffit d’invoquer composer pour accèder à la commande composer.
Création d’un répertoire pour le projet¶
$ mkdir test_composer
$ cd test_composer
Installation d’une librairie via composer¶
$ composer require rmccue/requests
Cette commande lance la récupération et l’installation de la librairie requests. Si c’est la première fois que la commande est lancée, celle-ci va créée plusieurs répertoires et fichiers :
|
pour spécifier les différentes dépendances, |
|
instannée des dépendances à un instant \(t\), |
|
le répertoire qui contient les fichies des dépendances. |
Note
L’avantage c’est que composer est capable de gérer les dépendances, donc si votre librairie à besoin d’une autre librairie pour fonctionner elle sera automatiquement téléchargée.
Avertissement
Il est intéressant de versionner le fichier composer.json toutefois le composer.lock ainsi que le dossier vendor viendrait en revanche polluer notre dépôt Git.
Utilisation de notre librairie¶
C’est bien beau de télécharger des librairie mais encore faut-il être en mesure de les inclure.
Note
Lors d’une install ou d’un update composer va automatiquement généré un autoloader autoload.php disponible à la racine du répertoire vendor.
Cet autoloader permet de ne pas avoir à inclure les fichier des différentes librairies mais utilise un système d’autoloader, vous n’avez qu’à vous soucier des namespace.
<?php
require "vendor/autoload.php"; // Inclusion de l'autoloader
Découverte de requests en PHP¶
Objectif
Nous allons voir dans cette partie comment développer une application en PHP appelant une API qui va récupérer le résultat de la requête en JSON (JavaScript Object Notation) pour l’afficher sur votre site web.
Les API sur Internet
Les API sont un moyen d’accéder aux données d’un site sans avoir l’autorisation d’accéder directement à la base de données. Il y a beaucoup de portails sécurisés permettant à vos applications web de manipuler les données renvoyées par ces sites.
L’exemple parfait est Twitter. Elle permet de lire la timeline d’une personne en particulier, de rechercher des statuts à partir d’un mot clé, de modifier les paramètres de votre compte, etc.
Pourquoi le format JSON ?
Vous pouvez utiliser les API avec de nombreux langages et retourner les données de plusieurs façons. L’une d’elles est le JSON (JavaScript Object Notation). C’est un format de données léger, facile à lire et à écrire et compatible avec pas mal de langages de développement.
Sa structure est composée d’objets et de tableaux. Sa flexibilité fait de JSON le parfait candidat pour retourner des données.
Requests et PHP¶
Faire une requête est très simple :
<?php
$response = Requests::get('https://ingen.ugaritic.fr/api/books/');
Et si on affiche le contenu de $response :
<?php
var_dump($response->body);
Pour obtenir le statut de la réponse:
<?php
var_dump($response->status_code);
Pour obtenir un tableau à partir du corp de la réponse:
<?php
$books = json_decode($response->body);
On peut maintenant itérer sur ce tableau pour afficher chaque titre de livre:
<?php
foreach($books as $book){
echo $book->title;
}
À faire
Modifiez votre fichier pour afficher sous une belle forme HTML chaque élément retourné par l’API,
Essayez de récupérer l’élément JSON à l’url suivante: /api/books/a-game-of-thrones comme précédemment,
Comment corriger ce problème ?
Indication
Vous venez de découvrir un gestionnaire de dépendances en PHP qui sera bien utile pour la suite de nos aventures ! Vous avez également découvert les API REST et le format de données JSON ! Félicitations !
PHP & la POO¶
La définition d’une classe commence par le mot-clé class suivi du nom de la classe.
<?php
class Warrior{
//Attributs
public $name = 'Conan'; //default value
public function my_name(){
echo $this->var;
}
}
Pour créer l’instance d’une classe on utilise le mot-clé new.
<?php
include('warrior.php');
$warrior1 = new Warrior();
$warrior1->my_name();
Note
Les attributs sont définies au sein d’une classe en utilisant un des mots-clés public, protected ou private suivie d’une déclaration classique de variable.
Important
Les variables au sein d’une classe sont appellées attributs (membres, propriétés ou encore champs).
Au sein des méthodes de classes, les attributs peuvent être appelées en utilisant la syntaxe -> (opérateur de l’objet):
$this->my_attr.
La pseudo-variable $this est disponible au sein de n’importe quelle méthode quand celle-ci est appelée depuis un objet. $this est une référence à l’objet appellant.
À faire
Dans notre classe warrior, ajouter les attributs (publiques) suivants avec une valeur par défault:
point de vie (hp)
point d’attaque (att)
point de défence (def)
Note
Les attributs sont définies comme publiques, protégées ou privées.
À faire
<?php
class Warrior{
//Attributs
public $name = 'Conan'; //default value
private $hp = 10;
private $att = 6;
private $def = 3;
public function my_name(){
echo $this->var;
}
}
<?php
include('warrior.php');
$warrior1 = new Warrior();
$warrior1->my_name();
echo $warrior->name;
echo $warrior->att;
echo $warrior->def;
echo $warrior->hp;
Que se passe t’il lors de l’accès au attributs hp, att et def ?
Astuce
Pour remédier aux soucis d’accessibilités, nous allons mettre en oeuvre la notion de getter et de setter
À faire
<?php
class Warrior{
//Attributs
public $name = 'Conan'; //default value
private $hp = 10;
private $att = 6;
private $def = 3;
public function get_hp(){
return $this->hp;
}
public function set_hp($hp){
$this->hp = $hp;
}
public function my_name(){
echo $this->var;
}
}
Tester ces deux nouvelles méthodes, quels est leur intérêt ? Ajouter les getters et setters pour les autres attributs de notre classe.
Note
La déclaration de constructeur permet d’ajouter un comportement qui sera appelé à chaque création d’une nouvelle instance de la classe.
<?php
class Warrior{
//Attributs
public $name = 'Conan'; //default value
private $hp = 10;
private $att = 6;
private $def = 3;
function __construct($name, $hp, $att, $def){
//TODO: complete behavior
}
}
Ce constructeur permet d’initialiser les attributs de notre instance lors de sa création.
À faire
Complétez le constructeur de notre classe puis instanciez plusieurs objets de type Warrior et enfin manipulez les.
Ajouter une méthode
slapqui permet à un guerrier de gifler un autre guerrier ..Ajouter une méthode
take_damagequi permet à un guerrier de subir des dégâts ..Simuler une « bagarre » entre deux guerriers ..
Un framework light php: Flight PHP¶
Flight est un framework performant, simple et extensible pour PHP.
Flight vous permet de créer rapidement et facilement des applications Web (RESTful).
Flight est un micro framework PHP intégrant notamment la gestion du routing , les views et bien d’autres choses..
Installation de Flight PHP¶
$ composer require mikecao/flight
Note
Pour rappel, composer ne fait que télécharger les sources des librairies PHP (les fichiers php donc) et créer automatiquement un autoloader que nous utiliserons dans notre projet. Il faut donc retenir que ces librairies ne sont pas installées dans le système mais uniquement télécharger dans le répertoire vendor. Il s’agit d’ailleurs d’une bonne pratique d’avoir un dossier vendor par projet.
Pour commencer¶
<?php
require "vendor/autoload.php";
Découverte du routing¶
Dans le fichier index.php rajouter le code suivant:
<?php
Flight::route('/', function(){
echo 'hello world!';
});
Flight::start();
Rendez-vous sur votre site web local
Normalement vous devriez voir une page blanche avec les mots hello world! ,
Félicitations!
Quelques précisions:
La méthode statique: start permet de charger le framework et de mettre en place le routing.
La méthode statique route permet d’associer à un pattern de route, une fonction. En l’occurrence / signifie l’index du site web et ensuite nous avons déclaré une fonction directement dans le passage de variable.
Ainsi, ceci:
<?php
Flight::route('/', function(){
echo 'hello world!';
});
est identique à cela:
<?php
function hello(){
echo 'hello world!';
}
Flight::route('/', 'hello');
Routing & paramètre¶
<?php
Flight::route('/hello/@name', function($name){
echo "hello, $name!";
});
À faire
Qu’elle est l’utilité de ce code ?
Indication
Nous venons de découvrir la base de routing et le principe d’un framework, voyez vous l’utilité du routing à présent ?
Composer et autoloading¶
Vous aurez remarqué que depuis le début nous utilisons le fichier autoload.php pour charger les librairies installées via composer.
Et si je souhaite charger automatiquement mes propres fichiers ?
Nous l’avons déjà vue, le fichier composer.json permet de lister les dépendances dont notre projet à besoin mais nous pouvons également y ajouter nos propres fichiers.
Nous adoptons la structure de projet suivante:
-- src/
-- vendor/
-- composer.json
-- composer.lock
-- index.php
Astuce
Si nous ajoutons un fichier functions.php dans notre dossier src, il suffit de modifier le fichier composer.json comme ceci pour ajouter notre fichier de fonctions à l’autoloader:
{
"require": {
"mikecao/flight": "^1.3",
},
"autoload": {
"files": [
"src/functions.php",
"src/utils.php"
]
}
}
Via l’objet autoload et sa propriété files nous indiquons à composer de charger les fichiers lister.
Ainsi functions.php et utils.php seront chargés automatiquement.
Attention
Il convient d’appeller la commande composer dump-autoload après chaque modification du fichier composer.json.
Vous savez désormais charger automatiquement vos fichiers via composer, ainsi seul le require de l’autoloader de composer sera requis. En clair vous n’aurez plus besoin de charger un à un vos fichiers PHP !
Test unitaire & d’intégration avec PHP¶
Plutôt que de tout tester à la main de manière répétitif et fastidieuse, nous allons apprendre à automatiser les tests unitaires et d’intégrations.
Pré-requis en PHP
Pour réaliser les tests unitaires et d’intégrations nous aurons besoin des librairies php suivantes:
phpunit/phpunitguzzlehttp/guzzlesymfony/process
Un petit composer require pour l’installation ?
Nous allons donc créer un répertoire tests à la racine de notre projet qui contiendra nos fichier de tests.
Premier test unitaire en PHP¶
L’exemple le plus simple pour comprendre le principe des tests unitaires est la fonction de multiplication.
Note
Pour rappel une multiplication est l’opération mathématique permettant d’obtenir le produit de deux facteur (nommés facteur gauche et facteur droite).
À faire
Créer un fichier (vide)
functions.phpdans le répertoiresrc/Créer dans le répertoire
tests/un fichierUnitariesTest.phpPensez à modifier votre
composer.jsonpour charger automatiquement le fichier de fonctions qui se trouve dans le répertoiresrc/
PHPUnit¶
PHPUnit est un framework open source de tests unitaires dédié au langage de programmation PHP.
Il permet l’implémentation des tests de non-régression en vérifiant que les exécutions correspondent aux assertions prédéfinies.
Premier test¶
Modifier le fichier UnitariesTest.php pour que son contenu soit:
1<?php
2
3require_once 'vendor/autoload.php';
4
5use PHPUnit\Framework\TestCase;
6
7class UnitariesTest extends TestCase {
8
9 public function test_multiply(){
10 $this->assertEquals(4, multiply(2, 2));
11 }
12}
Quelques précisions
Nous venons de définir une classe de tests qui posséde un fonction permettant de tester notre fonction de multiplication (notez la présence du require pour charger l’autoloader de composer).
La ligne 10 permet de vérifier que la fonction retourne bien le produit de 2 par 2 (à savoir 4).
Nous avons définit notre premier test, il serait donc temps de le lancer via la commmande suivante:
$ vendor/bin/phpunit tests/UnitariesTest.php
Normalement vous devriez avoir l’erreur suivante :
PHPUnit 7.5.2 by Sebastian Bergmann and contributors.
E 1 / 1 (100%)
Time: 15 ms, Memory: 4.00MB
There was 1 error:
1) MultiplyTest::test_multiply
Error: Call to undefined function multiply()
/home/sam/php/tests/UnitariesTest.php:8
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
C’est normal ! Nous respectons la philosophie TDD à savoir écrire nos tests d’abord puis vérifier que ceux-ci échouent !
À faire
Il convient de créer maintenant notre fonction de multiplication, copier-coller dans votre fichier functions.php le code (sans corriger erreur) suivant:
<?php
function multiply($facteur_gauche, $facteur_droite)
{
return $facteur_gauche + $facteur_droite;
}
Relancez votre test via la commmande suivante:
$ vendor/bin/phpunit tests/UnitariesTest.php
Attention
Notre test passe ! Mais notre jeu de données n’est pas complet ! Il convient donc d’ajouter une assertion supplémentaire à notre test.
À faire
Ajoutez l’assertion que le produit de 3 par 7 est 21
Relancez votre test que constatez vous ?
Corrigez le code de votre fonction pour régler ce souci.
Bravo vous venez de faire votre première suite de test unitaire.
Astuce
Le répertoire tests ainsi que le fait de suffixer nos fichier par Test.php est une convention qui permet à php unit de charger de manière automatique l’ensemble des tests du répertoire.
Il suffit de lancer la commande suivante pour lancer l’ensemble des tests du répertoire tests/ :
$ vendor/bin/phpunit tests/
Test d’intégration avec FlightPHP¶
Tester nos fonctions c’est bien, utile et indispensable. Toutefois il est tout aussi indispensable de tester le retour de nos pages, nous allons donc voir comment mettre en place des tests d’intégrations.
Création d’un client de test en PHP¶
Pour pouvoir tester notre site en intégration il convient de créer un client de tests. Votre professeur étant fort sympatique en voici un à placer dans le fichier src/utils.php
<?php
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Process;
use GuzzleHttp\Client;
abstract class IntegrationTestCase extends TestCase {
private static $process;
public static function setUpBeforeClass(): void
{
self::$process = new Process(["php", "-S", "localhost:8080", "-t", "."]);
self::$process->start();
usleep(100000); //wait for server to get going
}
public static function tearDownAfterClass(): void
{
self::$process->stop();
}
public function get_client()
{
return new Client(['http_errors' => false]);
}
public function build_url($url)
{
return "localhost:8080" . $url;
}
public function make_request($method, $url)
{
$client = $this->get_client();
return $client->request($method, $this->build_url($url));
}
}
Ce client lance en processus votre site web et permet de tester le retour de requête HTTP sur celui-ci.
Notre premier test d’intégration avec FlightPHP¶
Créer un fichier IntegrationsTest.php dans le répertoire tests/ avec le contenu suivant:
<?php
require_once 'vendor/autoload.php';
class PagesIntegrationTest extends IntegrationTestCase{
public function test_index()
{
$response = $this->make_request("GET", "/");
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals("Hello World!", $response->getBody()->getContents());
$this->assertContains("text/html", $response->getHeader('Content-Type')[0]);
}
}
Le test d’intégration test_index permet de:
Tester si le retour de la requête sur
/en méthode GET retourne le code de statut 200,Et que le contenu de la réponse est bien
Hello World!.Et que le type de contenu est bien du
text/html.
À faire
Lancez la suite de tests,
Que constatez vous ?
Comment corriger le problème ?
Comment mettre en place un test d’intégration pour la route
hellode votre site ? Pensez à l’esprit TDD et comment tester cela.Et si notre route
helloretourner une string sous la forme<h2>Hello $name </h2>?Et si on utiliser l’assertion
assertContains?
Twig¶
Maintenant que nous savons router nos fonctions PHP vers un pattern d’url (routing + Controller), il convient de s’intéresser au V de MVC à savoir les vues !
Des vues ?¶
Les vues (parfois nommés templates) permettent de séparer la présentation (le code HTML) de notre logique métier (le controller). Flight intégre un mécanisme de gestion des vues mais celui-ci est plutôt limité. Nous allons donc utiliser le moteur de templates du secteur PHP à savoir Twig.
Twig, un moteur de template¶
Twig est un moteur de templates pour le langage de programmation PHP, utilisé par défaut par le framework Symfony. Celui-ci est une traduction de jinja, moteur de templates écrit en python.
Twig dispose des fonctionnalités suivantes:
Contrôle de flux complexe
Échappement automatique
Héritage des templates
Filtres de variables
Internationalisation (via
gettext)Macros
Langage extensible
Syntaxe de Twig¶
{{ ma_variable }}
{{ book.title }}
{% for book in books %}
{{ book.title }}
{% endfor %}
<ul>
{% for book in books %}
<li>
<strong>{{ book.title }}</strong>
</li>
{% endfor %}
</ul>
Filtres dans un template Twig¶
Important
Les filtres fournissent des traitements sur une expression, si on les place après elle séparés par des pipes.
|
met une majuscule à la première lettre d’une chaine de caractères. |
|
met la chaine en lettres capitales. |
|
affiche la première ligne d’un tableau. |
|
renvoie la taille de la variable. |
Variables spéciales et Twig¶
loop: contient les informations de la boucle dans laquelle elle se trouve. Par exemple loop.index donne le nombre d’itérations déjà survenue._route: partie de URL située après le domaine
{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) }}
Installation de twig¶
Un composer require sur le package twig/twig ?
Twig et FlightPHP¶
Initialisation
Pour commencer créer un répertoire views/ à la racine du projet.
Puis nous devons indiquer à Flight que nous allons utiliser Twig et le charger en extension mais tout d’abord nous devons charger Twig et le configurer.
index.php¶<?php
$loader = new \Twig\Loader\FilesystemLoader(dirname(__FILE__) . '/views');
$twigConfig = array(
// 'cache' => './cache/twig/',
// 'cache' => false,
'debug' => true,
);
Une fois que nous avons charger et configurer Twig, il convient d’indiquer à Flight de l’inscrire en tant qu’extension
index.php¶<?php
Flight::register('view', '\Twig\Environment', array($loader, $twigConfig), function ($twig) {
$twig->addExtension(new \Twig\Extension\DebugExtension()); // Add the debug extension
});
Désormais Flight sera en mesure d’utiliser Twig.
Notre première vue¶
Et maintenant nous allons utiliser Twig conjointement avec Flight pour générer notre page à partir d’une vue.
<?php
Flight::route('/first_view/', function(){
Flight::view()->display('first_view.twig');
});
À faire
Que se passe t’il en se rendant sur notre page first_view ?
Créer le fichier
views/first_view.twigpour corriger l’erreur.Un fichier vide c’est bien, et si nous ajoutions un peu de HTML ?
Afficher des choses plus utiles¶
Afficher du HTML via Twig c’est bien, afficher le conteu de variables, tableaux, c’est encore mieux. Nous allons découvrir comment passé des données à notre vue.
En reprenant notre vue:
<?php
Flight::route('/first_view/', function(){
Flight::view()->display('first_view.twig');
});
Nous allons la modifier, pour lui passer quelques données:
<?php
Flight::route('/first_view/', function(){
$data = [
'contenu' => 'Hello World!',
'name' => 'Ben Kenobi',
];
Flight::view()->display('first_view.twig', $data);
});
Nous passons à notre fichier twig le tableau data qui contient deux éléments. Si nous rechargons notre page nous ne voyons pas nos données apparaître. Il faut donc modifier notre fichier .twig pour afficher les données:
Ajoutez le code suivant à votre vue:
{{ contenu }}
{{ name }}
Twig et l’héritage de template¶
Important
La partie la plus puissante de Twig est l’héritage des templates. L’héritage de templates vous permet de créer un « squelette » de base contenant tous les éléments communs de votre site et définissant les blocs que les modèles enfants peuvent remplacer.
Cela semble compliqué mais reste très basique. Il est plus facile de comprendre cela en commençant par un exemple.
Template de base¶
Ce template, que nous appellerons base.twig , définit un simple squelette de document HTML que vous pouvez utiliser pour une page simple à deux colonnes. C’est le travail des templates « enfants » de remplir les blocs vides de contenu:
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<main>{% block content %}{% endblock %}</main>
<footer>
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</footer>
</body>
</html>
Dans cet exemple, les balises {% block %} définissent quatre blocs que les modèles enfants peuvent remplir.
Note
La balise de bloc indique simplement au moteur de gabarit qu’un modèle enfant peut remplacer les espaces réservés qu’il contient.
Template enfant¶
Un modèle enfant pourrait ressembler à ceci:
{% extends "base.twig" %}
{% block title %}Index{% endblock %}
{% block head %}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock %}
Note
La balise {% extend %} est la clé ici. Il indique au moteur de modèle que ce modèle « étend » un autre modèle. Lorsque le système de gabarit évalue ce gabarit, il localise d’abord le parent. La balise extend devrait être la première balise du modèle.
Variables global et filtre¶
Parfois nous avons besoins de passer la même valeur à plusieurs voir toutes nos vues, Twig intégre un mécanisme très intéressant appellé global.
<?php
Flight::register('view', '\Twig\Environment', array($loader, $twigConfig), function ($twig) {
$twig->addExtension(new \Twig\Extension\DebugExtension()); // Add the debug extension
$twig->addGlobal('ma_valeur', "Hello There!");
});
Ainsi la variable ma_valeur sera passé à chaque vue, il est possible de rendre cela plus intéressant en passant des variables plus complexes, voir le résultat d’appel de fonction.
Twig et les Filtres¶
Twig intégre de nombreux filtres par défaut, il est possible de créer nos propres filtres au besoin:
<?php
Flight::register('view', 'Twig_Environment', array($loader, $twigConfig), function ($twig) {
// Twig loading treatment
$twig->addFilter(new \Twig\TwigFilter('trad', function($string){
return $string;
}));
});
Note
Il s’agit d’un filtre très intéressant qui retourne la valeur inchangée.. Le but est ici juste de vous présenter la syntaxe pour créer vos propres filtres.
Shortcut¶
Faire appel à Twig pour rendre nos templates c’est chouette, toutefois faire appel à cette ligne de code :
<?php
Flight::view()->display('first_view.twig');
Reste assez complexe. Et si nous utilisions un raccourci pour faire appel à cette fonction ?
Après l’enregistrement de Twig ajouter le code suivant:
<?php
Flight::map('render', function($template, $data=array()){
Flight::view()->display($template, $data);
});
Ce code permet de dire que lorsque que nous ferons appel à la méthode render de Flight, nous utiliserons Twig.
Sans shortcut |
Avec shortcut |
<?php
Flight::route('/first_view/', function(){
Flight::view()->display('first_view.twig');
});
|
<?php
Flight::route('/first_view/', function(){
Flight::render('first_view.twig');
});
|
À faire
En utilisant l’approche TDD, les Tests unitaires et d’intégrations
Créer une fonctions permettant de récupérer les livres lister à l’adresse suivante https://ingen.ugaritic.fr/api/books/,
Créer une route ainsi qu’une vue (template Twig) pour afficher le nombre de livres ainsi que les différents livres.
Voir aussi
Il reste encore pas mal d’aspects de twig à découvrir comme Les structures de contrôles , ou encore l’escaping
Formatter du Markdown en PHP¶
Voir aussi
Pour en savoir un peu plus sur le Markdown.
Pour générer du HTML à partir de markdown on utilise en PHP la librairie michelf/php-markdown (composer require ?)
Dans votre fichier functions.php , ajouter la fonction suivante:
<?php
use MichelfMarkdown;
function renderHTMLFromMarkdown($string_markdown_formatted)
{
return Markdown::defaultTransform($string_markdown_formatted);
}
Et si nous ajoutions une route (markdown_test) pour utiliser notre fonction ?
<?php
Flight::route('/markdown_test', function(){
echo renderHTMLFromMarkdown("##HELLO WORLD!");
});
Allons un peu plus loin¶
Il est plus intéressant de stocker le contenu de nos pages statiques (présentation de l’entreprise, mentions légales, à propos, ..) dans différents fichiers puis de lire leurs contenu à la demande.
Créer dans à la racine de votre projets un répertoire
pagesCréer dans le répertoire
pages/un fichiertest.mdavec le contenu suivant:
## Test
Indication
Pour récupérer le contenu d’un fichier, PHP nous fournit la fonction file_get_contents.
Ainsi nous pouvons créer dans notre fichier tests/UnitariesTest.php la fonction de test suivante:
<?php
public function test_readFileContent(){
$this->assertEquals("## Test", readFileContent("pages/test.md"));
}
On relance notre suite de tests, test_readFileContent échoue .. parfait !
Ajoutons la fonction
readFileContent
<?php
function readFileContent($filepath){
return file_get_contents($filepath);
}
Remplacer dans notre controller pour
/markdown_test, la string##HELLO WORLDpar le contenu du fichierpages/test.md
À faire
Créer un template Twig pour afficher le contenu de votre page dans un template HTML,
Le code HTML produit s’affiche correctement ? L’utilisation du filtre
rawpourra vous être utile,Créer une fonction
readPagesContentqui prend en paramètre uniquement le nom de votre fichier.md(sans l’extension.md) et qui vous retourne le contenu de la pages (TDD?),Convertir le markdown en HTML n’est pas un job pour notre controller, il s’agit d’un boulot pour notre vue ! Créer un filtre Twig pour convertir du Markdown en HTML
Indication
La fonction sprintf permet de faire du formatage de chaîne de caractéres.
<?php
$name = "Ben";
$hello_string = sprintf("hello %s", $name); // Hello Ben
Jurassic Park en PHP¶
Un site commandé par InGen¶
Note
La société InGen, spécialisée dans la création de créatures préhistoriques génétiquement modifiées; souhaite créer un site pour son dernier projet.
La majeure partie de son activité consiste à faire naître des dinosaures et à les présenter au sein de Jurassic Park. John Hammond, le PDG de l’entreprise InGen, travaille à la création d’un parc d’attractions sur Isla Nublar, une île qu’il possède au large du Costa Rica. Le parc comporte des animaux disparus tels que les dinosaures, comme attractions. Les créatures ont été clonés dans des installations spéciales à proximité sur Isla Sorna et amenés au parc.
Énoncé du projet Jurassic Park en PHP¶
Note
Le but de cet exercice est de contrôler votre acquisition de connaissances par un simple exercice. Il convient de mettre en application les notions vues.
En appliquant les notions vues sur PHP, composer, les frameworks, etc. vous réaliserez un site web basique présentant les différents dinosaures du parc. Les données vous sont pour cela fournit via l’API REST suivante API Dinosaurs.
Création de deux pages différentes (accueil et détails sur un dinosaure),
Mise en oeuvre du TDD (Test Driven Development),
Versionning sous Git (pensez à versionner votre projet au fur et à mesure de votre avancé),
Les données sont disponibles via l’API Dinosaurs ,
Les fichiers css, d’images, etc. sont disponibles
ici,Les templates HTML vous sont fournis dans la suite de cet énoncé,
Quelques conseils également.
Résultat du projet PHP
Page d’accueil listant l’ensemble des dinosaures
L’url de cette page devra être la racine de votre site (
/),Les données seront récupérer depuis l’API Dinosaurs.
Page de détails sur un dinosaure
L’url de cette page devra être sous la forme
/dinosaur/<slug du dinosaure>/(où le slug correspond au slug retourné par l’API Dinosaurs);Les données d’un seul dinosaure peuvent être récupérées via l’API Dinosaurs, par exemple https://ingen.ugaritic.fr/api/dinosaurs/brachiosaurus permet de récupérer les informations du brachiosaure;
Les « Top Rated Dinosaurs » sont une récupération aléatoire de trois dinosaures parmis les septs retournées par l’API Dinosaurs , l’utilisation de la fonction PHP
array_randvous est vivement conseillé.
Indication
Créer d’abord votre projet sous Gitlab et pensez directement à le cloner;
Récupérer les fichiers d’assets et placer les dans un répertoire assets à la racine de votre projet;
Penser à faire un composer require de toutes les libs;
L’approche TDD vous aidera grandement, quelles sont les fonctions que je vais devoir développer et les T.U associés, les pages et les T.I associées;
Avancer par étape, d’abord la récupération de tous les dinosaures (test ?) puis la page d’accueil, puis la page de détail et enfin les « Top Rated Dinosaurs « ;
Respirer c’est important;
Aller c’est cadeau un petit exemple de la fonction
array_rand(par contre la grande question demeure .. comment recréer un tableau à partir des clés d’un autre tableau ?):
<?php
$colors = array("red","green","blue","yellow","brown");
$random_keys = array_rand($colors, 3); // On récupére trois clés aléatoires
echo $colors[$random_keys[0]];
echo $colors[$random_keys[1]];
echo $colors[$random_keys[2]];
Quelques templates ?
<!DOCTYPE html>
<html>
<head>
<title>{%block title %}{% endblock %}Jurassic Park</title>
<meta charset="utf8" />
<link rel="stylesheet" type="text/css" href="/assets/css/reset.css" />
<link rel="stylesheet" type="text/css" href="/assets/css/style.css" />
<link href="https://fonts.googleapis.com/css?family=Montserrat|Staatliches" rel="stylesheet">
</head>
<body>
<header>
<a href="/">
Jurassic Park
</a>
<nav>
<a href="/">Home</a>
</nav>
</header>
<main>
{% block main %}{% endblock %}
</main>
<footer>
<img src="/assets/img/logojp.png" alt="Jurassic Park" />
</footer>
</body>
</html>
<!-- Héritage de template ? Block(s) ? -->
<section>
<h2>The world of Jurassic Park</h2>
<!-- iterate here ? -->
<a href="">
<figure>
<img src="" alt="" />
<figcaption>Discover the Brachiosaurus</figcaption>
</figure>
</a>
</section>
<!-- Héritage de template ? Block(s) ? -->
<div class="grid">
<article>
<h2>Discover the Brachiosaurus</h2>
<div class="description">
Lorem ispum
</div>
<div class="card">
<img src="" alt="" />
<dl>
<dt>Name</dt>
<dd>Brachiosaurus</dd>
<dt>Name meaning</dt>
<dd>Brachiosaurus</dd>
<dt>Diet</dt>
<dd>Brachiosaurus</dd>
<dt>Height</dt>
<dd>Brachiosaurus m.</dd>
<dt>Length</dt>
<dd>Brachiosaurus m.</dd>
<dt>Weight</dt>
<dd>Brachiosaurus kg.</dd>
</dl>
</div>
</article>
<aside>
<h3>Top rated dinosaurs</h3>
<!-- iterate here ? -->
<a href="">
<figure>
<img src="" alt="" />
<figcaption>Discover the Brachiosaurus</figcaption>
</figure>
</a>
</aside>
</div>
ORM et FlightPHP¶
Note
Un ORM est une technique de programmation informatique qui crée l’illusion d’une base de données orientée objet à partir d’une base de données relationnelle en définissant des correspondances entre cette base de données et les objets du langage utilisé. On pourrait le désigner par « correspondance entre monde objet et monde relationnel ».
Découverte de Paris¶
Paris est un ORM PHP open source disponible à cette adresse , il se base sur idiorm. Il s’agit d’une introduction à l’installation et l’utilisation de Paris. Je ne peux que vous encourager à aller consulter la documentation de celui-ci.
Comme toujours un simple composer require j4mie/paris
La base de données de tweets¶
Après avoir installé le package j4mie/paris il suffit de configurer une connexion à notre base de données. Pour la suite nous allons utiliser la base de données SQLite tweets.sqlite3.
Diagramme UML de la base de données tweets¶
Configuration de Paris¶
Astuce
Nous allons utiliser une capacité spéciale de Flight, le filtering pour ajouter un traitement avant que le framework ne traite notre requête.
Nous allons donc ajouter la connexion automatique à notre basse de données avant que celui-ci ne soit démarré (ce code doit être placé juste après l’enregistrement de l’extension Twig ):
<?php
Flight::before('start', function(&$params, &$output){
ORM::configure('sqlite:tweets.sqlite3');
});
Notre premier Model avec Paris¶
Dans le dossier src créer le fichier models.php , pensez à l’ajouter au composer.json
<?php
class User extends Model {
public static $_table = 'Users';
}
Voilà ! Notre model est créé, à nous de l’utiliser !
Note
Il est inutile de préciser la nature de notre table, l’ORM se chargera pour nous de déduire la structure de nos objets à partir de la base de données. Le mapping de nos champs se fait automatiquement à partir de la structure de notre table.
Ainsi il sera possible d’accéder au champ name d’un utilisateur via l’attribut name qui sera automatiquement généré lors de l’éxécution par l’ORM.
Par convention, les champs de nos tables ne doivent contenir ni préfixe, ni suffixe, les alias ont cette utilité ! De plus la clé primaire (unique) de notre table sera toujours nomée par convention id.
Requête via paris¶
Pour plus d’informations sur la manière d’écrire des requêtes, je vous invite à vous rendre à cette page de la documentation.
Dans le fichier index.php nous allons créer une route pour afficher l’ensemble des utilisateurs au sein d’un fichier de vue users.twig.
<?php
User::find_many();
Cette ligne permet de récupérer tout les utilisateurs présent en bases. A vous maintenant d’itérer sur le tableau retourner afin d’obtenir une liste des utilisateurs au sein de votre vue.
À faire
Créer la classes dans le fichier
models.phppour les tweets,Créer une route pour lister tous les tweets présent en base,
Récupérer les tweets de chaque utilisateur, cf la documentation sur les associations.
Créer une vue pour afficher uniquement les informations d’un seul utilisateur en prenant en paramètre
@usernamele nom d’utilisateur cf la partie sur le requêtage de la documentation.Créer une vue pour afficher un formulaire permettant d’ajouter un tweet en base.
Indication
Nous venons de voir que manipuler une base de données est extrèmement simple avec les bons outils, je vous invite donc à lire la documentation de Paris notamment sur la partie Querying.
API REST & JSON¶
Note
Une API (Application Programming Interface) est une interface pour les applications; elle permet la manipulations de données, méthodes, fonctions etc. C’est un mécanisme à la base de tout génie logiciel complexe.
Nous allons voir dans cette partie comment développer une API en PHP qui retournera un résultat sous la forme d’une réponse au format JSON (JavaScript Object Notation).
Note
REST ( Representational State Transfer ) est un standard créé en 2000 par Roy Fielding dans sa thèse « Architectural Styles and the Design of Network-based Software Architectures ».
Les API sur Internet
Les API sont un moyen d’accéder aux données d’un site sans avoir l’autorisation d’accéder directement à la base de données. Il y a beaucoup de portails sécurisés permettant à vos applications web de manipuler les données renvoyées par ces sites.
L’exemple parfait est Twitter. Elle permet de lire la timeline d’une personne en particulier, de rechercher des statuts à partir d’un mot clé, de modifier les paramètres de votre compte, etc.
Ou encore l’API proposer par sur ce site à savoir ingen.ugaritic.fr
Important
Les API REST sont basées sur le Protocole HTTP ( Hypertext Transfer Protocol ) le protocole au coeur du web ! Bien sûr, toutes les API ne sont pas basées sur HTTP, mais en choisissant une API REST, vous simplifiez l’intégration et l’utilisation de votre API en utilisant un protocole ouvert, standardisé et simple d’utilisation.
Les API REST imitent la façon dont le web lui-même marche dans les échanges entre un client et un serveur. Une API REST est donc:
Sans état (aucune idée de l’état du client entre deux requêtes, donc aucune liaison entre elles),
Cacheable (avec cache = mémoire),
Orienté client-serveur.
Pourquoi le format JSON ?
Vous pouvez utiliser les API avec de nombreux langages et retourner les données de plusieurs façons. L’une d’elles est le JSON (JavaScript Object Notation). C’est un format de données léger, facile à lire et à écrire et compatible avec pas mal de langages de développement.
Sa structure est composée d’objets et de tableaux. Sa flexibilité fait de JSON le parfait candidat pour retourner des données.
Une API avec PHP & Flight¶
Pour créer une api rien de bien complexe, il suffit de:
Définir une route (par exemple
/api/helloworld),Retourner des données au format JSON depuis cette route.
Nous souhaitons créer une API associé à la route /api/helloworld qui nous retourne un résultat équivalent à:
{"data":"Hello World!"}
Nous allons créer une nouvelle classe de test (donc un nouveau fichier php dans le répertoire tests ) ApiTest qui servira à tester notre api !
<?php
require_once 'vendor/autoload.php';
class ApiTest extends IntegrationTest{
public function test_api_helloworld()
{
$response = $this->make_request("GET", "/api/helloworld");
$this->assertEquals(200, $response->getStatusCode());
$this->assertContains("application/json", $response->getHeader('Content-Type')[0]);
$body = $response->getBody()->getContents();
$json_result = json_encode(['data' => 'Hello World!']);
$this->assertEquals($json_result, $body);
}
}
Quelques précisions
Le test test_api_helloworld effectue une requête sur /api/helloworld et vérifie les assertions suivantes:
Le code statut de la requête est égale à 200,
Le type de réponse est bien
application/jsonet nontext/html,Le contenu de la réponse est égale à la conversion en JSON d’un tableau associatif contenant une clé
dataavec la valeurHello World.
<?php
Flight::route('/api/helloworld', function(){
$data = [
'data' => 'Hello World!',
];
Flight::json($data);
});
Flight nous permet de retourner directement des données au format JSON via la méthode Flight::json.
À faire
Créer une route (et les tests associés)
/api/helloworld/@namequi retourneHello $name,Créer une route (et les tests associés)
/api/usersqui retourne l’ensemble des utilisateurs de la base de données tweets.Créer une route
/api/user/@usernamequi retourne un utilisateur selon son nom d’utilisateur et l’ensemble de ses tweets.
Javascript¶
Note
JavaScript (qui est souvent abrégé en « JS ») est un langage de script léger, orienté objet, principalement connu comme le langage de script des pages web.
Mais il est aussi utilisé dans de nombreux environnements extérieurs aux navigateurs web tels que Node.js, Apache CouchDB voire Adobe Acrobat.
Le code JavaScript est interprété ou compilé à la volée (JIT). C’est un langage à objets utilisant le concept de prototype, disposant d’un typage faible et dynamique qui permet de programmer suivant plusieurs paradigmes de programmation : fonctionnelle, impérative et orientée objet.
Une définition générale¶
JavaScript est un langage de programmation qui permet d’implémenter des mécanismes complexes sur une page web. À chaque fois qu’une page web fait plus que simplement afficher du contenu statique — afficher du contenu mis à jour à des temps déterminés, des cartes interactives, des animations 2D/3D, des menus vidéo défilants, etc.
JavaScript a de bonnes chances d’être impliqué. C’est la troisième couche des technologies standards du web, les deux premières étant HTML et CSS. Les trois couches viennent s’empiler naturellement.
Premier exemple de javascript¶
Attention
Je vous conseille de mettre cette exemple dans une route à part de votre site par exemple /js/exemple1
Nous pouvons la baliser en HTML pour définir sa structure et son but:
<p>Player 1: Ben</p>
Nous pouvons ensuite ajouter du CSS pour rendre ça plus joli:
p {
font-family: 'helvetica neue', helvetica, sans-serif;
letter-spacing: 1px;
text-transform: uppercase;
text-align: center;
border: 2px solid rgba(0,0,200,0.6);
background: rgba(0,0,200,0.3);
color: rgba(0,0,200,0.6);
box-shadow: 1px 1px 2px rgba(0,0,200,0.4);
border-radius: 10px;
padding: 3px 10px;
display: inline-block;
cursor:pointer;
}
Et enfin utiliser JavaScript pour ajouter un comportement dynamique :
let para = document.querySelector('p');
para.addEventListener('click', updateName);
function updateName() {
let name = prompt('Enter a new name');
para.textContent = 'Player 1: ' + name;
}
Pensez à placer le code javascript en fin de fichier HTML dans les balises:
<script type="text/javascript">
</script>
JavaScript peut faire bien plus. Voyons cela plus en détail.
Que peut-il vraiment faire ?¶
Le cœur de JavaScript est constitué de fonctionnalités communes de programmation permettant de :
- stocker des valeurs utiles dans des variables.
Dans l’exemple plus haut, nous demandons un nouveau nom à l’utilisateur puis le stockons dans une variable appellée name.
- faire des opérations sur des strings.
Dans l’exemple plus haut, nous prenons la chaîne de caractères « Player 1: « et lui adjoinions la variable name pour créer l’étiquette texte complète “”Player 1: Chris ».
- exécuter du code en réponse à certains évènements se produisant sur une page web.
Nous avons utilisé un évènement (« event ») click dans l’exemple plus haut pour détecter quand l’utilisateur clique sur le bouton et alors exécuter le code qui met à jour l’étiquette texte.
Et bien plus encore!
Ajax¶
AJAX est un raccourci pour Asynchronous JavaScript + XML (JavaScript asynchrone plus XML) inventé par Jesse James Garrett. Pour simplifier, il s’agit d’employer l’objet non standard XMLHttpRequest pour communiquer avec des scripts situés sur le serveur.
L’objet permet d’échanger des informations sous différents formats (dont XML, HTML ou texte), mais son principal attrait est sa nature « asynchrone » qui implique qu’il peut communiquer avec le serveur, échanger des données et mettre à jour sans avoir à sans recharger la page.
Les deux fonctionnalités combinées vous permettent de :
faire des requêtes vers le serveur sans avoir à recharger la page ;
analyser et travailler avec des documents XML.
XMLHttpRequest¶
Pour faire une requête HTTP vers le serveur à l’aide de JavaScript, il faut disposer d’une instance d’une classe fournissant cette fonctionnalité. C’est ici que la classe XMLHttpRequest intervient. Une telle classe a d’abord été introduite dans Internet Explorer sous la forme d’un objet ActiveX appelé XMLHTTP.
Par la suite, Mozilla, Safari et d’autres navigateurs ont suivi en implémentant une classe XMLHttpRequest qui fournit les mêmes méthodes et propriétés que l’objet ActiveX original de Microsoft. Dans l’intervalle, Microsoft a également implémenté XMLHttpRequest.
Par conséquent, pour créer une instance (un objet) de la classe désirée fonctionnant sur plusieurs navigateurs, vous pouvez utiliser :
httpRequest = new XMLHttpRequest();
Un exemple simple¶
Notre JavaScript demande un document HTML situé à l’URL /js/test/, qui contient le texte "Je suis un test.", et nous affichons le contenu de cette ressource dans un message alert().
Note
Remarquez que cet exemple n’utilise que JavaScript (jQuery n’est pas utilisé ici).
<script type="text/javascript" language="javascript">
function makeRequest(url) {
var httpRequest = false;
httpRequest = new XMLHttpRequest();
if (!httpRequest) {
alert('Abandon :( Impossible de créer une instance XMLHTTP');
return false;
}
httpRequest.onreadystatechange = function() { alertContents(httpRequest); };
httpRequest.open('GET', url, true);
httpRequest.send(null);
}
function alertContents(httpRequest) {
if (httpRequest.readyState == XMLHttpRequest.DONE) {
if (httpRequest.status == 200) {
alert(httpRequest.responseText);
} else {
alert('Un problème est survenu avec la requête.');
}
}
}
</script>
<span
style="cursor: pointer; text-decoration: underline"
onclick="makeRequest('/js/test')">
Effectuer une requête
</span>
Dans cet exemple :
L’utilisateur clique sur le lien « Effectuer une requête » dans le navigateur ;
Ceci appelle la fonction
makeRequest()avec un paramètre: l’url/js/test/;la requête est faite et ensuite (
onreadystatechange) l’exécution est passée àalertContents();alertContents()vérifie si la réponse a été reçue et est correcte, et affiche ensuite la contenu de la ressource dans un messagealert().
Un deuxième exemple¶
Pour finir, envoyons quelques données au serveur et réceptionons la réponse.
Notre JavaScript demandera cette fois çi une réponse dynamique, /api/hello/ qui prendra notre contenu envoyé et revera une chaîne « calculée »
"Bonjour, [user data] !"
que nous transmettrons à la fonction alert().
Nous allons déjà ajouter un boîte de texte dans notre HTML afin que l’utilisateur puisse saisir son nom :
<label>Vore nom :
<input type="text" id="ajaxTextbox" />
</label>
<span id="ajaxButton" style="cursor: pointer; text-decoration: underline">
Lancer une requête
</span>
Nous allons également ajouter une ligne à notre gestionnaire d’événement pour obtenir les données de l’utilisateur de la boite de texte et l’envoyer à la fonction makePostRequest() ainsi que l’URL de notre script côté serveur :
document.getElementById("ajaxButton").onclick = function() {
var userName = document.getElementById("ajaxTextbox").value;
makePostRequest('/api/hello',userName);
};
Nous devons créer makePostRequest() afin qu’il accepte les données de l’utilisateur et les transmette jusqu’au serveur.
function makeRequest(url, userName) {
var httpRequest = false;
httpRequest = new XMLHttpRequest();
if (!httpRequest) {
alert('Abandon :( Impossible de créer une instance XMLHTTP');
return false;
}
httpRequest.onreadystatechange = function() { alertNameContents(httpRequest); };
httpRequest.open('POST', url);
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
httpRequest.send('userName=' + encodeURIComponent(userName));
}
La fonction alertNameContent() peut être écrite de la même manière qu’au dessus pour afficher notre chaîne calculée, si c’est tout ce que le serveur renvoie.
Cependant, le serveur renvoie et la phrase calculée et la donnée originale. Donc si notre utilisateur saisi « Ben », la réponse du serveur ressemblera à :
{"userData":"Ben","computedString":"Bonjour, Ben !"}
Pour utiliser cette phrase dans alertNameContent() , nous pouvons simplement afficher une alerte avec le contenu de responseText, nous devons la récupérer et afficher computedString, la propriété que nous souhaitons :
function alertNameContents() {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
var response = JSON.parse(httpRequest.responseText);
alert(response.computedString);
} else {
alert('Un problème est survenu avec la requête.');
}
}
}
À faire
Créer une nouvelle route qui contiendra le code javascript qui chargera les dinosaures depuis l’api développé au sein de notre projet,
Et qui les ajoutera au sein de la page HTML, je vous conseille la lecture de cet article de MDN.