Dernièrement, j’ai eu à travailler sur un projet dont une des conditions du cahier des charges était de ne pas effacer les données présentes dans la base de données (excepté pour quelques tables…). À la place, le système devait changer l’état d’un champ de l’enregistrement qui indiquait au système qu’il ne devait plus le prendre en compte (pour info, le nom de ce champ était is_activated).

Première possibilité: modifier le code des contrôleurs Après avoir mûrement réfléchi pendant au moins 10 secondes… J’ai exclu l’approche qui consiste à modifier la logique de toutes les actions delete de mes contrôleurs.

Voici un exemple de ce qu’il faut éviter de faire:

1
2
3
4
5
6
7
8
9
10
11
public function delete($id = null) {
    $this->MyModel->id = $id;

    if (!$this->MyModel->exists()) {
        throw new NotFoundException(__('Invalid myModel'));
    }

    $this->request->onlyAllow('post', 'delete');

    $this->MyModel->saveField( 'is_activated', 0 );
}

Pourquoi?

  • J’avais un gros paquet de contrôleurs et je ne me voyais pas tous les modifier à la main (je les avais bien entendu créés avec l’outil cake bake).
  • De plus, cette logique d’utiliser un champ status en lieu et place d’un vrai effacement de données relève plus de la logique de données que de la logique de l’application. Le second point implique que mon code gérant l’effacement de données ne devrait pas se trouver dans le contrôleur, mais plutôt dans le modèle. Ceci nous amène à la deuxième possibilité…

Deuxième possibilité: utiliser un behavior

L’utilisation d’un behavior est la première solution à laquelle j’ai pensée… Je pensais vraiment que c’était une solution élégante. Pour rappel, un behavior permet d’attacher un comportement à certains modèles. Par exemple, dans ce cas-ci, mon idée était de réécrire la méthode callback beforeSave de la manière suivante:

1
2
3
4
5
6
7
public function beforeDelete( Model $Model, $cascade = true ) {
    $Model->save( array(
        'is_activated' => false,
    ) );

    return false;
}

Cette solution n’était malheuresement pas idéale pour plusieurs raisons:

  • Il faut modifier tous les modèles qui vont utiliser ce behavior (public $actsAs) et il y en a un paquet également!
  • La méthode beforeDelete doit renvoyer false, sinon CakePHP efface l’enregistrement. c’est là que le bât blesse, car cela implique en plus de devoir modifier toutes les actions delete de tous mes contrôleurs! (Beh oui, mes actions delete vérifient que l’effacement s’est produit sans heurt, et pour cela elles vérifient que la méthode delete du modèle renvoie bien true…).

Pour une solution élégante, elle me donne encore plus de boulot que la précédente possibilité!

Troisième possibilité: redéfinir la méthode delete dans AppModel

Je suis finalement arrivé à la conclusion que la meilleure méthode pour implémenter un soft delete avec CakePHP est de redéfinir la méthode AppModel::delete(), et voici ma solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function delete($id = null, $cascade = true) {
    // the table has no field `is_activated` so we use the normal delete method from the parent
    if( !isset( $this->_schema['is_activated'] ) ) {
        return parent::delete( $id, $cascade );
    }

    // there is a field `is_activated`, so update it to `0`

    //if $id is given as parameter of the function, set it to the current model
    if( $id != null ) {
        $this->id = $id;
    }
    return $this->saveField( 'is_activated', 0 );
}

Les explications sont en anglais dans le code, mais pour les moins anglophiles d’entre-nous, voici l’explication:

  1. on vérifie d’abord que le modèle sur lequel on travaille possède bien un champ appelé is_activated
  2. si ce n’est pas le cas, cela signifie que la table en cours n’a pas besoin de soft delete. On exécute dans ce cas l’effacement classique en appelant la méthode delete de la classe parent et on renvoie son résultat
  3. par contre, si le modèle en cours possède un champ dont le nom est is_activated, dans ce cas on met simplement sa valeur à jour et on renvoie le résultat de l’appel à la méthode saveField

Cette solution n’est peut-être pas la meilleure (par exemple, que se passe-t-il dans le cas d’une relation HABTM, ou plus communément appelé “Many to Many” ?); Mais dans mon cas, c’était, je pense, la solution la plus appropriée pour la gestion du delete.

Quelques nouvelles

Bien…. Ca fait plus de 5 mois que je n’ai plus rien posté sur ce blog, la honte! Pourtant les idées se bousculent au portillon…## Ce qu …… Continuer la lecture