Entities

class Cake\ORM\Entity

Tandis que les objets Table représentent et fournissent un accès à une collection d’objets, les entities représentent des lignes individuelles ou des objets de domaine dans votre application. Les entities contiennent des méthodes pour manipuler et accéder aux données qu’elles contiennent. Les champs sont aussi accessibles en tant que propriétés de l’objet.

Les entities sont créées pour vous par CakePHP à chaque fois que vous faites une itération sur l’objet query renvoyé par find() sur un objet table, ou quand vous appelez les méthodes all() ou first() sur l’objet query.

Créer des Classes Entity

Vous n’avez pas besoin de créer des classes entity pour utiliser l’ORM dans CakePHP. Cependant si vous souhaitez avoir de la logique personnalisée dans vos entities, vous devrez créer des classes. Par convention, les classes entity se trouvent dans src/Model/Entity/. Si notre application a une table articles, nous pouvons créer l’entity suivante:

// src/Model/Entity/Article.php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
}

Pour l’instant cette entity ne fait pas grand chose. Cependant quand nous chargeons les données de notre table articles, nous obtenons des instances de cette classe.

Note

Si vous ne définissez pas de classe entity, CakePHP va utiliser la classe de base Entity.

Créer des Entities

Les Entities peuvent être instanciées directement:

use App\Model\Entity\Article;

$article = new Article();

Lorsque vous instanciez une entity, vous pouvez lui passer des champs avec les données que vous voulez y stocker:

use App\Model\Entity\Article;

$article = new Article([
    'id' => 1,
    'title' => 'Nouvel Article',
    'created' => new DateTime('now')
]);

Pour obtenir une entity vierge, le meilleur moyen est d’appeler newEmptyEntity() sur l’objet Table:

use Cake\ORM\Locator\LocatorAwareTrait;

$article = $this->fetchTable('Articles')->newEmptyEntity();

$article = $this->fetchTable('Articles')->newEntity([
    'id' => 1,
    'title' => 'New Article',
    'created' => new DateTime('now')
]);

$article sera une instance de App\Model\Entity\Article, ou une instance de la classe par défaut Cake\ORM\Entity si vous n’avez pas créé la classe Article.

Note

Avant CakePHP 4.3, il fallait utiliser $this->getTableLocator->get('Articles') pour obtenir une instance de la table.

Accéder aux Données de l’Entity

Les entities fournissent plusieurs façons d’accéder aux données qu’elles contiennent. La plupart du temps, vous accéderez aux données dans une entity en utilisant la notation objet:

use App\Model\Entity\Article;

$article = new Article;
$article->title = 'Ceci est mon premier post';
echo $article->title;

Vous pouvez aussi utiliser les méthodes get() et set().

Cake\ORM\Entity::set($field, $value = null, array $options = [])
Cake\ORM\Entity::get($field)

Par exemple:

$article->set('title', 'Ceci est mon premier post');
echo $article->get('title');

Quand vous utilisez set(), vous pouvez mettre à jour plusieurs champs en une seule fois en utilisant un tableau:

$article->set([
    'title' => 'Mon premier post',
    'body' => "C'est le meilleur de tous!"
]);

Avertissement

Lors de la mise à jour des entities avec des données requêtées, vous devriez faire une liste des champs qui peuvent être définis par assignement de masse.

Vous pouvez vérifier si des champs sont définis dans vos entities avec has():

$article = new Article([
    'title' => 'Premier post',
    'user_id' => null
]);
$article->has('title'); // true
$article->has('user_id'); // false
$article->has('undefined'); // false.

La méthode has() va renvoyer true si un champ est défini est a une valeur non null. Vous pouvez utiliser isEmpty() et hasValue() pour vérifier si un champ contient une valeur “non-empty”:

$article = new Article([
    'title' => 'Premier post',
    'user_id' => null
    'text' => '',
    'links' => []
]);
]);
$article->has('title'); // true
$article->isEmpty('title');  // false
$article->hasValue('title'); // true

$article->has('user_id'); // true
$article->isEmpty('user_id');  // true
$article->hasValue('user_id'); // false

$article->has('text'); // true
$article->isEmpty('text');  // true
$article->hasValue('text'); // false

$article->has('links'); // true
$article->isEmpty('links');  // true
$article->hasValue('links'); // false

$article->has('text'); // true
$article->isEmpty('text');  // true
$article->hasValue('text'); // false

$article->has('links'); // true
$article->isEmpty('links');  // true
$article->hasValue('links'); // false

Accesseurs & Mutateurs

