AngularJS

AngularJs logicless templates : limitez la logique métier au sein de vos templates

Ceux qui ont utilisé un moteur de template logicless comme Mustache ou Handlebars savent immédiatement de quoi je parle. Un template doit contenir le minimum possible de logique métier. Et avec Mustachejs ou Handlebarsjs, le concept est poussé loin puisque c’est tout simplement impossible.

Logic-less templates – Semantic templates

J’ai découvert Handlebars sur mon premier projet EmberJs et son utilisation m’a paru bien déroutante au début. Voici un exemple de template Handlebars :

{{#if person.isAdmin}}
Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>!
{{/if}}

Jusque là, rien d’anormal ! Comment faire maintenant si je veux modifier un peu ma condition pour afficher ce bloc si la personne est Admin ou si elle est Super Admin ? L’extrait de code suivant semble tout indiqué :

{{#if person.isAdmin or person.isSuperAdmin}}
Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>!
{{/if}}

Pourtant c’est impossible ! Nous sommes dans un moteur de template logicless et nous n’avons pas la possibilité d’utiliser ce type de condition complexe.
Nous sommes dès lors obligés d’extraire la logique de notre template et d’injecter une condition simple, calculée en dehors. Exemple :

{{#if isAdminAreaAuthorized}}
Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>!
{{/if}}

Cela semble très contraignant au début mais on s’y fait finalement vite.
Ce type de template est en revanche parfaitement possible au sein d’une application AngularJs :

<p ng-show="person.isAdmin || person.isSuperAdmin">
    Welcome in admin area, <strong>{{person.firstName}} {{person.lastName}}</strong>!
</p>

Sur l’extrait de code précédent, la logique implémentée au sein du template reste assez concise. Mais je suis trop souvent tombé sur ce genre de templates sur des applications AngularJS :

<p ng-show="!isWorkInProgress && currentItem.price > 1000 && (user.id == currentItem.authorId || user.isAdmin || user.isSuperAdmin)">
  {{ currentItem.title }}
</p>

<p>Un paragraphe qui s'affiche toujours</p>

<p ng-show="!isWorkInProgress && currentItem.price > 1000  && (user.id == currentItem.authorId || user.isAdmin || user.isSuperAdmin)">
  {{ currentItem.description }}
</p>

Problèmes

Nous avons ici plusieurs problèmes :

  • Les conditions sont assez obscures et difficiles à comprendre
  • Les conditions sont répétées en plusieurs endroits au sein du même template
  • Mélange de logique métier et de logique d’affichage
  • La logique business est probablement dispatchée en différents endroits du code (des controllers, des services, des templates, …)

Difficile pour un intégrateur de travailler sur un template de ce type. Et quid du changement de condition pour l’affichage de nos blocs ? Nous sommes bons pour un chercher / remplacer douloureux.

Les moteurs de templates logic-less tels que que Mustache ou Handlebars sont loin de faire l’unanimité parmi les développeurs. En cause, leur aspect trop contraignant et l’obligation d’extraire la logique purement de présentation en même temps que la logique business. C’est vrai que finalement, la condition isWorkInProgress est liée uniquement à l’affichage et peut trouver légitimement sa place au sein du template.
Néanmoins, ces moteurs de templates logicless ont le mérite de forcer le développeur à extraire la logique business du template pour la centraliser au sein du controller ou de services dédiés.

Pour avoir le point de vue de personnes se positionnant contre les moteurs de templates logic-less, je vous conseille la lecture de The Case Against Logic-less Templates et de cet autre article dans la même veine : Cult of logicless templates.

AngularJS : extraction de la logique vers les controllers et services

En ce qui concerne vos applications AngularJS, de même qu’il est important de simplifier au maximum les controllers, il est important de limiter le plus possible la logique métier implémentée au sein des templates.

Dans les faits, le template précédent pourrait être repensé de la sorte :

<p ng-show="isItemViewableByUser(currentItem, user)">
  {{ currentItem.title }}
</p>

<p>Un paragraphe qui s'affiche toujours</p>

<p ng-show="isItemViewableByUser(currentItem, user)">
  {{ currentItem.description }}
</p>

Notre template est plus clair. Le nommage sémantique de la fonction qui conditionne l’affichage (isItemViewableByUser) rend le code compréhensible immédiatement. De plus, si la logique métier qui conditionne l’affichage change, le template n’a plus à changer.

Nous injectons cette logique dans le scope via le controller associé. Et pour ne pas faire l’erreur de remonter telle quelle une condition métier au sein du controller, n’oubliez pas d’extraire à son tour la logique métier de ce dernier vers un service dédié, rendant le tout plus facile à tester unitairement, plus facile à comprendre et donc plus facile à maintenir :

function MyCtrl($scope, UserService) {
    $scope.isWorkInProgress = false;
    // [...]
    $scope.isItemViewableByUser = function(item, user) {
        // Au choix, logique remontée au sein du Model
        // cf http://blog.overnetcity.com/2014/03/15/angularjs-models-donnees-model-data
        return !isWorkInProgress && user.canSeeItem(item);
        // ou au sein d'un service dédié
        return !isWorkInProgress && UserService.doesUserCanSeeItem(user, item);
    };
}

Pour aller plus loin

Quelques ressources pour aller plus loin :

Symfony 2

Symfony – Behavioral design pattern

Lorsque l’on développe une application avec Symfony, on implémente divers design pattern sans forcément trop sans rendre compte. En effet, le framework intègre directement un nombre important de bonnes pratiques.

Behavioral Design Pattern

Il existe un design pattern très intéressant, BEHAVIORAL, qui permet de définir des comportements sur des objets métiers par exemple. On peut ainsi définir un objet comme étant loggable, blameable ou bien encore timestampable.

Les traits ou l’héritage multiple en PHP

En PHP, ajouter un comportement à un objet n’est pas forcément chose aisée car l’héritage multiple n’existe pas. Heureusement, à partir de PHP 5.4, les « traits » permettent de simuler une forme d’héritage multiple en ajoutant à une classe des propriétés et des méthodes définies dans un « trait ».

<?php

namespace Knp\DoctrineBehaviors\Model\Timestampable;

trait Timestampable
{
    /**
     * @var \DateTime $createdAt
     *
     * @ORM\Column(type="datetime", nullable=true)
     */
    protected $createdAt;

    /**
     * @var \DateTime $updatedAt
     *
     * @ORM\Column(type="datetime", nullable=true)
     */
    protected $updatedAt;

    /**
     * Returns createdAt value.
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Returns updatedAt value.
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * @param \DateTime $createdAt
     * @return $this
     */
    public function setCreatedAt(\DateTime $createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * @param \DateTime $updatedAt
     * @return $this
     */
    public function setUpdatedAt(\DateTime $updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
     * Updates createdAt and updatedAt timestamps.
     */
    public function updateTimestamps()
    {
        if (null === $this->createdAt) {
            $this->createdAt = new \DateTime('now');
        }

        $this->updatedAt = new \DateTime('now');
    }
}

L’objet de l’article n’étant pas de détailler le fonctionnement des « traits » en PHP, je vous laisse parcourir la documentation à ce sujet. http://www.php.net/manual/fr/language.oop5.traits.php

Doctrine Behaviors

Avec Symfony et l’aide d’un bundle bien pratique, DoctrineBehaviors de KnpLabs, il devient très aisé d’implémenter ce design pattern en un temps record. De plus, si vous avez l’habitude d’utiliser les annotations pour votre mapping ORM/ODM, vous n’aurez rien d’autre à faire que d’utiliser les « traits » PHP offerts par le bundle pour ajouter des comportements à vos objets métiers. Le gestion (CRUD) de ces comportements en base de données se fera alors de manière automatique via les listeners Symfony/Doctrine.

<?php

use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;

/**
 * @ORM\Entity(repositoryClass="CategoryRepository")
 */
class Category implements ORMBehaviors\Tree\NodeInterface, \ArrayAccess
{
    use ORMBehaviors\Tree\Node,
        ORMBehaviors\Translatable\Translatable,
        ORMBehaviors\Timestampable\Timestampable,
        ORMBehaviors\SoftDeletable\SoftDeletable,
        ORMBehaviors\Blameable\Blameable,
        ORMBehaviors\Geocodable\Geocodable,
        ORMBehaviors\Loggable\Loggable,
        ORMBehaviors\Sluggable\Sluggable
    ;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="NONE")
     */
    protected $id;
}

Visitez vite le github du bundle : https://github.com/KnpLabs/DoctrineBehaviors

Traceur

Transpiler son code ES6 avec Gulp et Traceur

L’actualité est de plus en plus riche pour les développeurs web ces derniers temps. Entre Hack, HVVM, Symfony 3, Angular 2.0, Ember 1.6 ou ECMAScript 6, on ne sait plus où donner de la tête.

Alors que dans certaines entreprises, on se bat encore pour abandonner les navigateurs types IE6/7/8 et enfin pouvoir utiliser ES5 sans problème, ailleurs, on commence déjà à développer en ES6. C’est notamment le cas de la team AngularJs qui a annoncé que Angular 2.0 serait écrit en ES6. La démo du travail en cours sur le nouveau framework de DI d’Angular a même été faite avec des bouts de code en ECMAScript6.

Et la bonne nouvelle, c’est que si les navigateurs modernes sont encore loin de supporter le futur ES6 (beaucoup de nouvelles features restent à l’état de draft), rien n’empêche de le tester dès aujourd’hui. Et pourquoi pas même, en production… Madness?! Un peu je dois bien l’avouer… Mais si vous avez le goût du risque, après tout, pourquoi pas… J’ai bien eu l’occasion de travailler sur une grosse application avec Ember-Data en v0.13…

Quelles solutions pour tester ES6 ?

Alors comment faire pour tester ES6 ? Plusieurs solutions s’offrent à nous. Une des plus simples si on souhaite uniquement jouer avec les nouveaux modules ou s’essayer au modèle objet, c’est d’utiliser un équivalent à jsfiddle, compatible ECMA6. C’est le cas par exemple de es6fiddle.

Utiliser Gulp et Traceur pour « traduire » son code ES6 vers ES5

En revanche, si l’on souhaite commencer à coder une appli réelle en ES6 et la voir s’exécuter dans un navigateur moderne, il faut se tourner vers des solutions telles que le projet Traceur de Google.

Traceur is a JavaScript.next-to-JavaScript-of-today compiler that allows you to use features from the future today.

Traceur est ce qu’on appelle un transpiler, c’est à dire, un compilateur qui compile le code source d’un langage vers le code source d’un autre. En l’occurence donc, un transpiler ECMAScript6 vers ECMAScript5.

Traceur supporte une liste importante des features populaires d’ES6 et, cerise sur le gâteau, il a son propre plugin Gulp.

Commençons donc par se créer un répertoire pour notre projet et y installer gulp et son plugin gulp-traceur :

mkdir projet-es6 && cd projet-es6
npm init
npm install --save-dev gulp gulp-traceur
mkdir dist src

npm init

On crée ensuite un gulpfile simpliste :

// gulpfile.js
var gulp = require('gulp'),
    traceur = require('gulp-traceur')
    paths = {
       scripts: ['src/*.js', 'src/**/*.js']
    };

gulp.task('scripts', function () {
    gulp.src(paths.scripts)
        .pipe(traceur())
        .pipe(gulp.dest('dist'));
});

gulp.task('watch', function() {
  gulp.watch(paths.scripts, ['scripts']);
});

gulp.task('default', ['scripts', 'watch']);

Et voilà, nous sommes prêts à tester ES6 et à le transpiler vers ES5 grâce à Gulp.

On peut essayer avec une Arrow Function par exemple :

// src/app.js
var square = x => x * x;

Après avoir lancé la commande gulp scripts, notre fonction arrow est transformée en une bonne « vieille » fonction ES5 :

"use strict";
var __moduleName = "app";
var square = (function(x) {
  return x * x;
});

Ci-dessous, le résultat de transpiling avec une classe ES6 toute simple :

Screencast Gulp Traceur ES6

Il ne nous reste plus qu’à jouer avec les autres features spécifiques ES6 :

  • Let et Const
  • Paramètres par défaut
  • Proxies
  • Modules
  • Destructuring

Pour aller plus loin

On trouve toute une flopée d’exemples et de resources à propos d’ECMAScript 6. Je trouve ce document assez bien fait : https://github.com/lukehoban/es6features pour se lancer.

Quelques autres resources intéressantes :

Symfony 2

Symfony – Best Bundles

En tant que développeur Symfony, j’essaye lorsque c’est possible, de ne jamais réinventer la roue. Cela tombe bien car Symfony et sa vaste communauté, offrent de nombreux Bundles très bien faits répondant aux besoins les plus courants notamment en terme d’implémentation de bonnes pratiques.

Devenez un expert en utilisant les « Best Bundles » Symfony

The FOSUserBundle adds support for a database-backed user system in Symfony2. It provides a flexible framework for user management that aims to handle common tasks such as user registration and password retrieval.

This bundle provides various tools to rapidly develop RESTful API’s & applications with Symfony2.

Elastica and ElasticSearch integration in Symfony2

This Bundle provides a Symfony2 authentication provider so that users can login to a Symfony2 application via Facebook. Furthermore via custom user provider support the Facebook login can also be integrated with other data sources like the database based solution provided by FOSUserBundle.

JMSDiExtraBundle adds more powerful dependency injection features to Symfony2

This bundle allows you to create i18n routes.

This bundle adds AOP capabilities to Symfony2.

If you haven’t heard of AOP yet, it basically allows you to separate a cross-cutting concern (for example, security checks) into a dedicated class, and not having to repeat that code in all places where it is needed.

In other words, this allows you to execute custom code before, and after the invocation of certain methods in your service layer, or your controllers. You can also choose to skip the invocation of the original method, or throw exceptions.

XHProf is a hierarchical profiler for PHP. It reports function-level call counts and inclusive and exclusive metrics such as wall (elapsed) time, CPU time and memory usage. A function’s profile can be broken down by callers or callees. The raw data collection component is implemented in C as a PHP Zend extension called xhprof. XHProf has a simple HTML based user interface (written in PHP). The browser based UI for viewing profiler results makes it easy to view results or to share results with peers. A callgraph image view is also supported.

This bundle is a fork of AvalancheImagineBundle which provides easy image manipulation support for Symfony2. The goal of the fork is to make the code more extensible and as a result applicable for more use cases.

This php 5.4+ library is a collection of traits that add behaviors to Doctrine2 entites and repositories.

This bundle integrates the Doctrine2 Migrations library. into Symfony so that you can safely and quickly manage database migrations.

Provide markdown conversion (based on Michel Fortin work) to your Symfony2 projects.

Alice allows you to create a ton of fixtures/fake data for use while developing or testing your project. It gives you a few essential tools to make it very easy to generate complex data with constraints in a readable and easy to edit way, so that everyone on your team can tweak the fixtures if needed.

It adds in your WebProfiler extra sections (routing, container, twig,…)

This bundle allows you to expose your routing in your JavaScript code. That means you’ll be able to generate URL with given parameters like you can do with the Router component provided in the Symfony2 core.

Pour finir…

…la liste n’est bien sûr pas exhaustive, retenez que vous trouverez souvent des bundles existant répondant à vos besoins. Attention cependant, car certains sont de meilleure qualité que d’autres, certains sont bien maintenus d’autres pas, enfin plus grave, certains ne fonctionnent pas ou contiennent de nombreux bugs. Alors attention !

Quelques liens utiles