Slim, API, Framework – Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim


Il s’agit de comprendre rapidement comment concevoir puis de coder « grosses mailles » une API.

Les objectifs de cette expérience étaient les suivants :

  1. Découvrir les principes et les meilleures pratiques possibles pour concevoir une API.
  2. Choisir un micro framework ou petit framework pour réaliser ce prototype d’API afin d’avoir un exemple fonctionnel au bout d’une seule itération.
  3. Appliquer à la lettre un principe de la méthode « lean » : lancer au plus vite une réalisation même imparfaite dont la fonction première est de servir de base à la discussion et l’expérimentation en vue que chacun participe à son amélioration. Une API ne se construit pas seul, elle est le fruit d’une collaboration afin que l’API puisse embrasser toutes les complexités du développement à son usage, en passant par son coût.

Bien évidemment, une fois ce prototype d’API construit, ce premier « most viable product » si cher à la méthode « lean », l’API pourrait être confiée à de vrais développeurs pour que celle-ci soit développée à nouveau à l’aide d’un framework plus « balaise » comme Symfony, Laravel ou Kohana.

Il existe pas mal de ressources pour concevoir une API de production et non un seul prototype comme c’est le cas dans ce billet. On donne dans cette article que les premières bonnes pratiques « Restfull » pour concevoir une API « restish » !

1. Guidelines & bonnes pratiques de construction d’une API

1.1 Les méthodes HTTP (HTTP Methods)

Toute bonne API se doit supporter les méthodes HTTP les plus couramment utilisés (GET, POST, PUT et DELETE)*. Certes, il existes d’autres méthodes tel que OPTIONS ou HEAD mais notre API va se concentrer sur les plus couramment utilisés. Chaque méthode sera utilisée en fonction du type d’opération qui sera mené.

METHODE DESCRIPTION
GET Obtenir une ressource existante
POST Créer une nouvelle ressource
PUT Mettre à jour une ressource existante
DELETE Supprimer une ressource existante

*Ces différentes opérations sont aussi connus sous l’acronyme CRUD (Create, Retrieve, Use, Delete)

1.2 HTTP Status Code ou connaitre les codes


Les différents statuts de codes HTTP permettent d’informer quelle action doit être accomplie par l’application cliente. Fournir au plus vite un statut est du point de l’utilisation des serveurs, beaucoup plus efficient donc forcément économique en effet la réponse est immédiate, on indique donc au client (app, browser…) de ne pas chercher plus loin.

STATUT DESCRIPTION
200 OK
201 Created
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
422 Unprocessable Entity
500 Internal Server Error

1.3 URL Structure, une structure d’URL explicite

Dans la conception d’une API REST, les extrémités des URL (endpoints) doivent être explicites et faciles à comprendre.

L’URL d’accès à chaque ressource doit être trivial et être identifié de manière unique.

Si l’API nécessite une clé afin d’accéder aux ressources, cette clé doit être stockée dans les headers HTTP et non dans l’URL.

Exemples :

  • GET http://api.mondomaine.com/v1/projects/id/16 – Donne tous les détails du projet dont l’ID est 16.
  • POST http://api.smondomaine.com/v1/projects/new – Créer un nouveau projet

1.4 API Versioning ou l’épineuse question de la version

Il existe un débat sans fin sur où indiquer la version de l’application dans l’URL ou les en-têtes HTTP. Même si la recommandation qui prévaut est de faire figurer la version dans l’en-tête, on choisit à la manière de Twitter, de faire figurer la version dans l’URL, cela indique plus facilement à l’utilisateur la version sur laquelle et se révèle bien plus commode lors d’une migration.

Après tout, une API est là pour exposer l’intelligence et le contenu de votre application aux utilisateurs alors autant faciliter la dialogue et la compréhension.

Exemples :

  • http://api.mondomaine.com/v1.0/projects – version 1.0
  • http://api.mondomaine.com/v1.2/projects – version 1.2
  • http://api.mondomaine.com/v2.0/projects – version 2.0

1.5 Content Type, quel type de contenu


