Completed
Pull Request — develop (#132)
by
unknown
01:16
created

Attributable::clearAttributableCache()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 3
nc 2
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 Illuminate\Support\Str;
11
use SuperClosure\Serializer;
12
use Rinvex\Attributes\Models\Value;
13
use Rinvex\Attributes\Models\Attribute;
14
use Illuminate\Database\Eloquent\Builder;
15
use Illuminate\Database\Eloquent\Collection;
16
use Rinvex\Attributes\Events\EntityWasSaved;
17
use Rinvex\Attributes\Scopes\EagerLoadScope;
18
use Rinvex\Attributes\Events\EntityWasDeleted;
19
use Rinvex\Attributes\Support\ValueCollection;
20
use Illuminate\Support\Collection as BaseCollection;
21
22
trait Attributable
23
{
24
    /**
25
     * The entity attributes.
26
     *
27
     * @var \Illuminate\Database\Eloquent\Collection
28
     */
29
    protected static $entityAttributes;
30
31
    /**
32
     * The entity attribute value trash.
33
     *
34
     * @var \Illuminate\Support\Collection
35
     */
36
    protected $entityAttributeValueTrash;
37
38
    /**
39
     * The entity attribute relations.
40
     *
41
     * @var array
42
     */
43
    protected $entityAttributeRelations = [];
44
45
    /**
46
     * Determine if the entity attribute relations have been booted.
47
     *
48
     * @var bool
49
     */
50
    protected $entityAttributeRelationsBooted = false;
51
52
    /**
53
     * Boot the attributable trait for a model.
54
     *
55
     * @return void
56
     */
57
    public static function bootAttributable()
58
    {
59
        static::addGlobalScope(new EagerLoadScope());
60
        static::saved(EntityWasSaved::class.'@handle');
61
        static::deleted(EntityWasDeleted::class.'@handle');
62
    }
63
64
    /**
65
     * Check if the model needs to be booted and if so, do it.
66
     *
67
     * @return void
68
     */
69
    protected function bootIfNotBooted()
70
    {
71
        parent::bootIfNotBooted();
72
73
        if (! $this->entityAttributeRelationsBooted) {
74
            $attributes = $this->getEntityAttributes();
75
76
            // We will manually add a relationship for every attribute registered
77
            // of this entity. Once we know the relation method we have to use,
78
            // we will just add it to the entityAttributeRelations property.
79
            foreach ($attributes as $attribute) {
80
                $method = (bool) ($attribute->getAttributes()['is_collection'] ?? null) ? 'hasMany' : 'hasOne';
81
82
                // This will return a closure fully binded to the current entity instance,
83
                // which will help us to simulate any relation as if it was made in the
84
                // original entity class definition using a function statement.
85
                $relation = Closure::bind(function () use ($attribute, $method) {
86
                    $relation = $this->{$method}(Attribute::getTypeModel($attribute->getAttribute('type')), 'entity_id', $this->getKeyName());
0 ignored issues
show
Documentation Bug introduced by
The method getKeyName 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...
87
88
                    // Since an attribute could be attached to multiple entities, then values could have
89
                    // same entity ID, but for different entity types, so we need to add type where
90
                    // clause to fetch only values related to the given entity ID + entity type.
91
                    $relation->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...
92
93
                    // We add a where clause in order to fetch only the elements that are
94
                    // related to the given attribute. If no condition is set, it will
95
                    // fetch all the value rows related to the current entity.
96
                    return $relation->where($attribute->getForeignKey(), $attribute->getKey());
97
                }, $this, get_class($this));
98
99
                $this->setEntityAttributeRelation((string) ($attribute->getAttributes()['slug'] ?? null), $relation);
100
            }
101
102
            $this->entityAttributeRelationsBooted = true;
103
        }
104
    }
105
106
    /**
107
     * Set the given relationship on the model.
108
     *
109
     * @param string $relation
0 ignored issues
show
Bug introduced by
There is no parameter named $relation. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
110
     * @param mixed  $value
0 ignored issues
show
Bug introduced by
There is no parameter named $value. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
111
     *
112
     * @return $this
0 ignored issues
show
Documentation introduced by
Should the return type not be array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
113
     */
114
    public function relationsToArray()
115
    {
116
        $eavAttributes = [];
117
        $attributes = parent::relationsToArray();
118
        $relations = array_keys($this->getEntityAttributeRelations());
119
120
        foreach ($relations as $relation) {
121
            if (array_key_exists($relation, $attributes)) {
122
                $eavAttributes[$relation] = $this->getAttribute($relation) instanceof BaseCollection
123
                    ? $this->getAttribute($relation)->toArray() : $this->getAttribute($relation);
124
125
                // By unsetting the relation from the attributes array we will make
126
                // sure we do not provide a duplicity when adding the namespace.
127
                // Otherwise it would keep the relation as a key in the root.
128
                unset($attributes[$relation]);
129
            }
130
        }
131
132
        if (is_null($namespace = $this->getEntityAttributesNamespace())) {
133
            $attributes = array_merge($attributes, $eavAttributes);
134
        } else {
135
            Arr::set($attributes, $namespace, $eavAttributes);
136
        }
137
138
        return $attributes;
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    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...
145
    {
146
        if ($value instanceof ValueCollection) {
147
            $value->link($this, $this->getEntityAttributes()->get($key));
148
        }
149
150
        return parent::setRelation($key, $value);
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    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...
157
    {
158
        $value = parent::getRelationValue($key);
159
160
        // In case any relation value is found, we will just provide it as is.
161
        // Otherwise, we will check if exists any attribute relation for the
162
        // given key. If so, we will load the relation calling its method.
163
        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...
164
            $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...
165
        }
166
167
        if ($value instanceof ValueCollection) {
168
            $value->link($this, $this->getEntityAttributes()->get($key));
169
        }
170
171
        return $value;
172
    }
173
174
    /**
175
     * Get the entity attributes namespace if exists.
176
     *
177
     * @return string|null
178
     */
179
    public function getEntityAttributesNamespace(): ?string
180
    {
181
        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...
182
    }
183
184
    /**
185
     * Get the entity attributes.
186
     *
187
     * @return \Illuminate\Database\Eloquent\Collection
188
     */
189
    public function getEntityAttributes(): Collection
190
    {
191
        $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...
192
        static::$entityAttributes = static::$entityAttributes ?? collect();
193
194
        if (! static::$entityAttributes->has($morphClass) && Schema::hasTable(config('rinvex.attributes.tables.attribute_entity'))) {
195
            $locale = app()->getLocale();
196
197
            /* This is a trial to implement per resource attributes,
198
               it's working but I don't like current implementation.
199
            $routeParam = request()->route($morphClass);
200
201
            // @TODO: This is REALLY REALLY BAD DESIGN!! But can't figure out a better way for now!!
202
            // Refactor required, we need to catch `$this` itself, we should NOT use request and routes here!!
203
            // But still at this very early stage, `$this` still not bound to model's data, so it's just empty object!
204
            $entityId = $routeParam && collect(class_uses_recursive(static::class))->contains(HashidsTrait::class) && ! is_numeric($routeParam)
205
                ? optional(Hashids::decode($routeParam))[0] : $routeParam;
206
207
            $attributes = app('rinvex.attributes.attribute_entity')->where('entity_type', $morphClass)->where('entity_id', $entityId)->get()->pluck('attribute_id');
208
             */
209
210
            $attributes = app('rinvex.attributes.attribute_entity')->where('entity_type', $morphClass)->get()->pluck('attribute_id');
211
            static::$entityAttributes->put($morphClass, app('rinvex.attributes.attribute')->whereIn('id', $attributes)->orderBy('sort_order', 'ASC')->orderBy("name->\${$locale}", 'ASC')->get()->keyBy('slug'));
212
        }
213
214
        return static::$entityAttributes->get($morphClass) ?? new Collection();
215
    }
216
217
    /**
218
     * Clear the static attributes cache for this model.
219
     *
220
     * @return void
221
     */
222
    public function clearAttributableCache()
223
    {
224
        $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...
225
        if (static::$entityAttributes && static::$entityAttributes->has($morphClass)) {
226
            static::$entityAttributes->forget($morphClass);
227
        }
228
    }
229
230
    /**
231
     * Get the fillable attributes of a given array.
232
     *
233
     * @param array $attributes
234
     *
235
     * @return array
236
     */
237
    protected function fillableFromArray(array $attributes)
238
    {
239
        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...
240
            if ($this->isEntityAttribute($key)) {
241
                $this->setEntityAttribute($key, $value);
242
            }
243
        }
244
245
        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...
246
            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...
247
        }
248
249
        return $attributes;
250
    }
251
252
    /**
253
     * Set a given attribute on the model.
254
     *
255
     * @param string $key
256
     * @param mixed  $value
257
     *
258
     * @return mixed
259
     */
260
    public function setAttribute($key, $value)
261
    {
262
        return $this->isEntityAttribute($key) ? $this->setEntityAttribute($key, $value) : parent::setAttribute($key, $value);
263
    }
264
265
    /**
266
     * Get an attribute from the model.
267
     *
268
     * @param string $key
269
     *
270
     * @return mixed
271
     */
272
    public function getAttribute($key)
273
    {
274
        return $this->isEntityAttribute($key) ? $this->getEntityAttribute($key) : parent::getAttribute($key);
275
    }
276
277
    /**
278
     * Set the entity attribute relation.
279
     *
280
     * @param string $relation
281
     * @param mixed  $value
282
     *
283
     * @return $this
284
     */
285
    public function setEntityAttributeRelation(string $relation, $value)
286
    {
287
        $this->entityAttributeRelations[$relation] = $value;
288
289
        return $this;
290
    }
291
292
    /**
293
     * Check if the given key is an entity attribute relation.
294
     *
295
     * @param string $key
296
     *
297
     * @return bool
298
     */
299
    public function isEntityAttributeRelation(string $key): bool
300
    {
301
        return isset($this->entityAttributeRelations[$key]);
302
    }
303
304
    /**
305
     * Get the entity attribute value trash.
306
     *
307
     * @return \Illuminate\Support\Collection
308
     */
309
    public function getEntityAttributeValueTrash(): BaseCollection
310
    {
311
        return $this->entityAttributeValueTrash ?: $this->entityAttributeValueTrash = collect([]);
312
    }
313
314
    /**
315
     * Get the entity attribute relations.
316
     *
317
     * @return array
318
     */
319
    public function getEntityAttributeRelations(): array
320
    {
321
        return $this->entityAttributeRelations;
322
    }
323
324
    /**
325
     * Check if the given key is an entity attribute.
326
     *
327
     * @param string $key
328
     *
329
     * @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...
330
     */
331
    public function isEntityAttribute(string $key)
332
    {
333
        $key = $this->getEntityAttributeName($key);
334
335
        return $this->getEntityAttributes()->has($key);
336
    }
337
338
    /**
339
     * Get the entity attribute.
340
     *
341
     * @param string $key
342
     *
343
     * @return mixed
344
     */
345
    public function getEntityAttribute(string $key)
346
    {
347
        if ($this->isRawEntityAttribute($key)) {
348
            return $this->getEntityAttributeRelation($key);
349
        }
350
351
        return $this->getEntityAttributeValue($key);
352
    }
353
354
    /**
355
     * Get the entity attribute value.
356
     *
357
     * @param string $key
358
     *
359
     * @return mixed
360
     */
361
    protected function getEntityAttributeValue(string $key)
362
    {
363
        $value = $this->getEntityAttributeRelation($key);
364
365
        // In case we are accessing to a multivalued attribute, we will return
366
        // a collection with pairs of id and value content. Otherwise we'll
367
        // just return the single model value content as a plain result.
368
        if ($this->getEntityAttributes()->get($key)->is_collection) {
369
            return $value->pluck('content');
370
        }
371
372
        return ! is_null($value) ? $value->getAttribute('content') : null;
373
    }
374
375
    /**
376
     * Get the entity attribute relationship.
377
     *
378
     * @param string $key
379
     *
380
     * @return mixed
381
     */
382
    protected function getEntityAttributeRelation(string $key)
383
    {
384
        $key = $this->getEntityAttributeName($key);
385
386
        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...
387
            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...
388
        }
389
390
        return $this->getRelationValue($key);
391
    }
