Attention, cet article a désormais quelques années… Il se peut que toutes les informations qu’il contient ne soient plus pertinentes.

Je suis un utilisateur convaincu de CakePHP. J’ai commencé avec la version 1 il y a maintenant 5 ans et je suis passé à la version 2 quand celle-ci est sortie. C’est donc tout naturellement que j’attends avec impatience la sortie de la version 3.

Cette prochaine version est dans les tuyaux depuis un moment déjà puisque la communauté en parle depuis longtemps et qu’une première version de preview est sortie en tout début d’année. Après 3 versions de preview et 2 versions alpha, une première version bêta vient de faire son apparition. Je vous propose donc un petit tour d’horizon des promesses de la nouvelle version du framework avec les prochaines grandes fonctionnalités.


Version minimale de PHP

CakePHP 3 nécessitera au minimum la version 5.4.19. Les extensions mbstring (fonctionnalités liées à l’encodage), intl (fonctionnalités liées à la gestion des dates) et mcrypt (chiffrement / déchiffrement de données) devront être activées.


S’appuyer sur les standards de la communauté PHP

La nouvelle version s’appuiera sur de nouveaux standards liés au développement de framework PHP, notamment les normes PSR-0, PSR-1 et PSR-4. Ces normes sont mises au point par le « Framework Interop Group (FIG) » dont le but est de mettre en place des standards de développement et d’écriture de code entre les différents projets de Framework PHP (Symfony, CakePHP, Laravel, etc). PSR-0 met surtout en avant des règles de nommage pour les namespaces. PSR-1 met surtout en avant le nommage pour les classes, méthodes et constantes. PSR-4 met en avant le système d’autoload et l’organisation des fichiers que cela implique.

CakePHP peut désormais être récupéré via Composer qui se chargera alors tout seul de réaliser certaines tâches de configuration. On pourra utiliser Composer également pour charger toutes sortes de plugin et librairies tierces. Ce qui permettra d’apporter facilement un grand lot de fonctionnalités à CakePHP.

L’organisation des dossiers et fichiers de CakePHP a du coup quelque peu changé. A gauche l’organisation des fichiers des versions 2.x et à droite la nouvelle organisation pour la version 3 :

Dans les points importants, on remarque que le dossier Modèle a considérablement « gonflé ». Les explications de cette nouvelle organisation se trouve plus bas (Table, Entity, etc.). Un nouveau dossier Template fait son apparition, à côté de vue. Cela pour séparer le code logique du code d’affichage. Le dossier Template contient donc toutes les vues classiques en .ctp et le dossier View contient notamment les Helpers et les Cells (vois plus bas). On retrouve également les fichiers liés à Composer.


Utilisation des namespaces

Première nouveauté dans le code de CakePHP, l’utilisation des namespaces. Apparus dans la version 5.3 de PHP, ils permettent de regrouper des classes, des méthodes, des constantes qui sont liés. Ils permettent ainsi d’éviter les collisions de classes ou méthodes entre, par exemple, des fonctionnalités créées et une librairie importée. Deux fonctions au même nom pourront être utilisées en même temps si elles sont bien « rangées » dans leur namespace respectif. Les fichiers de CakePHP ressembleront donc désormais à cela : (exemple pour un Controller)

namespace App\Controller;

class ArticlesController extends AppController {

}

Cela peut paraitre compliqué au premier abord d’avoir à ajouter ces lignes dans chaque fichier mais la console (Bake) permettra de générer les fichiers avec ces lignes. De plus, on les retrouve facilement en regardant l’arborescence des fichiers. Par exemple, pour un Helper, le namespace serait App\View\Helper.


Un Framework plus léger, avec plus de plugins

Au fil du temps, CakePHP s’est enrichi de nombreuses fonctionnalités. Si bien que le framework est devenu « lourd » du point de vue de l’équipe en charge de son développement. En effet, chaque développement majeur devait prendre en compte ces nombreuses fonctionnalités. CakePHP 3 sera donc allégé pour ne garder que l’essentiel. Pour autant, toutes les fonctionnalités additionnelles ne seront pas abandonnées. Au contraire, elles seront désormais proposées sous forme, par exemple, de plugins. L’équipe de CakePHP souhaite également que ce nouveau fonctionnement booste la communauté en permettant à plus de monde de contribuer aux développements de plugins, sans pour autant avoir besoin de rentrer dans le coeur complet du framework.


Un nouvel ORM