Le « Content-Type » dans l’en-tête HTTP précise le type de données contenu qui est échangé entre le serveur et le client. Afin d’être totalement transparent, le terme « client » désigne parfois une réalité différente, cela peut-être un navigateur, une app iOS ou Android voir une autre API…

Les principaux format ou type de contenu que supporte habituellement les API Rest sont :

  • Le JSON ex Mime Content-Type: application/json
  • Le XML ex Content-Type: application/xml

Il existe bien sur d’autre formats ou type de contenu

Source : http://en.wikipedia.org/wiki/MIME_type

1.6 API Key, la clé du bonheur


Il est évident que l’on construit généralement une API publique afin de facilité l’adoption et d’exposer des contenus. Toutefois, même sur un mode gratuit, l’usage doit être contrôlé voir limité en volume et possibilité pour des raisons de sécurité et/ou de coût.

En effet, exploiter une API coûte des ressources et c’est aussi un des socles de la monétisation d’un SaaS, il faut donc prévoir de réduire ou de limiter l’accès à cette ressource via une clé d’API.

C’est une évolution importante concernant la gestion d’une API. En effet si l’on pense monétisation, il faudra de facto faire évoluer le compte utilisateur à l’instar d’un Facebook ou Twitter. Dans un monde parfait, l’utilisateur gère lui-même ses apps, il déclare son/ses application(s), mets une description, obtient une clé d’API, un token client privé… bref toutes les informations nécessaires à la gestion d’une application adossée à une API. Tout cela est délégué à l’utilisateur, c’est le cercle vertueux pour une API, vous déléguer la gestion client à vos utilisateurs ainsi que tout ce qui concerne le paramétrage.

2. Créer une REST API _ User stories

2.1 Lâcher le silex pour le slim

Cretes, notre choix aurait ou se porter sur Silex qui est une émanation « light » de Symfony mais bon, le choix s’est porté sur Slim. En effet, quelques-unes des ressources (tutorial, vidéo notamment…) cadraient parfaitement avec le volonté affichée de cette expérience d’apprentissage : choisir un de ces micro framework PHP facile à mettre en place et à manœuvrer.

En conclusion, on aurait pu choisir Silex mais les recherches sur internet ont permis de découvrir une suite de 10 vidéos qui expliquent pas à pas l’utilisation de Slim.

  1. Les videos d’Eduardo Diaz sur la maniement de Slim (spanish)
    http://www.youtube.com/user/alexdx/videos

2.2 Agile ?

Afin de se figurer comment concevoir l’API, le mieux est de décrire via des user stories ou des histoires utilisateurs l’usage que nous souhaitons faire de notre API.

(i) Simple User story ou actions habituelles

Bon les opération dites communes…

  • Toutes les opérations relatives à l’inscription (registration) et de login
  • Toutes les opérations liées à la gestion de projets (CRUD, Creating, Reading, Updating, Deleting). Pour toutes ces opérations, les appels à l’API doivent inclure une clé d’API d’autorisation dans le champ HEAD.
(ii) Advanced User story ou actions spécifiques

Les opérations plus avancées

  • Lister tous les projets d’un compte utilisateur soit comme créateur (case 1) soit comme contributeur (case 2) soit les 2 (case 3).
  • Lister tous les contributeurs à un projet donné
  • Lister tous les mots-clés d’un projet donné.

…to be continued

Pour information, les principaux types de Requests sur une API REST : GET,POST, PUT, PATCH, DELETE, HEAD, OPTIONS.

3.3 Nomenclature des URLs : deux écoles

(i) Type 1 : Les URLs sont identiques mais les méthodes d’appel à l’API différent.

On peut souvent utiliser la même URL pour l’appel à l’API. Si l’URL est identique la différence se fait surtout sur le type de méthode HTTP qui est utilisé dans l’une ou l’autre des URLs.

Imaginons que le client demande l’URL de l’API /projects/id/:id d’un projet spécifique avec la méthode GET, le client obtiendra la liste des informations affairantes à ce projet. Si par contre le client utilise la méthode PUT

  • /projects/id/:id GET -> Liste les infos d’un projet ayant cet id
  • /projects/id/:id PUT -> Met à jour le projet ayant cet id
  • /projects/id/:id DELETE -> Supprime le projet ayant cet id