En plus de l’interface simple get/set, les entities vous permettent de fournir des méthodes accesseurs et mutateurs. Avec ces méthodes, vous pouvez personnaliser la façon dont les champs sont lus ou définis.

Accesseurs

Les accesseurs personnalisent la façon dont les champs seront lus. Ils suivent la convention _get(NomDuChamp)(NomDuChamp) est la version CamelCase du nom du champ (les mots sont accollés avec une majuscule pour la première lettre de chacun).

Ils reçoivent la valeur basique stockée dans le tableau _fields pour seul argument. Par exemple:

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected function _getTitle($title)
    {
        return strtoupper($title);
    }
}

Cet exemple convertit en majuscules la valeur du champ title à chaque fois qu’il est lu. Il sera exécuté quand vous récupérerez le champ via une de ces deux manières:

echo $article->title; // renvoie FOO au lieu de foo
echo $article->get('title'); // renvoie FOO au lieu de foo

Note

Le code dans vos accesseurs est exécuté à chaque fois que vous faites référence au champ. Vous pouvez utiliser une variable locale de la façon suivante pour le mettre en cache si vous réalisez une opération gourmande en ressources: $maPropriete = $entity->ma_propriete.

Avertissement

Les accesseurs seront utilisés lors de la sauvegarde des entities. Faites donc attention lorsque vous définissez des méthodes qui formatent les données car ce sont ces données formatées qui seront sauvegardées.

Mutateurs

Avec les mutateurs, vous pouvez personnaliser la façon dont les champs seront écrits dans l’entity. Ils suivent la convention _set(NomDuChamp)(NomDuChamp) est la version CamelCase du nom du champ.

Les méthodes mutateurs doivent toujours retourner la valeur qui doit être stockée dans le champ. Vous pouvez aussi utiliser les mutateurs pour définir simultanément d’autres champs. Quand vous faites cela, soyez vigilant à ne pas introduire de boucles, car CakePHP n’empêchera pas les méthodes mutateurs de faire des boucles infinies. Par exemple:

namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\Utility\Text;

class Article extends Entity
{
    protected function _setTitle($title)
    {
        $this->slug = Text::slug($title);

        return strtouppercase($title);
    }

}

Cet exemple fait deux choses : il stocke une version modifiée de la valeur spécifiée dans le champ slug et stocke une version en majuscules dans le champ titre. Il sera executé lorsque vous définirez le champ via une de ces deux manières:

$user->title = 'foo' // définit le champ slug et stocke FOO au lieu de foo
$user->set('title', 'foo'); // définit le champ slug et stocke FOO au lieu de foo

Avertissement

Les accesseurs sont également appelés avant que l’entity ne soit persistée dans la base. Si vous souhaitez transformer un champ mais ne pas persister la transformation, il est recommandé d’utiliser les propriétés virtuelles car ces dernières ne seront pas persistées.

Créer des Champs Virtuels

En définissant des accesseurs, vous pouvez fournir un accès à des champs qui n’existent pas réellement. Par exemple si votre table users a des champs first_name et last_name, vous pouvez créer une méthode pour le nom complet:

namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
    protected function _getFullName()
    {
        return $this->first_name . '  ' . $this->last_name;
    }
}

Vous pouvez accéder aux champs virtuels comme s’ils existaient sur l’entity. Le nom du champ sera le nom de la méthode en minuscules, avec des underscores pour séparer les mots (full_name):

echo $user->full_name;
echo $user->get('full_name');

Souvenez-vous que les champs virtuels ne peuvent pas être utilisés dans les finds. Si vous voulez qu’ils fassent partie des données JSON ou dans des représentations en tableau de vos entités, reportez-vous à la section Montrer les Champs Virtuels.

Vérifier si une Entity a été Modifiée

Cake\ORM\Entity::dirty($field = null, $dirty = null)

Vous pourriez vouloir écrire du code conditionnel basé sur l’existence ou non de modifications dans l’entity. Par exemple, vous pourriez vouloir valider uniquement les champs lorsqu’ils ont été modifiés:

// Vérifie si le champ title n'a pas été modifié.
$article->isDirty('title');

Vous pouvez également marquer un champ comme ayant été modifié. C’est pratique lorsque vous ajoutez des données dans des champs contenant un tableau car sinon cela ne marque pas automatiquement le champ comme ayant été modifié, seule la redéfinition du tableau complet aurait cet effet:

// Ajoute un commentaire et marque le champ comme modifié.
$article->comments[] = $nouveauCommentaire;
$article->setDirty('comments', true);

De plus, vous pouvez également baser votre code conditionnel sur les valeurs initiales des champs en utilisant la méthode getOriginal(). Cette méthode retournera soit la valeur initiale de la propriété si elle a été modifiée soit la valeur actuelle.