392
393
    /**
394
     * Set the entity attribute.
395
     *
396
     * @param string $key
397
     * @param mixed  $value
398
     *
399
     * @return mixed
400
     */
401
    public function setEntityAttribute(string $key, $value)
402
    {
403
        $current = $this->getEntityAttributeRelation($key);
404
        $attribute = $this->getEntityAttributes()->get($key);
405
406
        // $current will always contain a collection when an attribute is multivalued
407
        // as morphMany provides collections even if no values were matched, making
408
        // us assume at least an empty collection object will be always provided.
409
        if ($attribute->is_collection) {
410
            if (is_null($current)) {
411
                $this->setRelation($key, $current = new ValueCollection());
412
            }
413
414
            $current->replace($value);
415
416
            return $this;
417
        }
418
419
        // If the attribute to set is a collection, it will be replaced by the
420
        // new value. If the value model does not exist, we will just create
421
        // and set a new value model, otherwise its value will get updated.
422
        if (is_null($current)) {
423
            return $this->setEntityAttributeValue($attribute, $value);
424
        }
425
426
        if ($value instanceof Value) {
427
            $value = $value->getAttribute('content');
428
        }
429
430
        $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...
431
432
        return $current->setAttribute('content', $value);
433
    }
434
435
    /**
436
     * Set the entity attribute value.
437
     *
438
     * @param \Rinvex\Attributes\Models\Attribute $attribute
439
     * @param mixed                               $value
440
     *
441
     * @return $this
442
     */
