Passed
Push — master ( d3aee3...cee369 )
by Julien
05:14
created

Blameable::notify()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 7.456

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 17
ccs 4
cts 10
cp 0.4
rs 9.9666
cc 4
nc 3
nop 2
crap 7.456
1
<?php
2
3
/**
4
 * This file is part of the Zemit Framework.
5
 *
6
 * (c) Zemit Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zemit\Mvc\Model\Behavior;
13
14
use Phalcon\Mvc\Model\Behavior;
15
use Phalcon\Mvc\ModelInterface;
16
use Zemit\Models\AbstractModel;
17
use Zemit\Models\Interfaces\AbstractInterface;
18
use Zemit\Mvc\Model;
19
use Zemit\Models\Audit;
20
use Zemit\Models\AuditDetail;
21
use Zemit\Models\Interfaces\AuditDetailInterface;
22
use Zemit\Models\Interfaces\AuditInterface;
23
use Zemit\Models\User;
24
use Zemit\Support\Helper;
25
26
/**
27
 * Zemit\Mvc\Model\Behavior\Blameable
28
 *
29
 * Allows to automatically update a model’s attribute saving the datetime when a
30
 * record is created or updated
31
 */
32
class Blameable extends Behavior
33
{
34
    use SkippableTrait;
35
    
36
    protected static ?int $parentId = null;
37
38
    protected ?array $snapshot = null;
39
40
    protected ?array $changedFields = null;
41
    
42
    protected string $auditClass = Audit::class;
43
44
    protected string $auditDetailClass = AuditDetail::class;
45
46
    protected string $userClass = User::class;
47
    
48 2
    public function __construct(array $options = [])
49
    {
50 2
        parent::__construct($options);
51 2
        $this->userClass = $options['userClass'] ?? $this->userClass;
52 2
        $this->auditClass = $options['auditClass'] ?? $this->auditClass;
53 2
        $this->auditDetailClass = $options['auditDetailClass'] ?? $this->auditDetailClass;
54
    }
55
    
56 2
    public function notify(string $type, ModelInterface $model): bool|null
57
    {
58 2
        assert($model instanceof Model);
59
        
60 2
        if ($this->isEnabled()) {
61 2
            return null;
62
        }
63
        
64
        // prevent auditing audit & audit_detail tables
65
        if ($model instanceof $this->auditClass || $model instanceof $this->auditDetailClass) {
66
            return null;
67
        }
68
        
69
        return match ($type) {
70
            'afterCreate', 'afterUpdate' => $this->createAudit($type, $model),
71
            'beforeUpdate' => $this->collectData($model),
72
            default => null,
73
        };
74
    }
75
    
76
    /**
77
     * Create new audit
78
     * Return true if the audit was created
79
     */
80
    public function createAudit(string $type, Model $model): bool
81
    {
82
        assert($model instanceof AbstractInterface);
83
            
84
        $event = lcfirst(Helper::uncamelize(str_replace(['before', 'after'], ['', ''], $type)));
85
        
86
        $auditClass = $this->auditClass;
87
        $auditDetailClass = $this->auditDetailClass;
88
        
89
        $metaData = $model->getModelsMetaData();
90
        $columns = $metaData->getAttributes($model);
91
        $columnMap = $metaData->getColumnMap($model);
92
        $changedFields = $this->changedFields;
93
        $snapshot = $this->snapshot;
94
        
95
        $audit = new $auditClass();
96
        assert($audit instanceof AuditInterface);
97
        assert($audit instanceof Model);
98
        
99
        $audit->setModel(get_class($model));
0 ignored issues
show
Bug introduced by
The method setModel() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

99
        $audit->/** @scrutinizer ignore-call */ 
100
                setModel(get_class($model));
Loading history...
100
        $audit->setTable($model->getSource());
0 ignored issues
show
Bug introduced by
The method setTable() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

100
        $audit->/** @scrutinizer ignore-call */ 
101
                setTable($model->getSource());
Loading history...
101
        $audit->setPrimary($model->getId());
0 ignored issues
show
Bug introduced by
The method getId() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

101
        $audit->setPrimary($model->/** @scrutinizer ignore-call */ getId());
Loading history...
Bug introduced by
The method setPrimary() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

101
        $audit->/** @scrutinizer ignore-call */ 
102
                setPrimary($model->getId());
Loading history...
102
        $audit->setEvent($event);
0 ignored issues
show
Bug introduced by
The method setEvent() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

102
        $audit->/** @scrutinizer ignore-call */ 
103
                setEvent($event);
Loading history...
103
        $audit->setColumns(json_encode($columnMap ?: $columns));
0 ignored issues
show
Bug introduced by
The method setColumns() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

103
        $audit->/** @scrutinizer ignore-call */ 
104
                setColumns(json_encode($columnMap ?: $columns));
Loading history...
104
        $audit->setBefore($snapshot ? json_encode($snapshot) : null);
0 ignored issues
show
Bug introduced by
The method setBefore() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

104
        $audit->/** @scrutinizer ignore-call */ 
105
                setBefore($snapshot ? json_encode($snapshot) : null);
Loading history...
105
        $audit->setAfter(json_encode($model->toArray()));
0 ignored issues
show
Bug introduced by
The method setAfter() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

105
        $audit->/** @scrutinizer ignore-call */ 
106
                setAfter(json_encode($model->toArray()));
Loading history...
106
        $audit->setParentId(self::$parentId);
0 ignored issues
show
Bug introduced by
The method setParentId() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

106
        $audit->/** @scrutinizer ignore-call */ 
107
                setParentId(self::$parentId);
Loading history...
107
        
108
        $auditDetailList = [];
109
        
110
        foreach ($columns as $column) {
111
            $map = empty($columnMap) ? $column : $columnMap[$column];
112
            $before = $snapshot[$map] ?? null;
113
            $after = $model->readAttribute($map);
114
            
115
            // skip unchanged
116
            if ($event === 'update' && $changedFields !== null && $snapshot !== null) {
117
                if ($before === $after || !in_array($map, $changedFields, true)) {
118
                    continue;
119
                }
120
            }
121
            
122
            $auditDetail = new $auditDetailClass();
123
            assert($auditDetail instanceof AuditDetailInterface);
124
            assert($model instanceof AbstractModel);
125
            
126
            $auditDetail->setTable($model->getSource());
127
            $auditDetail->setPrimary($model->getId());
0 ignored issues
show
Bug introduced by
The method getId() does not exist on Zemit\Models\AbstractModel. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

127
            $auditDetail->setPrimary($model->/** @scrutinizer ignore-call */ getId());
Loading history...
128
            $auditDetail->setEvent($event);
129
            $auditDetail->setColumn($column);
130
            $auditDetail->setMap($map);
131
            $auditDetail->setBefore($before);
132
            $auditDetail->setAfter($after);
133
            
134
            $auditDetailList[] = $auditDetail;
135
        }
136
        
137
        $audit->AuditDetailList = $auditDetailList;
0 ignored issues
show
Bug Best Practice introduced by
The property AuditDetailList does not exist on Zemit\Mvc\Model. Since you implemented __set, consider adding a @property annotation.
Loading history...
138
        $save = $audit->save();
139
        foreach ($audit->getMessages() as $message) {
140
            $message->setField('Audit.' . $message->getField());
141
            $model->appendMessage($message);
142
        }
143
        
144
        self::$parentId = (!empty($model->hasDirtyRelated())) ? $audit->getId() : null;
145
        return $save;
146
    }
147
    
148
    /**
149
     * Return true if data has been collected
150
     */
151
    protected function collectData(Model $model): bool
152
    {
153
        if ($model->hasSnapshotData()) {
154
            $this->snapshot = $model->getSnapshotData();
155
            $this->changedFields = $model->getChangedFields();
156
            return true;
157
        }
158
    
159
        $this->snapshot = null;
160
        $this->changedFields = null;
161
        return false;
162
    }
163
}
164