Concerns
1 month ago
BelongsTo.php
2 years ago
BelongsToMany.php
1 month ago
HasMany.php
2 years ago
HasManyThrough.php
1 month ago
HasOne.php
1 month ago
HasOneOrMany.php
1 month ago
HasOneThrough.php
2 years ago
MorphMany.php
2 years ago
MorphOne.php
1 month ago
MorphOneOrMany.php
2 years ago
MorphPivot.php
1 month ago
MorphTo.php
6 months ago
MorphToMany.php
2 years ago
Pivot.php
2 years ago
Relation.php
1 month ago
Relation.php
439 lines
| 1 | <?php |
| 2 | |
| 3 | namespace IAWPSCOPED\Illuminate\Database\Eloquent\Relations; |
| 4 | |
| 5 | use Closure; |
| 6 | use IAWPSCOPED\Illuminate\Contracts\Database\Eloquent\Builder as BuilderContract; |
| 7 | use IAWPSCOPED\Illuminate\Database\Eloquent\Builder; |
| 8 | use IAWPSCOPED\Illuminate\Database\Eloquent\Collection; |
| 9 | use IAWPSCOPED\Illuminate\Database\Eloquent\Model; |
| 10 | use IAWPSCOPED\Illuminate\Database\Eloquent\ModelNotFoundException; |
| 11 | use IAWPSCOPED\Illuminate\Database\MultipleRecordsFoundException; |
| 12 | use IAWPSCOPED\Illuminate\Database\Query\Expression; |
| 13 | use IAWPSCOPED\Illuminate\Support\Arr; |
| 14 | use IAWPSCOPED\Illuminate\Support\Traits\ForwardsCalls; |
| 15 | use IAWPSCOPED\Illuminate\Support\Traits\Macroable; |
| 16 | /** @internal */ |
| 17 | abstract class Relation implements BuilderContract |
| 18 | { |
| 19 | use ForwardsCalls, Macroable { |
| 20 | Macroable::__call as macroCall; |
| 21 | } |
| 22 | /** |
| 23 | * The Eloquent query builder instance. |
| 24 | * |
| 25 | * @var \Illuminate\Database\Eloquent\Builder |
| 26 | */ |
| 27 | protected $query; |
| 28 | /** |
| 29 | * The parent model instance. |
| 30 | * |
| 31 | * @var \Illuminate\Database\Eloquent\Model |
| 32 | */ |
| 33 | protected $parent; |
| 34 | /** |
| 35 | * The related model instance. |
| 36 | * |
| 37 | * @var \Illuminate\Database\Eloquent\Model |
| 38 | */ |
| 39 | protected $related; |
| 40 | /** |
| 41 | * Indicates if the relation is adding constraints. |
| 42 | * |
| 43 | * @var bool |
| 44 | */ |
| 45 | protected static $constraints = \true; |
| 46 | /** |
| 47 | * An array to map class names to their morph names in the database. |
| 48 | * |
| 49 | * @var array |
| 50 | */ |
| 51 | public static $morphMap = []; |
| 52 | /** |
| 53 | * Prevents morph relationships without a morph map. |
| 54 | * |
| 55 | * @var bool |
| 56 | */ |
| 57 | protected static $requireMorphMap = \false; |
| 58 | /** |
| 59 | * The count of self joins. |
| 60 | * |
| 61 | * @var int |
| 62 | */ |
| 63 | protected static $selfJoinCount = 0; |
| 64 | /** |
| 65 | * Create a new relation instance. |
| 66 | * |
| 67 | * @param \Illuminate\Database\Eloquent\Builder $query |
| 68 | * @param \Illuminate\Database\Eloquent\Model $parent |
| 69 | * @return void |
| 70 | */ |
| 71 | public function __construct(Builder $query, Model $parent) |
| 72 | { |
| 73 | $this->query = $query; |
| 74 | $this->parent = $parent; |
| 75 | $this->related = $query->getModel(); |
| 76 | $this->addConstraints(); |
| 77 | } |
| 78 | /** |
| 79 | * Run a callback with constraints disabled on the relation. |
| 80 | * |
| 81 | * @param \Closure $callback |
| 82 | * @return mixed |
| 83 | */ |
| 84 | public static function noConstraints(Closure $callback) |
| 85 | { |
| 86 | $previous = static::$constraints; |
| 87 | static::$constraints = \false; |
| 88 | // When resetting the relation where clause, we want to shift the first element |
| 89 | // off of the bindings, leaving only the constraints that the developers put |
| 90 | // as "extra" on the relationships, and not original relation constraints. |
| 91 | try { |
| 92 | return $callback(); |
| 93 | } finally { |
| 94 | static::$constraints = $previous; |
| 95 | } |
| 96 | } |
| 97 | /** |
| 98 | * Set the base constraints on the relation query. |
| 99 | * |
| 100 | * @return void |
| 101 | */ |
| 102 | public abstract function addConstraints(); |
| 103 | /** |
| 104 | * Set the constraints for an eager load of the relation. |
| 105 | * |
| 106 | * @param array $models |
| 107 | * @return void |
| 108 | */ |
| 109 | public abstract function addEagerConstraints(array $models); |
| 110 | /** |
| 111 | * Initialize the relation on a set of models. |
| 112 | * |
| 113 | * @param array $models |
| 114 | * @param string $relation |
| 115 | * @return array |
| 116 | */ |
| 117 | public abstract function initRelation(array $models, $relation); |
| 118 | /** |
| 119 | * Match the eagerly loaded results to their parents. |
| 120 | * |
| 121 | * @param array $models |
| 122 | * @param \Illuminate\Database\Eloquent\Collection $results |
| 123 | * @param string $relation |
| 124 | * @return array |
| 125 | */ |
| 126 | public abstract function match(array $models, Collection $results, $relation); |
| 127 | /** |
| 128 | * Get the results of the relationship. |
| 129 | * |
| 130 | * @return mixed |
| 131 | */ |
| 132 | public abstract function getResults(); |
| 133 | /** |
| 134 | * Get the relationship for eager loading. |
| 135 | * |
| 136 | * @return \Illuminate\Database\Eloquent\Collection |
| 137 | */ |
| 138 | public function getEager() |
| 139 | { |
| 140 | return $this->get(); |
| 141 | } |
| 142 | /** |
| 143 | * Execute the query and get the first result if it's the sole matching record. |
| 144 | * |
| 145 | * @param array|string $columns |
| 146 | * @return \Illuminate\Database\Eloquent\Model |
| 147 | * |
| 148 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> |
| 149 | * @throws \Illuminate\Database\MultipleRecordsFoundException |
| 150 | */ |
| 151 | public function sole($columns = ['*']) |
| 152 | { |
| 153 | $result = $this->take(2)->get($columns); |
| 154 | $count = $result->count(); |
| 155 | if ($count === 0) { |
| 156 | throw (new ModelNotFoundException())->setModel(\get_class($this->related)); |
| 157 | } |
| 158 | if ($count > 1) { |
| 159 | throw new MultipleRecordsFoundException($count); |
| 160 | } |
| 161 | return $result->first(); |
| 162 | } |
| 163 | /** |
| 164 | * Execute the query as a "select" statement. |
| 165 | * |
| 166 | * @param array $columns |
| 167 | * @return \Illuminate\Database\Eloquent\Collection |
| 168 | */ |
| 169 | public function get($columns = ['*']) |
| 170 | { |
| 171 | return $this->query->get($columns); |
| 172 | } |
| 173 | /** |
| 174 | * Touch all of the related models for the relationship. |
| 175 | * |
| 176 | * @return void |
| 177 | */ |
| 178 | public function touch() |
| 179 | { |
| 180 | $model = $this->getRelated(); |
| 181 | if (!$model::isIgnoringTouch()) { |
| 182 | $this->rawUpdate([$model->getUpdatedAtColumn() => $model->freshTimestampString()]); |
| 183 | } |
| 184 | } |
| 185 | /** |
| 186 | * Run a raw update against the base query. |
| 187 | * |
| 188 | * @param array $attributes |
| 189 | * @return int |
| 190 | */ |
| 191 | public function rawUpdate(array $attributes = []) |
| 192 | { |
| 193 | return $this->query->withoutGlobalScopes()->update($attributes); |
| 194 | } |
| 195 | /** |
| 196 | * Add the constraints for a relationship count query. |
| 197 | * |
| 198 | * @param \Illuminate\Database\Eloquent\Builder $query |
| 199 | * @param \Illuminate\Database\Eloquent\Builder $parentQuery |
| 200 | * @return \Illuminate\Database\Eloquent\Builder |
| 201 | */ |
| 202 | public function getRelationExistenceCountQuery(Builder $query, Builder $parentQuery) |
| 203 | { |
| 204 | return $this->getRelationExistenceQuery($query, $parentQuery, new Expression('count(*)'))->setBindings([], 'select'); |
| 205 | } |
| 206 | /** |
| 207 | * Add the constraints for an internal relationship existence query. |
| 208 | * |
| 209 | * Essentially, these queries compare on column names like whereColumn. |
| 210 | * |
| 211 | * @param \Illuminate\Database\Eloquent\Builder $query |
| 212 | * @param \Illuminate\Database\Eloquent\Builder $parentQuery |
| 213 | * @param array|mixed $columns |
| 214 | * @return \Illuminate\Database\Eloquent\Builder |
| 215 | */ |
| 216 | public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) |
| 217 | { |
| 218 | return $query->select($columns)->whereColumn($this->getQualifiedParentKeyName(), '=', $this->getExistenceCompareKey()); |
| 219 | } |
| 220 | /** |
| 221 | * Get a relationship join table hash. |
| 222 | * |
| 223 | * @param bool $incrementJoinCount |
| 224 | * @return string |
| 225 | */ |
| 226 | public function getRelationCountHash($incrementJoinCount = \true) |
| 227 | { |
| 228 | return 'laravel_reserved_' . ($incrementJoinCount ? static::$selfJoinCount++ : static::$selfJoinCount); |
| 229 | } |
| 230 | /** |
| 231 | * Get all of the primary keys for an array of models. |
| 232 | * |
| 233 | * @param array $models |
| 234 | * @param string|null $key |
| 235 | * @return array |
| 236 | */ |
| 237 | protected function getKeys(array $models, $key = null) |
| 238 | { |
| 239 | return \IAWPSCOPED\collect($models)->map(function ($value) use($key) { |
| 240 | return $key ? $value->getAttribute($key) : $value->getKey(); |
| 241 | })->values()->unique(null, \true)->sort()->all(); |
| 242 | } |
| 243 | /** |
| 244 | * Get the query builder that will contain the relationship constraints. |
| 245 | * |
| 246 | * @return \Illuminate\Database\Eloquent\Builder |
| 247 | */ |
| 248 | protected function getRelationQuery() |
| 249 | { |
| 250 | return $this->query; |
| 251 | } |
| 252 | /** |
| 253 | * Get the underlying query for the relation. |
| 254 | * |
| 255 | * @return \Illuminate\Database\Eloquent\Builder |
| 256 | */ |
| 257 | public function getQuery() |
| 258 | { |
| 259 | return $this->query; |
| 260 | } |
| 261 | /** |
| 262 | * Get the base query builder driving the Eloquent builder. |
| 263 | * |
| 264 | * @return \Illuminate\Database\Query\Builder |
| 265 | */ |
| 266 | public function getBaseQuery() |
| 267 | { |
| 268 | return $this->query->getQuery(); |
| 269 | } |
| 270 | /** |
| 271 | * Get a base query builder instance. |
| 272 | * |
| 273 | * @return \Illuminate\Database\Query\Builder |
| 274 | */ |
| 275 | public function toBase() |
| 276 | { |
| 277 | return $this->query->toBase(); |
| 278 | } |
| 279 | /** |
| 280 | * Get the parent model of the relation. |
| 281 | * |
| 282 | * @return \Illuminate\Database\Eloquent\Model |
| 283 | */ |
| 284 | public function getParent() |
| 285 | { |
| 286 | return $this->parent; |
| 287 | } |
| 288 | /** |
| 289 | * Get the fully qualified parent key name. |
| 290 | * |
| 291 | * @return string |
| 292 | */ |
| 293 | public function getQualifiedParentKeyName() |
| 294 | { |
| 295 | return $this->parent->getQualifiedKeyName(); |
| 296 | } |
| 297 | /** |
| 298 | * Get the related model of the relation. |
| 299 | * |
| 300 | * @return \Illuminate\Database\Eloquent\Model |
| 301 | */ |
| 302 | public function getRelated() |
| 303 | { |
| 304 | return $this->related; |
| 305 | } |
| 306 | /** |
| 307 | * Get the name of the "created at" column. |
| 308 | * |
| 309 | * @return string |
| 310 | */ |
| 311 | public function createdAt() |
| 312 | { |
| 313 | return $this->parent->getCreatedAtColumn(); |
| 314 | } |
| 315 | /** |
| 316 | * Get the name of the "updated at" column. |
| 317 | * |
| 318 | * @return string |
| 319 | */ |
| 320 | public function updatedAt() |
| 321 | { |
| 322 | return $this->parent->getUpdatedAtColumn(); |
| 323 | } |
| 324 | /** |
| 325 | * Get the name of the related model's "updated at" column. |
| 326 | * |
| 327 | * @return string |
| 328 | */ |
| 329 | public function relatedUpdatedAt() |
| 330 | { |
| 331 | return $this->related->getUpdatedAtColumn(); |
| 332 | } |
| 333 | /** |
| 334 | * Get the name of the "where in" method for eager loading. |
| 335 | * |
| 336 | * @param \Illuminate\Database\Eloquent\Model $model |
| 337 | * @param string $key |
| 338 | * @return string |
| 339 | */ |
| 340 | protected function whereInMethod(Model $model, $key) |
| 341 | { |
| 342 | return $model->getKeyName() === \IAWPSCOPED\last(\explode('.', $key)) && \in_array($model->getKeyType(), ['int', 'integer']) ? 'whereIntegerInRaw' : 'whereIn'; |
| 343 | } |
| 344 | /** |
| 345 | * Prevent polymorphic relationships from being used without model mappings. |
| 346 | * |
| 347 | * @param bool $requireMorphMap |
| 348 | * @return void |
| 349 | */ |
| 350 | public static function requireMorphMap($requireMorphMap = \true) |
| 351 | { |
| 352 | static::$requireMorphMap = $requireMorphMap; |
| 353 | } |
| 354 | /** |
| 355 | * Determine if polymorphic relationships require explicit model mapping. |
| 356 | * |
| 357 | * @return bool |
| 358 | */ |
| 359 | public static function requiresMorphMap() |
| 360 | { |
| 361 | return static::$requireMorphMap; |
| 362 | } |
| 363 | /** |
| 364 | * Define the morph map for polymorphic relations and require all morphed models to be explicitly mapped. |
| 365 | * |
| 366 | * @param array $map |
| 367 | * @param bool $merge |
| 368 | * @return array |
| 369 | */ |
| 370 | public static function enforceMorphMap(array $map, $merge = \true) |
| 371 | { |
| 372 | static::requireMorphMap(); |
| 373 | return static::morphMap($map, $merge); |
| 374 | } |
| 375 | /** |
| 376 | * Set or get the morph map for polymorphic relations. |
| 377 | * |
| 378 | * @param array|null $map |
| 379 | * @param bool $merge |
| 380 | * @return array |
| 381 | */ |
| 382 | public static function morphMap(array $map = null, $merge = \true) |
| 383 | { |
| 384 | $map = static::buildMorphMapFromModels($map); |
| 385 | if (\is_array($map)) { |
| 386 | static::$morphMap = $merge && static::$morphMap ? $map + static::$morphMap : $map; |
| 387 | } |
| 388 | return static::$morphMap; |
| 389 | } |
| 390 | /** |
| 391 | * Builds a table-keyed array from model class names. |
| 392 | * |
| 393 | * @param string[]|null $models |
| 394 | * @return array|null |
| 395 | */ |
| 396 | protected static function buildMorphMapFromModels(array $models = null) |
| 397 | { |
| 398 | if (\is_null($models) || Arr::isAssoc($models)) { |
| 399 | return $models; |
| 400 | } |
| 401 | return \array_combine(\array_map(function ($model) { |
| 402 | return (new $model())->getTable(); |
| 403 | }, $models), $models); |
| 404 | } |
| 405 | /** |
| 406 | * Get the model associated with a custom polymorphic type. |
| 407 | * |
| 408 | * @param string $alias |
| 409 | * @return string|null |
| 410 | */ |
| 411 | public static function getMorphedModel($alias) |
| 412 | { |
| 413 | return static::$morphMap[$alias] ?? null; |
| 414 | } |
| 415 | /** |
| 416 | * Handle dynamic method calls to the relationship. |
| 417 | * |
| 418 | * @param string $method |
| 419 | * @param array $parameters |
| 420 | * @return mixed |
| 421 | */ |
| 422 | public function __call($method, $parameters) |
| 423 | { |
| 424 | if (static::hasMacro($method)) { |
| 425 | return $this->macroCall($method, $parameters); |
| 426 | } |
| 427 | return $this->forwardDecoratedCallTo($this->query, $method, $parameters); |
| 428 | } |
| 429 | /** |
| 430 | * Force a clone of the underlying query builder when cloning. |
| 431 | * |
| 432 | * @return void |
| 433 | */ |
| 434 | public function __clone() |
| 435 | { |
| 436 | $this->query = clone $this->query; |
| 437 | } |
| 438 | } |
| 439 |