Tutoriel Vidéo PHP CakePHP ORM

Télécharger la vidéo

CakePHP est sorti dans sa version 3 il y a un petit moment maintenant et avec ce changement de version majeur l'équipe a décidé d'essayer de séparer les composants du framework pour les rendre utilisables indépendamment du framework. Aussi je vous propose de découvrir aujourd'hui un composant important : L'ORM.

Histoire de prouver ce que j'avance, on va utiliser l'ORM de CakePHP dans un nouveau projet PHP sans utiliser tout le framework.

Installation

L'ORM reste intégré au coeur du framework, mais un nouveau dépôt en lecture seule a été créé pour rendre la librairie utilisable depuis le packagist :

composer require cakephp/orm

Il va ensuite falloir créer une connexion qui permettra aux différents composants de l'ORM de se connecter à notre base de données. Pour cela on peut initialiser manuellement la classe Cake\Datasource\Connection ou utiliser le factory Cake\Datasource\ConnectionManager. On préfèrera utiliser le factory, car il permettra aux autres classes d'obtenir plus facilement une instance de la connexion à la base de données.

use Cake\Datasource\ConnectionManager;

ConnectionManager::config('default', [
    'className' => 'Cake\Database\Connection',
    'driver' => 'Cake\Database\Driver\Mysql',
    'database' => 'cakeorm',
    'username' => 'root',
    'password' => 'root',
    'host'     => 'localhost'
]);

ORM Data Mapper

Avant d'aller plus loin je pense qu'il est important de faire un petit point sur le pattern adopté : Data Mapper. Contrairement à ce que l'on peut voir sur Laravel ou Ruby on Rails (basé sur Active Record) on va ici se retrouver avec 2 niveaux de classe pour gérer notre persistance et la communication avec la base de données.

// Active Record
// L'entité s'occupe des données
$post = new Post();
$post->name = 'Salut les gens';
//, Mais s'occupe aussi de gérer la couche de persistance dans la base
$post->save()

// Data Mapper
// L'entité s'occupe des données
$post = new Post();
$post->name = 'Salut les gens';
// Une nouvelle classe se charge de la partie pesistance
$table = new PostTable();
$table->save($post);

Cette approche peut sembler un peu fastidieuse, mais permet au final d'obtenir un code beaucoup plus flexible, car si on souhaite mettre en place un nouveau système pour persister nos données (une gestion des XML par exemple) il suffira de changer la couche de persistance sans toucher au code de nos entités.

Cake/ORM/Table

Les premières classes que l'on va rencontrer seront les Tables. Elles permettront de créer, modifier, supprimer et récupérer des entités. Si je souhaite par exemple récupérer des données sur ma table posts :

$posts = TableRegistry::get('Posts')

Cela aura pour effet de générer une nouvelle instance de Cake/ORM/Table qui contiendra le nom de ma table. À partir de cet objet, je pourrais utiliser la méthode find() pour récupérer différents résultats.

$posts->find('all', ['conditions' => ['online' => 1]])

On peut aussi choisir de se créer une classe personnalisée pour gérer notre table. Si on choisit de le faire, il faudra faire attention de préciser au TableRegistry la classe que l'on souhaite utiliser. Par défaut il va chercher la classe `App/Model/Table/^.

$posts = TableRegistry::get('Posts', ['className' => App\Table\PostsTable::class]);

Et notre classe va ressembler à ça

<?php
namespace App\Table;

use App\Entity\Post;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class PostsTable extends Table{

    public function initialize(array $config)
    {
        $this->entityClass(Post::class);
        $this->belongsTo('Categories');
    }

    public function buildRules(RulesChecker $rules){
        $rules->add($rules->isUnique(['slug']));
        return $rules;
    }

}

Elle va permettre notamment de définir quelle entité sera utilisée pour représenter nos résultats, mais aussi les relations que notre table aura avec les autres. Si vous souhaitez en apprendre plus sur les possibilités de cette classe, n'hésitez pas à jeter un oeil sur la documentation

