Completed
Push — develop ( 57113a...e97eef )
by Abdelrahman
20:34 queued 22s
created

Attributable::getEntityAttributeRelations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Attributes\Traits;
6
7
use Schema;
8
use Closure;
9
use Illuminate\Support\Arr;
10
use SuperClosure\Serializer;
11
use Vinkla\Hashids\Facades\Hashids;
12
use Rinvex\Attributes\Models\Value;
13
use Rinvex\Attributes\Models\Attribute;
14
use Rinvex\Support\Traits\HashidsTrait;
15
use Illuminate\Database\Eloquent\Builder;
16
use Illuminate\Database\Eloquent\Collection;
17
use Rinvex\Attributes\Events\EntityWasSaved;
18
use Rinvex\Attributes\Scopes\EagerLoadScope;
19
use Rinvex\Attributes\Events\EntityWasDeleted;
20
use Rinvex\Attributes\Support\RelationBuilder;
21
use Rinvex\Attributes\Support\ValueCollection;
22
use Illuminate\Support\Collection as BaseCollection;
23
24
trait Attributable
25
{
26
    /**
27
     * The entity attributes.
28
     *
29
     * @var \Illuminate\Database\Eloquent\Collection
30
     */
31
    protected static $entityAttributes;
32
33
    /**
34
     * The entity attribute value trash.
35
     *
36
     * @var \Illuminate\Support\Collection
37
     */
38
    protected $entityAttributeValueTrash;
39
40
    /**
41
     * The entity attribute relations.
42
     *
43
     * @var array
44
     */
45
    protected $entityAttributeRelations = [];
46
47
    /**
48
     * Determine if the entity attribute relations have been booted.
49
     *
50
     * @var bool
51
     */
52
    protected $entityAttributeRelationsBooted = false;
53
54
    /**
55
     * Boot the attributable trait for a model.
56
     *
57
     * @return void
58
     */
59
    public static function bootAttributable()
60
    {
61
        static::addGlobalScope(new EagerLoadScope());
62
        static::saved(EntityWasSaved::class.'@handle');
63
        static::deleted(EntityWasDeleted::class.'@handle');
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    protected function bootIfNotBooted()
70
    {
71
        parent::bootIfNotBooted();
72
73
        if (! $this->entityAttributeRelationsBooted) {
74
            app(RelationBuilder::class)->build($this);
75
76
            $this->entityAttributeRelationsBooted = true;
77
        }
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function relationsToArray()
84
    {
85
        $eavAttributes = [];
86
        $attributes = parent::relationsToArray();
87
        $relations = array_keys($this->getEntityAttributeRelations());
88
89
        foreach ($relations as $relation) {
90
            if (array_key_exists($relation, $attributes)) {
91
                $eavAttributes[$relation] = $this->getAttribute($relation) instanceof BaseCollection
92
                    ? $this->getAttribute($relation)->toArray() : $this->getAttribute($relation);
93
94
                // By unsetting the relation from the attributes array we will make
95
                // sure we do not provide a duplicity when adding the namespace.
96
                // Otherwise it would keep the relation as a key in the root.
97
                unset($attributes[$relation]);
98
            }
99
        }
100
101
        if (is_null($namespace = $this->getEntityAttributesNamespace())) {
102
            $attributes = array_merge($attributes, $eavAttributes);
103
        } else {
104
            Arr::set($attributes, $namespace, $eavAttributes);
105
        }
106
107
        return $attributes;
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function setRelation($key, $value)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
114
    {
115
        if ($value instanceof ValueCollection) {
116
            $value->link($this, $this->getEntityAttributes()->get($key));
117
        }
118
119
        return parent::setRelation($key, $value);
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function getRelationValue($key)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
126
    {
127
        $value = parent::getRelationValue($key);
128
129
        // In case any relation value is found, we will just provide it as is.
130
        // Otherwise, we will check if exists any attribute relation for the
131
        // given key. If so, we will load the relation calling its method.
132
        if (is_null($value) && ! $this->relationLoaded($key) && $this->isEntityAttributeRelation($key)) {
0 ignored issues
show
Documentation Bug introduced by
The method relationLoaded does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
133
            $value = $this->getRelationshipFromMethod($key);
0 ignored issues
show
Documentation Bug introduced by
The method getRelationshipFromMethod does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
134
        }
135
136
        if ($value instanceof ValueCollection) {
137
            $value->link($this, $this->getEntityAttributes()->get($key));
138
        }
139
140
        return $value;
141
    }
142
143
    /**
144
     * Get the entity attributes namespace if exists.
145
     *
146
     * @return string|null
147
     */
148
    public function getEntityAttributesNamespace(): ?string
149
    {
150
        return property_exists($this, 'entityAttributesNamespace') ? $this->entityAttributesNamespace : null;
0 ignored issues
show
Bug introduced by
The property entityAttributesNamespace does not seem to exist. Did you mean entityAttributes?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
151
    }
152
153
    /**
154
     * Get the entity attributes.
155
     *
156
     * @return \Illuminate\Database\Eloquent\Collection
157
     */
158
    public function getEntityAttributes(): Collection
159
    {
160
        $morphClass = $this->getMorphClass();
0 ignored issues
show
Documentation Bug introduced by
The method getMorphClass does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
161
        static::$entityAttributes = static::$entityAttributes ?? collect();
162
163
        if (! static::$entityAttributes->has($morphClass) && Schema::hasTable(config('rinvex.attributes.tables.attribute_entity'))) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 133 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
164
            $locale = app()->getLocale();
165
166
            /* This is a trial to implement per resource attributes,
167
               it's working but I don't like current implementation.
168
            $routeParam = request()->route($morphClass);
169
170
            // @TODO: This is REALLY REALLY BAD DESIGN!! But can't figure out a better way for now!!
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
171
            // Refactor required, we need to catch `$this` itself, we should NOT use request and routes here!!
172
            // But still at this very early stage, `$this` still not bound to model's data, so it's just empty object!
173
            $entityId = $routeParam && collect(class_uses_recursive(static::class))->contains(HashidsTrait::class) && ! is_numeric($routeParam)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 143 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
174
                ? optional(Hashids::decode($routeParam))[0] : $routeParam;
175
176
            $attributes = app('rinvex.attributes.attribute_entity')->where('entity_type', $morphClass)->where('entity_id', $entityId)->get()->pluck('attribute_id');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 164 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
177
             */
178
179
            $attributes = app('rinvex.attributes.attribute_entity')->where('entity_type', $morphClass)->get()->pluck('attribute_id');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 133 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
180
            static::$entityAttributes->put($morphClass, app('rinvex.attributes.attribute')->whereIn('id', $attributes)->orderBy('sort_order', 'ASC')->orderBy("name->\${$locale}", 'ASC')->get()->keyBy('slug'));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 209 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
181
        }
182
183
        return static::$entityAttributes->get($morphClass) ?? new Collection();
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    protected function fillableFromArray(array $attributes)
190
    {
191
        foreach (array_diff_key($attributes, array_flip($this->getFillable())) as $key => $value) {
0 ignored issues
show
Documentation Bug introduced by
The method getFillable does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
192
            if ($this->isEntityAttribute($key)) {
193
                $this->setEntityAttribute($key, $value);
194
            }
195
        }
196
197
        if (count($this->getFillable()) > 0 && ! static::$unguarded) {
0 ignored issues
show
Documentation Bug introduced by
The method getFillable does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
198
            return array_intersect_key($attributes, array_flip($this->getFillable()));
0 ignored issues
show
Documentation Bug introduced by
The method getFillable does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
199
        }
200
201
        return $attributes;
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function setAttribute($key, $value)
208
    {
209
        return $this->isEntityAttribute($key) ? $this->setEntityAttribute($key, $value) : parent::setAttribute($key, $value);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 125 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function getAttribute($key)
216
    {
217
        return $this->isEntityAttribute($key) ? $this->getEntityAttribute($key) : parent::getAttribute($key);
218
    }
219
220
    /**
221
     * Set the entity attribute relation.
222
     *
223
     * @param string $relation
224
     * @param mixed  $value
225
     *
226
     * @return $this
227
     */
228
    public function setEntityAttributeRelation(string $relation, $value)
229
    {
230
        $this->entityAttributeRelations[$relation] = $value;
231
232
        return $this;
233
    }
234
235
    /**
236
     * Check if the given key is an entity attribute relation.
237
     *
238
     * @param string $key
239
     *
240
     * @return bool
241
     */
242
    public function isEntityAttributeRelation(string $key): bool
243
    {
244
        return isset($this->entityAttributeRelations[$key]);
245
    }
246
247
    /**
248
     * Get the entity attribute value trash.
249
     *
250
     * @return \Illuminate\Support\Collection
251
     */
252
    public function getEntityAttributeValueTrash(): BaseCollection
253
    {
254
        return $this->entityAttributeValueTrash ?: $this->entityAttributeValueTrash = collect([]);
255
    }
256
257
    /**
258
     * Get the entity attribute relations.
259
     *
260
     * @return array
261
     */
262
    public function getEntityAttributeRelations(): array
263
    {
264
        return $this->entityAttributeRelations;
265
    }
266
267
    /**
268
     * Check if the given key is an entity attribute.
269
     *
270
     * @param string $key
271
     *
272
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
273
     */
274
    public function isEntityAttribute(string $key)
275
    {
276
        $key = $this->getEntityAttributeName($key);
277
278
        return $this->getEntityAttributes()->has($key);
279
    }
280
281
    /**
282
     * Get the entity attribute.
283
     *
284
     * @param string $key
285
     *
286
     * @return mixed
287
     */
288
    public function getEntityAttribute(string $key)
289
    {
290
        if ($this->isRawEntityAttribute($key)) {
291
            return $this->getEntityAttributeRelation($key);
292
        }
293
294
        return $this->getEntityAttributeValue($key);
295
    }
296
297
    /**
298
     * Get the entity attribute value.
299
     *
300
     * @param string $key
301
     *
302
     * @return mixed
303
     */
304
    protected function getEntityAttributeValue(string $key)
305
    {
306
        $value = $this->getEntityAttributeRelation($key);
307
308
        // In case we are accessing to a multivalued attribute, we will return
309
        // a collection with pairs of id and value content. Otherwise we'll
310
        // just return the single model value content as a plain result.
311
        if ($this->getEntityAttributes()->get($key)->is_collection) {
312
            return $value->pluck('content');
313
        }
314
315
        return ! is_null($value) ? $value->getAttribute('content') : null;
316
    }
317
318
    /**
319
     * Get the entity attribute relationship.
320
     *
321
     * @param string $key
322
     *
323
     * @return mixed
324
     */
325
    protected function getEntityAttributeRelation(string $key)
326
    {
327
        $key = $this->getEntityAttributeName($key);
328
329
        if ($this->relationLoaded($key)) {
0 ignored issues
show
Documentation Bug introduced by
The method relationLoaded does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
330
            return $this->getRelation($key);
0 ignored issues
show
Bug introduced by
The method getRelation() does not exist on Rinvex\Attributes\Traits\Attributable. Did you maybe mean getRelationValue()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
331
        }
332
333
        return $this->getRelationValue($key);
334
    }
335
336
    /**
337
     * Set the entity attribute.
338
     *
339
     * @param string $key
340
     * @param mixed  $value
341
     *
342
     * @return mixed
343
     */
344
    public function setEntityAttribute(string $key, $value)
345
    {
346
        $current = $this->getEntityAttributeRelation($key);
347
        $attribute = $this->getEntityAttributes()->get($key);
348
349
        // $current will always contain a collection when an attribute is multivalued
350
        // as morphMany provides collections even if no values were matched, making
351
        // us assume at least an empty collection object will be always provided.
352
        if ($attribute->is_collection) {
353
            if (is_null($current)) {
354
                $this->setRelation($key, $current = new ValueCollection());
355
            }
356
357
            $current->replace($value);
358
359
            return $this;
360
        }
361
362
        // If the attribute to set is a collection, it will be replaced by the
363
        // new value. If the value model does not exist, we will just create
364
        // and set a new value model, otherwise its value will get updated.
365
        if (is_null($current)) {
366
            return $this->setEntityAttributeValue($attribute, $value);
367
        }
368
369
        if ($value instanceof Value) {
370
            $value = $value->getAttribute('content');
371
        }
372
373
        $current->setAttribute('entity_type', $this->getMorphClass());
0 ignored issues
show
Documentation Bug introduced by
The method getMorphClass does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
374
375
        return $current->setAttribute('content', $value);
376
    }
377
378
    /**
379
     * Set the entity attribute value.
380
     *
381
     * @param \Rinvex\Attributes\Models\Attribute $attribute
382
     * @param mixed                               $value
383
     *
384
     * @return $this
385
     */
386
    protected function setEntityAttributeValue(Attribute $attribute, $value)
387
    {
388
        if (! is_null($value) && ! $value instanceof Value) {
389
            $model = Attribute::getTypeModel($attribute->getAttribute('type'));
390
            $instance = new $model();
391
392
            $instance->setAttribute('entity_id', $this->getKey());
0 ignored issues
show
Documentation Bug introduced by
The method getKey does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
393
            $instance->setAttribute('entity_type', $this->getMorphClass());
0 ignored issues
show
Documentation Bug introduced by
The method getMorphClass does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
394
            $instance->setAttribute($attribute->getForeignKey(), $attribute->getKey());
395
            $instance->setAttribute('content', $value);
396
397
            $value = $instance;
398
        }
399
400
        return $this->setRelation($attribute->getAttribute('slug'), $value);
401
    }
402
403
    /**
404
     * Determine if the given key is a raw entity attribute.
405
     *
406
     * @param string $key
407
     *
408
     * @return bool
409
     */
410
    protected function isRawEntityAttribute(string $key): bool
411
    {
412
        return (bool) preg_match('/^raw(\w+)object$/i', $key);
413
    }
414
415
    /**
416
     * Get entity attribute bare name.
417
     *
418
     * @param string $key
419
     *
420
     * @return string
421
     */
422
    protected function getEntityAttributeName(string $key): string
423
    {
424
        return $this->isRawEntityAttribute($key) ? camel_case(str_ireplace(['raw', 'object'], ['', ''], $key)) : $key;
425
    }
426
427
    /**
428
     * Get the attributes attached to this entity.
429
     *
430
     * @return \Illuminate\Database\Eloquent\Collection|null
431
     */
432
    public function attributes(): ?Collection
433
    {
434
        return $this->getEntityAttributes();
435
    }
436
437
    /**
438
     * Scope query with the given entity attribute.
439
     *
440
     * @param \Illuminate\Database\Eloquent\Builder $builder
441
     * @param string                                $key
442
     * @param mixed                                 $value
443
     *
444
     * @return \Illuminate\Database\Eloquent\Builder
445
     */
446
    public function scopeHasAttribute(Builder $builder, string $key, $value): Builder
447
    {
448
        return $builder->whereHas($key, function (Builder $builder) use ($value) {
449
            $builder->where('content', $value)->where('entity_type', $this->getMorphClass());
0 ignored issues
show
Documentation Bug introduced by
The method getMorphClass does not exist on object<Rinvex\Attributes\Traits\Attributable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
450
        });
451
    }
452
453
    /**
454
     * Dynamically pipe calls to attribute relations.
455
     *
456
     * @param string $method
457
     * @param array  $parameters
458
     *
459
     * @return mixed
460
     */
461
    public function __call($method, $parameters)
462
    {
463
        if ($this->isEntityAttributeRelation($method)) {
464
            $relation = $this->entityAttributeRelations[$method] instanceof Closure
465
                ? $this->entityAttributeRelations[$method]
466
                : (new Serializer())->unserialize($this->entityAttributeRelations[$method]);
467
468
            return call_user_func_array($relation, $parameters);
469
        }
470
471
        return parent::__call($method, $parameters);
472
    }
473
474
    /**
475
     * Prepare the instance for serialization.
476
     *
477
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<integer|string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
478
     */
479
    public function __sleep()
480
    {
481
        if ($this->entityAttributeRelations && current($this->entityAttributeRelations) instanceof Closure) {
482
            $relations = $this->entityAttributeRelations;
483
            $this->entityAttributeRelations = [];
484
485
            foreach ($relations as $key => $value) {
486
                if ($value instanceof Closure) {
487
                    $this->setEntityAttributeRelation($key, (new Serializer())->serialize($value));
488
                }
489
            }
490
        }
491
492
        return array_keys(get_object_vars($this));
493
    }
494
495
    /**
496
     * Restore the model after serialization.
497
     *
498
     * @return void
499
     */
500
    public function __wakeup()
501
    {
502
        parent::__wakeup();
503
504
        if ($this->entityAttributeRelations && is_string(current($this->entityAttributeRelations))) {
505
            $relations = $this->entityAttributeRelations;
506
            $this->entityAttributeRelations = [];
507
508
            foreach ($relations as $key => $value) {
509
                if (is_string($value)) {
510
                    $this->setEntityAttributeRelation($key, (new Serializer())->unserialize($value));
511
                }
512
            }
513
        }
514
    }
515
}
516