| [ Index ] |
PHP Cross Reference of Akelos Framework |
[Summary view] [Print] [Text view]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4 // +----------------------------------------------------------------------+ 5 // | Akelos Framework - http://www.akelos.org | 6 // +----------------------------------------------------------------------+ 7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez | 8 // | Released under the GNU Lesser General Public License, see LICENSE.txt| 9 // +----------------------------------------------------------------------+ 10 // | Acts as Tree | 11 // +----------------------------------------------------------------------+ 12 // | Copyright (c) 2006, Raw Ideas Pty Ltd & Niels Ganser | 13 // | Released under the GNU Lesser General Public License, see LICENSE.txt| 14 // | If the Akelos Framework License is changed to another one as or less | 15 // | restrictive as the LGPL, permission is granted to also re-license | 16 // | this file. | 17 // +----------------------------------------------------------------------+ 18 19 /** 20 * @package ActiveRecord 21 * @subpackage Behaviours 22 * @author Niels Ganser <ng a.t depoll d.e> 23 * @copyright Copyright (c) 2006, Raw Ideas Pty Ltd 24 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html> 25 */ 26 27 require_once (AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkObserver.php'); 28 29 30 /** 31 * acts_as_tree 32 * 33 * Makes your model acts as a tree (surprise!). Consider the following example: 34 * 35 * class Category extends ActiveRecord { 36 * var $acts_as = 'tree'; 37 * } 38 * 39 * $Category = new Category; 40 * 41 * $CategoryA = $Category->create(); 42 * $CategoryAa = $Category->create(); 43 * $CategoryAa1 = $Category->create(); 44 * $CategoryAa2 = $Category->create(); 45 * $CategoryAb = $Category->create(); 46 * $CategoryB = $Category->create(); 47 * 48 * $CategoryA->tree->addChild($CategoryAa) 49 * $CategoryA->tree->addChild($CategoryAb) 50 * $CategoryAa->tree->addChild($CategoryAa1) 51 * $CategoryAa->tree->addChild($CategoryAa2) 52 * 53 * 54 * This will effectively give you: 55 * 56 * Category A 57 * \_ Category Aa 58 * \_ Category Aa1 59 * \_ Category Aa2 60 * \_ Category Ab 61 * Category B 62 * 63 * 64 * OK. Admittedly you won't get a graph in real life. But at least the following functions: 65 * 66 * $CategoryA->tree->hasChildren() # ==> true 67 * $CategoryA->tree->childrenCount() # ==> 2 68 * $CategoryA->tree->getChildren() # ==> array($CategoryAa, $CategoryAb) 69 * // fairly expensive operation follows 70 * // (yes, array(parent, array_of_children) is not nice but unfortunately PHP doesn't allow for objects as keys) 71 * $CategoryA->tree->getDescendants() # ==> array(array($CategoryAa, array($CategoryAa1, $CategoryAa2)), $CategoryAb) 72 * 73 * $CategoryAa->tree->getChildren() # ==> array($CategoryAa1, $CategoryAa2) 74 * $CategoryAa->tree->getSiblings() # ==> array($CategoryAb) 75 * $CategoryAa->tree->hasParent() # ==> true 76 * $CategoryAa->tree->getParent() # ==> $CategoryA 77 * 78 * $CatagoryAa1->tree->hasChildren() # ==> false 79 * $CategoryAa1->tree->getParent() # ==> $CategoryAa 80 * // fairly expensive operation follows 81 * $CategoryAa1->tree->getAncestors() # ==> array($CategoryAa, $CategoryA) 82 * // fairly expensive operation follows 83 * $CategoryAa1->tree->getAncestors(1) # ==> array($CategoryAa) 84 * 85 * 86 * To make this work your model needs a parent_id column (whose name can be overriden with +parent_column+. Furthermore 87 * you can set the +dependent+ option to automatically delete all children if their parent gets deleted. Otherwise they 88 * will become orphants (i.e. have parent_id = NULL) 89 * 90 * (Note that on adding a child it will be saved. If the parent has been unsaved until now it will also be saved.) 91 */ 92 class AkActsAsTree extends AkObserver 93 { 94 95 96 97 /** 98 * Configuration options are: 99 * 100 * * +parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id) 101 * * +dependent+ - set to true to automatically delete all children when its parent is deleted 102 * * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id" 103 * (if that hasn't been already) and use that as the foreign key restriction. It's also possible 104 * to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. 105 * Example: <tt>actsAsTree(array('scope' => array('todo_list_id = ? AND completed = 0',$todo_list_id)));</tt> 106 */ 107 108 var $parent_column = 'parent_id'; 109 var $scope; 110 var $scope_condition; 111 var $_parent_column_name = 'parent_id'; 112 var $_dependent = false; 113 114 var $_ActiveRecordInstance; 115 116 function AkActsAsTree(&$ActiveRecordInstance) 117 { 118 $this->_ActiveRecordInstance =& $ActiveRecordInstance; 119 } 120 121 function init($options = array()) 122 { 123 empty($options['parent_column']) ? null : ($this->_parent_column_name = $options['parent_column']); 124 empty($options['dependent']) ? null : ($this->_dependent = $options['dependent']); 125 empty($options['scope']) ? null : $this->setScopeCondition($options['scope']); 126 $this->parent_column = !empty($options['parent_column']) ? $options['parent_column'] : $this->parent_column; 127 return $this->_ensureIsActiveRecordInstance($this->_ActiveRecordInstance); 128 } 129 130 131 function _ensureIsActiveRecordInstance(&$ActiveRecordInstance) 132 { 133 if(is_object($ActiveRecordInstance) && method_exists($ActiveRecordInstance,'actsLike')){ 134 $this->_ActiveRecordInstance =& $ActiveRecordInstance; 135 if(!$this->_ActiveRecordInstance->hasColumn($this->_parent_column_name)){ 136 trigger_error(Ak::t( 137 'The following columns are required in the table "%table" for the model "%model" to act as a Tree: "%columns".',array( 138 '%columns'=>$this->getParentColumnName(),'%table'=>$this->_ActiveRecordInstance->getTableName(),'%model'=>$this->_ActiveRecordInstance->getModelName())),E_USER_ERROR); 139 unset($this->_ActiveRecordInstance->tree); 140 return false; 141 }else{ 142 $this->observe(&$ActiveRecordInstance); 143 } 144 }else{ 145 trigger_error(Ak::t('You are trying to set an object that is not an active record.'), E_USER_ERROR); 146 return false; 147 } 148 return true; 149 } 150 151 function reloadActiveRecordInstance(&$nodeInstance) 152 { 153 AK_PHP5 ? null : $nodeInstance->tree->_ensureIsActiveRecordInstance($nodeInstance); 154 } 155 156 function getType() 157 { 158 return 'tree'; 159 } 160 161 function getScopeCondition() 162 { 163 if (!empty($this->variable_scope_condition)){ 164 return $this->_ActiveRecordInstance->_getVariableSqlCondition($this->variable_scope_condition); 165 166 // True condition in case we don't have a scope 167 }elseif(empty($this->scope_condition) && empty($this->scope)){ 168 $this->scope_condition = ($this->_ActiveRecordInstance->_db->type() == 'postgre') ? 'true' : '1'; 169 }elseif (!empty($this->scope)){ 170 $this->setScopeCondition(join(' AND ',array_diff(array_map(array(&$this,'getScopedColumn'),(array)$this->scope),array('')))); 171 } 172 return $this->scope_condition; 173 } 174 175 176 function setScopeCondition($scope_condition) 177 { 178 if(!is_array($scope_condition) && strstr($scope_condition, '?')){ 179 $this->variable_scope_condition = $scope_condition; 180 }else{ 181 $this->scope_condition = $scope_condition; 182 } 183 } 184 185 function getScopedColumn($column) 186 { 187 if($this->_ActiveRecordInstance->hasColumn($column)){ 188 $value = $this->_ActiveRecordInstance->get($column); 189 $condition = $this->_ActiveRecordInstance->getAttributeCondition($value); 190 $value = $this->_ActiveRecordInstance->castAttributeForDatabase($column, $value); 191 return $column.' '.str_replace('?', $value, $condition); 192 }else{ 193 return $column; 194 } 195 } 196 197 function getParentColumnName() 198 { 199 return $this->_parent_column_name; 200 } 201 202 function setParentColumnName($parent_column_name) 203 { 204 $this->_parent_column_name = $parent_column_name; 205 } 206 207 function getDependent() 208 { 209 return $this->_dependent; 210 } 211 212 function setDependent($val) 213 { 214 $this->_dependent = (bool)$val; 215 } 216 217 function hasChildren() 218 { 219 return $this->childrenCount() > 0; 220 } 221 222 function hasParent() 223 { 224 $parent_id = $this->_ActiveRecordInstance->{$this->getParentColumnName()}; 225 return !empty($parent_id); 226 } 227 228 function addChild( &$child ) 229 { 230 $this->_ActiveRecordInstance->transactionStart(); 231 232 if ($this->_ActiveRecordInstance->isNewRecord()){ 233 if (!$this->_ActiveRecordInstance->save()) { 234 $this->_ActiveRecordInstance->transactionFail(); 235 $this->_ActiveRecordInstance->transactionComplete(); 236 return false; 237 } 238 } 239 240 if ($this->_ActiveRecordInstance->getId() == $child->getId()) { 241 $this->_ActiveRecordInstance->transactionFail(); 242 $this->_ActiveRecordInstance->transactionComplete(); 243 trigger_error(Ak::t('Cannot add myself as a child to myself'), E_USER_ERROR); 244 return false; 245 } 246 247 $child->{$this->getParentColumnName()} = $this->_ActiveRecordInstance->getId(); 248 if (!$child->save()) { 249 $this->_ActiveRecordInstance->transactionFail(); 250 $this->_ActiveRecordInstance->transactionComplete(); 251 return false; 252 } 253 254 $this->_ActiveRecordInstance->transactionComplete(); 255 256 $this->reloadActiveRecordInstance($child); 257 return $child; 258 } 259 260 function childrenCount() 261 { 262 263 return $this->_ActiveRecordInstance->isNewRecord() ? 0 : $this->_ActiveRecordInstance->count(" ".$this->getScopeCondition()." AND ".$this->getParentColumnName()." = ".$this->_ActiveRecordInstance->getId()); 264 } 265 266 function getChildren() 267 { 268 return $this->_ActiveRecordInstance->isNewRecord() ? false : $this->_ActiveRecordInstance->findAll(" ".$this->getScopeCondition()." AND ".$this->getParentColumnName()." = ".$this->_ActiveRecordInstance->getId()); 269 } 270 271 function getParent() 272 { 273 if (!$this->hasParent()){ 274 return false; 275 } else { 276 return $this->_ActiveRecordInstance->find('first', 277 array('conditions' => ' '.$this->getScopeCondition().' AND '.$this->_ActiveRecordInstance->getPrimaryKey()." = ".$this->_ActiveRecordInstance->{$this->getParentColumnName()})); 278 } 279 } 280 281 /** 282 * @param integer $level How deep do you want to search? everything <= 0 means infinite deep 283 */ 284 function getAncestors($level=0) 285 { 286 if (!$this->hasParent()) { 287 return array(); 288 } 289 290 $last = $this->getParent(); 291 $ancestors = array($last); 292 --$level; 293 294 // we can't do end($ancestors)->hasParent() due to PHP4 compatibility 295 while ($level != 0 && $last->tree->hasParent()) { 296 $last = $last->tree->getParent(); 297 $ancestors[]= $last; 298 --$level; 299 } 300 301 return $ancestors; 302 } 303 304 305 function getSiblings($options = array()) 306 { 307 $default_options = array('include_self'=>false); 308 $options = array_merge($default_options, $options); 309 $parent_condition = (is_null($this->_ActiveRecordInstance->{$this->getParentColumnName()})) ? 'ISNULL('. $this->getParentColumnName() .")" : $this->getParentColumnName() .' = '. $this->_ActiveRecordInstance->{$this->getParentColumnName()}; 310 $id_condition = !empty($options['include_self']) ? '' : ' AND '. $this->_ActiveRecordInstance->getPrimaryKey() .' != '. $this->_ActiveRecordInstance->getId(); 311 return $this->_ActiveRecordInstance->findAll(' '. $this->getScopeCondition(). 312 ' AND '. $parent_condition.$id_condition); 313 } 314 315 function getSelfAndSiblings() 316 { 317 return $this->getSiblings(array('include_self'=>true)); 318 } 319 320 /** 321 * @param integer $level How deep do you want to search? everything <= 0 means infinite deep 322 */ 323 function getDescendants($level=0) 324 { 325 if (!$this->hasChildren()) { 326 return array(); 327 } 328 329 return $this->_recursiveGetDescendants($level, $this->getChildren()); 330 } 331 function _recursiveGetDescendants($level, $from) { 332 --$level; 333 334 if ($level == 0) { 335 return $from; 336 } 337 338 $children = array(); 339 foreach ($from as $item) { 340 if ($item->tree->hasChildren()) { 341 $children[] = array($item, $this->_recursiveGetDescendants($level, $item->tree->getChildren())); 342 } else { 343 $children[] = $item; 344 } 345 } 346 347 return $children; 348 } 349 350 function beforeDestroy(&$object) 351 { 352 if(!$object->tree->hasChildren()){ 353 return true; 354 } 355 356 $object->transactionStart(); 357 358 if ($this->getDependent()){ 359 $object->deleteAll($this->getScopeCondition().' AND '.$this->getParentColumnName().' = '.$object->getId()); 360 }else{ 361 $object->updateAll( $this->getParentColumnName() .' = NULL', $this->getScopeCondition().' AND '.$this->getParentColumnName().' = '.$object->getId() ); 362 } 363 364 if($object->transactionHasFailed()){ 365 $object->transactionComplete(); 366 return false; 367 } 368 $object->transactionComplete(); 369 370 return true; 371 } 372 373 } 374 375 376 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Oct 27 12:43:49 2008 | Cross-referenced by PHPXref 0.6 |