L’ORM de CakePHP était souvent critiqué. Il est vrai que certaines requêtes pouvaient être complexes à réaliser. Il fallait souvent jouer avec les unbindModel et le comportement contain pour arriver exactement à ce que l’on voulait. Certains critiquaient également le fait que les fonctions de l’ORM ne retournaient que des tableaux. L’ORM a donc été complètement réécrit pour palier à ces problèmes et permettre de nouvelles fonctionnalités.

Toutes les requêtes sont donc traitées via le nouvel objet Query Builder. Imaginons le modèle suivant :

Nous aurons alors des syntaxes de ce type :

// Ancienne version
$articles = $this->Article->find('all' , array('conditions' => array('Articles.validated' => 1) , 'order' => array('Articles.created' => 'DESC')));

// CakePHP 3
$articles = $this->Articles->find()->where(array('Articles.validated' => 1))->order(array('Articles.created' => 'DESC'));

Sur cet exemple, on voit principalement des différences de syntaxe mais cela ouvre des possibilités très larges en terme de construction de requêtes. Les options sont vraiment très nombreuses. Il faut savoir aussi que le Query Builder peut « mémoriser » les différentes sous requêtes, en faisant des choses dans ce genre là :

$dixArticlesPublies = $this->Articles->find()->where(array('Articles.validated' => 1))->limit(10);

// On réutilise $dixArticlesPublies
$dixDerniersArticles = $dixArticlesPublies->order(array('Articles.created' => 'DESC'));
$dixPremiersArticles = $dixArticlesPublies->order(array('Articles.created' => 'ASC'));

En effet, les requêtes ne sont réellement effectuées que lorsqu’on cherche à récupérer les données pour les traiter ou les afficher. Le « Query Builder », comme son nom l’indique, sert donc surtout à construire les requêtes de façon plus efficace, avec plus de possibilités que l’ancien ORM.

Pour accéder aux données de modèles associés, on fera ça de la façon suivante :

$dixDerniersArticlesAvecAuteurs = $dixDerniersArticles->contain(array('Auteurs'));

On remarque que les modèles prennent des « S » désormais quand ils sont appelés dans les controllers.

Dans les vues, on accédera aux données de la façon suivante :

// Dans le controller
$this->set('articles' , $dixDerniersArticlesAvecAuteurs);

// Dans une vue, disons view.ctp
foreach($articles as $article) {
echo
'<div class="article">'.
    '<h3>'.$article->name.'</h3>'.
    '<div class="content">'.$article->content.'</div>'.
    '<p class="auteur">'.$article->auteur->name.'</p>'.
'</div>';
} 

On accède donc facilement aux informations des auteurs, grâce à ->auteur et non plus en passant par des tableaux de type $article[‘Auteur’][‘name’]. C’est surtout la partie suivante, les entités, qui apportent une puissance certaine à cette nouvelle syntaxe.


Table et Entity

Les modèles sont désormais des objets Table. Les données issues des tables sont appelées entités (Entity).

La structure des Table a donc évolué par rapport à l’ancienne structure des modèles. La fonction initialize va permettre de déclarer l’ensemble de la configuration du modèle alors qu’on ajoutait avant des propriétés à notre classe. Cela est valable notamment pour déclarer les associations avec d’autres modèles. Les règles de validation nécessite également une fonction à la place des propriétés, notamment pour permettre plus de flexibilité. Voici un exemple de paramétrage de notre classe Article.

namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Validation\Validator;

class ArticlesTable extends Table {
    
    public function initialize(array $config) {
        $this->belongsTo('Auteurs');
    }

    public function validationDefault(Validator $validator) {
        // Le champ name ne peut pas être vide
        $validator->allowEmpty('name', false);

        return $validator;
    }
    
}

Et pour notre table Article, on crée donc l’entité correspondante :

// Fichier Article.php
namespace APP\Model\Entity;

// Importation des différentes fonctionnalités de CakePHP
use Cake\ORM\Entity;
use Cake\Routing\Router;
use Cake\Utility\Inflector;

class Article extends Entity {
    
    // On crée un getteur "magique" qui permet de mettre le nom en majuscule
    public function _getNameEnGros() {
        
        return strtoupper($this->name);
    }
    
    // On crée un autre getteur qui retourne cette fois ci l'URL de l'article (en lien avec une route créée d'avance, utilisant les nouvelles fonctionnalités du Router de CakePHP
    public function _getUrl() {
        
        return Router::url(['_name' => 'article' , 'id' => $this->id , 'slug' => Inflector::slug($this->name , '-')]);
    }

}

