Créer une API REST

Configuration

Connexion à la base de données

Éditez le fichier config/config.php :

<?php
return [
    'development_environment' => false,
    'default_locale' => 'en_US',
    'default_timezone' => 'Europe/Paris',
    'base_url' => 'http://example.com',
    'database' => [
        'type' => 'pgsql',
        'server' => 'localhost',
        'username' => 'postgres',
        'password' => 'password123',
        'name' => 'mydb',
        'schema' => 'bluebird'
    ],
    'filters' => [
        'RoutingFilter'
    ]
];
?>

Éditez le fichier config/dependencies.php et ajoutez les lignes suivantes :

IoC::register( 'settings', function() {
    return new Configuration( new JSONFileConfigurationHandler( CONFIG_DIR . 'settings.json' ) );
}, true );

Créez un fichier nommé settings.json sous config avec le contenu suivant :

{
    "users.items-per-page": 20
}

Modèle de données

Créez la table suivante dans la base de données :

CREATE TABLE `user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) NOT NULL,
  `encrypted_password` varchar(100) NOT NULL,
  `email` varchar(100) NOT NULL,
  PRIMARY KEY (`user_id`)
);

Le modèle

Dans application/models créez un fichier nommé user.class.php contenant :

<?php
namespace models;

use \ActiveRecord;

class User extends ActiveRecord {

    protected function validate() {
        $this->validateNotEmpty( 'username', 'Username cannot be empty.' );
        $this->validateMaxLength( 'username', 30, 'Username is too long (maximum is 30 characters).' );
    }

    protected function validateOnInsert() {
        $this->validateNotEmpty( 'password', 'Password cannot be empty.' );
    }

    protected function validateOnUpdate() {
        if ( $this->isAttributeChanged( 'username' ) ) {
            $this->validateUnique( 'username', 'Username already taken.' );
        }
        if ( $this->isAttributeChanged( 'email' ) ) {
            $this->validateUnique( 'email', 'Email already in use.' );
        }
    }

    protected function beforeSave() {
        if ( !empty( $this->password ) ) {
            $this->password = trim( $this->password );
            $this->encrypted_password = password_hash( $this->password, PASSWORD_DEFAULT );
            unset( $this->password );
        }
        $this->email = trim( $this->email );
        $this->email = strtolower( $this->email );
    }

}

Voir Le modèle : ActiveRecord pour plus de détails sur l'utilisation d'ActiveRecord pour définir les classes de modèles.

Les routes

Dans config/routes.json, définissez les routes suivantes :

[
    ["GET", "api/v1/users", "controllers\\UserController@list"],
    ["GET", "api/v1/users/{id:\\d+}", "controllers\\UserController@show"],
    ["POST", "api/v1/users/", "controllers\\UserController@add"],
    ["PATCH", "api/v1/users/{id:\\d+}", "controllers\\UserController@update"],
    ["DELETE", "api/v1/users/{id:\\d+}", "controllers\\UserController@delete"]
]

Le contrôleur

Lister les utilisateurs

Dans application/controllers, créez un fichier nommé usercontroller.class.php contenant :

<?php
namespace controllers;

use \ActionController;
use \IoC;
use \Paginator;
use \JSONHandler;
use models\User;

class UserController extends ActionController {

    public function doList() {
        $currentPage = $this->request->getParameters()->getInt( 'page', 1 );

        if ( !\Validator::validateInteger( $currentPage ) ) {
            $this->forward( 'controllers\\ErrorController', '400' );
        }

        $settings = IoC::resolve( 'settings' );
        $totalItems = User::count();

        $paginator = new Paginator( $settings['users.items-per-page'], $totalItems, $currentPage );

        $this->response->addHeader( 'Content-type', 'application/json' );
        $this->renderText( JSONHandler::encode( User::findMany( ['user_id', 'username', 'email'], ['LIMIT' => [$paginator->getOffset(), $paginator->getLimit()]] ) ) );

    }
}

La classe UserController étend la classe de base pour les contrôleurs d'action ActionController.

L'action doList retourne la liste des utilisateurs.

La classe Paginator permet de paginer les résultats.

Le nombre d'enregistrements par page est paramétrable dans le fichier de paramétrage applicatif config/settings.json.

La page courante est fournie en paramètre query string.

Le paramètre LIMIT dans la requête permet de spécifier la clause LIMIT.

L'appel à addHeader sur l'objet Response permet de définir le type MIME de la réponse : application/json.

Récupérer un utilisateur

L'action doShow retourne l'utilisateur correspond au user_id spécifié :

public function doShow() {
    $user = User::findOne( ['user_id', 'username', 'email'], ['user_id' => $this->request->getAttribute( 'id' )] );
    if ( empty( $user ) ) {
        $this->forward( 'controllers\\ErrorController', '404' );
    }
    $this->response->addHeader( 'Content-type', 'application/json' );
    $this->renderText( JSONHandler::encode( $user ) );
}

Si l'utilisateur n'existe pas, une erreur 404 sera retournée.

Créer un utilisateur

L'action doAdd() crée un utilisateur.

public function doAdd() {
    $json = file_get_contents( 'php://input' );
    $data = JSONHandler::decode( $json, true );
    $user = new User( $data );
    if ( !$user->save() ) {
        $this->response->setStatus( 400 );
        $this->response->addHeader( 'Content-type', 'application/json' );
        $this->renderText( JSONHandler::encode( $user->getErrors() ) );
    } else {
        $this->renderText( 'OK' );
    }
}

Modifier un utilisateur

L'action doUpdate() permet de modifier un utilisateur.

public function doUpdate() {
    $json = file_get_contents( 'php://input' );
    $data = JSONHandler::decode( $json, true );

    $user = User::findOne( $this->request->getAttribute( 'id' ) );
    if ( empty( $user ) ) {
        $this->forward( 'controllers\\ErrorController', '404' );
    }

    if ( isset( $data['username'] ) ) {
        $user->username = $data['username'];
    }
    if ( isset( $data['password'] ) ) {
        $user->password = $data['password'];
    }
    if ( isset( $data['email'] ) ) {
        $user->email = $data['email'];
    }

    if ( !$user->save() ) {
        $this->response->setStatus( 400 );
        $this->renderText( JSONHandler::encode( $user->getErrors() ) );
    } else {
        $this->renderText( 'OK' );
    }
}

Si l'utilisateur n'existe pas, une erreur 404 sera retournée.

Supprimer un utilisateur

L'action doDelete() supprime un utilisateur.

public function doDelete() {
    $user = User::findOne( $this->request->getAttribute( 'id' ) );
    if ( empty( $user ) ) {
        $this->forward( 'controllers\\ErrorController', '404' );
    }
    $user->delete();
    $this->renderText( '' );
}

Si l'utilisateur n'existe pas, une erreur 404 sera retournée.

Gestion des erreurs : ErrorController

Ouvrez le fichier application/controllers/errorcontroller.class.php et ajoutez la ligne suivante :

use \JSONHandler;

Modifiez la méthode do404() comme suit :

public function do404() {
    $this->response->setStatus( 404 );
    $this->renderText( JSONHandler::encode( ['error' => ['status' => 404, 'message' => 'Resource not found.'] ] ) );
}

Modifiez la méthode do500() comme suit :

public function do500() {
    $this->response->setStatus( 500 );
    $this->renderText( JSONHandler::encode( ['error' => ['status' => 500, 'message' => 'Unexpected error.'] ] ) );
}

Modifiez la méthode do400() comme suit :

public function do400() {
    $this->response->setStatus( 400 );
    $this->renderText( JSONHandler::encode( ['error' => ['status' => 400, 'message' => 'Bad Request.'] ] ) );
}

Test de l'API

Nous utilisons cURL pour tester l'API.

Lister les utilisateurs

curl http://[servername]/api/v1/users

Récupérer un utilisateur

curl http://[servername]/api/v1/users/1

Créer un utilisateur

curl -X POST -H "Content-Type: application/json" -d '{"username": "jon", "password": "secret123@", "email": "jon@example.com"}' http://[servername]/api/v1/users/

Modifier un utilisateur

curl -X PATCH -H "Content-Type: application/json" -d '{"email": "jon2@example.com"}' http://[servername]/api/v1/users/1

Supprimer un utilisateur

curl -X DELETE http://[servername]/api/v1/users/2