Login | Register
My pages Projects Community openCollabNet

Reply to message

* = Required fields
* Subject
* Body
Send reply to
Author (directly in email)
Please type the letters in the image above.

Original message

Author Thomas Rabaix <thomas@rabaix.net>
Full name Thomas Rabaix <thomas@rabaix.net>
Date 2006-12-28 07:31:08 PST
Message I will try to explain how work the caching system in my application. I have this table : tree_line_id, parent_id, tree_id, other fields ... - each line of a same tree share the same tree_id - I load the tree data with 1 query SELECT * FROM table where tree_id = 1 - I have added to my propel object TreeLine::addChild, TreeLine::setParent and TreeLine::removeChild methods - I have added to my propel peer object TreeLinePeer::_loadTree, TreeLinePeer::loadTree, TreeLinePeer::saveTree, TreeLinePeer::addChild, TreeLinePeer::deleteChild - In the loop to assign child and parent, I also create an hash array with reference to my propel object, so I can get a node with $array[$id] - This code is used with propel 1.2 For the following code I have this relation : Estimate > EstimateLine (nested set). The root node id is always -1 The load function look like this, the nested set is on the EstimateLine object: private static function &_loadTree(Estimate $estimate) { $c = new Criteria; $c->add(EstimateLinePeer::ESTIMATE_ID, $estimate->getEstimateId(), Criteria::EQUAL); $lines = EstimateLinePeer::doSelect($c); $aLines = array(); foreach($lines as &$line) { $pId = $line->getParentId(); $id = $line->getEstimateLineId(); if(!$aLines[$pId]) { $aLines[$pId] = new EstimateLine; $aLines[$pId]->setEstimateId($estimate->getEstimateId()); $aLines[$pId]->setEstimateLineId($pId); $aLines[$pId]->setNew(false); } if(!($aLines[$id] instanceof EstimateLine)) { $aLines[$id] =& $line; } else { $line->copyInto($aLines[$id]); $aLines[$id]->setNew(false); $aLines[$id]->setEstimateLineId($id); } $aLines[$pId]->addChild($aLines[$id]); $aLines[$id]->setParent($aLines[$pId]); } if(count($aLines) == 0) { // create the default one $aLines[-1] = new EstimateLine; $aLines[-1]->setEstimateId($estimate->getEstimateId()); $aLines[-1]->setEstimateLineId(-1); } if(!($aLines[-1] instanceof EstimateLine)) { throw new OreoException('Consistancy error, no root node for thre current estimate (id='.$estimate->getEstimateId().')'); } $aLines[-1]->setDescription(__('Estimate').' '.$estimate- >getReference()); return $aLines; } The tree is stored in a static variable in the Peer class. In order to get the nodes you just provide the tree_id the method loadTree check if a cache version exist or not and return the reference to the array public static function &loadTree(Estimate $estimate) { @mkdir(MORO_DATA_MODULES_PATH.'/estimates/cache/', 0777, true); $file_name = MORO_DATA_MODULES_PATH.'/estimates/cache/'.$estimate- >getEstimateId().'_estimate_lines.cache'; if(self::$tree[$estimate->getEstimateId()]) { return self::$tree[$estimate->getEstimateId()]; } else if(is_file($file_name)) { Syslog::debug('Reload from serialization, estimate_id='.$estimate- >getEstimateId()); self::$tree[$estimate->getEstimateId()] = unserialize (file_get_contents($file_name)); } else { Syslog::debug('Serialize + cache, estimate_id='.$estimate- >getEstimateId()); self::$tree[$estimate->getEstimateId()] =& EstimatePeer::_loadTree ($estimate); self::saveTree($estimate); } return self::$tree[$estimate->getEstimateId()]; } When you make any change to one object you have to save the tree public function saveTree(Estimate &$estimate) { @mkdir(MORO_DATA_MODULES_PATH.'/estimates/cache/', 0777, true); $file_name = MORO_DATA_MODULES_PATH.'/estimates/cache/'.$estimate- >getEstimateId().'_estimate_lines.cache'; Syslog::debug('Saving Cache, estimate_id='.$estimate->getEstimateId ()); file_put_contents($file_name,serialize(self::$tree[$estimate- >getEstimateId()])); } When you delete an object or add a child you have to update the database and the cache public function addChild($estimate, &$child) { if($estimate instanceof Estimate) { $id = $estimate->getEstimateId(); } else { $id = $estimate; } self::$tree[$id][$child->getEstimateLineId()] =& $child; } public function deleteChild($estimateLine) { $parent =& $estimateLine->getParent(); $idx = array_merge($estimateLine->getIdxChild(), array ($estimateLine->getEstimateLineId())); $estimate = EstimatePeer::retrieveByPK($estimateLine->getEstimateId ()); if(!($estimate instanceof Estimate)) { throw new OreoException('Unable get the estimate', OREO_ERROR_CHECK); } $c = new Criteria; $c->add(EstimateLinePeer::ESTIMATE_LINE_ID, $idx, Criteria::IN); EstimateLinePeer::doDelete($c); // Update the cache tree foreach($idx as $idChild) { syslog::debug('Delete node from cache tree : estimate_id='. $estimate->getEstimateId().' , estimate_line_id='.$idChild); // remove parent reference $parent = self::$tree[$estimate->getEstimateId()][$idChild]- >getParent(); if($parent instanceof EstimateLine) { $parent->removeChild(self::$tree[$estimate->getEstimateId()] [$idChild]); } unset(self::$tree[$estimate->getEstimateId()][$idChild]); } EstimatePeer::saveTree($estimate); } you can reimplement the retrievebypk method : public static function &retrieveByPk($pk) { $line = parent::retrieveByPk($pk); $estimate = EstimatePeer::retrieveByPk($line->getEstimateId()); $lines =& EstimatePeer::loadTree($estimate); return $lines[$pk]; } Of course you have to implement save and delete method in your object. So when you use this cache system it is transparency in your action : $estimate = new Estimate; $estimate->save(); $estimate2 = new Estimate; $estimate2->save(); $estimate->addChild($estimate2); $estimate->save(); $estimate2->delete(); Other important points : - this optimization improves processing speed if you have to load 1000 objects. Up to 10x faster and it is very easy to access to any point of the nested set. However it is very easy to get lost with references. And you have to make sure you always use the reference object and not a copy. - If you have a complex nested set relation, ie : margin calculation over nested set with up and down propagation. I suppose that your database contains always correct values, the propagation/ cascade update is not required when loading from database. You may find usefull in the loading method to set a state variable to not cascade information over child or/and parent. function setMargin($val) { parent::setMargin($val); if(self::$propagation) { $this->getParent()->updateMargin(); } } - the caching solution rely on the filesystem as the serialization is store in a file. Depends on the load and disk access this method can be worth that reloading from database. If you have APC installed on your server you can also use the share memory to store the cache array. I hope these few lines can help you to create a caching solution for your nested set. Thomas. On 27 déc. 06, at 23:46, Eric Fredj wrote: > Thomas, > > Have you some hints to share with us to implement some caching > system for Nested Set ? > I am interested in implement a common caching system for trees > implementations (Nested Set or Materialized Path). > > ErIC >