Passed
Pull Request — master (#684)
by Morten
14:08
created

Auditable   F

Complexity

Total Complexity 97

Size/Duplication

Total Lines 645
Duplicated Lines 0 %

Test Coverage

Coverage 94.52%

Importance

Changes 36
Bugs 16 Features 5
Metric Value
eloc 191
dl 0
loc 645
ccs 207
cts 219
cp 0.9452
rs 2
c 36
b 16
f 5
wmc 97

36 Methods

Rating   Name   Duplication   Size   Complexity  
A disableAuditing() 0 3 1
A getAuditEvent() 0 3 1
A generateTags() 0 3 1
A enableAuditing() 0 3 1
B resolveAttributeGetter() 0 17 7
A getAuditStrict() 0 3 1
B resolveAuditExclusions() 0 33 9
A setAuditExcludedAttributes() 0 3 1
A getUpdatedEventAttributes() 0 15 3
A getRetrievedEventAttributes() 0 8 1
A getRestoredEventAttributes() 0 4 1
A getAuditEvents() 0 7 1
A modifyAttributeValue() 0 19 4
A getCreatedEventAttributes() 0 13 3
A runResolvers() 0 14 4
A getAuditInclude() 0 3 1
A getAuditTimestamps() 0 3 1
A isEventAuditable() 0 3 1
A bootAuditable() 0 4 3
A transformAudit() 0 3 1
A getCustomEventAttributes() 0 5 1
A readyForAuditing() 0 11 3
B toAudit() 0 46 10
A getAuditThreshold() 0 3 1
A getAttributeModifiers() 0 3 1
A resolveUser() 0 9 2
B transitionTo() 0 49 9
A getAuditDriver() 0 3 1
A audits() 0 5 1
A getAuditExclude() 0 3 1
A auditDetach() 0 16 5
A isAuditingEnabled() 0 7 3
A setAuditEvent() 0 5 2
A isAttributeAuditable() 0 12 3
A auditAttach() 0 15 5
A getDeletedEventAttributes() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like Auditable often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Auditable, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace OwenIt\Auditing;
4
5
use Illuminate\Database\Eloquent\Relations\MorphMany;
6
use Illuminate\Database\Eloquent\SoftDeletes;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Facades\App;
9
use Illuminate\Support\Facades\Config;
10
use Illuminate\Support\Facades\Event;
11
use OwenIt\Auditing\Contracts\AttributeEncoder;
12
use OwenIt\Auditing\Contracts\AttributeRedactor;
13
use OwenIt\Auditing\Contracts\Resolver;
14
use OwenIt\Auditing\Events\AuditCustom;
15
use OwenIt\Auditing\Exceptions\AuditableTransitionException;
16
use OwenIt\Auditing\Exceptions\AuditingException;
17
18
trait Auditable
19
{
20
    /**
21
     * Auditable attributes excluded from the Audit.
22
     *
23
     * @var array
24
     */
25
    protected $excludedAttributes = [];
26
27
    /**
28
     * Audit event name.
29
     *
30
     * @var string
31
     */
32
    protected $auditEvent;
33
34
    /**
35
     * Is auditing disabled?
36
     *
37
     * @var bool
38
     */
39
    public static $auditingDisabled = false;
40
41
    /**
42
     * @var array
43
     */
44
    protected $auditExclude;
45
46
    /**
47
     * Property may set custom event data to register
48
     * @var null|array
49
     */
50
    public $auditCustomOld = null;
51
52
    /**
53
     * Property may set custom event data to register
54
     * @var null|array
55
     */
56
    public $auditCustomNew = null;
57
58
    /**
59
     * If this is a custom event (as opposed to an eloquent event
60
     * @var bool
61
     */
62
    public $isCustomEvent = false;
63
64
    /**
65
     * Auditable boot logic.
66
     *
67
     * @return void
68
     */
69 198
    public static function bootAuditable()
70
    {
71 198
        if (!self::$auditingDisabled && static::isAuditingEnabled()) {
72 194
            static::observe(new AuditableObserver());
73
        }
74 198
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79 34
    public function audits(): MorphMany
80
    {
81 34
        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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

81
        return $this->/** @scrutinizer ignore-call */ morphMany(
Loading history...
82 34
            Config::get('audit.implementation', Models\Audit::class),
83 34
            'auditable'
84
        );
85
    }
86
87
    /**
88
     * Resolve the Auditable attributes to exclude from the Audit.
89
     *
90
     * @return void
91
     */
92 126
    protected function resolveAuditExclusions()
93
    {
94 126
        $this->excludedAttributes = $this->getAuditExclude();
95
96
        // When in strict mode, hidden and non visible attributes are excluded
97 126
        if ($this->getAuditStrict()) {
98
            // Hidden attributes
99 2
            $this->excludedAttributes = array_merge($this->excludedAttributes, $this->hidden);
100
101
            // Non visible attributes
102 2
            if ($this->visible) {
103 2
                $invisible = array_diff(array_keys($this->attributes), $this->visible);
104
105 2
                $this->excludedAttributes = array_merge($this->excludedAttributes, $invisible);
106
            }
107
        }
108
109
        // Exclude Timestamps
110 126
        if (!$this->getAuditTimestamps()) {
111 126
            array_push($this->excludedAttributes, $this->getCreatedAtColumn(), $this->getUpdatedAtColumn());
0 ignored issues
show
Bug introduced by
It seems like getUpdatedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

111
            array_push($this->excludedAttributes, $this->getCreatedAtColumn(), $this->/** @scrutinizer ignore-call */ getUpdatedAtColumn());
Loading history...
Bug introduced by
It seems like getCreatedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

111
            array_push($this->excludedAttributes, $this->/** @scrutinizer ignore-call */ getCreatedAtColumn(), $this->getUpdatedAtColumn());
Loading history...
112
113 126
            if (in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))) {
114 118
                $this->excludedAttributes[] = $this->getDeletedAtColumn();
0 ignored issues
show
Bug introduced by
It seems like getDeletedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

114
                /** @scrutinizer ignore-call */ 
115
                $this->excludedAttributes[] = $this->getDeletedAtColumn();
Loading history...
115
            }
116
        }
117
118
        // Valid attributes are all those that made it out of the exclusion array
119 126
        $attributes = Arr::except($this->attributes, $this->excludedAttributes);
120
121 126
        foreach ($attributes as $attribute => $value) {
122
            // Apart from null, non scalar values will be excluded
123 118
            if (is_array($value) || (is_object($value) && !method_exists($value, '__toString'))) {
124 4
                $this->excludedAttributes[] = $attribute;
125
            }
126
        }
127 126
    }
128
129
    /**
130
     * Get the old/new attributes of a retrieved event.
131
     *
132
     * @return array
133
     */
134 6
    protected function getRetrievedEventAttributes(): array
135
    {
136
        // This is a read event with no attribute changes,
137
        // only metadata will be stored in the Audit
138
139
        return [
140 6
            [],
141
            [],
142
        ];
143
    }
144
145
    /**
146
     * Get the old/new attributes of a created event.
147
     *
148
     * @return array
149
     */
150 112
    protected function getCreatedEventAttributes(): array
151
    {
152 112
        $new = [];
153
154 112
        foreach ($this->attributes as $attribute => $value) {
155 104
            if ($this->isAttributeAuditable($attribute)) {
156 104
                $new[$attribute] = $value;
157
            }
158
        }
159
160
        return [
161 112
            [],
162 112
            $new,
163
        ];
164
    }
165
166 2
    protected function getCustomEventAttributes(): array
167
    {
168
        return [
169 2
            $this->auditCustomOld,
170 2
            $this->auditCustomNew
171
        ];
172
    }
173
174
    /**
175
     * Get the old/new attributes of an updated event.
176
     *
177
     * @return array
178
     */
179 18
    protected function getUpdatedEventAttributes(): array
180
    {
181 18
        $old = [];
182 18
        $new = [];
183
184 18
        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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

184
        foreach ($this->/** @scrutinizer ignore-call */ getDirty() as $attribute => $value) {
Loading history...
185 16
            if ($this->isAttributeAuditable($attribute)) {
186 14
                $old[$attribute] = Arr::get($this->original, $attribute);
187 14
                $new[$attribute] = Arr::get($this->attributes, $attribute);
188
            }
189
        }
190
191
        return [
192 18
            $old,
193 18
            $new,
194
        ];
195
    }
196
197
    /**
198
     * Get the old/new attributes of a deleted event.
199
     *
200
     * @return array
201
     */
202 8
    protected function getDeletedEventAttributes(): array
203
    {
204 8
        $old = [];
205
206 8
        foreach ($this->attributes as $attribute => $value) {
207 8
            if ($this->isAttributeAuditable($attribute)) {
208 8
                $old[$attribute] = $value;
209
            }
210
        }
211
212
        return [
213 8
            $old,
214
            [],
215
        ];
216
    }
217
218
    /**
219
     * Get the old/new attributes of a restored event.
220
     *
221
     * @return array
222
     */
223 4
    protected function getRestoredEventAttributes(): array
224
    {
225
        // A restored event is just a deleted event in reverse
226 4
        return array_reverse($this->getDeletedEventAttributes());
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232 148
    public function readyForAuditing(): bool
233
    {
234 148
        if (static::$auditingDisabled) {
235 2
            return false;
236
        }
237
238 148
        if ($this->isCustomEvent) {
239 2
            return true;
240
        }
241
242 148
        return $this->isEventAuditable($this->auditEvent);
243
    }
244
245
    /**
246
     * Modify attribute value.
247
     *
248
     * @param string $attribute
249
     * @param mixed $value
250
     *
251
     * @return mixed
252
     * @throws AuditingException
253
     *
254
     */
255 4
    protected function modifyAttributeValue(string $attribute, $value)
256
    {
257 4
        $attributeModifiers = $this->getAttributeModifiers();
258
259 4
        if (!array_key_exists($attribute, $attributeModifiers)) {
260 2
            return $value;
261
        }
262
263 4
        $attributeModifier = $attributeModifiers[$attribute];
264
265 4
        if (is_subclass_of($attributeModifier, AttributeRedactor::class)) {
266 2
            return call_user_func([$attributeModifier, 'redact'], $value);
267
        }
268
269 4
        if (is_subclass_of($attributeModifier, AttributeEncoder::class)) {
270 2
            return call_user_func([$attributeModifier, 'encode'], $value);
271
        }
272
273 2
        throw new AuditingException(sprintf('Invalid AttributeModifier implementation: %s', $attributeModifier));
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     */
279 136
    public function toAudit(): array
280
    {
281 136
        if (!$this->readyForAuditing()) {
282 2
            throw new AuditingException('A valid audit event has not been set');
283
        }
284
285 134
        $attributeGetter = $this->resolveAttributeGetter($this->auditEvent);
286
287 134
        if (!method_exists($this, $attributeGetter)) {
0 ignored issues
show
Bug introduced by
It seems like $attributeGetter can also be of type null; however, parameter $method of method_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

287
        if (!method_exists($this, /** @scrutinizer ignore-type */ $attributeGetter)) {
Loading history...
288 8
            throw new AuditingException(sprintf(
289 8
                'Unable to handle "%s" event, %s() method missing',
290 8
                $this->auditEvent,
291
                $attributeGetter
292
            ));
293
        }
294
295 126
        $this->resolveAuditExclusions();
296
297 126
        list($old, $new) = $this->$attributeGetter();
298
299 126
        if ($this->getAttributeModifiers() && !$this->isCustomEvent) {
300 4
            foreach ($old as $attribute => $value) {
301 2
                $old[$attribute] = $this->modifyAttributeValue($attribute, $value);
302
            }
303
304 4
            foreach ($new as $attribute => $value) {
305 4
                $new[$attribute] = $this->modifyAttributeValue($attribute, $value);
306
            }
307
        }
308
309 124
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
310
311 124
        $tags = implode(',', $this->generateTags());
312
313 124
        $user = $this->resolveUser();
314
315 122
        return $this->transformAudit(array_merge([
316 122
            'old_values'           => $old,
317 122
            'new_values'           => $new,
318 122
            'event'                => $this->auditEvent,
319 122
            '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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

319
            'auditable_id'         => $this->/** @scrutinizer ignore-call */ getKey(),
Loading history...
320 122
            '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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

320
            'auditable_type'       => $this->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
321 122
            $morphPrefix . '_id'   => $user ? $user->getAuthIdentifier() : null,
322 122
            $morphPrefix . '_type' => $user ? $user->getMorphClass() : null,
323 122
            'tags'                 => empty($tags) ? null : $tags,
324 122
        ], $this->runResolvers()));
325
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330 114
    public function transformAudit(array $data): array
331
    {
332 114
        return $data;
333
    }
334
335
    /**
336
     * Resolve the User.
337
     *
338
     * @return mixed|null
339
     * @throws AuditingException
340
     *
341
     */
342 124
    protected function resolveUser()
343
    {
344 124
        $userResolver = Config::get('audit.user.resolver');
345
346 124
        if (is_subclass_of($userResolver, \OwenIt\Auditing\Contracts\UserResolver::class)) {
347 122
            return call_user_func([$userResolver, 'resolve']);
348
        }
349
350 2
        throw new AuditingException('Invalid UserResolver implementation');
351
    }
352
353 122
    protected function runResolvers(): array
354
    {
355 122
        $resolved = [];
356 122
        foreach (Config::get('audit.resolvers', []) as $name => $implementation) {
357 122
            if (empty($implementation)) {
358 2
                continue;
359
            }
360
361 122
            if (!is_subclass_of($implementation, Resolver::class)) {
362 6
                throw new AuditingException('Invalid Resolver implementation for: ' . $name);
363
            }
364 120
            $resolved[$name] = call_user_func([$implementation, 'resolve'], $this);
365
        }
366 116
        return $resolved;
367
    }
368
369
    /**
370
     * Determine if an attribute is eligible for auditing.
371
     *
372
     * @param string $attribute
373
     *
374
     * @return bool
375
     */
376 116
    protected function isAttributeAuditable(string $attribute): bool
377
    {
378
        // The attribute should not be audited
379 116
        if (in_array($attribute, $this->excludedAttributes, true)) {
380 110
            return false;
381
        }
382
383
        // The attribute is auditable when explicitly
384
        // listed or when the include array is empty
385 116
        $include = $this->getAuditInclude();
386
387 116
        return empty($include) || in_array($attribute, $include, true);
388
    }
389
390
    /**
391
     * Determine whether an event is auditable.
392
     *
393
     * @param string $event
394
     *
395
     * @return bool
396
     */
397 150
    protected function isEventAuditable($event): bool
398
    {
399 150
        return is_string($this->resolveAttributeGetter($event));
400
    }
401
402
    /**
403
     * Attribute getter method resolver.
404
     *
405
     * @param string $event
406
     *
407
     * @return string|null
408
     */
409 150
    protected function resolveAttributeGetter($event)
410
    {
411 150
        if (empty($event)) {
412 56
            return;
413
        }
414
415 150
        if ($this->isCustomEvent) {
416 2
            return 'getCustomEventAttributes';
417
        }
418
419 150
        foreach ($this->getAuditEvents() as $key => $value) {
420 150
            $auditableEvent = is_int($key) ? $value : $key;
421
422 150
            $auditableEventRegex = sprintf('/%s/', preg_replace('/\*+/', '.*', $auditableEvent));
423
424 150
            if (preg_match($auditableEventRegex, $event)) {
425 146
                return is_int($key) ? sprintf('get%sEventAttributes', ucfirst($event)) : $value;
426
            }
427
        }
428 56
    }
429
430
    /**
431
     * {@inheritdoc}
432
     */
433 150
    public function setAuditEvent(string $event): Contracts\Auditable
434
    {
435 150
        $this->auditEvent = $this->isEventAuditable($event) ? $event : null;
436
437 150
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type OwenIt\Auditing\Auditable which is incompatible with the type-hinted return OwenIt\Auditing\Contracts\Auditable.
Loading history...
438
    }
439
440 4
    public function setAuditExcludedAttributes(array $excludedAttributes)
441
    {
442 4
        $this->auditExclude = $excludedAttributes;
443 4
    }
444
445
    /**
446
     * {@inheritdoc}
447
     */
448 8
    public function getAuditEvent()
449
    {
450 8
        return $this->auditEvent;
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456 156
    public function getAuditEvents(): array
457
    {
458 156
        return $this->auditEvents ?? Config::get('audit.events', [
0 ignored issues
show
Bug introduced by
The property auditEvents does not exist on OwenIt\Auditing\Auditable. Did you mean auditEvent?
Loading history...
459 156
                'created',
460
                'updated',
461
                'deleted',
462
                'restored',
463
            ]);
464
    }
465
466
    /**
467
     * Disable Auditing.
468
     *
469
     * @return void
470
     */
471 2
    public static function disableAuditing()
472
    {
473 2
        static::$auditingDisabled = true;
474 2
    }
475
476
    /**
477
     * Enable Auditing.
478
     *
479
     * @return void
480
     */
481 2
    public static function enableAuditing()
482
    {
483 2
        static::$auditingDisabled = false;
484 2
    }
485
486
    /**
487
     * Determine whether auditing is enabled.
488
     *
489
     * @return bool
490
     */
491 202
    public static function isAuditingEnabled(): bool
492
    {
493 202
        if (App::runningInConsole()) {
494 198
            return Config::get('audit.enabled', true) && Config::get('audit.console', false);
495
        }
496
497 4
        return Config::get('audit.enabled', true);
498
    }
499
500
    /**
501
     * {@inheritdoc}
502
     */
503 120
    public function getAuditInclude(): array
504
    {
505 120
        return $this->auditInclude ?? [];
0 ignored issues
show
Bug introduced by
The property auditInclude does not exist on OwenIt\Auditing\Auditable. Did you mean auditExclude?
Loading history...
506
    }
507
508
    /**
509
     * {@inheritdoc}
510
     */
511 130
    public function getAuditExclude(): array
512
    {
513 130
        return $this->auditExclude ?? [];
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 132
    public function getAuditStrict(): bool
520
    {
521 132
        return $this->auditStrict ?? Config::get('audit.strict', false);
522
    }
523
524
    /**
525
     * {@inheritdoc}
526
     */
527 132
    public function getAuditTimestamps(): bool
528
    {
529 132
        return $this->auditTimestamps ?? Config::get('audit.timestamps', false);
530
    }
531
532
    /**
533
     * {@inheritdoc}
534
     */
535 120
    public function getAuditDriver()
536
    {
537 120
        return $this->auditDriver ?? Config::get('audit.driver', 'database');
538
    }
539
540
    /**
541
     * {@inheritdoc}
542
     */
543 114
    public function getAuditThreshold(): int
544
    {
545 114
        return $this->auditThreshold ?? Config::get('audit.threshold', 0);
546
    }
547
548
    /**
549
     * {@inheritdoc}
550
     */
551 126
    public function getAttributeModifiers(): array
552
    {
553 126
        return $this->attributeModifiers ?? [];
554
    }
555
556
    /**
557
     * {@inheritdoc}
558
     */
559 126
    public function generateTags(): array
560
    {
561 126
        return [];
562
    }
563
564
    /**
565
     * {@inheritdoc}
566
     */
567 22
    public function transitionTo(Contracts\Audit $audit, bool $old = false): Contracts\Auditable
568
    {
569
        // The Audit must be for an Auditable model of this type
570 22
        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?
Loading history...
571 4
            throw new AuditableTransitionException(sprintf(
572 4
                'Expected Auditable type %s, got %s instead',
573 4
                $this->getMorphClass(),
574 4
                $audit->auditable_type
575
            ));
576
        }
577
578
        // The Audit must be for this specific Auditable model
579 18
        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?
Loading history...
580 4
            throw new AuditableTransitionException(sprintf(
581 4
                'Expected Auditable id %s, got %s instead',
582 4
                $this->getKey(),
583 4
                $audit->auditable_id
584
            ));
585
        }
586
587
        // Redacted data should not be used when transitioning states
588 14
        foreach ($this->getAttributeModifiers() as $attribute => $modifier) {
589 2
            if (is_subclass_of($modifier, AttributeRedactor::class)) {
590 2
                throw new AuditableTransitionException('Cannot transition states when an AttributeRedactor is set');
591
            }
592
        }
593
594
        // The attribute compatibility between the Audit and the Auditable model must be met
595 12
        $modified = $audit->getModified();
596
597 12
        if ($incompatibilities = array_diff_key($modified, $this->getAttributes())) {
0 ignored issues
show
Bug introduced by
It seems like $modified can also be of type string; however, parameter $array1 of array_diff_key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

597
        if ($incompatibilities = array_diff_key(/** @scrutinizer ignore-type */ $modified, $this->getAttributes())) {
Loading history...
Bug introduced by
The method getAttributes() does not exist on OwenIt\Auditing\Auditable. Did you maybe mean getAttributeModifiers()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

597
        if ($incompatibilities = array_diff_key($modified, $this->/** @scrutinizer ignore-call */ getAttributes())) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
598 2
            throw new AuditableTransitionException(sprintf(
599 2
                'Incompatibility between [%s:%s] and [%s:%s]',
600 2
                $this->getMorphClass(),
601 2
                $this->getKey(),
602 2
                get_class($audit),
603 2
                $audit->getKey()
0 ignored issues
show
Bug introduced by
The method getKey() does not exist on OwenIt\Auditing\Contracts\Audit. Since it exists in all sub-types, consider adding an abstract or default implementation to OwenIt\Auditing\Contracts\Audit. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

603
                $audit->/** @scrutinizer ignore-call */ 
604
                        getKey()
Loading history...
604 2
            ), array_keys($incompatibilities));
605
        }
606
607 10
        $key = $old ? 'old' : 'new';
608
609 10
        foreach ($modified as $attribute => $value) {
610 6
            if (array_key_exists($key, $value)) {
611 6
                $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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

611
                $this->/** @scrutinizer ignore-call */ 
612
                       setAttribute($attribute, $value[$key]);
Loading history...
612
            }
613
        }
614
615 10
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type OwenIt\Auditing\Auditable which is incompatible with the type-hinted return OwenIt\Auditing\Contracts\Auditable.
Loading history...
616
    }
617
618
    /**
619
     * @param $relationName
620
     * @param $id
621
     * @return void
622
     * @throws AuditingException
623
     */
624 2
    public function auditAttach($relationName, $id)
625
    {
626 2
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) {
627
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method attach');
628
        }
629 2
        $this->auditEvent = 'attach';
630 2
        $this->isCustomEvent = true;
631 2
        $this->auditCustomOld = [
632 2
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
633
        ];
634 2
        $this->{$relationName}()->attach($id);
635 2
        $this->auditCustomNew = [
636 2
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
637
        ];
638 2
        Event::dispatch(AuditCustom::class, [$this]);
639 2
    }
640
641
    /**
642
     * @param $relationName
643
     * @param $id
644
     * @return void
645
     * @throws AuditingException
646
     */
647
    public function auditDetach($relationName, $id)
648
    {
649
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) {
650
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method detach');
651
        }
652
653
        $this->auditEvent = 'detach';
654
        $this->isCustomEvent = true;
655
        $this->auditCustomOld = [
656
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
657
        ];
658
        $this->{$relationName}()->detach($id);
659
        $this->auditCustomNew = [
660
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
661
        ];
662
        Event::dispatch(AuditCustom::class, [$this]);
663
    }
664
}
665