(ii) Type 2 : Les URLs sont explicites et donnent un indication même sur la méthode employé.

Les URLs d’appel figurent autant que possible l’usage qui est fait. Personnellement c’est ce que je préfère, sortir de ce qui est implicite pour avoir de l’explicite. En effet, l’API peut-être testée et/ou utilisée par des personnes qui ignorent tous des conventions « techniques » et une simple lecture de la documentation de l’API suffit à convaincre que des nombreuses opérations sont possibles.

  • /projects/id/:id GET -> Liste les infos d’un projet ayant cet id
  • /projects/update/:id PUT -> Met à jour le projet ayant cet id
  • /projects/delete/:id DELETE -> Supprime le projet ayant cet id
(iii) Tableau des structures Url de l’API + méthode

On a appliqué la nomenclature de Type 2. Pour mémoire, on décompose à nouveau une URL complète d’appel http://api.mondomaine.com/v2.0/projects/all.

  • url_part_1 : http://api.mondomaine.com -> C’est l’URL de base, simple et facile à retenir.
  • url_part_2 : /v2.0 -> C’est la partie qui indique la version de l’API à laquelle on fait l’appel.
  • url_part_3 : /projects/all -> C’est la partie d’interaction avec l’API dans l’URL d’appel.

Ce qui donne le tableau suivant pour les combinaisons de la part_3

(A) User management

URL Method Parameters Description
/register POST mail, password User registration
/login POST mail, password User login

(B) Project management

/projects/all GET Fetching all projects
/projects/new POST project To create new project
/projects/id/:id GET Fetching single project
/projects/update/:id PUT Updating single project
/projects/delete/:id DELETE project, status Deleting single project

(C) Tag management

/tags/all GET Fetching all tags
/tags/new POST tag To create new tag or hashtag
/tags/id/:id GET Fetching single tag
/tags/update/:id PUT Updating single tag
/tags/delete/:id DELETE tag, status Deleting single tag

(D) Author management

/authors/all GET Fetching all authors
/authors/new POST author To create new author
/authors/id/:id GET Fetching single author
/authors/update/:id PUT Updating single author
/authors/delete/:id DELETE author, status Deleting single author

(E) Downloads management

/downloads/all GET Fetching all downloads
/downloads/id/:id GET Fetching nb of downloads for a single project

