Autodoc
  • Namespace
  • Class
  • Tree

Namespaces

  • BlueTihi
    • Context
  • Brickrouge
    • Element
      • Nodes
    • Renderer
    • Widget
  • ICanBoogie
    • ActiveRecord
    • AutoConfig
    • CLDR
    • Composer
    • Core
    • Event
    • Exception
    • HTTP
      • Dispatcher
      • Request
    • I18n
      • Translator
    • Mailer
    • Modules
      • Taxonomy
        • Support
      • Thumbnailer
        • Versions
    • Object
    • Operation
      • Dispatcher
    • Prototype
    • Routes
    • Routing
      • Dispatcher
    • Session
  • Icybee
    • ActiveRecord
      • Model
    • ConfigOperation
    • Document
    • EditBlock
    • Element
      • ActionbarContextual
      • ActionbarSearch
      • ActionbarToolbar
    • FormBlock
    • Installer
    • ManageBlock
    • Modules
      • Articles
      • Cache
        • Collection
        • ManageBlock
      • Comments
        • ManageBlock
      • Contents
        • ManageBlock
      • Dashboard
      • Editor
        • Collection
      • Files
        • File
        • ManageBlock
      • Forms
        • Form
        • ManageBlock
      • I18n
      • Images
        • ManageBlock
      • Members
      • Modules
        • ManageBlock
      • Nodes
        • ManageBlock
        • Module
      • Pages
        • BreadcrumbElement
        • LanguagesElement
        • ManageBlock
        • NavigationBranchElement
        • NavigationElement
        • Page
        • PageController
      • Registry
      • Search
      • Seo
      • Sites
        • ManageBlock
      • Taxonomy
        • Terms
          • ManageBlock
        • Vocabulary
          • ManageBlock
      • Users
        • ManageBlock
        • NonceLogin
        • Roles
      • Views
        • ActiveRecordProvider
        • Collection
        • View
    • Operation
      • ActiveRecord
      • Constructor
      • Module
      • Widget
    • Rendering
  • None
  • Patron
  • PHP

Classes

  • ActiveRecordCache
  • CollectDependenciesEvent
  • Connection
  • Connections
  • DateTimePropertySupport
  • Helpers
  • Hooks
  • Model
  • Models
  • Query
  • RunTimeActiveRecordCache
  • Statement
  • Table

Interfaces

  • ActiveRecordCacheInterface

Traits

  • CreatedAtProperty
  • DateTimeProperty
  • UpdatedAtProperty

Exceptions

  • ActiveRecordException
  • ConnectionAlreadyEstablished
  • ConnectionNotDefined
  • ConnectionNotEstablished
  • ModelAlreadyInstantiated
  • ModelNotDefined
  • RecordNotFound
  • ScopeNotDefined
  • StatementInvalid

