API Platform (beta) : le framework web PHP nouvelle génération (partie 3)

Troisième et dernière partie du tutoriel consacré à API Platform, le framework PHP développé par notre gérant Kévin Dunglas et auquel nous contribuons activement. Cet article est toujours disponible en version anglaise.

Le client Angular

Prérequis : Node.js et NPM doivent être installés.

Dunglas’ API Platform est indépendant de toute technologie côté client. Vous pouvez utiliser la plate-forme, le langage ou le framework de votre choix. Pour illustrer les portes qu’ouvre l’adoption d’une architecture API-first et la facilité de développement d’une application monopage, nous allons créer un petit client AngularJS.

Gardez à l’esprit qu’il ne s’agit pas d’un tutoriel AngularJS et que je ne prétends pas suivre les bonnes pratiques AngularJS. Il s’agit juste d’un exemple visant à montrer à quel point le développement d’applications devient simple lorsque toute la logique métier est encapsulée dans une API. La seule responsabilité de notre client de blog AngularJS est d’afficher les informations récupérées par l’API (couche présentation).

L’application AngularJS est totalement indépendante de l’API. C’est une application HTML/JS/CSS qui interroge une API via AJAX. Elle a son propre dépôt Git et est hébergée sur son propre serveur web. Puisqu’elle ne contient que des assets, elle peut même être hébergée directement sur un Content Delivery Network comme Amazon CloudFront ou Akamai (nous hébergeons la démo sur GitHub Pages).

Pour échafauder notre application AngularJS, nous allons utiliser le générateur Angular officiel de Yeoman. Installez le générateur puis générez un squelette de client :

mkdir blog-client
cd blog-client
yo angular blog

Yeoman vous posera des questions. Nous voulons que l’application reste minimale :

  • n’installez pas le support de Sass ;
  • installez Twitter Bootsrap (c’est optionnel, mais l’application aura meilleur aspect) ;
  • décochez tous les modules Angular suggérés.

Toutefois, nous allons installer Restangular, un super client REST pour Angular qui s’intègre bien à API Platform :

bower install --save lodash restangular

Le générateur Angular est accompagné de l’outil de build Grunt. Il compile les assets (réduction, compression) et peut exposer la web app via son serveur intégré. D’ailleurs, lancez-le.

grunt serve

DunglasApiBundle fournit dans sa documentation un guide d’intégration Restangular. Une fois configuré, Restangular va fonctionner avec l’API JSON-LD/Hydra comme avec n’importe quelle autre API REST classique.

Ensuite, modifiez le fichier app/app.js pour enregistrer Restangular et le configurer correctement :

// app/scripts/app.js

'use strict';

/**
 * @ngdoc overview
 * @name blogApp
 * @description
 * # blogApp
 *
 * Main module of the application.
 */
angular
    .module('blogApp', ['restangular'])
    .config(['RestangularProvider', function (RestangularProvider) {
        // The URL of the API endpoint
        RestangularProvider.setBaseUrl('http://localhost:8000');

        // JSON-LD @id support
        RestangularProvider.setRestangularFields({
            id: '@id'
        });
        RestangularProvider.setSelfLinkAbsoluteUrl(false);

        // Hydra collections support
        RestangularProvider.addResponseInterceptor(function (data, operation) {
            // Remove trailing slash to make Restangular working
            function populateHref(data) {
                if (data['@id']) {
                    data.href = data['@id'].substring(1);
                }
            }

            // Populate href property for the collection
            populateHref(data);

            if ('getList' === operation) {
                var collectionResponse = data['hydra:member'];
                collectionResponse.metadata = {};

                // Put metadata in a property of the collection
                angular.forEach(data, function (value, key) {
                    if ('hydra:member' !== key) {
                        collectionResponse.metadata[key] = value;
                    }
                });

                // Populate href property for all elements of the collection
                angular.forEach(collectionResponse, function (value) {
                    populateHref(value);
                });

                return collectionResponse;
            }

            return data;
        });
    }])
;

Prenez soin de changer l’URL de base avec celle de votre API en production (PROTIP : utilisez grunt-ng-constant).

Et voici le contrôleur qui récupère la liste des articles et nous permet d’en créer une nouvelle :

// app/scripts/controllers/main.js

'use strict';

/**
 * @ngdoc function
 * @name blogApp.controller:MainCtrl
 * @description
 * # MainCtrl
 * Controller of the blogApp
 */
angular.module('blogApp')
    .controller('MainCtrl', function ($scope, Restangular) {
        var blogPostingApi = Restangular.all('blog_postings');
        var peopleApi = Restangular.all('people');

        function loadPosts() {
            blogPostingApi.getList().then(function (posts) {
                $scope.posts = posts;
            });
        }

        loadPosts();
        peopleApi.getList().then(function (people) {
            $scope.people = people;
        });

        $scope.newPost = {};
        $scope.success = false;
        $scope.errorTitle = false;
        $scope.errorDescription = false;

        $scope.createPost = function (form) {
            blogPostingApi.post($scope.newPost).then(function () {
                loadPosts();

                $scope.success = true;
                $scope.errorTitle = false;
                $scope.errorDescription = false;

                $scope.newPost = {};
                form.$setPristine();
            }, function (response) {
                $scope.success = false;
                $scope.errorTitle = response.data['hydra:title'];
                $scope.errorDescription = response.data['hydra:description'];
            });
        };
    });