Cake/ORM/Query

Lorsque l'on utilise la méthode find() on ne se retrouve pas avec les résultats de la requête à la base, mais avec une instance de Cake/ORM/Query. Cette classe, comme son nom l'indique, permet de construire des requêtes et évidemment d'obtenir les résultats sous différentes formes :

// Brrrrrr des tableaux
$posts->find('all', ['conditions' => ['online' => 1]]);

// On peut aussi écrire
$query = $posts->find()->where(['online' => 1]);

// La requête ne sera exécutée que lors d'une itération
foreach($query as $post){}
// Mais, on peut la déclencher manuellement en appelant la méthode all()
$resultSet = $query->all();

Encore une fois pour en apprendre plus sur l'utilisation de cette classe il faut se rendre sur le book dans le chapitre concernant l'objet Query. Lorsque l'on effectue une requête qui renvoie plusieurs résultats on obtient alors une Collection. Cette classe contient tout un tas de fonctions plus ou moins utiles pour réorganiser les résultats à votre convenance (on se souvient de la méthode combine() de CakePHP 2)

Cake/ORM/Entity

Les entités représentent le bout de la chaine : un résultat. Dans la logique du pattern, cet objet ne fait donc aucune interaction avec la base de données, mais permet d'organiser les différents champs et si besoin, il pourra avoir des accesseurs et des mutateurs pour modifier certains champs ou créer de nouveaux chams virtuels.

<?php

namespace App\Entity;


use Cake\ORM\Entity;
use Cake\Utility\Inflector;

class Post extends Entity
{

    protected $_accessible = [
        'name' => true
    ];

    protected function _getName($name){
        return ucfirst($name);
    }

    protected function _setName($name){
        if(!$this->slug){
            $this->_setSlug($name);
        }
        return $name;
    }

    protected function _setSlug($slug){
        return strtolower(Inflector::slug($slug, '-'));
    }


}

L'entité sera créée automatiquement depuis nos tables, mais sera aussi utilisée pour représenter les données que l'on souhaite enregistrer.

$posts = TableRegistry::get('Posts');
$post = new Post(['name' => 'Mon premier article']);
$posts->save($post);

// La classe Table peut générer les entités pour nous
$post = $posts->newEntity(['name' => 'Mon premier article']); // créé une nouvelle entité (en prenant en compte les champs accessibles)
$posts->patchEntity($post, ['name' => 'Article modifié']); // modifie une entité (en prenant en compte les champs accessibles)
$posts->save($post); // Update ou Insert l'entité
$posts->delete($post); // Supprime l'entité

Là ou ce système devient super efficace c'est qu'il est capable de faire des enregistrement en cascade et de persister à travers plusieurs tables :

$post = $posts->newEntity(['name' => 'Mon premier article']);
$post->category = $categories->newEntity(['name' => 'Nouvelle catégorie']);
$posts->save($post); // Créera d'abord la catégorie et sauvegardera son ID dans la table posts

Conclusion

Je n'ai fait ici que de survoler le fonctionnement de l'ORM, mais on voit clairement le chemin parcouru depuis CakePHP 2. Même si au premier abord ces changements peuvent rendent l'ORM plus complexe, ils permettent d'obtenir un très grand niveau de flexibilité et d'organisation ( finit les classes Model avec 500 lignes de codes).

Avoir rendu la couche ORM autonome est aussi une très bonne nouvelle, car on peut faire le choix de ne pas utiliser CakePHP tout en profitant de son ORM très abouti, et si plus tard le projet évolue, la migration peut se faire facilement, car la couche Model sera déjà développée et compatible avec CakePHP.

Donc, même si vous n'aimez pas forcément le framework (ou les frameworks de manière générale) n'hésitez pas à découvrir cet ORM et à jeter un oeil à la documentation pour voir l'étendue des possiblités.