Issues (23)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Traits/Attributable.php (17 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\RelationBuilder;
20
use Rinvex\Attributes\Support\ValueCollection;
21
use Illuminate\Support\Collection as BaseCollection;
22
23
trait Attributable
24
{
25
    /**
26
     * The entity attributes.
27
     *
28
     * @var \Illuminate\Database\Eloquent\Collection
29
     */
30
    protected static $entityAttributes;
31
32
    /**
33
     * The entity attribute value trash.
34
     *
35
     * @var \Illuminate\Support\Collection
36
     */
37
    protected $entityAttributeValueTrash;
38
39
    /**
40
     * The entity attribute relations.
41
     *
42
     * @var array
43
     */
44
    protected $entityAttributeRelations = [];
45
46
    /**
47
     * Determine if the entity attribute relations have been booted.
48
     *
49
     * @var bool
50
     */
51
    protected $entityAttributeRelationsBooted = false;
52
53
    /**
54
     * Boot the attributable trait for a model.
55
     *
56
     * @return void
57
     */
58
    public static function bootAttributable()
59
    {
60
        static::addGlobalScope(new EagerLoadScope());
61
        static::saved(EntityWasSaved::class.'@handle');
62
        static::deleted(EntityWasDeleted::class.'@handle');
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    protected function bootIfNotBooted()
69
    {
70
        parent::bootIfNotBooted();
71
72
        if (! $this->entityAttributeRelationsBooted) {
73
            app(RelationBuilder::class)->build($this);
74
75
            $this->entityAttributeRelationsBooted = true;
76
        }
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function relationsToArray()
83
    {
84
        $eavAttributes = [];
85
        $attributes = parent::relationsToArray();
86
        $relations = array_keys($this->getEntityAttributeRelations());
87
88
        foreach ($relations as $relation) {
89
            if (array_key_exists($relation, $attributes)) {
90
                $eavAttributes[$relation] = $this->getAttribute($relation) instanceof BaseCollection
91
                    ? $this->getAttribute($relation)->toArray() : $this->getAttribute($relation);
92
93
                // By unsetting the relation from the attributes array we will make
94
                // sure we do not provide a duplicity when adding the namespace.
95
                // Otherwise it would keep the relation as a key in the root.
96
                unset($attributes[$relation]);
97
            }
98
        }
99
100
        if (is_null($namespace = $this->getEntityAttributesNamespace())) {
101
            $attributes = array_merge($attributes, $eavAttributes);
102
        } else {
103
            Arr::set($attributes, $namespace, $eavAttributes);
104
        }
105
106
        return $attributes;
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function setRelation($key, $value)
0 ignored issues
show
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...
113
    {
114
        if ($value instanceof ValueCollection) {
115
            $value->link($this, $this->getEntityAttributes()->get($key));
116
        }
117
118
        return parent::setRelation($key, $value);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function getRelationValue($key)
0 ignored issues
show
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...
125
    {
126
        $value = parent::getRelationValue($key);
127
128
        // In case any relation value is found, we will just provide it as is.
129
        // Otherwise, we will check if exists any attribute relation for the
130
        // given key. If so, we will load the relation calling its method.
131
        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...
132
            $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...
133
        }
134
135
        if ($value instanceof ValueCollection) {
136
            $value->link($this, $this->getEntityAttributes()->get($key));
137
        }
138
139
        return $value;
140
    }
141
142
    /**
143
     * Get the entity attributes namespace if exists.
144
     *
145
     * @return string|null
146
     */
147
    public function getEntityAttributesNamespace(): ?string
148
    {
149
        return property_exists($this, 'entityAttributesNamespace') ? $this->entityAttributesNamespace : null;
0 ignored issues
show
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...
150
    }
151
152
    /**
153
     * Get the entity attributes.
154
     *
155
     * @return \Illuminate\Database\Eloquent\Collection
156
     */
157
    public function getEntityAttributes(): Collection
158
    {
159
        $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...
160
        static::$entityAttributes = static::$entityAttributes ?? collect();
161
162
        if (! static::$entityAttributes->has($morphClass) && Schema::hasTable(config('rinvex.attributes.tables.attribute_entity'))) {
163
            $locale = app()->getLocale();
164
165
            /* This is a trial to implement per resource attributes,
166
               it's working but I don't like current implementation.
167
            $routeParam = request()->route($morphClass);
168
169
            // @TODO: This is REALLY REALLY BAD DESIGN!! But can't figure out a better way for now!!
170
            // Refactor required, we need to catch `$this` itself, we should NOT use request and routes here!!
171
            // But still at this very early stage, `$this` still not bound to model's data, so it's just empty object!
172
            $entityId = $routeParam && collect(class_uses_recursive(static::class))->contains(HashidsTrait::class) && ! is_numeric($routeParam)
173
                ? optional(Hashids::decode($routeParam))[0] : $routeParam;
174
175
            $attributes = app('rinvex.attributes.attribute_entity')->where('entity_type', $morphClass)->where('entity_id', $entityId)->get()->pluck('attribute_id');
176
             */
177
178
            $attributes = app('rinvex.attributes.attribute_entity')->where('entity_type', $morphClass)->get()->pluck('attribute_id');
179
            static::$entityAttributes->put($morphClass, app('rinvex.attributes.attribute')->whereIn('id', $attributes)->orderBy('sort_order', 'ASC')->orderBy("name->\${$locale}", 'ASC')->get()->keyBy('slug'));
180
        }
181
182
        return static::$entityAttributes->get($morphClass) ?? new Collection();
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    protected function fillableFromArray(array $attributes)
189
    {
190
        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...
191
            if ($this->isEntityAttribute($key)) {
192
                $this->setEntityAttribute($key, $value);
193
            }
194
        }
195
196
        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...
197
            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...
198
        }
199
200
        return $attributes;
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function setAttribute($key, $value)
207
    {
208
        return $this->isEntityAttribute($key) ? $this->setEntityAttribute($key, $value) : parent::setAttribute($key, $value);
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function getAttribute($key)
215
    {
216
        return $this->isEntityAttribute($key) ? $this->getEntityAttribute($key) : parent::getAttribute($key);
217
    }
218
219
    /**
220
     * Set the entity attribute relation.
221
     *
222
     * @param string $relation
223
     * @param mixed  $value
224
     *
225
     * @return $this
226
     */
227
    public function setEntityAttributeRelation(string $relation, $value)
228
    {
229
        $this->entityAttributeRelations[$relation] = $value;
230
231
        return $this;
232
    }
233
234
    /**
235
     * Check if the given key is an entity attribute relation.
236
     *
237
     * @param string $key
238
     *
239
     * @return bool
240
     */
241
    public function isEntityAttributeRelation(string $key): bool
242
    {
243
        return isset($this->entityAttributeRelations[$key]);
244
    }
245
246
    /**
247
     * Get the entity attribute value trash.
248
     *
249
     * @return \Illuminate\Support\Collection
250
     */
251
    public function getEntityAttributeValueTrash(): BaseCollection
252
    {
253
        return $this->entityAttributeValueTrash ?: $this->entityAttributeValueTrash = collect([]);
254
    }
255
256
    /**
257
     * Get the entity attribute relations.
258
     *
259
     * @return array
260
     */
261
    public function getEntityAttributeRelations(): array
262
    {
263
        return $this->entityAttributeRelations;
264
    }
265
266
    /**
267
     * Check if the given key is an entity attribute.
268
     *
269
     * @param string $key
270
     *
271
     * @return mixed
0 ignored issues
show
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...
272
     */
273
    public function isEntityAttribute(string $key)
274
    {
275
        $key = $this->getEntityAttributeName($key);
276
277
        return $this->getEntityAttributes()->has($key);
278
    }
279
280
    /**
281
     * Get the entity attribute.
282
     *
283
     * @param string $key
284
     *
285
     * @return mixed
286
     */
287
    public function getEntityAttribute(string $key)
288
    {
289
        if ($this->isRawEntityAttribute($key)) {
290
            return $this->getEntityAttributeRelation($key);
291
        }
292
293
        return $this->getEntityAttributeValue($key);
294
    }
295
296
    /**
297
     * Get the entity attribute value.
298
     *
299
     * @param string $key
300
     *
301
     * @return mixed
302
     */
303
    protected function getEntityAttributeValue(string $key)
304
    {
305
        $value = $this->getEntityAttributeRelation($key);
306
307
        // In case we are accessing to a multivalued attribute, we will return
308
        // a collection with pairs of id and value content. Otherwise we'll
309
        // just return the single model value content as a plain result.
310
        if ($this->getEntityAttributes()->get($key)->is_collection) {
311
            return $value->pluck('content');
312
        }
313
314
        return ! is_null($value) ? $value->getAttribute('content') : null;
315
    }
316
317
    /**
318
     * Get the entity attribute relationship.
319
     *
320
     * @param string $key
321
     *
322
     * @return mixed
323
     */
324
    protected function getEntityAttributeRelation(string $key)
325
    {
326
        $key = $this->getEntityAttributeName($key);
327
328
        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...
329
            return $this->getRelation($key);
0 ignored issues
show
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...
330
        }
331
332
        return $this->getRelationValue($key);
333
    }
334
335
    /**
336
     * Set the entity attribute.
337
     *
338
     * @param string $key
339
     * @param mixed  $value
340
     *
341
     * @return mixed
342
     */
343
    public function setEntityAttribute(string $key, $value)
344
    {
345
        $current = $this->getEntityAttributeRelation($key);
346
        $attribute = $this->getEntityAttributes()->get($key);
347
348
        // $current will always contain a collection when an attribute is multivalued
349
        // as morphMany provides collections even if no values were matched, making
350
        // us assume at least an empty collection object will be always provided.
351
        if ($attribute->is_collection) {
352
            if (is_null($current)) {
353
                $this->setRelation($key, $current = new ValueCollection());
354
            }
355
356
            $current->replace($value);
357
358
            return $this;
359
        }
360
361
        // If the attribute to set is a collection, it will be replaced by the
362
        // new value. If the value model does not exist, we will just create
363
        // and set a new value model, otherwise its value will get updated.
364
        if (is_null($current)) {
365
            return $this->setEntityAttributeValue($attribute, $value);
366
        }
367
368
        if ($value instanceof Value) {
369
            $value = $value->getAttribute('content');
370
        }
371
372
        $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...
373
374
        return $current->setAttribute('content', $value);
375
    }
376
377
    /**
378
     * Set the entity attribute value.
379
     *
380
     * @param \Rinvex\Attributes\Models\Attribute $attribute
381
     * @param mixed                               $value
382
     *
383
     * @return $this
384
     */
385
    protected function setEntityAttributeValue(Attribute $attribute, $value)
386
    {
387
        if (! is_null($value) && ! $value instanceof Value) {
388
            $model = Attribute::getTypeModel($attribute->getAttribute('type'));
389
            $instance = new $model();
390
391
            $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...
392
            $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...
393
            $instance->setAttribute($attribute->getForeignKey(), $attribute->getKey());
394
            $instance->setAttribute('content', $value);
395
396
            $value = $instance;
397
        }
398
399
        return $this->setRelation($attribute->getAttribute('slug'), $value);
400
    }
401
402
    /**
403
     * Determine if the given key is a raw entity attribute.
404
     *
405
     * @param string $key
406
     *
407
     * @return bool
408
     */
409
    protected function isRawEntityAttribute(string $key): bool
410
    {
411
        return (bool) preg_match('/^raw(\w+)object$/i', $key);
412
    }
413
414
    /**
415
     * Get entity attribute bare name.
416
     *
417
     * @param string $key
418
     *
419
     * @return string
420
     */
421
    protected function getEntityAttributeName(string $key): string
422
    {
423
        return $this->isRawEntityAttribute($key) ? Str::camel(str_ireplace(['raw', 'object'], ['', ''], $key)) : $key;
424
    }
425
426
    /**
427
     * Get the attributes attached to this entity.
428
     *
429
     * @return \Illuminate\Database\Eloquent\Collection|null
430
     */
431
    public function attributes(): ?Collection
432
    {
433
        return $this->getEntityAttributes();
434
    }
435
436
    /**
437
     * Scope query with the given entity attribute.
438
     *
439
     * @param \Illuminate\Database\Eloquent\Builder $builder
440
     * @param string                                $key
441
     * @param mixed                                 $value
442
     *
443
     * @return \Illuminate\Database\Eloquent\Builder
444
     */
445
    public function scopeHasAttribute(Builder $builder, string $key, $value): Builder
446
    {
447
        return $builder->whereHas($key, function (Builder $builder) use ($value) {
448
            $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...
449
        });
450
    }
451
452
    /**
453
     * Dynamically pipe calls to attribute relations.
454
     *
455
     * @param string $method
456
     * @param array  $parameters
457
     *
458
     * @return mixed
459
     */
460
    public function __call($method, $parameters)
461
    {
462
        if ($this->isEntityAttributeRelation($method)) {
463
            $relation = $this->entityAttributeRelations[$method] instanceof Closure
464
                ? $this->entityAttributeRelations[$method]
465
                : (new Serializer())->unserialize($this->entityAttributeRelations[$method]);
466
467
            return call_user_func_array($relation, $parameters);
468
        }
469
470
        return parent::__call($method, $parameters);
471
    }
472
473
    /**
474
     * Prepare the instance for serialization.
475
     *
476
     * @return array
0 ignored issues
show
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...
477
     */
478
    public function __sleep()
479
    {
480
        if ($this->entityAttributeRelations && current($this->entityAttributeRelations) instanceof Closure) {
481
            $relations = $this->entityAttributeRelations;
482
            $this->entityAttributeRelations = [];
483
484
            foreach ($relations as $key => $value) {
485
                if ($value instanceof Closure) {
486
                    $this->setEntityAttributeRelation($key, (new Serializer())->serialize($value));
487
                }
488
            }
489
        }
490
491
        return array_keys(get_object_vars($this));
492
    }
493
494
    /**
495
     * Restore the model after serialization.
496
     *
497
     * @return void
498
     */
499
    public function __wakeup()
500
    {
501
        parent::__wakeup();
502
503
        if ($this->entityAttributeRelations && is_string(current($this->entityAttributeRelations))) {
504
            $relations = $this->entityAttributeRelations;
505
            $this->entityAttributeRelations = [];
506
507
            foreach ($relations as $key => $value) {
508
                if (is_string($value)) {
509
                    $this->setEntityAttributeRelation($key, (new Serializer())->unserialize($value));
510
                }
511
            }
512
        }
513
    }
514
}
515