Les vidéos
- Création du projet dans NetBeans, installation du micro Framework Slim 3 avec composer
- Création de l’API. mise en place du routage, interrogation de ma base mySQL
- Installation du moteur de vue ng-admin et affichage d’une liste de données.
- Ajout, modification et suppression de données en faisant interagir ng-admin et l’API Slim 3
Prérequis
L’ordinateur hôte de la solution doit avoir :
- nodejs avec npm (installation : https://openclassrooms.com/courses/des-applications-ultra-rapides-avec-node-js/installer-node-js )
- composer (installation : https://getcomposer.org/doc/00-intro.md )
- nous utiliserons ici l’IDE Netbeans (https://netbeans.org/downloads/ ) avec PHP.
- un serveur web avec PHP (ici nous utiliserons laragon)
- la base de données exemple apidb (le fichier sql de la base est téléchargeable ici)
Tous ces logiciels sont multiplateformes et fonctionnent quel que soit l’OS.
Création de la solution (Netbeans)
Dans Netbeans (exemple pour créer un projet portant le nom : « monProjet »)
Fichier->Nouveau Projet cela lance un assistant en 5 points
- Sélectionner un projet : Choisir Catégories -> PHP et Projets -> PHP Application, cliquer sur Suivant
- Name and Location : Project Name : monProjet, Sources Folder : pointer sur le dossier root du serveur et un sous dossier portant le nom du projet : par exemple c:\laragon\www\monProjet, PHP version (celle qui est installée sur l’ordinateur), Default Encoding : UTF-8, cliquer sur Suivant.
- Run Configuration : Run As : Local Web Site, project URL : le chemin du virtual host créé sur le serveur (si ce n’est fait automatiquement, il faut le faire) : http://monProjet.dev:8080
- PHP Frameworks : laisser vide, cliquer sur Suivant
- Composer : (installation des bibliothèques php nécessaires au projet, ici nous allons ajouter le paquet Slim 3 et le paquet Slim PDO).
Dans token -> slim puis cliquer sur Search, sélectionner slim/slim, dans la liste déroulante Version choisir 3.x-dev , puis cliquer sur le bouton afin de faire apparaitre slim/slim (3.x-dev) dans la fenêtre de droite.
refaire la même chose avec le paquet slim/PDO version dev-master
Cliquer sur Terminer, les différents dossiers du projet vont être créés, et composer va télécharger et installer les bibliothèques
A l’issue de l’opération, le dossier du projet ressemble à ceci :
Le dossier nbprojet contient les fichiers de paramétrage du projet necessaires à NetBeans (on n’y touche jamais)
Le dossier vendor, qui contient les bibliothèques téléchargées et un fichier autoload.php, qui comme son nom l’indique, permet de charger automatiquement les classes et leur espace de nom afin de les rendre disponibles facilement.
Composer.json est le fichier qui contient les références des packages installés,
{ "name": "vendor/mon-projet", "description": "Description of project monProjet.", "authors": [ { "name": "francois", "email": "your@email.here" } ], "require": { "slim/slim": "3.x-dev", "slim/pdo": "dev-master" } }
Si on ajoute ou supprime une dépendance dans la partie « require » du fichier, puis qu’on effectue une commande composer update, le projet se met à jour automatiquement et chargeant ou déchargeant les bibliothèques ajoutées ou retirées. Un autoload.php est alors recréer. Composer.lock contient les informations de l’autoload actuel. (on ne touche jamais ce fichier).
Enfin un fichier index.php et un squelette de page web
L’API
Dans notre application, tous les accès au modèle de données se font au travers de requêtes web vers une API en respectant le format REST[1].
Le format REST considère l’url, non plus comme l’appel d’une page web mais comme l’appel à une fonction, les arguments étant passés soit dans l’url (pour les requêtes de type GET, DELETE) dans le corps (pour les requêtes POST) ou bien les deux (requêtes PUT).
Dans le format REST, les verbes des requêtes permettent de définir le type d’action que le serveur devra effectuer.
Exemple :
Verbe |
url |
Action |
GET |
/products |
Récupère un jeu de |
GET |
/products/:id |
Récupère un jeu de |
POST |
/products |
Créer un « product » |
PUT |
/products/:id |
Mise à jour du product |
DELETE |
/products/:id |
Destruction du product |
Grâce à ces 4 verbes, on peut créer des services web qu’on nomme CRUD (Create Read Modify Delete).
Le serveur renvoie au client un jeu de données soit au format XML soit au format JSON.
Exemple :
GET /products
Renvoie un tableau JSON de type :
[ { "id": 1, "name": "LG P880 4X HD", "description": "My first awesome phone!", "price": null, "category_id": 5, "created": "2014-06-01 01:12:26", "modified": "2014-05-31 17:12:26" }, { "id": 2, "name": "Google Nexus 4", "description": "The most awesome phone of 2013!", "price": "56", "category_id": 2, "created": "2014-06-01 01:12:26", "modified": "2014-05-31 17:12:26" }, … ]
GET /products/2
Renvoie un objet JSON ressemblant à ceci :
{ "id": 2, "name": "Google Nexus 4", "description": "The most awesome phone of 2013!", "price": "56", "category_id": 2, "created": "2014-06-01 01:12:26", "modified": "2014-05-31 17:12:26" }
L’API sera construite à l’aide du microframework Slim Framework 3[2]. Ce dernier a été créé pour développer simplement des API, il possède un routeur pour gérer les diverses requêtes et un injecteur de dépendance qui permet d’utiliser les bibliothèques tierces.
Créer un dossier « api » sur la racine du projet. Ajouter un fichier index.php et un fichier .htaccess
Comme indiqué sur la page https://www.slimframework.com/docs/start/web-servers.html , compléter la fichier .htaccess de la manière suivante :
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.php [QSA,L]
Vérifier aussi que dans la configuration de Apache l’option AllowOverride est sur All.
Sur un raspberry il s’agit du fichier /etc/apache2/apache2.conf, section :
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory>
Comme indiqué page https://www.slimframework.com/docs/tutorial/first-app.html , le squelette de la page index est le suivant :
<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '../vendor/autoload.php'; $app = new \Slim\App; $app->get('/hello/{name}', function (Request $request, Response $response) { $name = $request->getAttribute('name'); $response->getBody()->write("Hello, $name"); return $response; }); $app->run();
Nous allons nous inspirer de ce squelette afin que l’api puisse répondre des données statiques dans un premier temps, puis des données issues de la BDD
exemple avec des données issues de la base de données
On suppose que la base de données est créée et qu’elle contient une table « ouvrants » (champs id et nom) avec quelques valeurs.
Dans Slim l’accès à la base de donnée est simplifié grâce à la bibliothèque slim/PDO . D’après la documentation[3]
L’usage typique se fait de la manière suivante :
require_once 'vendor/autoload.php'; $dsn = 'mysql:host=your_db_host;dbname=your_db_name;charset=utf8'; $usr = 'your_db_username'; $pwd = 'your_db_password'; $pdo = new \Slim\PDO\Database($dsn, $usr, $pwd); // SELECT * FROM users WHERE id = ? $selectStatement = $pdo->select() ->from('users') ->where('id', '=', 1234); $stmt = $selectStatement->execute(); $data = $stmt->fetch(); // INSERT INTO users ( id , usr , pwd ) VALUES ( ? , ? , ? ) $insertStatement = $pdo->insert(array('id', 'usr', 'pwd')) ->into('users') ->values(array(1234, 'your_username', 'your_password')); $insertId = $insertStatement->execute(false); // UPDATE users SET pwd = ? WHERE id = ? $updateStatement = $pdo->update(array('pwd' => 'your_new_password')) ->table('users') ->where('id', '=', 1234); $affectedRows = $updateStatement->execute(); // DELETE FROM users WHERE id = ? $deleteStatement = $pdo->delete() ->from('users') ->where('id', '=', 1234); $affectedRows = $deleteStatement->execute();
On a ici les prototypes de l’instanciation d’un objet $pdo et des diverses requêtes select, delete, insert et update. L’intégration dans Slim se fait via un « container » permettant d’instancier un objet de la bibliothèque slim/PDO qui se situe dans un autre espace de nom que l’objet $app de Slim…
Le container va permettre de préparer, gérer et injecter des dépendances à l’application. L’avantage est que l’objet lié au container est créé à la demande et non de manière permanente. D’autre part les objets de l’application pourront accéder à des ressources dans d’autres espaces de nom.
Pour créer un container « db », insérer le code suivant :
<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '../vendor/autoload.php'; $config = [ 'settings' => [ 'displayErrorDetails' => true, ], ]; $app = new \Slim\App($config); $container = $app->getContainer(); $container['db'] = function () { $dsn = 'mysql:host=localhost;dbname=apidb;charset=utf8'; $usr = 'root'; $pwd = ''; $pdo = new \Slim\PDO\Database($dsn, $usr, $pwd); return $pdo; };
Cela permet de créer un objet « db » qui sera instancié à la demande.
création d’une requête SELECT
Remplacer le code de la fonction $app->get(…) par celui-ci
/********************************************************* * gestion des requêtes GET ******************************************************** */ $app->get('/{table}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); // SELECT * FROM {table} $selectStatement = $this->db->select() ->from($table); $stmt = $selectStatement->execute(); $data = $stmt->fetchAll(); return $response->withJson($data); });
$this->db fait référence au container « db » créé précédemment.
Sur le même principe si on veut que l’api renvoie juste un produit dont on spécifie l’id, le code sera le suivant :
$app->get('/{table}/{id}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); $id = $request->getAttribute('id'); // SELECT * FROM {table} WHERE id = {id} $selectStatement = $this->db->select()->from($table)->where("id","=",$id); $stmt = $selectStatement->execute(); $data = $stmt->fetch(); return $response->withJson($data); });
Code complet de la page /api/index.php
<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '../vendor/autoload.php'; $config = [ 'settings' => [ 'displayErrorDetails' => true, ], ]; $app = new \Slim\App($config); $container = $app->getContainer(); $container['db'] = function () { $dsn = 'mysql:host=localhost;dbname=apidb;charset=utf8'; $usr = 'root'; $pwd = ''; $pdo = new \Slim\PDO\Database($dsn, $usr, $pwd); return $pdo; }; $app->get('/{table}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); // SELECT * FROM users WHERE id = ? $selectStatement = $this->db->select() ->from($table); $stmt = $selectStatement->execute(); $data = $stmt->fetchAll(); return $response->withJson($data); }); $app->get('/{table}/{id}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); $id = $request->getAttribute('id'); // SELECT * FROM {table} WHERE id = {id} $selectStatement = $this->db->select()->from($table)->where("id","=",$id); $stmt = $selectStatement->execute(); $data = $stmt->fetch(); return $response->withJson($data); }); $app->run();
Installation de NG-ADMIN
ouvrir un terminal dans le dossier qui contient la solution puis effectuer la commande
npm install -g npm@2.x (sous linux faire précéder de sudo) npm install ng-admin –-save
Cela installera le dossier node_modules et le sous dossier ng-admin qui contient tout le matériel pour le front-end.
Le dossier du projet va ressembler à ceci :
Modifier la page web index.php à la racine du site. Le code de la page est le suivant :
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Controle d'accès</title> <link rel="stylesheet" href="node_modules/ng-admin/build/ng-admin.min.css"> </head> <body ng-app="myApp"> <div ui-view="ng-admin"></div> <script src="node_modules/ng-admin/build/ng-admin.min.js" type="text/javascript"></script> <script src="js/app.js"></script> </body> </html>
Le corps de la page est constitué d’une simple balise <div ui-view=”ng-admin”></div> et le <body> fait référence à l’application angularJS sous-jacente grâce à l’attribut ng-app=”myApp”[4]
Rajouter un dossier « js » et un fichier nommé app.js (dont il est fait référence dans la page index.php). Le script va créer un module angularJS nommé « myApp » et qui intégrera la bibliothèque [‘ng-admin’], la suite permet de configurer l’application. La documentation détaillée au format pdf de ng-admin est accessible ici : https://www.gitbook.com/book/marmelab/ng-admin/details
Code de /js/app.js
// declare a new module called 'myApp', and make it require the `ng-admin` module as a dependency var myApp = angular.module('myApp', ['ng-admin']); // declare a function to run when the module bootstraps (during the 'config' phase) myApp.config(['NgAdminConfigurationProvider', function (nga) { // create an admin application var admin = nga.application('Mes produits') .baseApiUrl('/api/'); // main API endpoint; /************************************ * entité produts ************************************/ var produits = nga.entity('products'); var categories= nga.entity('categories'); categories.listView(); // tableau = listview produits.listView() .title('les produits') .fields([ nga.field('id'), nga.field('name').label('nom'), nga.field('description'), nga.field('price').label('prix') ]) ; admin.addEntity(produits); admin.addEntity(categories); nga.configure(admin); }]);
L’application fonctionne,
Reste à intégrer dasn l’API les fonctions d’ajout, de mise à jour et d’effacement des produits.
code complet
/api /index.php
<?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; require '../vendor/autoload.php'; $config = [ 'settings' => [ 'displayErrorDetails' => true, ], ]; $app = new \Slim\App($config); $container = $app->getContainer(); $container['db'] = function () { $dsn = 'mysql:host=localhost;dbname=apidb;charset=utf8'; $usr = 'root'; $pwd = ''; $pdo = new \Slim\PDO\Database($dsn, $usr, $pwd); return $pdo; }; $app->get('/{table}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); // SELECT * FROM users WHERE id = ? $selectStatement = $this->db->select() ->from($table); $stmt = $selectStatement->execute(); $data = $stmt->fetchAll(); return $response->withJson($data); }); $app->get('/{table}/{id}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); $id = $request->getAttribute('id'); // SELECT * FROM users WHERE id = ? $selectStatement = $this->db->select()->from($table)->where("id","=",$id); $stmt = $selectStatement->execute(); $data = $stmt->fetch(); return $response->withJson($data); }); $app->post('/{table}', function (Request $request, Response $response) { $data=$request->getParsedBody(); $table = $request->getAttribute('table'); $keys= array_keys($data); $values=array_values($data); $insertStatement= $this->db->insert($keys) ->into($table) ->values($values); $stmt = $insertStatement->execute(); $response->withJson($stmt); }); $app->put('/{table}/{id}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); $id = $request->getAttribute('id'); $data=$request->getParsedBody(); $updateStatement = $this->db->update($data) ->table($table) ->where('id',"=",$id); $stmt = $updateStatement->execute(); $response->withJson($stmt); }); $app->delete('/{table}/{id}', function (Request $request, Response $response) { $table = $request->getAttribute('table'); $id = $request->getAttribute('id'); $deleteStatement = $this->db->delete()->from($table)->where('id', '=', $id); $stmt = $deleteStatement->execute(); $response->withJson($stmt); }); $app->run();
/js/app.js
// declare a new module called 'myApp', and make it require the `ng-admin` module as a dependency var myApp = angular.module('myApp', ['ng-admin']); // declare a function to run when the module bootstraps (during the 'config' phase) myApp.config(['NgAdminConfigurationProvider', function (nga) { // create an admin application var admin = nga.application('Mes produits') .baseApiUrl('/api/'); // main API endpoint; /************************************ * entité produts ************************************/ var produits = nga.entity('products'); var categories= nga.entity('categories'); categories.listView(); // tableau = listview produits.listView() .title('les produits') .fields([ nga.field('id'), nga.field('name').label('nom'), nga.field('description'), nga.field('price').label('prix'), nga.field('category_id','reference') .targetEntity(categories) .targetField(nga.field('name')) .label('catégorie') ]) .listActions(['edit']); produits.creationView() .title('Ajout d\'un produit') .fields(produits.listView().fields()); produits.editionView() .fields(produits.creationView().fields()); admin.addEntity(produits); admin.addEntity(categories); nga.configure(admin); }]);
- https://marmelab.com/fr/blog
- https://openclassrooms.com/courses/utilisez-des-api-rest-dans-vos-projets-web/pourquoi-rest ↑
- https://www.grafikart.fr/tutoriels/php/slim-framework-831 ↑
- https://github.com/FaaPz/Slim-PDO ↑
- Voir formation angularJS : https://www.grafikart.fr/formations/angularjs
ou le livre « AngularJS » editions ENI ISBN 978-2-7460-9334-8 ↑