443
    protected function setEntityAttributeValue(Attribute $attribute, $value)
444
    {
445
        if (! is_null($value) && ! $value instanceof Value) {
446
            $model = Attribute::getTypeModel($attribute->getAttribute('type'));
447
            $instance = new $model();
448
449
            $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...
450
            $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...
451
            $instance->setAttribute($attribute->getForeignKey(), $attribute->getKey());
452
            $instance->setAttribute('content', $value);
453
454
            $value = $instance;
455
        }
456
457
        return $this->setRelation($attribute->getAttribute('slug'), $value);
458
    }
459
460
    /**
461
     * Determine if the given key is a raw entity attribute.
462
     *
463
     * @param string $key
464
     *
465
     * @return bool
466
     */
467
    protected function isRawEntityAttribute(string $key): bool
468
    {
469
        return (bool) preg_match('/^raw(\w+)object$/i', $key);
470
    }
471
472
    /**
473
     * Get entity attribute bare name.
474
     *
475
     * @param string $key
476
     *
477
     * @return string
478
     */
479
    protected function getEntityAttributeName(string $key): string
480
    {
481
        return $this->isRawEntityAttribute($key) ? Str::camel(str_ireplace(['raw', 'object'], ['', ''], $key)) : $key;
482
    }
483
484
    /**
485
     * Get the attributes attached to this entity.
486
     *
487
     * @return \Illuminate\Database\Eloquent\Collection|null
488
     */