Vous pouvez également vérifier si l’un quelconque des champs de l’entity a été modifié:

// Vérifier si l'entity a changé
$article->isDirty();

Pour retirer le marquage dirty (modifié) des champs d’une entity, vous pouvez utiliser la méthode clean():

$article->clean();

Lors de la création d’un nouvelle entity, vous pouvez empêcher les champs d’être marqués dirty en passant une option supplémentaire:

$article = new Article(['title' => 'Nouvel Article'], ['markClean' => true]);

Pour récupérer la liste des propriétés dirty d’une Entity, vous pouvez appeler:

$dirtyFields = $entity->getDirty();

Erreurs de Validation

Après avoir sauvegardé une entity, toute erreur de validation sera stockée sur l’entity elle-même. Vous pouvez accéder à toutes les erreurs de validation en utilisant les méthodes getErrors(), getError() ou hasErrors():

// Récupère toutes les erreurs
$errors = $user->getErrors();

// Récupère les erreurs pour un seul champ.
$errors = $user->getError('password');

// L'entity (ou une entity imbriquée) a-t-elle une erreur ?
$user->hasErrors();

// L'entity racine (uniquement) a-t-elle une erreur ?
$user->hasErrors(false);

Les méthodes setErrors() et setError() peuvent aussi être utilisées pour définir les erreurs sur une entity, ce qui facilite les tests du code qui fonctionne avec des messages d’erreur:

$user->setError('password', ['Le mot de passe est obligatoire.']);
$user->setErrors([
    'password' => ['Le mot de passe est obligatoire'],
    'username' => ['Le nom d\'utilisateur est obligatoire']
]);

Assignement de Masse

Bien que la définition en masse de champs des entities soit simple et pratique, elle peut créer d’importants problèmes de sécurité. Assigner en masse les données d’utilisateur à partir de la requête dans une entity permet à l’utilisateur de modifier n’importe quelles colonnes (voire toutes). Utiliser des classes entity anonymes ou créer des classes entity avec la commande Console Bake de CakePHP ne protège pas contre l’assignement en masse.

La propriété _accessible vous permet de fournir une liste des champs et d’indiquer s’ils peuvent être assignés en masse ou non. Les valeurs true et false indiquent si un champ peut ou ne peut pas être assigné massivement:

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected array $_accessible = [
        'title' => true,
        'body' => true
    ];
}

En plus des champs réels, il existe un champ spécial * qui définit le comportement par défaut si un champ n’est pas nommé spécifiquement:

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected array $_accessible = [
        'title' => true,
        'body' => true,
        '*' => false,
    ];
}

Note

Si la propriété * n’est pas définie, elle sera par défaut à false.

Éviter la Protection Contre l’Assignement de Masse

Lors de la création d’un nouvelle entity en utilisant le mot clé new, vous pouvez lui spécifier de ne pas se protéger contre l’assignement de masse:

use App\Model\Entity\Article;

$article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]);

Modifier les Champs Protégés à la Volée

Vous pouvez modifier à la volée la liste des champs protégés en utilisant la méthode setAccess():

// Rendre user_id accessible.
$article->setAccess('user_id', true);

// Rendre title protégé.
$article->setAccess('title', false);

Note

Modifier des champs accessibles agit seulement sur l’instance sur laquelle la méthode est appelée.

Lorsque vous utilisez les méthodes newEntity() et patchEntity() dans les objets Table vous pouvez également utiliser des options pour personnaliser la protection de masse. Référez-vous à la section Changer les Champs Accessibles pour plus d’information.

Outrepasser la Protection de Champ

Il arrive parfois que vous souhaitiez permettre un assignement en masse aux champs protégés:

$article->set($fields, ['guard' => false]);

En définissant l’option guard à false. vous pouvez ignorer la liste des champs accessibles pour un appel unique de set().

Vérifier si une Entity a été Sauvegardée

Il est souvent nécessaire de savoir si une entity représente une ligne qui est déjà présente en base de données. Pour cela, utilisez la méthode isNew():

if (!$article->isNew()) {
    echo 'Cette article a déjà été sauvegardé!';
}

Si vous êtes certains qu’une entity a déjà été sauvegardée, vous pouvez utiliser setNew():

$article->setNew(false);

$article->setNew(true);

Lazy Loading des Associations

Bien que la façon la plus efficace pour accéder à vos associations soit généralement de les charger en eager loading, il peut arriver que vous ayez besoin d’utiliser le lazy loading des données associées. Avant de voir comment utiliser le Lazy loading des associations, nous allons devoir parler des différences entre le chargement eager et lazy:

