Changeset 7387
- Timestamp:
- 02/06/08 22:18:40 (10 months ago)
- Files:
-
- plugins/sfPropelVersionableBehaviorPlugin/trunk/README (modified) (7 diffs)
- plugins/sfPropelVersionableBehaviorPlugin/trunk/config/config.php (modified) (1 diff)
- plugins/sfPropelVersionableBehaviorPlugin/trunk/config/schema.yml (modified) (1 diff)
- plugins/sfPropelVersionableBehaviorPlugin/trunk/lib/model/ResourceVersion.php (modified) (1 diff)
- plugins/sfPropelVersionableBehaviorPlugin/trunk/lib/sfPropelVersionableBehavior.class.php (modified) (11 diffs)
- plugins/sfPropelVersionableBehaviorPlugin/trunk/lib/sfPropelVersionableBehaviorToolkit.class.php (deleted)
- plugins/sfPropelVersionableBehaviorPlugin/trunk/package.xml (modified) (1 diff)
- plugins/sfPropelVersionableBehaviorPlugin/trunk/test/unit/PropelVersionableBehaviorTest.php (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
plugins/sfPropelVersionableBehaviorPlugin/trunk/README
r7371 r7387 5 5 == Features == 6 6 7 * Revert objects to previous versions easily 8 * Track and browse history of modifications on every object 9 * Conditional versioning 7 10 * Fully unit tested 8 * Revert objects to previous versions easily9 * Conditional versioning10 11 11 12 == Installation == … … 14 15 15 16 {{{ 16 symfony plugin-install http://plugins.symfony-project.com/sfPropelVersionableBehaviorPlugin 17 #!sh 18 > php symfony plugin-install http://plugins.symfony-project.com/sfPropelVersionableBehaviorPlugin 17 19 }}} 18 20 … … 23 25 }}} 24 26 25 * Add necessary fields to your each of yourmodel tables that you want to make versionable:27 * Add a `version` field to each of the model tables that you want to make versionable: 26 28 27 29 {{{ 28 30 #!xml 29 31 <!-- schema.xml --> 30 <column name="uuid" type="CHAR" size="36" required="true" /> 31 <column name="version" type="INTEGER" size="11" required="true" /> 32 <column name="version" type="INTEGER" required="true" /> 32 33 or 33 34 <!-- schema.yml --> 34 uuid: { type: char, size: 36, required: true } 35 version: { type: integer, size: 11, required: true } 36 }}} 37 38 * Rebuild your database and model (the plugin uses two tables to store object version history) 35 version: { type: integer, required: true } 36 }}} 37 38 Alternatively, you can choose another name that `version` and declare it in the behavior initialization. Even though, the behavior will still provide a `getVersion` and `setVersion` method for your versionable models. 39 40 * Rebuild your model and sql, insert the plugin tables to your database, and the new version column to your versionable tables: 39 41 40 42 {{{ 41 43 #!sh 42 > php symfony propel-build-all 44 > php symfony propel-build-model 45 > php symfony propel-build-sql 46 > mysql -uroot -p mydb < data/sql/plugins.sfPropelVersionableBehaviorPlugin.lib.model.schema.sql 47 > mysql -uroot -p mydb -e 'ALTER TABLE `Article` ADD `version` INTEGER NOT NULL;' 43 48 }}} 44 49 … … 53 58 } 54 59 55 $columns_map = array('uuid' => ArticlePeer::UUID, 56 'version' => ArticlePeer::VERSION); 57 58 sfPropelBehavior::add('Article', array('versionable' => array('columns' => $columns_map))); 60 sfPropelBehavior::add('Article', array('versionable')); 59 61 }}} 60 62 61 Column map values mean: 62 * uuid : Model column holding resource's universally unique identifier (behavior takes care of generating these) 63 * version : Model column holding resource's current version number 63 If the model uses a version column name diffeent than `version`, declare it here as the 'version' parameter of the behavior initialization: 64 65 {{{ 66 #!php 67 sfPropelBehavior::add('Article', array('versionable' => array('columns' => array('version' => 'my_version_column')))); 68 }}} 69 70 * Clear the cache 71 72 {{{ 73 #!sh 74 > php symfony cc 75 }}} 64 76 65 77 == Usage == … … 88 100 <?php 89 101 90 foreach ($article->getAllVersions() as $ version)102 foreach ($article->getAllVersions() as $history_article) 91 103 { 92 $history_article = $version->getResourceInstance();93 104 echo sprintf("Version %d title : %s\n", 94 $history_article->getVersion(), 95 $history_article->getTitle()); 105 $history_article->getVersion(), 106 $history_article->getTitle() 107 ); 96 108 } 97 109 … … 162 174 Enabling the behaviors adds / modifies the following method to the Propel objects : 163 175 164 * `void save()`: Adds a new version to resource's version history 165 * `void delete()`: Deletes resource's version history 166 * `void toVersion(integer $version_number)`: Populates resource properties with values from the requested version 167 * `ResourceVersion getLastVersion()`: Returns resource's last version 168 * `array getAllVersions()`: Returns all resource versions in an array 176 * `void save()`: Adds a new version to the object version history and increments the `version` property 177 * `void delete()`: Deletes the object version history 178 * `void toVersion(integer $version_number)`: Populates the properties of the current object with values from the requested version. Beware that saving the object afterwards will create a new version (and not update the previous version). 179 * `array getAllVersions()`: Returns all versions of the object in an ordered array 180 * `ResourceVersion getLastResourceVersion()`: Returns the object's last version object 181 * `array getAllResourceVersions()`: Returns all version objects in an array 169 182 170 183 === !ResourceVersion API === … … 183 196 === 0.3 === 184 197 185 * Add unit tests to check version_attributes deletion186 198 * Make plugin compatible with sfPropel's i18n capabilities 187 * Replace uuid by a simple composite key class name + id199 * Change the calls to `getPrimaryKey` by calls to a [wiki:sfPropelActAsRatableBehaviorPlugin]-style `getReferenceKey` to allow extending objects with multiple primary keys. 188 200 189 201 == Changelog == 190 202 191 === Trunk === 203 === 2008-02-06 | Trunk === 204 205 * francois: More explicit documentation on installation 206 * francois: Added a few unit tests 207 * francois: Added a `getAllVersions` method returning an array of origin objects in a single query 208 * francois: [BC Break] Renamed `getAllVersions` to `getAllResourceVersions` 209 * francois: [BC Break] Renamed `getLastVersions` to `getLastResourceVersion` 210 * francois: [BC Break] Replaced uuid by a simple composite key class name + id 192 211 193 212 === 2008-02-06 | 0.2.2 alpha === plugins/sfPropelVersionableBehaviorPlugin/trunk/config/config.php
r3485 r7387 26 26 array ( 27 27 'sfPropelVersionableBehavior', 28 'get LastVersion'28 'getAllResourceVersions' 29 29 ), 30 30 array ( 31 31 'sfPropelVersionableBehavior', 32 'setUuid' 33 ), 34 array ( 35 'sfPropelVersionableBehavior', 36 'getUuid' 32 'getLastResourceVersion' 37 33 ), 38 34 array ( plugins/sfPropelVersionableBehaviorPlugin/trunk/config/schema.yml
r7368 r7387 1 1 propel: 2 2 _attributes: { package: plugins.sfPropelVersionableBehaviorPlugin.lib.model } 3 3 4 resource_version: 4 5 id: 5 6 number: { type: integer, size: 11, required: true, primaryKey: true } 6 resource_ uuid: { type: char, size: 36, required: true, index: true }7 resource_id: { type: integer, required: true } 7 8 resource_name: { type: varchar, size: 255, required: true } 9 _indexes: 10 resource_version_index: [resource_id, resource_name] 11 8 12 resource_attribute_version: 9 13 id: plugins/sfPropelVersionableBehaviorPlugin/trunk/lib/model/ResourceVersion.php
r3485 r7387 24 24 public function populateFromObject(BaseObject $resource) 25 25 { 26 $this->setResource Uuid($resource->getUuid());26 $this->setResourceId($resource->getPrimaryKey()); 27 27 $this->setResourceName(get_class($resource)); 28 28 $this->setNumber($resource->getVersion()); plugins/sfPropelVersionableBehaviorPlugin/trunk/lib/sfPropelVersionableBehavior.class.php
r7368 r7387 15 15 * 16 16 * <code> 17 * $columns_map = array('uuid' => MyClassPeer::UUID, 18 * 'version' => MyClassPeer::VERSION); 17 * $columns_map = array('version' => MyClassPeer::VERSION); 19 18 * 20 19 * sfPropelBehavior::add('MyClass', array('versionable' => array('columns' => $columns_map))); … … 23 22 * Column map values signification : 24 23 * 25 * - uuid : Model column holding resource's universally unique identifier (behavior takes care of generating these)26 24 * - version : Model column holding resource's current version number 27 25 * 28 26 * @author Tristan Rivoallan <tristan@rivoallan.net> 27 * @author Francois Zaninotto <francois.zaninotto@symfony-project.com> 29 28 */ 30 29 class sfPropelVersionableBehavior … … 50 49 { 51 50 $c = new Criteria(); 52 $c->add(ResourceVersionPeer::RESOURCE_UUID, $resource->getUuid()); 51 $c->add(ResourceVersionPeer::RESOURCE_ID, $resource->getPrimaryKey()); 52 $c->add(ResourceVersionPeer::RESOURCE_NAME, get_class($resource)); 53 53 $c->add(ResourceVersionPeer::NUMBER, $version_num); 54 54 $version = ResourceVersionPeer::doSelectOne($c); … … 56 56 if (is_null($version)) 57 57 { 58 $msg = sprintf('Resource "%s" has no version "%d"', $resource->get Uuid(), $version_num);58 $msg = sprintf('Resource "%s" has no version "%d"', $resource->getPrimaryKey(), $version_num); 59 59 throw new Exception($msg); 60 60 } 61 61 62 62 $resource = self::populateResourceFromVersion($resource, $version); 63 64 63 } 65 64 … … 70 69 * @return ResourceVersion 71 70 */ 72 public function getLastVersion(BaseObject $resource) 73 { 74 $c = new Criteria(); 75 $c->add(ResourceVersionPeer::RESOURCE_UUID, $resource->getUuid()); 71 public function getLastResourceVersion(BaseObject $resource) 72 { 73 $c = new Criteria(); 74 $c->add(ResourceVersionPeer::RESOURCE_ID, $resource->getPrimaryKey()); 75 $c->add(ResourceVersionPeer::RESOURCE_NAME, get_class($resource)); 76 76 $c->addDescendingOrderByColumn(ResourceVersionPeer::NUMBER); 77 77 78 return ResourceVersionPeer::doSelectOne($c); 78 79 } 79 80 80 81 /** 81 * Returns all versions of resource. 82 * 83 * @param BaseObject $resource 84 * @return array 82 * Returns all ResourceVersion instances related to the object, ordered by version asc. 83 * 84 * @param BaseObject $resource 85 * @return array List of ResourceVersion objects 86 */ 87 public function getAllResourceVersions(BaseObject $resource) 88 { 89 $c = new Criteria(); 90 $c->add(ResourceVersionPeer::RESOURCE_ID, $resource->getPrimaryKey()); 91 $c->add(ResourceVersionPeer::RESOURCE_NAME, get_class($resource)); 92 $c->addAscendingOrderByColumn(ResourceVersionPeer::NUMBER); 93 94 return ResourceVersionPeer::doSelect($c); 95 } 96 97 /** 98 * Returns all ResourceVersion instances related to the object, ordered by version asc. 99 * 100 * @param BaseObject $resource 101 * @return array List of BaseObject objects 85 102 */ 86 103 public function getAllVersions(BaseObject $resource) 87 104 { 88 105 $c = new Criteria(); 89 $c->add(ResourceVersionPeer::RESOURCE_UUID, $resource->getUuid()); 90 return ResourceVersionPeer::doSelect($c); 106 $c->add(ResourceVersionPeer::RESOURCE_ID, $resource->getPrimaryKey()); 107 $c->add(ResourceVersionPeer::RESOURCE_NAME, get_class($resource)); 108 $c->addAscendingOrderByColumn(ResourceVersionPeer::NUMBER); 109 $c->addJoin(ResourceAttributeVersionPeer::RESOURCE_VERSION_ID, ResourceVersionPeer::ID); 110 $attributes = ResourceAttributeVersionPeer::doSelect($c); 111 112 $objects = array(); 113 $object = null; 114 $class= get_class($resource); 115 $current_id = null; 116 foreach($attributes as $attribute) 117 { 118 if($attribute->getResourceVersionId() != $current_id) 119 { 120 if($object) 121 { 122 $objects[]= $object; 123 } 124 $current_id = $attribute->getResourceVersionId(); 125 $object = new $class; 126 } 127 $attrib_name = $attribute->getAttributeName(); 128 $setter = sprintf('set%s', $attrib_name); 129 130 if (!method_exists($resource, $setter)) 131 { 132 $msg = sprintf('Impossible to set attribute "%s" on resource "%s"', 133 $attrib_name, get_class($resource)); 134 throw new Exception($msg); 135 } 136 $object->$setter($attribute->getAttributeValue()); 137 138 } 139 $objects[] = $object; 140 141 return $objects; 91 142 } 92 143 … … 117 168 } 118 169 119 /**120 * Returns resource UUID. Proxy method to real getter.121 *122 * @param BaseObject $resource123 * @return string124 */125 public function getUuid(BaseObject $resource)126 {127 $getter = self::forgeMethodName($resource, 'get', 'uuid');128 return $resource->$getter();129 }130 131 /**132 * Sets resource UUID. Proxy method to real setter.133 *134 * @param BaseObject $resource135 * @param string $uuid136 */137 public function setUuid(BaseObject $resource, $uuid)138 {139 $setter = self::forgeMethodName($resource, 'set', 'uuid');140 return $resource->$setter($uuid);141 }142 143 170 # ---- HOOKS 144 171 145 172 /** 146 * This hook is called before object is saved. It takes care of generating a new UUID if necessary.173 * This hook is called before object is saved. 147 174 * 148 175 * @param BaseObject $resource … … 152 179 if (self::versionConditionMet($resource)) 153 180 { 154 if ($resource->isNew()) 155 { 156 $resource->setUuid(sfPropelVersionableBehaviorToolkit::generateUuid()); 157 } 158 159 if ($version = $resource->getLastVersion()) 181 if ($version = $resource->getLastResourceVersion()) 160 182 { 161 183 $resource->setVersion($version->getNumber() + 1); … … 180 202 $version->populateFromObject($resource); 181 203 $version->setNumber($resource->getVersion()); 182 $version->setResourceUuid($resource->getUuid());183 204 $version->save(); 184 205 } … … 191 212 { 192 213 $c = new Criteria(); 193 $c->add(ResourceVersionPeer::RESOURCE_UUID, $resource->getUuid()); 214 $c->add(ResourceVersionPeer::RESOURCE_ID, $resource->getPrimaryKey()); 215 $c->add(ResourceVersionPeer::RESOURCE_NAME, get_class($resource)); 194 216 ResourceVersionPeer::doDelete($c); 195 217 } … … 213 235 if (!method_exists($resource, $setter)) 214 236 { 215 $msg = sprint ('Impossible to set attribute "%s" on resource "%s"',237 $msg = sprintf('Impossible to set attribute "%s" on resource "%s"', 216 238 $attrib_name, get_class($resource)); 217 239 throw new Exception($msg); … … 229 251 * @param BaseObject $resource 230 252 * @param string $prefix Usually 'get' or 'set' 231 * @param string $column uuid|version253 * @param string $column version 232 254 */ 233 255 private static function forgeMethodName($resource, $prefix, $column) plugins/sfPropelVersionableBehaviorPlugin/trunk/package.xml
r7371 r7387 41 41 </dir> 42 42 <file name="sfPropelVersionableBehavior.class.php" role="data"/> 43 <file name="sfPropelVersionableBehaviorToolkit.class.php" role="data"/>44 43 </dir> 45 44 </dir> plugins/sfPropelVersionableBehaviorPlugin/trunk/test/unit/PropelVersionableBehaviorTest.php
r7368 r7387 45 45 sfPropelVersionableBehaviorPlugin: 46 46 test_class: Article 47 test_uuid_column: uuid48 47 test_version_column: version 49 48 test_title_column: title … … 55 54 */ 56 55 $test_class = sfConfig::get('app_sfPropelVersionableBehaviorPlugin_test_class', 'Post'); 57 $test_class_uuid_column = sfConfig::get('app_sfPropelVersionableBehaviorPlugin_test_uuid_column', 'uuid');58 56 $test_class_version_column = sfConfig::get('app_sfPropelVersionableBehaviorPlugin_test_version_column', 'version'); 59 57 $test_class_title_column = sfConfig::get('app_sfPropelVersionableBehaviorPlugin_test_title_column', 'title'); … … 72 70 // cleanup database 73 71 call_user_func(array(_create_resource()->getPeer(), 'doDeleteAll')); 72 ResourceAttributeVersionPeer::doDeleteAll(); 74 73 ResourceVersionPeer::doDeleteAll(); 75 74 76 75 // register behavior on test object 77 76 sfPropelBehavior::add($test_class, array('versionable' => array( 78 'uuid' => $test_class_uuid_column,79 77 'version' => $test_class_version_column 80 78 ))); 81 79 82 $t = new lime_test( 19, new lime_output_color());80 $t = new lime_test(26, new lime_output_color()); 83 81 84 82 // save() … … 88 86 $r->setByName($test_class_title_column, 'V1', BasePeer::TYPE_FIELDNAME); 89 87 $r->save(); 90 91 $t->isnt($r->getByName($test_class_uuid_column, BasePeer::TYPE_FIELDNAME), null, 'save() generates a universal unique id for new resources'); 92 93 $uuid = $r->getByName($test_class_uuid_column, BasePeer::TYPE_FIELDNAME); 94 $r->setByName($test_class_title_column, 'V2', BasePeer::TYPE_FIELDNAME); 95 $r->save(); 96 97 $t->is($r->getByName($test_class_uuid_column, BasePeer::TYPE_FIELDNAME), $uuid, 'save() does not generate a new universal unique id for existing resources'); 88 $t->is($r->getByName($test_class_version_column, BasePeer::TYPE_FIELDNAME), 1, 'save() initializes the version number to 1 for new objects'); 98 89 99 90 $c = new Criteria(); 100 $c->add(ResourceVersionPeer::RESOURCE_UUID, $r->getByName($test_class_uuid_column, BasePeer::TYPE_FIELDNAME)); 91 $c->add(ResourceVersionPeer::RESOURCE_ID, $r->getPrimaryKey()); 92 $c->add(ResourceVersionPeer::RESOURCE_NAME, get_class($r)); 101 93 $c->add(ResourceVersionPeer::NUMBER, $r->getByName($test_class_version_column, BasePeer::TYPE_FIELDNAME)); 102 94 $version = ResourceVersionPeer::doSelectOne($c); … … 107 99 $t->is($attrib_version->getAttributeValue(), $r->$getter(), 'save() creates a new version of resource in database with appropriate parameters'); 108 100 } 109 110 // getLastVersion() 111 $t->diag('getLastVersion()'); 112 113 $t->is($r->getLastVersion()->getResourceInstance()->getByName($test_class_title_column, BasePeer::TYPE_FIELDNAME), 'V2', 'getLastVersion() returns last version of resource'); 101 $r->setByName($test_class_title_column, 'V2', BasePeer::TYPE_FIELDNAME); 102 $r->save(); 103 $t->is($r->getByName($test_class_version_column, BasePeer::TYPE_FIELDNAME), 2, 'save() increments the version number'); 104 105 // getLastResourceVersion() 106 $t->diag('getLastResourceVersion()'); 107 108 $t->is($r->getLastResourceVersion()->getResourceInstance()->getByName($test_class_title_column, BasePeer::TYPE_FIELDNAME), 'V2', 'getLastVersion() returns last version of resource'); 114 109 115 110 $r->setByName($test_class_title_column, 'do not version me', BasePeer::TYPE_FIELDNAME); … … 121 116 122 117 $r->toVersion(1); 118 $t->is($r->getByName($test_class_version_column, BasePeer::TYPE_FIELDNAME), 1, 'toVersion() sets resource version to appropriate values'); 123 119 $t->is($r->getByName($test_class_title_column, BasePeer::TYPE_FIELDNAME), 'V1', 'toVersion() sets resource attributes to appropriate values'); 124 120 $r->save(); … … 134 130 } 135 131 132 // getAllResourceVersions() 133 $t->diag('getAllResourceVersions()'); 134 135 $r->setByName($test_class_title_column, 'V4', BasePeer::TYPE_FIELDNAME); 136 $r->save(); 137 $all_versions = $r->getAllResourceVersions(); 138 $target_versions = array('V1', 'V2', 'V1', 'V4'); 139 $t->is(count($all_versions), 4, 'getAllResourceVersions() returns right count of versions'); 140 $versions_titles = array(); 141 foreach($all_versions as $v) 142 { 143 $versions_titles[] = $v->getResourceInstance()->getByName($test_class_title_column, BasePeer::TYPE_FIELDNAME); 144 } 145 $t->is($versions_titles, $target_versions, 'getAllResourceVersions() returns the right versions'); 146 136 147 // getAllVersions() 137 148 $t->diag('getAllVersions()'); 138 139 $r->setByName($test_class_title_column, 'V4', BasePeer::TYPE_FIELDNAME); 140 $r->save(); 141 $all_versions = $r->getAllVersions(); 142 $target_versions = array('V1', 'V2', 'V1', 'V4'); 143 $t->diag('getAllVersions()'); 144 $t->is(count($all_versions), 4, 'getAllVersions() returns right count of versions'); 149 $all_object_versions = $r->getAllVersions(); 150 $t->is(count($all_object_versions), 4, 'getAllVersions() returns right count of objects'); 145 151 $versions_titles = array(); 146 foreach($all_versions as $v) 147 { 148 $versions_titles[] = $v->getResourceInstance()->getByName($test_class_title_column, BasePeer::TYPE_FIELDNAME); 152 $versions_versions = array(); 153 foreach($all_object_versions as $obj) 154 { 155 $versions_titles[] = $obj->getByName($test_class_title_column, BasePeer::TYPE_FIELDNAME); 156 $versions_versions[] = $obj->getVersion(); 149 157 } 150 158 $t->is($versions_titles, $target_versions, 'getAllVersions() returns the right versions'); 159 $t->is($versions_versions, array(1, 2, 3, 4), 'getAllVersions() returns the array of ordered versions'); 151 160 152 161 // delete() 153 162 $t->diag('delete()'); 154 163 $versions = $r->getAllResourceVersions(); 155 164 $r->delete(); 156 $t->is($r->getAllVersions(), null, 'delete() also deletes resource version history'); 165 $t->is($r->getAllResourceVersions(), null, 'delete() also deletes resource version history'); 166 foreach($versions as $version) 167 { 168 // These verison objects now have no counterpart in database, but they are a convenient way to get to the ResourceAttributeVersion objects 169 $t->is($version->getResourceAttributeVersions(), null, 'delete() also deletes resource attribute version history'); 170 } 157 171 158 172 // setVersionConditionMethod() … … 176 190 $r->setByName($test_class_title_column, 'do not version me', BasePeer::TYPE_FIELDNAME); 177 191 $r->save(); 178 $t->is($r->getLast Version()->getNumber(), 2, 'save() creates a version even if YourClass::versionConditionMet() is not found');192 $t->is($r->getLastResourceVersion()->getNumber(), 2, 'save() creates a version even if YourClass::versionConditionMet() is not found'); 179 193 180 194 // #1564 crashes while creating a new version if no prior version exists