1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
<?php
/*
* This file is part of the ICanBoogie package.
*
* (c) Olivier Laviale <olivier.laviale@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ICanBoogie\ActiveRecord;
use ICanBoogie\Accessor\AccessorTrait;
use ICanBoogie\ActiveRecord;
use ICanBoogie\Prototype;
/**
* Representation of a relation.
*
* @property-read Model $parent The parent model of the relation.
* @property-read Model $related The related model of the relation.
* @property-read string $as The name of the relation.
* @property-read string $local_key The local key.
* @property-read string $foreign_key The foreign key.
*/
abstract class Relation
{
use AccessorTrait;
/**
* The parent model of the relation.
*
* @var Model
*/
protected $parent;
protected function get_parent()
{
return $this->parent;
}
/**
* The related model of the relation.
*
* @var Model
*/
protected $related;
protected function get_related()
{
$related = $this->related;
if ($related instanceof Model)
{
return $related;
}
/* @var $related string */
return $this->related = $this->parent->models[$related];
}
/**
* The name of the relation.
*
* @var string
*/
protected $as;
protected function get_as()
{
return $this->as;
}
/**
* Local key. Default: The parent model's primary key.
*
* @var string
*/
protected $local_key;
protected function get_local_key()
{
return $this->local_key;
}
/**
* Foreign key. Default: The parent model's primary key.
*
* @var string
*/
protected $foreign_key;
protected function get_foreign_key()
{
return $this->foreign_key;
}
/**
* Initialize the {@link $parent}, {@link $related}, {@link $as}, {@link $local_key}, and
* {@link $foreign_key} properties.
*
* @param Model $parent The parent model of the relation.
* @param Model|string $related The related model of the relation. Can be specified using its
* instance or its identifier.
* @param array $options the following options are available:
*
* - `as`: The name of the magic property to add to the prototype. Default: a plural name
* resolved from the foreign model's id.
* - `local_key`: The name of the local key. Default: The parent model's primary key.
* - `foreign_key`: The name of the foreign key. Default: The parent model's primary key.
*/
public function __construct(Model $parent, $related, array $options = [])
{
$options += [
'as' => null,
'local_key' => $parent->primary,
'foreign_key' => $parent->primary
];
$this->parent = $parent;
$this->related = $related;
$this->as = $options['as'] ?: $this->resolve_property_name($related);
$this->local_key = $options['local_key'];
$this->foreign_key = $options['foreign_key'];
$activerecord_class = $this->resolve_activerecord_class($parent);
$prototype = Prototype::from($activerecord_class);
$this->alter_prototype($prototype, $this->as);
}
/**
* Create a query with the relation.
*
* @param ActiveRecord $record
*
* @return Query
*/
abstract public function __invoke(ActiveRecord $record);
/**
* Add a getter for the relation to the prototype.
*
* @param Prototype $prototype The activerecord prototype.
* @param string $property The name of the property.
*/
protected function alter_prototype(Prototype $prototype, $property)
{
$prototype["get_$property"] = $this;
}
/**
* Resolve the active record class name from the specified model.
*
* @param Model $model
*
* @throws \LogicException if the class is {@link ActiveRecord}.
*
* @return string
*/
protected function resolve_activerecord_class(Model $model)
{
$activerecord_class = $model->activerecord_class;
if (!$activerecord_class || $activerecord_class == ActiveRecord::class)
{
throw new ActiveRecordClassNotValid($activerecord_class, 'The Active Record class cannot be <code>ICanBoogie\ActiveRecord</code> for a relationship.');
}
return $activerecord_class;
}
/**
* Resolve the property name from the related model.
*
* @param Model|string $related The related model of the relation.
*
* @return string
*/
protected function resolve_property_name($related)
{
$related_id = $related instanceof Model ? $related->id : $related;
$parts = explode('.', $related_id);
return array_pop($parts);
}
/**
* Resolve the related model.
*
* @return Model
*/
protected function resolve_related()
{
$related = $this->related;
if (!($related instanceof Model))
{
$this->related = $related = $this->parent->models[$related];
}
return $related;
}
}