Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Database\Eloquent\Relations;
4
 
5
use BadMethodCallException;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Collection;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
10
 
11
class MorphTo extends BelongsTo
12
{
13
    use InteractsWithDictionary;
14
 
15
    /**
16
     * The type of the polymorphic relation.
17
     *
18
     * @var string
19
     */
20
    protected $morphType;
21
 
22
    /**
23
     * The models whose relations are being eager loaded.
24
     *
25
     * @var \Illuminate\Database\Eloquent\Collection
26
     */
27
    protected $models;
28
 
29
    /**
30
     * All of the models keyed by ID.
31
     *
32
     * @var array
33
     */
34
    protected $dictionary = [];
35
 
36
    /**
37
     * A buffer of dynamic calls to query macros.
38
     *
39
     * @var array
40
     */
41
    protected $macroBuffer = [];
42
 
43
    /**
44
     * A map of relations to load for each individual morph type.
45
     *
46
     * @var array
47
     */
48
    protected $morphableEagerLoads = [];
49
 
50
    /**
51
     * A map of relationship counts to load for each individual morph type.
52
     *
53
     * @var array
54
     */
55
    protected $morphableEagerLoadCounts = [];
56
 
57
    /**
58
     * A map of constraints to apply for each individual morph type.
59
     *
60
     * @var array
61
     */
62
    protected $morphableConstraints = [];
63
 
64
    /**
65
     * Create a new morph to relationship instance.
66
     *
67
     * @param  \Illuminate\Database\Eloquent\Builder  $query
68
     * @param  \Illuminate\Database\Eloquent\Model  $parent
69
     * @param  string  $foreignKey
70
     * @param  string  $ownerKey
71
     * @param  string  $type
72
     * @param  string  $relation
73
     * @return void
74
     */
75
    public function __construct(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
76
    {
77
        $this->morphType = $type;
78
 
79
        parent::__construct($query, $parent, $foreignKey, $ownerKey, $relation);
80
    }
81
 
82
    /**
83
     * Set the constraints for an eager load of the relation.
84
     *
85
     * @param  array  $models
86
     * @return void
87
     */
88
    public function addEagerConstraints(array $models)
89
    {
90
        $this->buildDictionary($this->models = Collection::make($models));
91
    }
92
 
93
    /**
94
     * Build a dictionary with the models.
95
     *
96
     * @param  \Illuminate\Database\Eloquent\Collection  $models
97
     * @return void
98
     */
99
    protected function buildDictionary(Collection $models)
100
    {
101
        foreach ($models as $model) {
102
            if ($model->{$this->morphType}) {
103
                $morphTypeKey = $this->getDictionaryKey($model->{$this->morphType});
104
                $foreignKeyKey = $this->getDictionaryKey($model->{$this->foreignKey});
105
 
106
                $this->dictionary[$morphTypeKey][$foreignKeyKey][] = $model;
107
            }
108
        }
109
    }
110
 
111
    /**
112
     * Get the results of the relationship.
113
     *
114
     * Called via eager load method of Eloquent query builder.
115
     *
116
     * @return mixed
117
     */
118
    public function getEager()
119
    {
120
        foreach (array_keys($this->dictionary) as $type) {
121
            $this->matchToMorphParents($type, $this->getResultsByType($type));
122
        }
123
 
124
        return $this->models;
125
    }
126
 
127
    /**
128
     * Get all of the relation results for a type.
129
     *
130
     * @param  string  $type
131
     * @return \Illuminate\Database\Eloquent\Collection
132
     */
133
    protected function getResultsByType($type)
134
    {
135
        $instance = $this->createModelByType($type);
136
 
137
        $ownerKey = $this->ownerKey ?? $instance->getKeyName();
138
 
139
        $query = $this->replayMacros($instance->newQuery())
140
                            ->mergeConstraintsFrom($this->getQuery())
141
                            ->with(array_merge(
142
                                $this->getQuery()->getEagerLoads(),
143
                                (array) ($this->morphableEagerLoads[get_class($instance)] ?? [])
144
                            ))
145
                            ->withCount(
146
                                (array) ($this->morphableEagerLoadCounts[get_class($instance)] ?? [])
147
                            );
148
 
149
        if ($callback = ($this->morphableConstraints[get_class($instance)] ?? null)) {
150
            $callback($query);
151
        }
152
 
153
        $whereIn = $this->whereInMethod($instance, $ownerKey);
154
 
155
        return $query->{$whereIn}(
156
            $instance->getTable().'.'.$ownerKey, $this->gatherKeysByType($type, $instance->getKeyType())
157
        )->get();
158
    }
159
 
160
    /**
161
     * Gather all of the foreign keys for a given type.
162
     *
163
     * @param  string  $type
164
     * @param  string  $keyType
165
     * @return array
166
     */
167
    protected function gatherKeysByType($type, $keyType)
168
    {
169
        return $keyType !== 'string'
170
                    ? array_keys($this->dictionary[$type])
171
                    : array_map(function ($modelId) {
172
                        return (string) $modelId;
173
                    }, array_filter(array_keys($this->dictionary[$type])));
174
    }
175
 
176
    /**
177
     * Create a new model instance by type.
178
     *
179
     * @param  string  $type
180
     * @return \Illuminate\Database\Eloquent\Model
181
     */
182
    public function createModelByType($type)
183
    {
184
        $class = Model::getActualClassNameForMorph($type);
185
 
186
        return tap(new $class, function ($instance) {
187
            if (! $instance->getConnectionName()) {
188
                $instance->setConnection($this->getConnection()->getName());
189
            }
190
        });
191
    }
192
 
193
    /**
194
     * Match the eagerly loaded results to their parents.
195
     *
196
     * @param  array  $models
197
     * @param  \Illuminate\Database\Eloquent\Collection  $results
198
     * @param  string  $relation
199
     * @return array
200
     */
201
    public function match(array $models, Collection $results, $relation)
202
    {
203
        return $models;
204
    }
205
 
206
    /**
207
     * Match the results for a given type to their parents.
208
     *
209
     * @param  string  $type
210
     * @param  \Illuminate\Database\Eloquent\Collection  $results
211
     * @return void
212
     */
213
    protected function matchToMorphParents($type, Collection $results)
214
    {
215
        foreach ($results as $result) {
216
            $ownerKey = ! is_null($this->ownerKey) ? $this->getDictionaryKey($result->{$this->ownerKey}) : $result->getKey();
217
 
218
            if (isset($this->dictionary[$type][$ownerKey])) {
219
                foreach ($this->dictionary[$type][$ownerKey] as $model) {
220
                    $model->setRelation($this->relationName, $result);
221
                }
222
            }
223
        }
224
    }
225
 
226
    /**
227
     * Associate the model instance to the given parent.
228
     *
229
     * @param  \Illuminate\Database\Eloquent\Model  $model
230
     * @return \Illuminate\Database\Eloquent\Model
231
     */
232
    public function associate($model)
233
    {
234
        if ($model instanceof Model) {
235
            $foreignKey = $this->ownerKey && $model->{$this->ownerKey}
236
                            ? $this->ownerKey
237
                            : $model->getKeyName();
238
        }
239
 
240
        $this->parent->setAttribute(
241
            $this->foreignKey, $model instanceof Model ? $model->{$foreignKey} : null
242
        );
243
 
244
        $this->parent->setAttribute(
245
            $this->morphType, $model instanceof Model ? $model->getMorphClass() : null
246
        );
247
 
248
        return $this->parent->setRelation($this->relationName, $model);
249
    }
250
 
251
    /**
252
     * Dissociate previously associated model from the given parent.
253
     *
254
     * @return \Illuminate\Database\Eloquent\Model
255
     */
256
    public function dissociate()
257
    {
258
        $this->parent->setAttribute($this->foreignKey, null);
259
 
260
        $this->parent->setAttribute($this->morphType, null);
261
 
262
        return $this->parent->setRelation($this->relationName, null);
263
    }
264
 
265
    /**
266
     * Touch all of the related models for the relationship.
267
     *
268
     * @return void
269
     */
270
    public function touch()
271
    {
272
        if (! is_null($this->child->{$this->foreignKey})) {
273
            parent::touch();
274
        }
275
    }
276
 
277
    /**
278
     * Make a new related instance for the given model.
279
     *
280
     * @param  \Illuminate\Database\Eloquent\Model  $parent
281
     * @return \Illuminate\Database\Eloquent\Model
282
     */
283
    protected function newRelatedInstanceFor(Model $parent)
284
    {
285
        return $parent->{$this->getRelationName()}()->getRelated()->newInstance();
286
    }
287
 
288
    /**
289
     * Get the foreign key "type" name.
290
     *
291
     * @return string
292
     */
293
    public function getMorphType()
294
    {
295
        return $this->morphType;
296
    }
297
 
298
    /**
299
     * Get the dictionary used by the relationship.
300
     *
301
     * @return array
302
     */
303
    public function getDictionary()
304
    {
305
        return $this->dictionary;
306
    }
307
 
308
    /**
309
     * Specify which relations to load for a given morph type.
310
     *
311
     * @param  array  $with
312
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
313
     */
314
    public function morphWith(array $with)
315
    {
316
        $this->morphableEagerLoads = array_merge(
317
            $this->morphableEagerLoads, $with
318
        );
319
 
320
        return $this;
321
    }
322
 
323
    /**
324
     * Specify which relationship counts to load for a given morph type.
325
     *
326
     * @param  array  $withCount
327
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
328
     */
329
    public function morphWithCount(array $withCount)
330
    {
331
        $this->morphableEagerLoadCounts = array_merge(
332
            $this->morphableEagerLoadCounts, $withCount
333
        );
334
 
335
        return $this;
336
    }
337
 
338
    /**
339
     * Specify constraints on the query for a given morph type.
340
     *
341
     * @param  array  $callbacks
342
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
343
     */
344
    public function constrain(array $callbacks)
345
    {
346
        $this->morphableConstraints = array_merge(
347
            $this->morphableConstraints, $callbacks
348
        );
349
 
350
        return $this;
351
    }
352
 
353
    /**
354
     * Replay stored macro calls on the actual related instance.
355
     *
356
     * @param  \Illuminate\Database\Eloquent\Builder  $query
357
     * @return \Illuminate\Database\Eloquent\Builder
358
     */
359
    protected function replayMacros(Builder $query)
360
    {
361
        foreach ($this->macroBuffer as $macro) {
362
            $query->{$macro['method']}(...$macro['parameters']);
363
        }
364
 
365
        return $query;
366
    }
367
 
368
    /**
369
     * Handle dynamic method calls to the relationship.
370
     *
371
     * @param  string  $method
372
     * @param  array  $parameters
373
     * @return mixed
374
     */
375
    public function __call($method, $parameters)
376
    {
377
        try {
378
            $result = parent::__call($method, $parameters);
379
 
380
            if (in_array($method, ['select', 'selectRaw', 'selectSub', 'addSelect', 'withoutGlobalScopes'])) {
381
                $this->macroBuffer[] = compact('method', 'parameters');
382
            }
383
 
384
            return $result;
385
        }
386
 
387
        // If we tried to call a method that does not exist on the parent Builder instance,
388
        // we'll assume that we want to call a query macro (e.g. withTrashed) that only
389
        // exists on related models. We will just store the call and replay it later.
390
        catch (BadMethodCallException $e) {
391
            $this->macroBuffer[] = compact('method', 'parameters');
392
 
393
            return $this;
394
        }
395
    }
396
}