2015-08-02 4 views
3

J'utilise Propel 1 dans un projet assez important, et la version live utilise actuellement le comportement Archivable. Ainsi, lorsqu'une ligne est supprimée, le comportement intercepte l'appel de façon transparente et déplace la ligne dans une table d'archivage. Cela fonctionne bien.Existe-t-il un moyen de combiner les comportements Archivable et Versionable dans Propel 1?

Je cherche à changer le fonctionnement de cette table afin que toutes les sauvegardes soient versionnées. Sur une branche de fonctionnalité, j'ai donc supprimé le Archivable et ajouté le comportement Versionable. Cela supprime la table générée automatiquement (table)_archive et ajoute une table (table)_version à la place.

Cependant, il est intéressant de noter que la table de version a un PK de (id, version) avec une clé étrangère à la table en direct de id à id. Cela signifie que les versions ne peuvent pas exister sans une ligne live, ce qui n'est pas ce que je veux: je veux pouvoir supprimer une ligne et conserver les versions. Je pensais que ce comportement agirait comme Archivable, c'est-à-dire que la méthode delete() serait interceptée et modifiée à partir de son approche habituelle. Malheureusement, comme l'a confirmé par the documentation, cette méthode supprime la ligne en direct et toutes les versions précédentes:

void delete(): Supprime l'historique de version d'objet

J'ai essayé le mélange à la fois Archivable et Versionable, mais cela semble pour générer du code qui se bloque dans l'API Query: il essaie d'appeler une méthode archive() qui n'existe pas. Je m'attends à ce que ce mélange de comportement n'ait jamais été conçu pour fonctionner (idéalement, il devrait être pris au moment de la construction du schéma, et peut-être que cela sera corrigé dans Propel 2).

Une solution consiste à essayer le comportement SoftDelete au lieu de Archivable - cela marque simplement les enregistrements comme supprimés plutôt que de les déplacer vers une autre table. Cependant, cela peut être problématique, car se joindre à une table avec ce comportement peut donner des comptes erronés pour les lignes non supprimées (et l'équipe Propel a décidé de la déprécier pour cette raison). Cela ressemble aussi à un trou de lapin que je ne veux pas descendre, car la quantité de refactoring peut devenir hors de contrôle.

Ainsi, il me reste à chercher une meilleure approche pour implémenter un système de versionnage qui ne supprime pas les anciennes versions lorsque la copie en direct est supprimée. Je peux le faire manuellement en intercepter les méthodes de sauvegarde et de suppression dans la classe du modèle, mais cela semble un gâchis quand Versionable fait presque ce que je veux. Y a-t-il des paramètres pertinents que je peux modifier, ou y a-t-il de la valeur dans l'écriture d'un comportement personnalisé? Un rapide coup d'oeil sur le code de génération de modèles pour les comportements de base me donne envie de fuir le dernier!

+0

Hi. Cela vous dérangerait-il de partager votre solution? – sanders

Répondre

1

Voici la solution que j'ai trouvée. Ma mémoire est plutôt floue mais il semble que j'ai pris le VersionableBehaviour existant et en ai dérivé un nouveau comportement, que j'ai appelé HistoryVersionableBehaviour. Il utilise donc toutes les fonctionnalités du comportement du noyau et remplace simplement la suppression générée par son propre code.

Voici le comportement lui-même:

<?php 

// This is how the versionable behaviour works 
require_once dirname(__FILE__) . '/HistoryVersionableBehaviorObjectBuilderModifier.php'; 

class HistoryVersionableBehavior extends VersionableBehavior 
{ 
    /** 
    * Reset the FKs from CASCADE ON DELETE to no action 
    * 
    * (I expect all future migration diffs will incorrectly try to re-add the constraint 
    * I manually removed from the migration that introduced versioning, may try to fix 
    * that another time. 'Tis fine for now). 
    */ 
    public function addVersionTable() 
    { 
     parent::addVersionTable(); 

     $this->swapAllForeignKeysToNoDeleteAction(); 
     $this->addVersionArchivedColumn(); 
    } 

    protected function swapAllForeignKeysToNoDeleteAction() 
    { 
     $versionTable = $this->lookupVersionTable(); 
     $fks = $versionTable->getForeignKeys(); 
     foreach ($fks as $fk) 
     { 
      $fk->setOnDelete(null); 
     } 
    } 

    protected function addVersionArchivedColumn() 
    { 
     $versionTable = $this->lookupVersionTable(); 
     $versionTable->addColumn(array(
      'name' => 'archived_at', 
      'type' => 'timestamp', 
     )); 
    } 

    protected function lookupVersionTable() 
    { 
     $table = $this->getTable(); 
     $versionTableName = $this->getParameter('version_table') ? 
      $this->getParameter('version_table') : 
      ($table->getName() . '_version'); 
     $database = $table->getDatabase(); 

     return $database->getTable($versionTableName); 
    } 

    /** 
    * Point to the custom object builder class 
    * 
    * @return HistoryVersionableBehaviorObjectBuilderModifier 
    */ 
    public function getObjectBuilderModifier() 
    { 
     if (is_null($this->objectBuilderModifier)) { 
      $this->objectBuilderModifier = new HistoryVersionableBehaviorObjectBuilderModifier($this); 
     } 

     return $this->objectBuilderModifier; 
    } 
} 

Ce besoin de quelque chose appelé un modificateur qui est exécuté au moment de la génération pour produire les classes d'instance de base:

<?php 

class HistoryVersionableBehaviorObjectBuilderModifier extends \VersionableBehaviorObjectBuilderModifier 
{ 
    /** 
    * Don't do any version deletion after the main deletion 
    * 
    * @param \PHP5ObjectBuilder $builder 
    */ 
    public function postDelete(\PHP5ObjectBuilder $builder) 
    { 
     $this->builder = $builder; 
     $script = "// Look up the latest version 
\$latestVersion = {$this->getVersionQueryClassName()}::create()-> 
    filterBy{$this->table->getPhpName()}(\$this)-> 
    orderByVersion(\Criteria::DESC)-> 
    findOne(\$con); 
\$latestVersion-> 
    setArchivedAt(time())-> 
    save(\$con); 
     "; 

     return $script; 
    } 
} 

La classe parent a 798 lignes , donc mon approche semble avoir sauvé beaucoup de code, en construisant tout à partir de zéro!

Vous devez spécifier le comportement dans votre fichier XML pour chaque table que vous souhaitez activer pour:

<table name="job"> 
    <!--- your columns... --> 
    <behavior name="timestampable" /> 
    <behavior name="history_versionable" /> 
</table> 

Je ne suis pas sûr que mon comportement nécessite la présence du comportement timestampable - je pense est non, car il semble que le comportement parent ajoute simplement des colonnes à la table versionnée et non à la table elle-même. Si vous êtes en mesure d'essayer cela sans le comportement timestampable faites-moi savoir comment vous allez, afin que je puisse mettre à jour ce post.

Enfin, vous devrez spécifier l'emplacement de votre classe pour que l'autochargeur personnalisé Propel 1 sache où le trouver. Je l'utilise dans mon build.properties:

# Declare a custom behaviour 
propel.behavior.history_versionable.class = ${propel.php.dir}.WebScraper.Behaviours.HistoryVersionable.HistoryVersionableBehavior