Functions

  • get_model
  1 <?php
  2 
  3 /*
  4  * This file is part of the ICanBoogie package.
  5  *
  6  * (c) Olivier Laviale <olivier.laviale@gmail.com>
  7  *
  8  * For the full copyright and license information, please view the LICENSE
  9  * file that was distributed with this source code.
 10  */
 11 
 12 namespace ICanBoogie\ActiveRecord;
 13 
 14 use ICanBoogie\ActiveRecord;
 15 use ICanBoogie\OffsetNotWritable;
 16 use ICanBoogie\PropertyNotWritable;
 17 
 18 /**
 19  * Base class for activerecord models.
 20  *
 21  * @method Query select() select($expression) The method is forwarded to {@link Query::select}.
 22  * @method Query joins() joins($expression) The method is forwarded to {@link Query::joins}.
 23  * @method Query where() where($conditions, $conditions_args=null) The method is forwarded to {@link Query::where}.
 24  * @method Query group() group($group) The method is forwarded to {@link Query::group}.
 25  * @method Query order() order($order) The method is forwarded to {@link Query::order}.
 26  * @method Query limit() limit($limit, $offset=null) The method is forwarded to {@link Query::limit}.
 27  * @method Query offset() offset($offset) The method is forwarded to {@link Query::offset}.
 28  * @method bool exists() exists($key=null) The method is forwarded to {@link Query::exists}.
 29  * @method mixed count() count($column=null) The method is forwarded to {@link Query::count}.
 30  * @method string average() average($column) The method is forwarded to {@link Query::average}.
 31  * @method string maximum() maximum($column) The method is forwarded to {@link Query::maximum}.
 32  * @method string minimum() minimum($column) The method is forwarded to {@link Query::minimum}.
 33  * @method int sum() sum($column) The method is forwarded to {@link Query::sum}.
 34  * @method array all() all() The method is forwarded to {@link Query::all}.
 35  * @method ActiveRecord one() one() The method is forwarded to {@link Query::one}.
 36  *
 37  * @property-read array $all Retrieve all the records from the model.
 38  * @property-read string $activerecord_class Class of the active records of the model.
 39  * @property-read int $count The number of records of the model.
 40  * @property-read bool $exists Whether the SQL table associated with the model exists.
 41  * @property-read string $id The identifier of the model.
 42  * @property-read ActiveRecord Retrieve the first record from the mode.
 43  * @property ActiveRecordCacheInterface $activerecord_cache The cache use to store activerecords.
 44  */
 45 class Model extends Table implements \ArrayAccess
 46 {
 47     // TODO-20130216: deprecate all T_*
 48 
 49     const T_ACTIVERECORD_CLASS = 'activerecord_class';
 50     const T_CLASS = 'class';
 51     const T_ID = 'id';
 52 
 53     const ACTIVERECORD_CLASS = 'activerecord_class';
 54     const BELONGS_TO = 'belongs_to';
 55     const CLASSNAME = 'class';
 56     const ID = 'id';
 57 
 58     /**
 59      * Active record instances class.
 60      *
 61      * @var string
 62      */
 63     protected $activerecord_class;
 64 
 65     /**
 66      * Attributes of the model.
 67      *
 68      * @var array[string]mixed
 69      */
 70     protected $attributes;
 71 
 72     /**
 73      * Override the constructor to provide support for the {@link ACTIVERECORD_CLASS} tag and
 74      * extended support for the {@link EXTENDING} tag.
 75      *
 76      * If {@link EXTENDING} is defined but the model has no schema ({@link SCHEMA} is empty),
 77      * the name of the model and the schema are inherited from the extended model and
 78      * {@link EXTENDING} is set to the parent model object. If {@link ACTIVERECORD_CLASS} is
 79      * empty, its value is set to the extended model's active record class.
 80      *
 81      * If {@link ACTIVERECORD_CLASS} is set, its value is saved in the
 82      * {@link $activerecord_class} property.
 83      *
 84      * @param array $tags Tags used to construct the model.
 85      */
 86     public function __construct(array $tags)
 87     {
 88         $tags += [
 89 
 90             self::BELONGS_TO => null,
 91             self::EXTENDING => null,
 92             self::ID => null,
 93             self::SCHEMA => null,
 94             self::ACTIVERECORD_CLASS => null
 95         ];
 96 
 97         if ($tags[self::EXTENDING] && !$tags[self::SCHEMA])
 98         {
 99             $extends = $tags[self::EXTENDING];
100 
101             $tags[self::NAME] = $extends->name_unprefixed;
102             $tags[self::SCHEMA] = $extends->schema;
103             $tags[self::EXTENDING] = $extends->parent;
104 
105             if (!$tags[self::ACTIVERECORD_CLASS])
106             {
107                 $tags[self::ACTIVERECORD_CLASS] = $extends->activerecord_class;
108             }
109         }
110 
111         if (empty($tags[self::ID]))
112         {
113             $tags[self::ID] = $tags[self::NAME];
114         }
115 
116         $this->attributes = $tags;
117 
118         parent::__construct($tags);
119 
120         #
121         # Resolve the active record class.
122         #
123 
124         $activerecord_class = $tags[self::ACTIVERECORD_CLASS];
125 
126         if (!$activerecord_class && $this->parent)
127         {
128             $activerecord_class = $this->parent->activerecord_class;
129         }
130 
131         $this->activerecord_class = $activerecord_class;
132 
133         # belongs_to
134 
135         $belongs_to = $tags[self::BELONGS_TO];
136 
137         if ($belongs_to)
138         {
139             $this->belongs_to($belongs_to);
140         }
141     }
142 
143     /**
144      * Handles the _belongs to_ relationship of the model.
145      *
146      * <pre>
147      * $cars->belongs_to([ $drivers, $brands ]);
148      * # or
149      * $cars->belongs_to([ 'drivers', 'brands' ]);
150      * # or
151      * $cars->belongs_to($drivers, $brands);
152      * # or
153      * $cars->belongs_to($drivers);
154      * $cars->belongs_to($brands);
155      * </pre>
156      *
157      * @param string|array $belongs_to
158      *
159      * @return Model
160      *
161      * @throws ActiveRecordException if the class of the active record is `ICanBoogie\ActiveRecord`.
162      */
163     public function belongs_to($belongs_to)
164     {
165         if (func_num_args() > 1)
166         {
167             $belongs_to = func_get_args();
168         }
169 
170         if (is_array($belongs_to))
171         {
172             foreach ($belongs_to as $b)
173             {
174                 $this->belongs_to($b);
175             }
176 
177             return $this;
178         }
179 
180         if ($belongs_to instanceof self)
181         {
182             $belongs_to_model = $belongs_to;
183             $belongs_to_id = $belongs_to->id;
184         }
185         else
186         {
187             $belongs_to_model = null;
188             $belongs_to_id = $belongs_to;
189         }
190 
191         $activerecord_class = $this->activerecord_class;
192         $getter_name = 'lazy_get_' . \ICanBoogie\singularize($belongs_to_id);
193 
194         if (!$activerecord_class || $activerecord_class == 'ICanBoogie\ActiveRecord')
195         {
196             throw new ActiveRecordException('The Active Record class cannot be <code>ICanBoogie\ActiveRecord</code> for a <em>belongs to</em> relationship.');
197         }
198 
199         $prototype = \ICanBoogie\Prototype::from($activerecord_class);
200 
201         $prototype[$getter_name] = function(ActiveRecord $ar) use($belongs_to_model, $belongs_to_id)
202         {
203             $model = $belongs_to_model ? $belongs_to_model : get_model($belongs_to_id);
204             $primary = $model->primary;
205             $key = $ar->$primary;
206 
207             return $key ? $model[$key] : null;
208         };
209 
210         return $this;
211     }
212 
213     /**
214      * Handles query methods, dynamic filters and scopes.
215      */
216     public function __call($method, $arguments)
217     {
218         if (is_callable([ 'ICanBoogie\ActiveRecord\Query', $method ])
219         || strpos($method, 'filter_by_') === 0
220         || method_exists($this, 'scope_' . $method))
221         {
222             $query = new Query($this);
223 
224             return call_user_func_array([ $query, $method ], $arguments);
225         }
226 
227         return parent::__call($method, $arguments);
228     }
229 
230     /**
231      * Overrides the method to handle scopes.
232      */
233     public function __get($property)
234     {
235         $method = 'scope_' . $property;
236 
237         if (method_exists($this, $method))
238         {
239             return $this->$method(new Query($this));
240         }
241 
242         return parent::__get($property);
243     }
244 
245     /**
246      * Returns the identifier of the model.
247      *
248      * @return string
249      */
250     protected function get_id()
251     {
252         return $this->attributes[self::ID];
253     }
254 
255     /**
256      * Returns the class of the active records of the model.
257      *
258      * @return string
259      */
260     protected function get_activerecord_class()
261     {
262         return $this->activerecord_class;
263     }
264 
265     /**
266      * Finds a record or a collection of records.
267      *
268      * @param mixed $key A key or an array of keys.
269      *
270      * @throws RecordNotFound when the record, or one or more records of the records
271      * set, could not be found.
272      *
273      * @return ActiveRecord|array A record or a set of records.
274      */
275     public function find($key)
276     {
277         if (func_num_args() > 1)
278         {
279             $key = func_get_args();
280         }
281 
282         if (is_array($key))
283         {
284             $records = array_combine($key, array_fill(0, count($key), null));
285             $missing = $records;
286 
287             foreach ($records as $key => $dummy)
288             {
289                 $record = $this->retrieve($key);
290 
291                 if (!$record)
292                 {
293                     continue;
294                 }
295 
296                 $records[$key] = $record;
297                 unset($missing[$key]);
298             }
299 
300             if ($missing)
301             {
302                 $primary = $this->primary;
303                 $query_records = $this->where([ $primary => array_keys($missing) ])->all;
304 
305                 foreach ($query_records as $record)
306                 {
307                     $key = $record->$primary;
308                     $records[$key] = $record;
309                     unset($missing[$key]);
310 
311                     $this->store($record);
312                 }
313             }
314 
315             if ($missing)
316             {
317                 if (count($missing) > 1)
318                 {
319                     throw new RecordNotFound
320                     (
321                         "Records " . implode(', ', array_keys($missing)) . " do not exists in model <q>{$this->name_unprefixed}</q>.", $records
322                     );
323                 }
324                 else
325                 {
326                     $key = array_keys($missing);
327                     $key = array_shift($key);
328 
329                     throw new RecordNotFound
330                     (
331                         "Record <q>{$key}</q> does not exists in model <q>{$this->name_unprefixed}</q>.", $records
332                     );
333                 }
334             }
335 
336             return $records;
337         }
338 
339         $record = $this->retrieve($key);
340 
341         if ($record === null)
342         {
343             $record = $this->where([ $this->primary => $key ])->one;
344 
345             if (!$record)
346             {
347                 throw new RecordNotFound
348                 (
349                     "Record <q>{$key}</q> does not exists in model <q>{$this->name_unprefixed}</q>.", [ $key => null ]
350                 );
351             }
352 
353             $this->store($record);
354         }
355 
356         return $record;
357     }
358 
359     /**
360      * Because records are cached, we need to remove the record from the cache when it is saved,
361      * so that loading the record again returns the updated record, not the one in the cache.
362      */
363     public function save(array $properties, $key=null, array $options=[])
364     {
365         if ($key)
366         {
367             $this->eliminate($key);
368         }
369 
370         return parent::save($properties, $key, $options);
371     }
372 
373     /**
374      * Eliminates the record from the cache.
375      */
376     public function delete($key)
377     {
378         $this->activerecord_cache->eliminate($key);
379 
380         return parent::delete($key);
381     }
382 
383     /**
384      * Stores a record in the records cache.
385      *
386      * @param ActiveRecord $record The record to store.
387      *
388      * @TODO-20140414: Remove the method and use {@link $activerecord_cache}
389      */
390     protected function store(ActiveRecord $record)
391     {
392         $this->activerecord_cache->store($record);
393     }
394 
395     /**
396      * Retrieves a record from the records cache.
397      *
398      * @param int $key
399      *
400      * @return ActiveRecord|null Returns the active record found in the cache or null if it wasn't
401      * there.
402      *
403      * @TODO-20140414: Remove the method and use {@link $activerecord_cache}
404      */
405     protected function retrieve($key)
406     {
407         return $this->activerecord_cache->retrieve($key);
408     }
409 
410     /**
411      * Eliminates an object from the cache.
412      *
413      * @param int $key
414      *
415      * @TODO-20140414: Remove the method and use {@link $activerecord_cache}
416      */
417     protected function eliminate($key)
418     {
419         $this->activerecord_cache->eliminate($key);
420     }
421 
422     /**
423      * Checks that the SQL table associated with the model exists.
424      *
425      * @return bool
426      */
427     protected function get_exists()
428     {
429         return $this->exists();
430     }
431 
432     /**
433      * Returns the number of records of the model.
434      *
435      * @return int
436      */
437     protected function get_count()
438     {
439         return $this->count();
440     }
441 
442     /**
443      * Returns all the records of the model.
444      *
445      * @return array[]ActiveRecord
446      */
447     protected function get_all()
448     {
449         return $this->all();
450     }
451 
452     /**
453      * Returns the first record of the model.
454      *
455      * @return ActiveRecord
456      */
457     protected function get_one()
458     {
459         return $this->one();
460     }
461 
462     /**
463      * Checks if the model has a given scope.
464      *
465      * Scopes are defined using method with the "scope_" prefix. As an example, the `visible`
466      * scope can be defined by implementing the `scope_visible` method.
467      *
468      * @param string $name Scope name.
469      *
470      * @return boolean
471      */
472     public function has_scope($name)
473     {
474         return method_exists($this, 'scope_' . $name);
475     }
476 
477     /**
478      * Calls a given scope on the active record query specified in the scope_args.
479      *
480      * @param string $scope_name Name of the scope to apply to the query.
481      * @param array $scope_args Arguments to forward to the scope method.
482      *
483      * @throws ScopeNotDefined when the specified scope is not defined.
484      *
485      * @return Query
486      */
487     public function scope($scope_name, $scope_args=null)
488     {
489         $callback = 'scope_' . $scope_name;
490 
491         if (!method_exists($this, $callback))
492         {
493             throw new ScopeNotDefined($scope_name, $this);
494         }
495 
496         return call_user_func_array([ $this, $callback ], $scope_args);
497     }
498 
499     /*
500      * ArrayAccess implementation
501      */
502 
503     /**
504      * Offsets are not settable.
505      *
506      * @throws OffsetNotWritable when one tries to write an offset.
507      */
508     public function offsetSet($offset, $value)
509     {
510         throw new OffsetNotWritable([ $offset, $this ]);
511     }
512 
513     /**
514      * Checks if the record identified by the given key exists.
515      *
516      * The call is forwarded to {@link exists()}.
517      */
518     public function offsetExists($key)
519     {
520         return $this->exists($key);
521     }
522 
523     /**
524      * Deletes the record specified by the given key.
525      *
526      * @see Model::delete();
527      */
528     public function offsetUnset($key)
529     {
530         $this->delete($key);
531     }
532 
533     /**
534      * Alias for the {@link find()} method.
535      *
536      * @see Model::find()
537      */
538     public function offsetGet($key)
539     {
540         return $this->find($key);
541     }
542 
543     /**
544      * Creates a new active record instance.
545      *
546      * The class of the instance is defined by the {@link $activerecord_class} property.
547      *
548      * @return ActiveRecord
549      */
550     public function new_record()
551     {
552         $class = $this->activerecord_class;
553 
554         return new $class($this);
555     }
556 }
557 
558 /**
559  * Exception thrown when an active record cannot be found.
560  *
561  * @property-read array[int]ActiveRecord|null $records
562  */
563 class RecordNotFound extends ActiveRecordException
564 {
565     /**
566      * A key/value array where keys are the identifier of the record, and the value is the result
567      * of finding the record. If the record was found the value is a {@link ActiveRecord}
568      * object, otherwise the `null` value.
569      *
570      * @var array[int]ActiveRecord|null
571      */
572     private $records;
573 
574     /**
575      * Initializes the {@link $records} property.
576      *
577      * @param string $message
578      * @param array $records
579      * @param int $code Defaults to 404.
580      * @param \Exception $previous Previous exception.
581      */
582     public function __construct($message, array $records, $code=404, \Exception $previous=null)
583     {
584         $this->records = $records;
585 
586         parent::__construct($message, $code, $previous);
587     }
588 
589     public function __get($property)
590     {
591         switch ($property)
592         {
593             case 'records': return $this->records;
594         }
595     }
596 }
597 
598 /**
599  * Exception thrown when a scope is not defined.
600  *
601  * @property-read string $scope_name
602  * @property-read Model $model
603  */
604 class ScopeNotDefined extends ActiveRecordException
605 {
606     /**
607      * Name of the scope.
608      *
609      * @var string
610      */
611     private $scope_name;
612 
613     /**
614      * Model on which the scope was invoked.
615      *
616      * @var Model
617      */
618     private $model;
619 
620     /**
621      * Initializes the {@link $scope_name} and {@link $model} properties.
622      *
623      * @param string $scope_name Name of the scope.
624      * @param Model $model Model on which the scope was invoked.
625      * @param int $code Default to 404.
626      * @param \Exception $previous Previous exception.
627      */
628     public function __construct($scope_name, Model $model, $code=500, \Exception $previous)
629     {
630         $this->scope_name = $scope_name;
631         $this->model = $model;
632 
633         parent::__construct("Unknown scope <q>{$scope_name}</q> for model <q>{$model->name_unprefixed}</q>.", $code, $previous);
634     }
635 
636     public function __get($property)
637     {
638         switch ($property)
639         {
640             case 'scope_name': return $this->scope_name;
641             case 'model': return $this->model;
642         }
643     }
644 }
Autodoc API documentation generated by ApiGen 2.8.0