// Fichier view.ctp
// On pourra donc avoir dans la vue, en reprenant l'exemple précédent :
foreach($articles as $article) {
    echo
    '<div class="article">'.
        // Utilisation de name_gros
        '<h3>'.$article->name_en_gros.'</h3>'.
        '<div class="content">'.$article->content.'</div>'.
        '<p class="auteur">'.$article->auteur->name.'</p>'.
        // Utilisation de url
        '<p class="lien">'.$this->Html->link($article->name , $article->url).'</p>'.
    '</div>';
} 

Je trouve que l’exemple avec l’URL est vraiment quelque chose de puissant. De cette façon, les URL réécrites seront définies seulement dans le Router et dans l’Entité. Plus besoin de les réécrire pour chaque lien. Les entités proposent encore d’autres fonctionnalités (setteurs, gestion des erreurs de validation, lazy loading, accès aux modèles associés, etc.).

Edit : En fait, selon la remarque de Pierre Martin (cf. commentaires), il s’avère que les entités ne doivent pas être l’emplacement pour programmer des choses liées à de la mise en forme. L’exemple de la fonction _getNameEnGros n’est donc pas correct. Même s’il peut être tentant de mettre dans les entités des fonctions de mises en forme comme ça, l’équipe de développement de CakePHP le déconseille, cela ne colle pas à la logique des entités et pourra être un problème lors de prochaines mises à jour. L’exemple de _getUrl reste excellent .


L’association belongsToMany

L’association hasAndBelongsToMany (HABTM) disparait au profit de l’association belongsToMany. Ce sera globalement la même chose, à savoir 2 tables reliées en « plusieurs à plusieurs » et qui nécessitent donc une troisième table pour faire la liaison. 2 des avantages de cette nouvelle liaison est qu’elle permet tout d’abord de gérer d’autres champs et non seulement « id, modele1_id, modele2_id » alors qu’il fallait avant jouer avec les liaisons hasMany et belongsTo pour arriver au même résultat. Le deuxième avantage réside dans le mode d’enregistrement, qui peut être « append » ou « replace ». Dans la version 2.x de CakePHP, lorsqu’on enregistrait de nouvelles entrées dans une liaison HABTM, tous les enregistrements préexistants étaient supprimés (pour les clés correspondantes). On pourra donc choisir entre ce comportement (replace) ou un comportement moins destructif (append) permettant d’ajouter les entrées les unes après les autres.


Les cells

Les cells sont comme de minis controllers, qui peuvent utiliser la logique des vues (Helpers, etc.) et afficher des templates. Ils proposent une alternative intéressante au « couple » élément + requestAction. Personnellement, j’utilises très peu les requestAction. Etant donné que cela coûte aux performances globales de l’application, je préfère « contourner » leur utilisation. Mais les cells apportent plus de flexibilité et semblent vraiment palier à ce soucis de performance. Ils seront donc très intéressants à utiliser.


Encore plein d’autres choses à découvrir

Je ne présente ici bien sûr que quelques unes des nouvelles fonctionnalités. Il y en a un grand nombre que je découvrirais au fur et à mesure de l’utilisation du Framework (thèmes, gestion de l’authentification, nouveaux plugins, etc.).

Pour conclure sur ce premier aperçu, je dirais qu’il y vraiment tout un tas de nouvelles fonctionnalités qui vont permettre de dépasser les limites ou obstacles que l’on pouvait rencontrer dans les versions précédentes, comme la difficulté à créer des requêtes complexes. En plus de ces nouvelles fonctionnalités, le code devrait gagner en clarté, notamment dans les vues, grâce aux possibilités offertes par les Entity. Par contre, il y a bien une vraie rupture dans le code entre la version 2 et la version 3. Bien plus qu’entre les versions 1 et 2. Je pense que la migration d’une application existante de CakePHP 2 vers CakePHP 3 pourra être vraiment compliqué.

Bien que certains concepts plus avancés de programmation font leur apparition (namespaces, etc.), le framework devrait rester accessible au plus grand nombre.

Quelques liens pour aller plus loin :

Publié par Arthur Weill

Directeur de l’agence rennaise Web and Cow, directeur digital du Groupe Valorex, impliqué dans l'informatique et la programmation depuis plus de 10 ans.