489
    public function attributes(): ?Collection
490
    {
491
        return $this->getEntityAttributes();
492
    }
493
494
    /**
495
     * Scope query with the given entity attribute.
496
     *
497
     * @param \Illuminate\Database\Eloquent\Builder $builder
498
     * @param string                                $key
499
     * @param mixed                                 $value
500
     *
501
     * @return \Illuminate\Database\Eloquent\Builder
502
     */
503
    public function scopeHasAttribute(Builder $builder, string $key, $value): Builder
504
    {
505
        return $builder->whereHas($key, function (Builder $builder) use ($value) {
506
            $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...
507
        });
508
    }
509
510
    /**
511
     * Dynamically pipe calls to attribute relations.
512
     *
513
     * @param string $method
514
     * @param array  $parameters
515
     *
516
     * @return mixed
517
     */
518
    public function __call($method, $parameters)
519
    {
520
        if ($this->isEntityAttributeRelation($method)) {
521
            $relation = $this->entityAttributeRelations[$method] instanceof Closure
522
                ? $this->entityAttributeRelations[$method]
523
                : (new Serializer())->unserialize($this->entityAttributeRelations[$method]);
524
525
            return call_user_func_array($relation, $parameters);
526
        }
527
528
        return parent::__call($method, $parameters);
529
    }
530
531
    /**
532
     * Prepare the instance for serialization.
533
     *
534
     * @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...
535
     */
536
    public function __sleep()
537
    {
538
        if ($this->entityAttributeRelations && current($this->entityAttributeRelations) instanceof Closure) {
539
            $relations = $this->entityAttributeRelations;
540
            $this->entityAttributeRelations = [];
541
542
            foreach ($relations as $key => $value) {
543
                if ($value instanceof Closure) {
544
                    $this->setEntityAttributeRelation($key, (new Serializer())->serialize($value));
545
                }
546
            }
547
        }
548
549
        return array_keys(get_object_vars($this));
550
    }
551
552
    /**
553
     * Restore the model after serialization.
554
     *
555
     * @return void
556
     */
557
    public function __wakeup()
558
    {
559
        parent::__wakeup();
560
561
        if ($this->entityAttributeRelations && is_string(current($this->entityAttributeRelations))) {
562
            $relations = $this->entityAttributeRelations;
563
            $this->entityAttributeRelations = [];
564
565
            foreach ($relations as $key => $value) {
566
                if (is_string($value)) {
567
                    $this->setEntityAttributeRelation($key, (new Serializer())->unserialize($value));
568
                }
569
            }
570
        }
571
    }
572
}
573