Passed
Pull Request — master (#390)
by Quetzy
04:34
created

Auditable::enableAuditing()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * This file is part of the Laravel Auditing package.
4
 *
5
 * @author     Antério Vieira <[email protected]>
6
 * @author     Quetzy Garcia  <[email protected]>
7
 * @author     Raphael França <[email protected]>
8
 * @copyright  2015-2018
9
 *
10
 * For the full copyright and license information,
11
 * please view the LICENSE.md file that was distributed
12
 * with this source code.
13
 */
14
15
namespace OwenIt\Auditing;
16
17
use Illuminate\Database\Eloquent\Relations\MorphMany;
18
use Illuminate\Support\Facades\App;
19
use Illuminate\Support\Facades\Config;
20
use OwenIt\Auditing\Contracts\IpAddressResolver;
21
use OwenIt\Auditing\Contracts\UrlResolver;
22
use OwenIt\Auditing\Contracts\UserAgentResolver;
23
use OwenIt\Auditing\Contracts\UserResolver;
24
use OwenIt\Auditing\Exceptions\AuditableTransitionException;
25
use OwenIt\Auditing\Exceptions\AuditingException;
26
27
trait Auditable
28
{
29
    /**
30
     * Auditable attributes excluded from the Audit.
31
     *
32
     * @var array
33
     */
34
    protected $excludedAttributes = [];
35
36
    /**
37
     * Audit event name.
38
     *
39
     * @var string
40
     */
41
    protected $auditEvent;
42
43
    /**
44
     * Is auditing disabled?
45
     *
46
     * @var bool
47
     */
48
    public static $auditingDisabled = false;
49
50
    /**
51
     * Auditable boot logic.
52
     *
53
     * @return void
54
     */
55 79
    public static function bootAuditable()
56
    {
57 79
        if (static::isAuditingEnabled()) {
58 78
            static::observe(new AuditableObserver());
59
        }
60 79
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 12
    public function audits(): MorphMany
66
    {
67 12
        return $this->morphMany(
0 ignored issues
show
Bug introduced by
It seems like morphMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
68 12
            Config::get('audit.implementation', Models\Audit::class),
69 12
            'auditable'
70
        );
71
    }
72
73
    /**
74
     * Resolve the Auditable attributes to exclude from the Audit.
75
     *
76
     * @return void
77
     */
78 43
    protected function resolveAuditExclusions()
79
    {
80 43
        $this->excludedAttributes = $this->getAuditExclude();
81
82
        // When in strict mode, hidden and non visible attributes are excluded
83 43
        if ($this->getAuditStrict()) {
84
            // Hidden attributes
85
            $this->excludedAttributes = array_merge($this->excludedAttributes, $this->hidden);
0 ignored issues
show
Bug introduced by
The property hidden does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
86
87
            // Non visible attributes
88
            if ($this->visible) {
89
                $invisible = array_diff(array_keys($this->attributes), $this->visible);
0 ignored issues
show
Bug introduced by
The property attributes does not seem to exist. Did you mean excludedAttributes?

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...
Bug introduced by
The property visible does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
90
91
                $this->excludedAttributes = array_merge($this->excludedAttributes, $invisible);
92
            }
93
        }
94
95
        // Exclude Timestamps
96 43
        if (!$this->getAuditTimestamps()) {
97 43
            array_push($this->excludedAttributes, static::CREATED_AT, static::UPDATED_AT);
98
99 43
            if (defined('static::DELETED_AT')) {
100
                $this->excludedAttributes[] = static::DELETED_AT;
101
            }
102
        }
103
104
        // Valid attributes are all those that made it out of the exclusion array
105 43
        $attributes = array_except($this->attributes, $this->excludedAttributes);
0 ignored issues
show
Bug introduced by
The property attributes does not seem to exist. Did you mean excludedAttributes?

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...
106
107 43
        foreach ($attributes as $attribute => $value) {
108
            // Apart from null, non scalar values will be excluded
109 39
            if (is_object($value) && !method_exists($value, '__toString') || is_array($value)) {
110 39
                $this->excludedAttributes[] = $attribute;
111
            }
112
        }
113 43
    }
114
115
    /**
116
     * Get the old/new attributes of a retrieved event.
117
     *
118
     * @return array
119
     */
120 2
    protected function getRetrievedEventAttributes(): array
121
    {
122
        // This is a read event with no attribute changes,
123
        // only metadata will be stored in the Audit
124
125
        return [
126 2
            [],
127
            [],
128
        ];
129
    }
130
131
    /**
132
     * Get the old/new attributes of a created event.
133
     *
134
     * @return array
135
     */
136 37 View Code Duplication
    protected function getCreatedEventAttributes(): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
137
    {
138 37
        $new = [];
139
140 37
        foreach ($this->attributes as $attribute => $value) {
0 ignored issues
show
Bug introduced by
The property attributes does not seem to exist. Did you mean excludedAttributes?

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...
141 33
            if ($this->isAttributeAuditable($attribute)) {
142 33
                $new[$attribute] = $value;
143
            }
144
        }
145
146
        return [
147 37
            [],
148 37
            $new,
149
        ];
150
    }
151
152
    /**
153
     * Get the old/new attributes of an updated event.
154
     *
155
     * @return array
156
     */
157 4
    protected function getUpdatedEventAttributes(): array
158
    {
159 4
        $old = [];
160 4
        $new = [];
161
162 4
        foreach ($this->getDirty() as $attribute => $value) {
0 ignored issues
show
Bug introduced by
It seems like getDirty() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
163 3
            if ($this->isAttributeAuditable($attribute)) {
164 3
                $old[$attribute] = array_get($this->original, $attribute);
0 ignored issues
show
Bug introduced by
The property original does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
165 3
                $new[$attribute] = array_get($this->attributes, $attribute);
0 ignored issues
show
Bug introduced by
The property attributes does not seem to exist. Did you mean excludedAttributes?

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...
166
            }
167
        }
168
169
        return [
170 4
            $old,
171 4
            $new,
172
        ];
173
    }
174
175
    /**
176
     * Get the old/new attributes of a deleted event.
177
     *
178
     * @return array
179
     */
180 4 View Code Duplication
    protected function getDeletedEventAttributes(): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
    {
182 4
        $old = [];
183
184 4
        foreach ($this->attributes as $attribute => $value) {
0 ignored issues
show
Bug introduced by
The property attributes does not seem to exist. Did you mean excludedAttributes?

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...
185 4
            if ($this->isAttributeAuditable($attribute)) {
186 4
                $old[$attribute] = $value;
187
            }
188
        }
189
190
        return [
191 4
            $old,
192
            [],
193
        ];
194
    }
195
196
    /**
197
     * Get the old/new attributes of a restored event.
198
     *
199
     * @return array
200
     */
201 2
    protected function getRestoredEventAttributes(): array
202
    {
203
        // A restored event is just a deleted event in reverse
204 2
        return array_reverse($this->getDeletedEventAttributes());
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210 54
    public function readyForAuditing(): bool
211
    {
212 54
        if (static::$auditingDisabled) {
213 1
            return false;
214
        }
215
216 54
        return $this->isEventAuditable($this->auditEvent);
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222 48
    public function toAudit(): array
223
    {
224 48
        if (!$this->readyForAuditing()) {
225 1
            throw new AuditingException('A valid audit event has not been set');
226
        }
227
228 47
        $attributeGetter = $this->resolveAttributeGetter($this->auditEvent);
229
230 47
        if (!method_exists($this, $attributeGetter)) {
231 4
            throw new AuditingException(sprintf(
232 4
                'Unable to handle "%s" event, %s() method missing',
233 4
                $this->auditEvent,
234 4
                $attributeGetter
235
            ));
236
        }
237
238 43
        $this->resolveAuditExclusions();
239
240 43
        list($old, $new) = call_user_func([$this, $attributeGetter]);
241
242 43
        $userForeignKey = Config::get('audit.user.foreign_key', 'user_id');
243
244 43
        $tags = implode(',', $this->generateTags());
245
246 43
        return $this->transformAudit([
247 43
            'old_values'     => $old,
248 43
            'new_values'     => $new,
249 43
            'event'          => $this->auditEvent,
250 43
            'auditable_id'   => $this->getKey(),
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
251 43
            'auditable_type' => $this->getMorphClass(),
0 ignored issues
show
Bug introduced by
It seems like getMorphClass() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
252 43
            $userForeignKey  => $this->resolveUser(),
253 42
            'url'            => $this->resolveUrl(),
254 41
            'ip_address'     => $this->resolveIpAddress(),
255 40
            'user_agent'     => $this->resolveUserAgent(),
256 39
            'tags'           => empty($tags) ? null : $tags,
257
        ]);
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 38
    public function transformAudit(array $data): array
264
    {
265 38
        return $data;
266
    }
267
268
    /**
269
     * Resolve the User.
270
     *
271
     * @throws AuditingException
272
     *
273
     * @return mixed|null
274
     */
275 43 View Code Duplication
    protected function resolveUser()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
276
    {
277 43
        $userResolver = Config::get('audit.resolver.user');
278
279 43
        if (is_subclass_of($userResolver, UserResolver::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \OwenIt\Auditing\Contracts\UserResolver::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
280 42
            return call_user_func([$userResolver, 'resolve']);
281
        }
282
283 1
        throw new AuditingException('Invalid UserResolver implementation');
284
    }
285
286
    /**
287
     * Resolve the URL.
288
     *
289
     * @throws AuditingException
290
     *
291
     * @return string
292
     */
293 42 View Code Duplication
    protected function resolveUrl(): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
    {
295 42
        $urlResolver = Config::get('audit.resolver.url');
296
297 42
        if (is_subclass_of($urlResolver, UrlResolver::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \OwenIt\Auditing\Contracts\UrlResolver::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
298 41
            return call_user_func([$urlResolver, 'resolve']);
299
        }
300
301 1
        throw new AuditingException('Invalid UrlResolver implementation');
302
    }
303
304
    /**
305
     * Resolve the IP Address.
306
     *
307
     * @throws AuditingException
308
     *
309
     * @return string
310
     */
311 41 View Code Duplication
    protected function resolveIpAddress(): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
312
    {
313 41
        $ipAddressResolver = Config::get('audit.resolver.ip_address');
314
315 41
        if (is_subclass_of($ipAddressResolver, IpAddressResolver::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \OwenIt\Auditing\Contrac...pAddressResolver::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
316 40
            return call_user_func([$ipAddressResolver, 'resolve']);
317
        }
318
319 1
        throw new AuditingException('Invalid IpAddressResolver implementation');
320
    }
321
322
    /**
323
     * Resolve the User Agent.
324
     *
325
     * @throws AuditingException
326
     *
327
     * @return string|null
328
     */
329 40 View Code Duplication
    protected function resolveUserAgent()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
    {
331 40
        $userAgentResolver = Config::get('audit.resolver.user_agent');
332
333 40
        if (is_subclass_of($userAgentResolver, UserAgentResolver::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \OwenIt\Auditing\Contrac...serAgentResolver::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
334 39
            return call_user_func([$userAgentResolver, 'resolve']);
335
        }
336
337 1
        throw new AuditingException('Invalid UserAgentResolver implementation');
338
    }
339
340
    /**
341
     * Determine if an attribute is eligible for auditing.
342
     *
343
     * @param string $attribute
344
     *
345
     * @return bool
346
     */
347 38
    protected function isAttributeAuditable(string $attribute): bool
348
    {
349
        // The attribute should not be audited
350 38
        if (in_array($attribute, $this->excludedAttributes)) {
351 35
            return false;
352
        }
353
354
        // The attribute is auditable when explicitly
355
        // listed or when the include array is empty
356 38
        $include = $this->getAuditInclude();
357
358 38
        return in_array($attribute, $include) || empty($include);
359
    }
360
361
    /**
362
     * Determine whether an event is auditable.
363
     *
364
     * @param string $event
365
     *
366
     * @return bool
367
     */
368 55
    protected function isEventAuditable($event): bool
369
    {
370 55
        return is_string($this->resolveAttributeGetter($event));
371
    }
372
373
    /**
374
     * Attribute getter method resolver.
375
     *
376
     * @param string $event
377
     *
378
     * @return string|null
379
     */
380 55
    protected function resolveAttributeGetter($event)
381
    {
382 55
        foreach ($this->getAuditEvents() as $key => $value) {
383 55
            $auditableEvent = is_int($key) ? $value : $key;
384
385 55
            $auditableEventRegex = sprintf('/%s/', preg_replace('/\*+/', '.*', $auditableEvent));
386
387 55
            if (preg_match($auditableEventRegex, $event)) {
388 55
                return is_int($key) ? sprintf('get%sEventAttributes', ucfirst($event)) : $value;
389
            }
390
        }
391 21
    }
392
393
    /**
394
     * {@inheritdoc}
395
     */
396 55
    public function setAuditEvent(string $event): Contracts\Auditable
397
    {
398 55
        $this->auditEvent = $this->isEventAuditable($event) ? $event : null;
399
400 55
        return $this;
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     */
406 2
    public function getAuditEvent()
407
    {
408 2
        return $this->auditEvent;
409
    }
410
411
    /**
412
     * {@inheritdoc}
413
     */
414 58
    public function getAuditEvents(): array
415
    {
416 58
        return $this->auditEvents ?? Config::get('audit.events', [
0 ignored issues
show
Bug introduced by
The property auditEvents does not seem to exist. Did you mean auditEvent?

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...
417 58
            'created',
418
            'updated',
419
            'deleted',
420
            'restored',
421
        ]);
422
    }
423
424
    /**
425
     * Disable Auditing.
426
     *
427
     * @return void
428
     */
429 1
    public static function disableAuditing()
430
    {
431 1
        static::$auditingDisabled = true;
432 1
    }
433
434
    /**
435
     * Enable Auditing.
436
     *
437
     * @return void
438
     */
439 1
    public static function enableAuditing()
440
    {
441 1
        static::$auditingDisabled = false;
442 1
    }
443
444
    /**
445
     * Determine whether auditing is enabled.
446
     *
447
     * @return bool
448
     */
449 82
    public static function isAuditingEnabled(): bool
450
    {
451 82
        if (App::runningInConsole()) {
452 80
            return Config::get('audit.console', false);
453
        }
454
455 2
        return true;
456
    }
457
458
    /**
459
     * {@inheritdoc}
460
     */
461 40
    public function getAuditInclude(): array
462
    {
463 40
        return $this->auditInclude ?? [];
0 ignored issues
show
Bug introduced by
The property auditInclude does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
464
    }
465
466
    /**
467
     * {@inheritdoc}
468
     */
469 45
    public function getAuditExclude(): array
470
    {
471 45
        return $this->auditExclude ?? [];
0 ignored issues
show
Bug introduced by
The property auditExclude does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
472
    }
473
474
    /**
475
     * {@inheritdoc}
476
     */
477 46
    public function getAuditStrict(): bool
478
    {
479 46
        return $this->auditStrict ?? Config::get('audit.strict', false);
0 ignored issues
show
Bug introduced by
The property auditStrict does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
480
    }
481
482
    /**
483
     * {@inheritdoc}
484
     */
485 46
    public function getAuditTimestamps(): bool
486
    {
487 46
        return $this->auditTimestamps ?? Config::get('audit.timestamps', false);
0 ignored issues
show
Bug introduced by
The property auditTimestamps does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
488
    }
489
490
    /**
491
     * {@inheritdoc}
492
     */
493 43
    public function getAuditDriver()
494
    {
495 43
        return $this->auditDriver ?? Config::get('audit.driver', 'database');
0 ignored issues
show
Bug introduced by
The property auditDriver does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
496
    }
497
498
    /**
499
     * {@inheritdoc}
500
     */
501 40
    public function getAuditThreshold(): int
502
    {
503 40
        return $this->auditThreshold ?? Config::get('audit.threshold', 0);
0 ignored issues
show
Bug introduced by
The property auditThreshold does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
504
    }
505
506
    /**
507
     * {@inheritdoc}
508
     */
509 44
    public function generateTags(): array
510
    {
511 44
        return [];
512
    }
513
514
    /**
515
     * {@inheritdoc}
516
     */
517 8
    public function transitionTo(Contracts\Audit $audit, bool $old = false): Contracts\Auditable
518
    {
519
        // The Audit must be for an Auditable model of this type
520 8
        if ($this->getMorphClass() !== $audit->auditable_type) {
0 ignored issues
show
Bug introduced by
Accessing auditable_type on the interface OwenIt\Auditing\Contracts\Audit suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
It seems like getMorphClass() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
521 2
            throw new AuditableTransitionException(sprintf(
522 2
                'Expected Auditable type %s, got %s instead',
523 2
                $this->getMorphClass(),
0 ignored issues
show
Bug introduced by
It seems like getMorphClass() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
524 2
                $audit->auditable_type
0 ignored issues
show
Bug introduced by
Accessing auditable_type on the interface OwenIt\Auditing\Contracts\Audit suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
525
            ));
526
        }
527
528
        // The Audit must be for this specific Auditable model
529 6
        if ($this->getKey() !== $audit->auditable_id) {
0 ignored issues
show
Bug introduced by
Accessing auditable_id on the interface OwenIt\Auditing\Contracts\Audit suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
530 1
            throw new AuditableTransitionException(sprintf(
531 1
                'Expected Auditable id %s, got %s instead',
532 1
                $this->getKey(),
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
533 1
                $audit->auditable_id
0 ignored issues
show
Bug introduced by
Accessing auditable_id on the interface OwenIt\Auditing\Contracts\Audit suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
534
            ));
535
        }
536
537
        // The attribute compatibility between the Audit and the Auditable model must be met
538 5
        $modified = $audit->getModified();
539
540 5
        if ($incompatibilities = array_diff_key($modified, $this->getAttributes())) {
0 ignored issues
show
Bug introduced by
It seems like getAttributes() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
541 1
            throw new AuditableTransitionException(sprintf(
542 1
                'Incompatibility between [%s:%s] and [%s:%s]',
543 1
                $this->getMorphClass(),
0 ignored issues
show
Bug introduced by
It seems like getMorphClass() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
544 1
                $this->getKey(),
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
545 1
                get_class($audit),
546 1
                $audit->getKey()
547 1
            ), array_keys($incompatibilities));
548
        }
549
550 4
        $key = $old ? 'old' : 'new';
551
552 4
        foreach ($modified as $attribute => $value) {
0 ignored issues
show
Bug introduced by
The expression $modified of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
553 3
            if (array_key_exists($key, $value)) {
554 3
                $this->setAttribute($attribute, $value[$key]);
0 ignored issues
show
Bug introduced by
It seems like setAttribute() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
555
            }
556
        }
557
558 4
        return $this;
559
    }
560
}
561