Overview

Namespaces

  • Charcoal
    • Object
    • User
      • Acl

Classes

  • Charcoal\Object\Content
  • Charcoal\Object\ObjectRevision
  • Charcoal\Object\ObjectRoute
  • Charcoal\Object\ObjectSchedule
  • Charcoal\Object\UserData
  • Charcoal\User\AbstractUser
  • Charcoal\User\Acl\Manager
  • Charcoal\User\Acl\Permission
  • Charcoal\User\Acl\PermissionCategory
  • Charcoal\User\Acl\Role
  • Charcoal\User\Authenticator
  • Charcoal\User\Authorizer
  • Charcoal\User\AuthToken
  • Charcoal\User\AuthTokenMetadata
  • Charcoal\User\GenericUser

Interfaces

  • Charcoal\Object\ArchivableInterface
  • Charcoal\Object\CategorizableInterface
  • Charcoal\Object\CategorizableMultipleInterface
  • Charcoal\Object\CategoryInterface
  • Charcoal\Object\ContentInterface
  • Charcoal\Object\HierarchicalInterface
  • Charcoal\Object\ObjectRevisionInterface
  • Charcoal\Object\ObjectRouteInterface
  • Charcoal\Object\ObjectScheduleInterface
  • Charcoal\Object\PublishableInterface
  • Charcoal\Object\RevisionableInterface
  • Charcoal\Object\RoutableInterface
  • Charcoal\Object\UserDataInterface
  • Charcoal\User\UserInterface