Eager loading

Le Eager loading utilise les joins (quand c’est possible) pour récupérer les données de la base de données avec aussi peu de requêtes que possible. Quand une requête séparée est nécessaire, comme dans le cas d’une association HasMany, une requête unique est émise pour récupérer toutes les données associées pour l’ensemble des objets à récupérer.

Lazy loading

Le Lazy loading retarde le chargement des données de l’association jusqu’à ce que ce soit absolument nécessaire. Si cela peut certes économiser du temps CPU car des données possiblement non utilisées ne sont pas hydratées dans les objets, cela peut aussi résulter en beaucoup plus de requêtes émises vers la base de données. Par exemple faire des boucles sur un ensemble d’articles et leurs commentaires va fréquemment émettre N requêtes, où N est le nombre d’articles itérés.

Bien que le lazy loading ne soit pas inclus dans l’ORM de CakePHP, vous pouvez tout simplement utiliser un des plugins de la communauté pour le faire. Nous recommandons le plugin LazyLoad

Après avoir ajouté le plugin à votre entity, vous pourrez faire ceci:

$article = $this->Articles->findById($id);

// La propriété commentaires a été chargée en lazy
foreach ($article->comments as $comment) {
    echo $comment->body;
}

Créer du Code Réutilisable avec les Traits

Vous pouvez vous retrouver dans un cas où vous avez besoin de la même logique dans plusieurs classes d’entity. Les traits de PHP sont parfaits pour cela. Vous pouvez mettre les traits de votre application dans src/Model/Entity. Par convention, les traits dans CakePHP sont suffixés avec Trait pour qu’ils soient discernables des classes ou des interfaces. Les traits sont souvent un bon allié des behaviors, vous permettant de fournir des fonctionnalités pour les objets table et entity.

Par exemple si nous avions un plugin SoftDeletable, il pourrait fournir un trait. Ce trait pourrait donner des méthodes pour marquer les entities comme “supprimées”, la méthode softDelete pouvant être fournie par un trait:

// SoftDelete/Model/Entity/SoftDeleteTrait.php

namespace SoftDelete\Model\Entity;

trait SoftDeleteTrait
{
    public function softDelete()
    {
        $this->set('deleted', true);
    }
}

Vous pourriez ensuite utiliser ce trait dans votre classe d’entity par une importation et une inclusion:

namespace App\Model\Entity;

use Cake\ORM\Entity;
use SoftDelete\Model\Entity\SoftDeleteTrait;

class Article extends Entity
{
    use SoftDeleteTrait;
}

Convertir en Tableaux/JSON

Lors de la construction d’APIs, il est probable que vous aurez fréquemment besoin de convertir des entities en tableaux ou en données JSON. CakePHP rend cela très simple:

// Obtenir un tableau.
// Les associations seront aussi converties par toArray().
$array = $user->toArray();

// Convertir en JSON
// Les associations seront aussi converties avec le hook jsonSerialize.
$json = json_encode($user);

Lors de la conversion d’une entity en JSON, les listes de champs virtuels & cachés sont utilisées. Les entities sont aussi converties récursivement en JSON. Cela signifie que si les entities et leurs associations sont chargées en eager loading, CakePHP va gérer correctement la conversion des données associées dans le bon format.

Montrer les Champs Virtuels

Par défaut, les champs virtuels ne sont pas exportés lors de la conversion des entities en tableaux ou en JSON. Pour exposer les champs virtuels, vous devez les rendre visibles. Lors de la définition de votre classe entity, vous pouvez fournir une liste de champs virtuels qui doivent être exposés:

namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
    protected $_virtual = ['full_name'];
}

Cette liste peut être modifiée à la volée en utilisant la méthode setVirtual:

$user->setVirtual(['full_name', 'is_admin']);

Cacher les Champs

Il arrive souvent que vous ne souhaitiez pas exporter certains champs dans des formats JSON ou en tableau. Par exemple il est souvent mal avisé de montrer les hashs de mot de passe ou les questions de récupération du compte. Lors de la définition d’une classe entity, définissez quels champs doivent être cachés:

namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
    protected $_hidden = ['password'];
}

Cette liste peut être modifiée à la volée en utilisant la méthode setHidden:

$user->setHidden(['password', 'recovery_question']);

Stocker des Types Complexes

Les accesseurs et mutateurs n’ont pas pour objectif de contenir de la logique pour sérialiser et desérialiser les données complexes venant de la base de données. Consultez la section Sauvegarder les Types Complexes pour comprendre la façon dont votre application peut stocker des types de données complexes comme les tableaux et les objets.