Comme vous pouvez le voir, il est très facile d’interroger l’API avec Restangular. La bibliothèque émet automatiquement des requêtes HTTP au serveur et hydrate des objets JavaScript “magiques” correspondant aux réponses JSON qui peuvent être manipulés pour modifier les ressources distantes (via des requêtes PUT, POST et DELETE).

Nous tirons également parti de la gestion des erreurs côté serveur pour afficher de jolis messages quand les données soumises sont invalides ou lorsque quelque chose ne va pas.

Et la vue correspondante bouclant sur les publications et affichant le formulaire et les erreurs :

<!-- app/views/main.html -->

<!-- ... -->

<article ng-repeat="post in posts" id="{{ post['@id'] }}" class="row marketing">
    <h1>{{ post.name }}</h1>
    <h2>{{ post.headline }}</h2>

    <header>
        Date: {{ post.datePublished | date:'medium' }}
        <span ng-hide="post.isFamilyFriendly"> - <b>NSFW</b></span>
    </header>

    <p>{{ post.articleBody }}</p>

    <footer>
        Section: {{ post.articleSection }}
    </footer>
</article>

<form name="createPostForm" ng-submit="createPost(createPostForm)" class="row marketing">
    <h1>Post a new article</h1>

    <div ng-show="success" class="alert alert-success" role="alert">Post published.</div>
    <div ng-show="errorTitle" class="alert alert-danger" role="alert">
        <b>{{ errorTitle }}</b><br>

        {{ errorDescription }}
    </div>

    <div class="form-group">
        <input ng-model="newPost.name" placeholder="Name" class="form-control">
    </div>

    <div class="form-group">
        <input ng-model="newPost.headline" placeholder="Headline" class="form-control">
    </div>

    <div class="form-group">
        <textarea ng-model="newPost.articleBody" placeholder="Body" class="form-control"></textarea>
    </div>

    <div class="form-group">
        <label for="author">Author</label>
        <select ng-model="newPost.author" ng-options="person['@id'] as person.name for person in people" id="author">
        </select>
    </div>

    <div class="form-group">
        <input ng-model="newPost.datePublished" placeholder="Date published" class="form-control">
    </div>

    <div class="form-group">
        <input ng-model="newPost.articleSection" placeholder="Section" class="form-control">
    </div>

    <div class="checkbox">
        <label>
            <input type="checkbox" ng-model="newPost.isFamilyFriendly"> is family friendly?
        </label>
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
</form>

C’est la fin de ce tutoriel ! Notre client blog est prêt et il fonctionne ! Vous vous souvenez de l’ancienne façon de créer des applications web synchrones ? Que pensez-vous de cette nouvelle approche ? C’est plus simple et beaucoup plus puissant, vous ne trouvez pas ?

Bien entendu, il reste encore pas mal de boulot afin d’obtenir un client fini incluant le routing, le support de la pagination et permettant d’intégrer du JSON-LD brut dans le HTML généré pour les moteurs de recherche (utilisez le hook Response Extractor fourni par Restangular).  Dans la mesure où il s’agit uniquement de tâches Angular, cela dépasse le cadre de cette introduction à API Platform. Ajouter de telles fonctionnalités au client est néanmoins un bon exercice. N’hésitez pas à partager vos snippets dans les commentaires.

La facilité de développement et de la puissance d’API Platform vous ont convaincu ? Donnez-nous votre feedback dans les commentaires !

Roadmap

La première bêta est prête et il y a des tâches à accomplir avant la première version stable :

  • Le code est presque prêt et est déjà utilisé par des entreprises comme Les-Tilleuls.coop, Smile, le groupe Trip Advisor et ExaqtWorld. Mais API Platform doit encore être fiabilisé. La première tâche avant la release est de tester et corriger les bugs.
  • La documentation doit être améliorée. Ce tutoriel sera la base de la toute nouvelle documentation qui est en cours d’écriture. Un site internet dédié est également en préparation.
  • Nous avons également besoin de plus d’intégration (documentation ou bibliothèques) avec des technologies frontend populaires !

J’espère publier la première version stable très prochainement. Vous pouvez m’y aider en contribuant (rapports de bug, documentation, code). Tout est hébergé sur GitHub et les Pull Requests sont les bienvenues.

Remerciements

Je remercie tout spécialement Samuel Roze (ancien membre de l’équipe de Les-Tilleuls.coop et aujourd’hui employé chez Inviqa), Théo Fidry et Fabien Gasser (Smile), Maxime Steinhausser, Jérémy Derussé et Luc Vieillescazes (SensioLabs) ainsi que Michaël Labrut (La Fourchette) d’avoir contribué au projet en y apportant du code, de la documentation et une tonne de bonnes idées.

API Platform n’aurait jamais vu le jour sans les formidables technologies et bibliothèques qu’il utilise comme bases, particulièrement Symfony, Doctrine, JSON-LD, Hydra et Schema.org. Un grand merci à tous leurs contributeurs.

Support commercial

Vous avez peur d’essayer ce nouveau framework ? Le support commercial est déjà disponible : Les-Tilleuls.coop est en mesure de développer vos logiciels basés sur API Platform (et Symfony). Notre équipe d’ingénieurs expérimentés propose des prestations de formation et de consulting. N'hésitez pas à nous contacter pour en savoir plus !