((F) Goodies management

Quelques actions juste pour le fun maintenant, il faut voir si elles ne sont pas consommatrices de resources.

/projects/all/top/day GET Transverse, Sur une journée, les derniers projets mis à jour du plus récent au plus ancien
/projects/all/top/week GET Idem sur une semaine
/projects/all/top/month GET Idem sur un mois
/projects/all/top/year GET Idem sur une année
/projects/all/top/alltime GET Idem depuis que le service existe
/projects/id/:id/authors/all/top/month GET Par projet, les auteurs les plus actifs du mois sur le projet dont l’id est :id
/authors/all/top/month GET Transverse, les auteurs les plus actifs du mois tous projets confondues

L’idée est sans doute de comprendre le fonctionnement et de s’amuser à faire toutes les combinaisons, ensuite voir si cela fait sens et si c’est techniquement faisable notamment pas chargeant en terme de requête ! Cache ?

3. Créer une REST API _ Ride the ORM

Pour la construction de l’API, il faut d’évidence s’appuyer sur l’ORM d’un framework.

Prenons par exemple par Kohana, il existe quelques exemples dans Kohana sur l’utilisation de l’ORM, voir /kohana/modules/orm/guide/orm/examples/ notamment le fichier simple.md

3.1 Notes sur l’ORM de Kohana

The ORM allows for manipulation and control of data within a database as though it was a PHP object. Once you define the relationships ORM allows you to pull data from your database, manipulate the data in any way you like, and then save the result back to the database without the use of SQL. By creating relationships between models that follow convention over configuration, much of the repetition of writing queries to create, read, update, and delete information from the database can be reduced or entirely removed. All of the relationships can be handled automatically by the ORM library and you can access related data as standard object properties.

ORM is included with the Kohana 3.x install but needs to be enabled before you can use it. In your `application/bootstrap.php` file modify the call to Kohana::modules and include the ORM modules.

Bon dans notre exemple, on va utiliser RedBeanPHP accessible sans trop de souci via Slim

4. Créer une REST API _ Installer Slim

On va installer à l’aide de composer un ensemble d’éléments nécessaires au bon fonctionnement de notre application. On a renoncé à Laravel et son ORM Eloquent.

Les commandes pour se rendre dans votre répertoire puis installer le tout.

cd /Applications/MAMP/htdocs/slim/slim_test_api/
composer install
Le contenu de composer.json avec slim et RedBeanPHP

	{
	    "require": {
	        "slim/slim": "2.*",
			"gabordemooij/redbean": "*"
 
	    }
	}

Lancer le composer composer install
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

C’est fait, vous avez slim et redbean dans un répertoire vendor
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Confirmation ls -l
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Confirmation ls -la pour voir le .htaccess
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Le code de .htaccess

	RewriteEngine On
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteRule ^ index.php [QSA,L]

5. Utiliser notre API _ Quelques exemples…

Pour jouer avec notre prototype d’API, on va utiliser l’application de Chrome nommé « Advanced REST client » afin de passer quelques commandes, du même on indique aussi le moyen de passer la même commande à l’aide de cURL.

Dans Chrome, une application nommée « Advanced REST client »
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Source : Advanced REST client de Google Chrome

5.1 Voir les utilisateurs de la BDD

En cURL

curl -i -X GET http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/all
curl -i -X GET http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/id/4

5.2 Ajouter des utilisateurs

http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/new
{"nombre": "Barack","apellidos":"Obama","email":"bo@whitehouse.gov"}
{"nombre": "Michelle","apellidos":"Obama","email":"mo@whitehouse.gov"}
{"nombre": "John","apellidos":"Smith","email":"mdoe@whitehouse.gov"}
curl -i -X POST -H 'Content-Type: application/json' -d '{"nombre": "Damian","apellidos":"Winograd","email":"dwg@whitehouse.gov"}' http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/new

Ajouter un utilisateur via Advanced REST client de Google Chrome
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Bien ajouter application/json
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Le message de notre api en espagnol
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Ajouter un utilisateur en cURL
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Vue utilisateur unique ID 4
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Vue tous les utilisateurs
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Vue tous les utilisateurs via cURL
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

5.3 Supprimer des utilisateurs

http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/delete/13
curl -i -X DELETE http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/delete/13

Supprimer un utilisateur via Advanced REST client de Google Chrome
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Supprimer un utilisateur en cURL
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

Le dump .sql de la base via phpmyadmin
Slim, API, Framework - Les bonnes pratiques pour créer une API avec le micro-framework PHP Slim

	-- phpMyAdmin SQL Dump
	-- version 4.2.5
	-- http://www.phpmyadmin.net
 
	SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
	SET time_zone = "+00:00";
 
	--
	-- Base de données :  `slim`
	--
 
	-- --------------------------------------------------------
 
	--
	-- Structure de la table `usuarios`
	--
 
	CREATE TABLE `usuarios` (
	`id` INT(9) NOT NULL,
	  `nombre` VARCHAR(50) NOT NULL DEFAULT '',
	  `apellidos` VARCHAR(100) NOT NULL DEFAULT '',
	  `email` VARCHAR(255) NOT NULL DEFAULT '',
	  `alta` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
	) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=14 ;
 
	--
	-- Contenu de la table `usuarios`
	--
 
	INSERT INTO `usuarios` (`id`, `nombre`, `apellidos`, `email`, `alta`) VALUES
	(4, 'Bob', 'Obama', 'bobobama@whitehouse.gouv', '2014-10-31 07:37:35'),
	(12, 'Damian', 'Winograd', 'dwg@whitehouse.gov', '2014-11-03 04:34:41'),
	(7, 'Ramon', 'Castro', 'ramonc@hotmail.com', '2014-10-31 08:23:43'),
	(9, 'Barack', 'Obama', 'bo@whitehouse.gov', '2014-11-03 04:15:22'),
	(10, 'Michelle', 'Obama', 'mo@whitehouse.gov', '2014-11-03 04:15:41');
 
	--
	-- Index pour les tables exportées
	--
 
	--
	-- Index pour la table `usuarios`
	--
	ALTER TABLE `usuarios`
	 ADD PRIMARY KEY (`id`);

6. Utiliser notre API _ le code

Le fichier index.php de notre prototype de API

	<?php
	/**
	 * Slim - a micro PHP 5 framework
	 *
	*/
 
			require 'vendor/autoload.php';
			use RedBean_Facade as R;
 
			$db_host = "localhost";
			$db_dbname = "slim";
			// $db_dbname = "all_traductions";
			$db_username = "root";
	        $db_password = "root";
 
 
			R::setup ('mysql:host='.$db_host.';dbname='.$db_dbname.';chartset=utf8',''.$db_username.'',''.$db_password.'');
 
			R::freeze(true);
 
			$app = new \Slim\Slim();
			$app->config(array(
			    'debug' => true
			));
 
			$app->get('/', function () use($app) {
				echo ('<h1>Hello to redbean ($app)</h1>');
			});
 
			/* ALL */
			// http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/all
			$app->group('/api', function () use ($app){
					$app->group('/usuarios', function () use ($app){
						$app->response->headers->set('Content-Type', 'application/json');
 
							$app->get('/all', function () use($app) {
								$all = R::find('usuarios');
								$all_users = R::exportAll($all);
								echo json_encode($all_users);
							});
							/* LOOK for an ID */
							// http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/id/1
							$app->get('/id/:id', function ($id) use($app) {
								try{
									//all infos about the user based on the id
									$usuario = R::load('usuarios', $id);
									if ($usuario->id) {
										$user = $usuario->export();
										echo json_encode($user);
									} else {
										throw new Exception('User does not exist');
									}
								}catch (Exception $e) {
									$app->status(400);
									echo json_encode(array('status' => 'error', 'message' => $e->getMessage()));
								}
							});
							/* NEW ID */
							// http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/new
							$app->post('/new', function () use($app) {
								try{
									$request = $app->request();
									$data = json_decode ($request->getBody());
									$usuario = R::dispense('usuarios');
									$usuario->nombre = $data->nombre;
									$usuario->apellidos = $data->apellidos;
									$usuario->email = $data->email;
									$insertado = R::store($usuario);
									if ($insertado) {
											echo json_encode(array('status' => 'success', 'message' => 'Insertado correctamente'));
									} else {
										throw new Exception('Error while inserting the user');
									}
								}catch (Exception $e) {
									$app->status(400);
									echo json_encode(array('status' => 'error', 'message' => $e->getMessage()));
								}
							});
 
							/* UPDATE */
							// http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/update/2
							$app->put('/update/:id', function ($id) use($app) {
								try{
									$id = (int)$id;
									$request = $app->request();
									$data = json_decode ($request->getBody());
									$usuario = R::load('usuarios', $id);
									if ($usuario->id) {
										//Actulizamos datos del usuario
										$usuario->nombre = $data->nombre;
										$usuario->apellidos = $data->apellidos;
										$usuario->email = $data->email;	
										R::store($usuario);
										echo json_encode(array('status' => 'success', 'message' => 'Actualizado correctamente'));
									} else {
										throw new Exception('Error while updating the user');
									}
								}catch (Exception $e) {
									$app->status(400);
									echo json_encode(array('status' => 'error', 'message' => $e->getMessage()));
								}
							});
							/* DELETE */
							// http://127.0.0.1/slim/slim_test_api_redbean/index.php/api/usuarios/delete/8
							$app->delete('/delete/:id', function ($id) use($app) {
								$id = (int)$id;
								try{
									// Buscamos y borramos
	 								$usuario = R::load('usuarios', $id);
									if ($usuario->id) {
										R::trash($usuario);
										echo json_encode(array('status' => 'success', 'message' => 'Borrado correctamente'));
									} else {
										throw new Exception('Error while deleting the user');
									}
								}catch (Exception $e) {
									$app->status(400);
									echo json_encode(array('status' => 'error', 'message' => $e->getMessage()));
								}
 
							});
 
					});
				});
 
			$app->run();

En savoir plus