() translation by (you can also view the original English article)
Les API dites "RESTful" sont difficiles ! La conception et la rédaction d'une API réussie couvrent en effet beaucoup d'aspects. L'authentification, l'hypermédia/HATEOS, le versioning, les limites de taux et la négociation de contenus sont quelques-unes des thématiques que vous pourriez ainsi être amené à traiter. Mais plutôt que de s'attaquer à tous ces concepts, recentrons-nous ici sur les bases de REST. Nous allons ainsi concevoir des points terminaux JSON derrière un système d'authentification, tout en apprenant quelques astuces de Laravel 4.
L'application
Construisons une API pour une application toute simple du type "Signets de lecture". Les utilisateurs pourront ainsi créer, lire, mettre à jour et supprimer les URLS correspondant aux pages de sites qu'ils souhaitent lire plus tard. Prêt à plonger et démarrer ?
Installer Laravel 4
Créez une nouvelle installation de Laravel 4. Si vous êtes à l'aise avec l'interface en ligne de commande (CLI), essayez ce guide de démarrage rapide. Sinon, nous avons un tutoriel vidéo ici sur Nettuts+ qui couvre le process.
Nous allons d'abord créer une clé d'encryption pour permettre le hashage de mots de passe sécurisés. Vous pouvez le faire très facilement en lançant cette commande à partir de la racine de votre projet :
1 |
$ php artisan key:generate
|
Alternativement, vous pouvez simplement éditer votre clé d'encryption dans app/config/app.php
:
1 |
/*
|
2 |
|--------------------------------------------------------------------------
|
3 |
| Encryption Key
|
4 |
|--------------------------------------------------------------------------
|
5 |
|
|
6 |
| This key is used by the Illuminate encrypter service and should be set
|
7 |
| to a random, long string, otherwise these encrypted values will not
|
8 |
| be safe. Make sure to change it before deploying any application!
|
9 |
|
|
10 |
*/
|
11 |
|
12 |
'key' => md5('this is one way to get an encryption key set'), |
Base de données
Une fois que vous avez une installation fonctionnelle de Laravel 4, nous pouvons commencer à nous amuser un peu ! Commençons, justement, par créer la base de données du projet.
Seules deux tables de base de données seront nécessaires :
- Users ("Utilisateurs"), avec un nom d'utilisateur et un mot de passe
- URLs, avec une url et sa description
Nous utiliserons les migrations de Laravel pour créer et remplir cette base de données.
Configurer votre base de données
Editez app/config/database.php
et saisissez vos paramètres de base de données. Note : ceci signifie qu'il faut créer une base de données qui sera utilisée par l'application. Le présent article part du principe qu'il s'agit d'une base de données MySQL.
1 |
'connections' => array( |
2 |
|
3 |
'mysql' => array( |
4 |
'driver' => 'mysql', |
5 |
'host' => 'localhost', |
6 |
'database' => 'read_it_later', |
7 |
'username' => 'your_username', |
8 |
'password' => 'your_password', |
9 |
'charset' => 'utf8', |
10 |
'collation' => 'utf8_unicode_ci', |
11 |
'prefix' => '', |
12 |
),
|
13 |
),
|
Créer les fichiers de migration
1 |
$ php artisan migrate:make create_users_table --table=users --create |
2 |
$ php artisan migrate:make create_urls_table --table=urls --create |
Ces commandes définissent les scripts basiques de migration que nous allons utiliser pour créer les tables de la base de données. Notre mission consiste maintenant à les structurer avec les colonnes de tables correspondantes.
Editez app/database/migrations/SOME_DATE_create_users_table.php
et ajoutez à la méthode up()
:
1 |
public function up() |
2 |
{
|
3 |
Schema::create('users', function(Blueprint $table) |
4 |
{
|
5 |
$table->increments('id'); |
6 |
$table->string('username')->unique(); |
7 |
$table->string('password'); |
8 |
$table->timestamps(); |
9 |
});
|
10 |
}
|
Ici, nous venons de définir un nom d'utilisateur ("username") qui doit être unique, un mot de passe ainsi que les horodatages de création et mise à jour ("timestamps"). Enregistrez le tout puis éditez app/database/migrations/SOME_DATE_create_urls_table.php
et enfin ajoutez à la méthode up()
:
1 |
public function up() |
2 |
{
|
3 |
Schema::create('urls', function(Blueprint $table) |
4 |
{
|
5 |
$table->increments('id'); |
6 |
$table->integer('user_id'); |
7 |
$table->string('url'); |
8 |
$table->string('description'); |
9 |
$table->timestamps(); |
10 |
});
|
11 |
}
|
La seule chose importante à noter dans ce bout de code est que nous créons un lien entre les tablesurls
et users
, via le champ user_id
.
Ajouter des utilisateurs de test
Nous pouvons utiliser les seeds ("") de Laravel pour créer quelques utilisateurs à titre de test.
Créez un fichier dans le dossier app/database/seeds
avec le même nom, au singulier, que la table à laquelle il correspond ; dans notre cas, UserTableSeeder.php
, donc. Ajoutez :
1 |
<?php
|
2 |
|
3 |
class UserTableSeeder extends Seeder { |
4 |
|
5 |
public function run() |
6 |
{
|
7 |
DB::table('users')->delete(); |
8 |
|
9 |
User::create(array( |
10 |
'username' => 'firstuser', |
11 |
'password' => Hash::make('first_password') |
12 |
));
|
13 |
|
14 |
User::create(array( |
15 |
'username' => 'seconduser', |
16 |
'password' => Hash::make('second_password') |
17 |
));
|
18 |
}
|
19 |
|
20 |
}
|
Ensuite, assurez-vous que la classe de remplissage de données se lance bien quand la base de données s'apprête à être remplie. Editez app/database/seeds/DatabaseSeeder.php
:
1 |
public function run() |
2 |
{
|
3 |
Eloquent::unguard(); |
4 |
|
5 |
// Ajoutez ou décommentez cette ligne
|
6 |
$this->call('UserTableSeeder'); |
7 |
}
|
Lancer les migrations
Voici comment créer ces deux tables et insérer nos utilisateurs de test.
1 |
// Créer les deux tables
|
2 |
$ php artisan migrate |
3 |
|
4 |
// Créer les utilisateurs de test
|
5 |
$ php artisan db:seed |
Modèles
Laravel 4 continue d'utiliser, comme son prédécesseur, l'excellent ORM Eloquent. La gestion des appels à la base de données devient ainsi simple comme bonjour. Nous n'aurons besoin ici que d'un seul modèle par table.
Par chance, Laravel propose par défaut un modèle User prédéfini ; créons donc un modèle pour notre table d'urls.
Créez et éditez le fichier app/models/Url.php
.
1 |
<?php
|
2 |
|
3 |
class Url extends Eloquent { |
4 |
|
5 |
protected $table = 'urls'; |
6 |
|
7 |
}
|
Authentification
Les filtres fournis par Laravel peuvent prendre en charge l'authentification pour nous. Laravel propose ainsi maintenant un filtre d'authentification basique, appelé "Basic Authentication", que nous pouvons utiliser comme modèle d'authentification rapide pour gérer nos requêtes API.
Si vous ouvrez app/filters.php
, vous verrez à quoi il ressemble :
1 |
Route::filter('auth.basic', function() |
2 |
{
|
3 |
return Auth::basic(); |
4 |
});
|
Nous n'avons qu'un seul ajustement à effectuer. Par défaut, ce filtre cherche en effet un champ "email" pour identifier l'utilisateur. Comme nous utilisons des noms d'utilisateurs ("usernames") à la place des emails, il faut que nous ajustions cette préférence. Modifiez donc l'appel Auth::basic()
en lui donnant notre champ "username" comme paramètre :
1 |
Route::filter('auth.basic', function() |
2 |
{
|
3 |
return Auth::basic("username"); |
4 |
});
|
Routes
Testons le tout. Créez une route, que vous appellerez testauth
et assurez-vous que notre filtre auth.basic
se lance bien avant elle.
Editez app/routes.php
:
1 |
Route::get('/authtest', array('before' => 'auth.basic', function() |
2 |
{
|
3 |
return View::make('hello'); |
4 |
}));
|
On peut tester ceci avec une simple requête curl. Dans votre terminal, pointez sur votre projet Laravel. Dans le mien, cela ressemble à ça (votre URL sera probablement différente !):
1 |
$ curl -i localhost/l4api/public/index.php/authtest |
2 |
HTTP/1.1 401 Unauthorized |
3 |
Date: Tue, 21 May 2013 18:47:59 GMT |
4 |
WWW-Authenticate: Basic |
5 |
Vary: Accept-Encoding |
6 |
Content-Type: text/html; charset=UTF-8 |
7 |
|
8 |
Invalid credentials |
Comme vous pouvez le voir, une requête interdite est détectée et un message "Invalid Credentials" ("codes invalides") est retourné avec un code de statut 401. Essayons ensuite d'ajouter une authentification basique.
1 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/authtest |
2 |
HTTP/1.1 200 OK |
3 |
Date: Tue, 21 May 2013 18:50:51 GMT |
4 |
Vary: Accept-Encoding |
5 |
Content-Type: text/html; charset=UTF-8 |
6 |
|
7 |
<h1>Hello World!</h1> |
Ça marche !
A ce point du tutoriel, le travail de définition de base de notre API est terminé. Nous avons :
- Installé Laravel 4
- Créé notre base de données
- Créé nos modèles
- Créé un modèle d'authentification
Créer des requêtes fonctionnelles
Vous connaissiez peut-être les contrôleurs RESTful de Laravel 3. Ils sont toujours présents dans Laravel 4 mais maintenant nous pouvons aussi utiliser les contrôleurs de ressources de Laravel, qui définissent les paradigmes que nous pouvons utiliser pour rendre notre interface d'API plus cohérente. Nous allons donc utiliser ici un contrôleur de ressources.
Voici, en décomposé, ce que gére chaque méthode du contrôleur de ressources. Notez bien que vous pouvez supprimer les routes /resource/create et /resource/{id}/edit puisque nous n'aurons pas besoin d'afficher des formulaires de création et d'édition dans notre API.
Créez un contrôleur de ressources
1 |
$ php artisan controller:make UrlController
|
Ensuite, définissez une route pour utiliser le contrôleur et demandez à ce que chaque route fasse l'objet d'une authentification préalable.
Editez app/routes.php
et ajoutez :
1 |
// Groupe de routes pour le versioning d'API
|
2 |
Route::group(array('prefix' => 'api/v1', 'before' => 'auth.basic'), function() |
3 |
{
|
4 |
Route::resource('url', 'UrlController'); |
5 |
});
|
Quelques détails à noter ici.
- Ceci va répondre à des requêtes effectuées à l'adresse
http://example.com/api/v1/url
. - Ceci nous permet aussi d'ajouter des routes supplémentaires, si nous avons besoin d'étendre notre API. Par exemple, si vous ajoutez un point terminal utilisateur, du type
/api/v1/user
.
- Un mécanisme de nomenclature a aussi été mis en place ici pour versionner notre API. Ceci nous permet d'activer de nouvelles versions d'API sans casser les versions précédentes - nous pouvons ainsi tout simplement créer un groupe de routes v2 et le faire pointer sur un nouveau contrôleur !
Note : vous pouvez aussi envisager d'utiliser des techniques de versioning d'API plus poussées, comme l'utilisation d'un en-tête Accept
ou d'un sous-domaine qui vous permette de pointer différentes versions d'API sur différentes bases de code.
Ajouter les fonctionnalités
Editez le nouveau fichier app/controllers/UrlController.php
:
1 |
// Editez ceci :
|
2 |
public function index() |
3 |
{
|
4 |
return 'Hello, API'; |
5 |
}
|
Et maintenant, testons :
1 |
$ curl -i localhost/l4api/public/index.php/api/v1/url |
2 |
HTTP/1.1 401 Unauthorized |
3 |
Date: Tue, 21 May 2013 19:02:59 GMT |
4 |
WWW-Authenticate: Basic |
5 |
Vary: Accept-Encoding |
6 |
Content-Type: text/html; charset=UTF-8 |
7 |
|
8 |
Invalid credentials. |
9 |
|
10 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url |
11 |
HTTP/1.1 200 OK |
12 |
Date: Tue, 21 May 2013 19:04:19 GMT |
13 |
Vary: Accept-Encoding |
14 |
Content-Type: text/html; charset=UTF-8 |
15 |
|
16 |
Hello, API |
Nous disposons maintenant d'un contrôleur de ressources avec une authentification active et pouvons donc ajouter nos fonctionnalités.
Créer une URL
Editez app/controllers/UrlController.php
:
1 |
/**
|
2 |
* Store a newly created resource in storage.
|
3 |
*
|
4 |
* @return Response
|
5 |
*/
|
6 |
public function store() |
7 |
{
|
8 |
$url = new Url; |
9 |
$url->url = Request::get('url'); |
10 |
$url->description = Request::get('description'); |
11 |
$url->user_id = Auth::user()->id; |
12 |
|
13 |
// La validation et le filtrage sont indispensables !!!
|
14 |
// Vraiment, je suis impardonnable de laisser ça comme ça...
|
15 |
|
16 |
$url->save(); |
17 |
|
18 |
return Response::json(array( |
19 |
'error' => false, |
20 |
'urls' => $urls->toArray()), |
21 |
200
|
22 |
);
|
23 |
}
|
Testons maintenant avec une autre requête curl. Celle-ci va envoyer une requête POST, qui correspondra à la méthode store()
créée ci-dessus.
1 |
$ curl -i --user firstuser:first_password -d 'url=http://google.com&description=A Search Engine' localhost/l4api/public/index.php/api/v1/url |
2 |
HTTP/1.1 201 Created |
3 |
Date: Tue, 21 May 2013 19:10:52 GMT |
4 |
Content-Type: application/json |
5 |
|
6 |
{"error":false,"message":"URL created"} |
Cool ! Créons-en quelques-unes de plus, pour chacun de nos utilisateurs.
1 |
$ curl --user firstuser:first_password -d 'url=http://fideloper.com&description=A Great Blog' localhost/l4api/public/index.php/api/v1/url |
2 |
|
3 |
$ curl --user seconduser:second_password -d 'url=http://digitalsurgeons.com&description=A Marketing Agency' localhost/l4api/public/index.php/api/v1/url |
4 |
|
5 |
$ curl --user seconduser:second_password -d 'url=http://www.poppstrong.com/&description=I feel for him' localhost/l4api/public/index.php/api/v1/url |
Ensuite, créons les méthodes pour récupérer les URLs.
1 |
/**
|
2 |
* Display a listing of the resource.
|
3 |
*
|
4 |
* @return Response
|
5 |
*/
|
6 |
public function index() |
7 |
{
|
8 |
//Formerly: return 'Hello, API';
|
9 |
|
10 |
$urls = Url::where('user_id', Auth::user()->id)->get(); |
11 |
|
12 |
return Response::json(array( |
13 |
'error' => false, |
14 |
'urls' => $urls->toArray()), |
15 |
200
|
16 |
);
|
17 |
}
|
18 |
|
19 |
/**
|
20 |
* Display the specified resource.
|
21 |
*
|
22 |
* @param int $id
|
23 |
* @return Response
|
24 |
*/
|
25 |
public function show($id) |
26 |
{
|
27 |
// Make sure current user owns the requested resource
|
28 |
$url = Url::where('user_id', Auth::user()->id) |
29 |
->where('id', $id) |
30 |
->take(1) |
31 |
->get(); |
32 |
|
33 |
return Response::json(array( |
34 |
'error' => false, |
35 |
'urls' => $url->toArray()), |
36 |
200
|
37 |
);
|
38 |
}
|
Testons-les :
1 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url |
2 |
{
|
3 |
"error": false, |
4 |
"urls": [ |
5 |
{
|
6 |
"created_at": "2013-02-01 02:39:10", |
7 |
"description": "A Search Engine", |
8 |
"id": "2", |
9 |
"updated_at": "2013-02-01 02:39:10", |
10 |
"url": "http://google.com", |
11 |
"user_id": "1" |
12 |
},
|
13 |
{
|
14 |
"created_at": "2013-02-01 02:44:34", |
15 |
"description": "A Great Blog", |
16 |
"id": "3", |
17 |
"updated_at": "2013-02-01 02:44:34", |
18 |
"url": "http://fideloper.com", |
19 |
"user_id": "1" |
20 |
}
|
21 |
]
|
22 |
}
|
23 |
|
24 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url/1 |
25 |
{
|
26 |
"error": false, |
27 |
"urls": [ |
28 |
{
|
29 |
"created_at": "2013-02-01 02:39:10", |
30 |
"description": "A Search Engine", |
31 |
"id": "2", |
32 |
"updated_at": "2013-02-01 02:39:10", |
33 |
"url": "http://google.com", |
34 |
"user_id": "1" |
35 |
}
|
36 |
]
|
37 |
}
|
Presque terminé. Donnons maintenant aux utilisateurs la possibilité de supprimer des urls.
1 |
/**
|
2 |
* Remove the specified resource from storage.
|
3 |
*
|
4 |
* @param int $id
|
5 |
* @return Response
|
6 |
*/
|
7 |
public function destroy($id) |
8 |
{
|
9 |
$url = Url::where('user_id', Auth::user()->id)->find($id); |
10 |
|
11 |
$url->delete(); |
12 |
|
13 |
return Response::json(array( |
14 |
'error' => false, |
15 |
'message' => 'url deleted'), |
16 |
200
|
17 |
);
|
18 |
}
|
Nous pouvons maintenant supprimer une URL en utilisant une requête DELETE :
1 |
$ curl -i -X DELETE --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url/1 |
2 |
HTTP/1.1 200 OK |
3 |
Date: Tue, 21 May 2013 19:24:19 GMT |
4 |
Content-Type: application/json |
5 |
|
6 |
{"error":false,"message":"url deleted"} |
Enfin, donnons aux utilisateurs la possibilité de mettre à jour une url.
1 |
/**
|
2 |
* Update the specified resource in storage.
|
3 |
*
|
4 |
* @param int $id
|
5 |
* @return Response
|
6 |
*/
|
7 |
public function update($id) |
8 |
{
|
9 |
$url = Url::where('user_id', Auth::user()->id)->find($id); |
10 |
|
11 |
if ( Request::get('url') ) |
12 |
{
|
13 |
$url->url = Request::get('url'); |
14 |
}
|
15 |
|
16 |
if ( Request::get('description') ) |
17 |
{
|
18 |
$url->description = Request::get('description'); |
19 |
}
|
20 |
|
21 |
$url->save(); |
22 |
|
23 |
return Response::json(array( |
24 |
'error' => false, |
25 |
'message' => 'url updated'), |
26 |
200
|
27 |
);
|
28 |
}
|
Pour tester les mises à jour d'URL, lancez la commande suivante :
1 |
$ curl -i -X PUT --user seconduser:second_password -d 'url=http://yahoo.com' localhost/l4api/public/index.php/api/v1/url/4 |
2 |
HTTP/1.1 200 OK |
3 |
Date: Tue, 21 May 2013 19:34:21 GMT |
4 |
Content-Type: application/json |
5 |
|
6 |
{"error":false,"message":"url updated"} |
7 |
|
8 |
// View our changes |
9 |
$ curl --user seconduser:second_password localhost/l4api/public/index.php/api/v1/url/4 |
10 |
{
|
11 |
"error": false, |
12 |
"urls": [ |
13 |
{
|
14 |
"created_at": "2013-02-01 02:44:34", |
15 |
"description": "I feel for him", |
16 |
"id": "3", |
17 |
"updated_at": "2013-02-02 18:44:18", |
18 |
"url": "http://yahoo.com", |
19 |
"user_id": "1" |
20 |
}
|
21 |
]
|
22 |
}
|
Et c'est tout
Nous avons maintenant les bases d'une API entièrement fonctionnelle. J'espère que vous avez appris pas mal de choses sur la manière d'articuler une API avec Laravel 4.
Pour résumer, dans cette leçon, nous avons appris à :
- Installer Laravel
- Créer la base de données en utilisant les migrations et le seeding
- Utiliser les modèles de l'ORM Eloquent
- Authentifier avec le Basic Authentication
- Définir des Routes, avec un versioning de l'API
- Créer les fonctionnalités de l'API en utilisant des contrôleurs de ressources
Les étapes suivantes
Si vous voulez donner un petit coup de boost à votre API, voici quelques étapes complémentaires à envisager :
- Validation (Indice : Laravel propose une bibliothèque de Validation).
- Gestion des erreurs de requêtes d'API – recevoir des retours HTML à des requêtes API reste possible (Indice : Gestion des erreurs Laravel, plus la négociation de contenus.)
- Négociation de contenus - réagir à l'en-tête Accept. (Indice : la classe Request de Laravel vous donnera les en-têtes de requêtes).
- Consultez le groupe Google API Craft Google Group
- Penchez-vous sur les différents types de cache et apprenez comment un cache de validation peut améliorer votre API
- Soumettez votre code à des tests unitaires
- Jetez un œil à l'excellent site de ressources API d'Apigee