Traits

  • Charcoal\Object\ArchivableTrait
  • Charcoal\Object\CategorizableMultipleTrait
  • Charcoal\Object\CategorizableTrait
  • Charcoal\Object\CategoryTrait
  • Charcoal\Object\HierarchicalTrait
  • Charcoal\Object\PublishableTrait
  • Charcoal\Object\RevisionableTrait
  • Charcoal\Object\RoutableTrait
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace Charcoal\Object;
  4: 
  5: use \InvalidArgumentException;
  6: use \UnexpectedValueException;
  7: 
  8: // From 'charcoal-core'
  9: use \Charcoal\Model\ModelInterface;
 10: 
 11: /**
 12:  * Full implementation, as a trait, of the `HierarchicalInterface`
 13:  */
 14: trait HierarchicalTrait
 15: {
 16:     /**
 17:      * The object's parent, if any, in the hierarchy.
 18:      *
 19:      * @var HierarchicalInterface|null
 20:      */
 21:     protected $master;
 22: 
 23:     /**
 24:      * Store a copy of the object's ancestry.
 25:      *
 26:      * @var HierarchicalInterface[]|null
 27:      */
 28:     private $hierarchy = null;
 29: 
 30:     /**
 31:      * Store a copy of the object's descendants.
 32:      *
 33:      * @var HierarchicalInterface[]|null
 34:      */
 35:     private $children;
 36: 
 37:     /**
 38:      * Store a copy of the object's siblings.
 39:      *
 40:      * @var HierarchicalInterface[]|null
 41:      */
 42:     private $siblings;
 43: 
 44:     /**
 45:      * A store of cached objects.
 46:      *
 47:      * @var ModelInterface[] $objectCache
 48:      */
 49:     public static $objectCache = [];
 50: 
 51:     /**
 52:      * Reset this object's hierarchy.
 53:      *
 54:      * The object's hierarchy can be rebuilt with {@see self::hierarchy()}.
 55:      *
 56:      * @return HierarchicalInterface Chainable
 57:      */
 58:     public function resetHierarchy()
 59:     {
 60:         $this->hierarchy = null;
 61: 
 62:         return $this;
 63:     }
 64: 
 65:     /**
 66:      * Set this object's immediate parent.
 67:      *
 68:      * @param  mixed $master The object's parent (or master).
 69:      * @throws UnexpectedValueException The current object cannot be its own parent.
 70:      * @return HierarchicalInterface Chainable
 71:      */
 72:     public function setMaster($master)
 73:     {
 74:         $master = $this->objFromIdent($master);
 75: 
 76:         if ($master instanceof ModelInterface) {
 77:             if ($master->id() === $this->id()) {
 78:                 throw new UnexpectedValueException(
 79:                     sprintf('Can not be ones own parent: %s', $master->id())
 80:                 );
 81:             }
 82:         }
 83: 
 84:         $this->master = $master;
 85: 
 86:         $this->resetHierarchy();
 87: 
 88:         return $this;
 89:     }
 90: 
 91:     /**
 92:      * Retrieve this object's immediate parent.
 93:      *
 94:      * @return HierarchicalInterface|null
 95:      */
 96:     public function master()
 97:     {
 98:         return $this->master;
 99:     }
100: 
101:     /**
102:      * Determine if this object has a direct parent.
103:      *
104:      * @return boolean
105:      */
106:     public function hasMaster()
107:     {
108:         return ($this->master() !== null);
109:     }
110: 
111:     /**
112:      * Determine if this object is the head (top-level) of its hierarchy.
113:      *
114:      * Top-level objects do not have a parent (master).
115:      *
116:      * @return boolean
117:      */
118:     public function isTopLevel()
119:     {
120:         return ($this->master() === null);
121:     }
122: 
123:     /**
124:      * Determine if this object is the tail (last-level) of its hierarchy.
125:      *
126:      * Last-level objects do not have a children.
127:      *
128:      * @return boolean
129:      */
130:     public function isLastLevel()
131:     {
132:         return !$this->hasChildren();
133:     }
134: 
135:     /**
136:      * Retrieve this object's position (level) in its hierarchy.
137:      *
138:      * Starts at "1" (top-level).
139:      *
140:      * The level is calculated by loading all ancestors with {@see self::hierarchy()}.
141:      *
142:      * @return integer
143:      */
144:     public function hierarchyLevel()
145:     {
146:         $hierarchy = $this->hierarchy();
147:         $level = (count($hierarchy) + 1);
148: 
149:         return $level;
150:     }
151: 
152:     /**
153:      * Retrieve the top-level ancestor of this object.
154:      *
155:      * @return HierarchicalInterface|null
156:      */
157:     public function toplevelMaster()
158:     {
159:         $hierarchy = $this->invertedHierarchy();
160:         if (isset($hierarchy[0])) {
161:             return $hierarchy[0];
162:         } else {
163:             return null;
164:         }
165:     }
166: 
167:     /**
168:      * Determine if this object has any ancestors.
169:      *
170:      * @return boolean
171:      */
172:     public function hasParents()
173:     {
174:         return !!count($this->hierarchy());
175:     }
176: 
177:     /**
178:      * Retrieve this object's ancestors (from immediate parent to top-level).
179:      *
180:      * @return array
181:      */
182:     public function hierarchy()
183:     {
184:         if (!isset($this->hierarchy)) {
185:             $hierarchy = [];
186:             $master = $this->master();
187:             while ($master) {
188:                 $hierarchy[] = $master;
189:                 $master = $master->master();
190:             }
191: 
192:             $this->hierarchy = $hierarchy;
193:         }
194: 
195:         return $this->hierarchy;
196:     }
197: 
198:     /**
199:      * Retrieve this object's ancestors, inverted from top-level to immediate.
200:      *
201:      * @return array
202:      */
203:     public function invertedHierarchy()
204:     {
205:         $hierarchy = $this->hierarchy();
206:         return array_reverse($hierarchy);
207:     }
208: 
209:     /**
210:      * Determine if the object is the parent of the given object.
211:      *
212:      * @param mixed $child The child (or ID) to match against.
213:      * @return boolean
214:      */
215:     public function isMasterOf($child)
216:     {
217:         $child = $this->objFromIdent($child);
218:         return ($child->master() == $this);
219:     }
220: 
221:     /**
222:      * Determine if the object is a parent/ancestor of the given object.
223:      *
224:      * @param mixed $child The child (or ID) to match against.
225:      * @return boolean
226:      * @todo Implementation needed.
227:      */
228:     public function recursiveIsMasterOf($child)
229:     {
230:         $child = $this->objFromIdent($child);
231: 
232:         return false;
233:     }
234: 
235:     /**
236:      * Get wether the object has any children at all
237:      * @return boolean
238:      */
239:     public function hasChildren()
240:     {
241:         $numChildren = $this->numChildren();
242:         return ($numChildren > 0);
243:     }
244: 
245:     /**
246:      * Get the number of children directly under this object.
247:      * @return integer
248:      */
249:     public function numChildren()
250:     {
251:         $children = $this->children();
252:         return count($children);
253:     }
254: 
255:     /**
256:      * Get the total number of children in the entire hierarchy.
257:      * This method counts all children and sub-children, unlike `numChildren()` which only count 1 level.
258:      * @return integer
259:      */
260:     public function recursiveNumChildren()
261:     {
262:         // TODO
263:         return 0;
264:     }
265: 
266:     /**
267:      * @param array $children The children to set.
268:      * @return HierarchicalInterface Chainable
269:      */
270:     public function setChildren(array $children)
271:     {
272:         $this->children = [];
273:         foreach ($children as $c) {
274:             $this->addChild($c);
275:         }
276:         return $this;
277:     }
278: 
279:     /**
280:      * @param mixed $child The child object (or ident) to add.
281:      * @throws UnexpectedValueException The current object cannot be its own child.
282:      * @return HierarchicalInterface Chainable
283:      */
284:     public function addChild($child)
285:     {
286:         $child = $this->objFromIdent($child);
287: 
288:         if ($child instanceof ModelInterface) {
289:             if ($child->id() === $this->id()) {
290:                 throw new UnexpectedValueException(
291:                     sprintf('Can not be ones own child: %s', $child->id())
292:                 );
293:             }
294:         }
295: 
296:         $this->children[] = $child;
297: 
298:         return $this;
299:     }
300: 
301:     /**
302:      * Get the children directly under this object.
303:      * @return array
304:      */
305:     public function children()
306:     {
307:         if ($this->children !== null) {
308:             return $this->children;
309:         }
310: 
311:         $this->children = $this->loadChildren();
312:         return $this->children;
313:     }
314: 
315:     /**
316:      * @return array
317:      */
318:     abstract public function loadChildren();
319: 
320:     /**
321:      * @param mixed $master The master object (or ident) to check against.
322:      * @return boolean
323:      */
324:     public function isChildOf($master)
325:     {
326:         $master = $this->objFromIdent($master);
327:         if ($master === null) {
328:             return false;
329:         }
330:         return ($master == $this->master());
331:     }
332: 
333:     /**
334:      * @param mixed $master The master object (or ident) to check against.
335:      * @return boolean
336:      */
337:     public function recursiveIsChildOf($master)
338:     {
339:         $master = $this->objFromIdent($master);
340:         if ($master === null) {
341:             return false;
342:         }
343:         // TODO
344:     }
345: 
346:     /**
347:      * @return boolean
348:      */
349:     public function hasSiblings()
350:     {
351:         $numSiblings = $this->numSiblings();
352:         return ($numSiblings > 1);
353:     }
354: 
355:     /**
356:      * @return integer
357:      */
358:     public function numSiblings()
359:     {
360:         $siblings = $this->siblings();
361:         return count($siblings);
362:     }
363: 
364:     /**
365:      * Get all the objects on the same level as this one.
366:      * @return array
367:      */
368:     public function siblings()
369:     {
370:         if ($this->siblings !== null) {
371:             return $this->siblings;
372:         }
373:         $master = $this->master();
374:         if ($master === null) {
375:             // Todo: return all top-level objects.
376:             $siblings = [];
377:         } else {
378:             // Todo: Remove "current" object from siblings
379:             $siblings = $master->children();
380:         }
381:         $this->siblings = $siblings;
382:         return $this->siblings;
383:     }
384: 
385:     /**
386:      * @param mixed $sibling The sibling to check.
387:      * @return boolean
388:      */
389:     public function isSiblingOf($sibling)
390:     {
391:         $sibling = $this->objFromIdent($sibling);
392:         return ($sibling->master() == $this->master());
393:     }
394: 
395:     /**
396:      * @param mixed $ident The ident.
397:      * @throws InvalidArgumentException If the identifier is not a scalar value.
398:      * @return HierarchicalInterface|null
399:      */
400:     private function objFromIdent($ident)
401:     {
402:         if ($ident === null) {
403:             return null;
404:         }
405: 
406:         $class = get_called_class();
407: 
408:         if (is_object($ident) && ($ident instanceof $class)) {
409:             return $ident;
410:         }
411: 
412:         if (!is_scalar($ident)) {
413:             throw new InvalidArgumentException(
414:                 sprintf('Can not load object (not a scalar or a "%s")', $class)
415:             );
416:         }
417: 
418:         $cached = $this->loadObjectFromCache($ident);
419:         if ($cached !== null) {
420:             return $cached;
421:         }
422: 
423:         $obj = $this->loadObjectFromSource($ident);
424: 
425:         if ($obj !== null) {
426:             $this->addObjectToCache($obj);
427:         }
428: 
429:         return $obj;
430:     }
431: 
432:     /**
433:      * Retrieve an object from the storage source by its ID.
434:      *
435:      * @param mixed $id The object id.
436:      * @return null|ModelInterface
437:      */
438:     private function loadObjectFromSource($id)
439:     {
440:         $obj = $this->modelFactory()->create($this->objType());
441:         $obj->load($id);
442: 
443:         if ($obj->id()) {
444:             return $obj;
445:         } else {
446:             return null;
447:         }
448:     }
449: 
450:     /**
451:      * Retrieve an object from the cache store by its ID.
452:      *
453:      * @param mixed $id The object id.
454:      * @return null|ModelInterface
455:      */
456:     private function loadObjectFromCache($id)
457:     {
458:         $objType = $this->objType();
459:         if (isset(static::$objectCache[$objType][$id])) {
460:             return static::$objectCache[$objType][$id];
461:         } else {
462:             return null;
463:         }
464:     }
465: 
466:     /**
467:      * Add an object to the cache store.
468:      *
469:      * @param ModelInterface $obj The object to store.
470:      * @return HierarchicalInterface Chainable
471:      */
472:     private function addObjectToCache(ModelInterface $obj)
473:     {
474:         static::$objectCache[$this->objType()][$obj->id()] = $obj;
475: 
476:         return $this;
477:     }
478: 
479:     /**
480:      * Retrieve the object model factory.
481:      *
482:      * @return \Charcoal\Factory\FactoryInterface
483:      */
484:     abstract public function modelFactory();
485: }
486: 
API documentation generated by ApiGen