Test Failed
Push — master ( b1cf76...d8fe36 )
by Julien
08:28
created

Blameable   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 129
Duplicated Lines 0 %

Test Coverage

Coverage 82.35%

Importance

Changes 6
Bugs 3 Features 0
Metric Value
eloc 71
dl 0
loc 129
ccs 56
cts 68
cp 0.8235
rs 10
c 6
b 3
f 0
wmc 19

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A collectData() 0 11 2
C createAudit() 0 62 12
A notify() 0 16 4
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\User;
17
use Zemit\Models\Audit;
18
use Zemit\Models\AuditDetail;
19
use Zemit\Models\Interfaces\AuditDetailInterface;
20
use Zemit\Models\Interfaces\AuditInterface;
21
use Zemit\Mvc\Model;
22
use Zemit\Mvc\Model\Behavior\Traits\SkippableTrait;
23
use Zemit\Support\Helper;
24
25
/**
26
 * Zemit\Mvc\Model\Traits\Behavior\Blameable
27
 *
28
 * Allows to automatically update a model’s attribute saving the datetime when a
29
 * record is created or updated
30
 */
31
class Blameable extends Behavior
32
{
33
    use SkippableTrait;
34
    
35
    protected static ?int $parentId = null;
36
37
    protected ?array $snapshot = null;
38
39
    protected ?array $changedFields = null;
40
    
41
    protected string $auditClass = Audit::class;
42
43
    protected string $auditDetailClass = AuditDetail::class;
44
45
    protected string $userClass = User::class;
46
    
47 56
    public function __construct(array $options = [])
48
    {
49 56
        parent::__construct($options);
50 56
        $this->userClass = $options['userClass'] ?? $this->userClass;
51 56
        $this->auditClass = $options['auditClass'] ?? $this->auditClass;
52 56
        $this->auditDetailClass = $options['auditDetailClass'] ?? $this->auditDetailClass;
53
    }
54
    
55
    /**
56
     * @throws \Exception
57
     */
58 3
    public function notify(string $type, ModelInterface $model): ?bool
59
    {
60 3
        if (!$this->isEnabled()) {
61
            return null;
62
        }
63
        
64
        // prevent auditing audit & audit_detail tables
65 3
        if ($model instanceof $this->auditClass || $model instanceof $this->auditDetailClass) {
66 3
            return null;
67
        }
68
        
69 3
        assert($model instanceof Model);
70 3
        return match ($type) {
71 3
            'afterCreate', 'afterUpdate' => $this->createAudit($type, $model),
72
            'beforeUpdate' => $this->collectData($model),
73 3
            default => null,
74 3
        };
75
    }
76
    
77
    /**
78
     * Create new audit
79
     * Return true if the audit was created
80
     * @throws \Exception
81
     */
82 3
    public function createAudit(string $type, Model $model): bool
83
    {
84 3
        $event = lcfirst(Helper::uncamelize(str_replace(['before', 'after'], ['', ''], $type)));
85
        
86 3
        $auditClass = $this->auditClass;
87 3
        $auditDetailClass = $this->auditDetailClass;
88
        
89 3
        $metaData = $model->getModelsMetaData();
90 3
        $columns = $metaData->getAttributes($model);
91 3
        $columnMap = $metaData->getColumnMap($model);
92 3
        $changedFields = $this->changedFields;
93 3
        $snapshot = $this->snapshot;
94
        
95 3
        $audit = new $auditClass();
96 3
        assert($audit instanceof AuditInterface);
97
        
98 3
        $audit->setModel(get_class($model));
99 3
        $audit->setTable($model->getSource());
100 3
        $audit->setPrimary($model->readAttribute('id'));
101 3
        $audit->setEvent($event);
102 3
        $audit->setColumns(json_encode($columnMap ?: $columns));
103 3
        $audit->setBefore($snapshot ? json_encode($snapshot) : null);
104 3
        $audit->setAfter(json_encode($model->toArray()));
105 3
        $audit->setParentId(self::$parentId);
106
        
107 3
        $auditDetailList = [];
108
        
109 3
        foreach ($columns as $column) {
110 3
            $map = empty($columnMap) ? $column : $columnMap[$column];
111 3
            $before = $snapshot[$map] ?? null;
112 3
            $after = $model->readAttribute($map);
113
            
114
            // skip unchanged
115 3
            if ($event === 'update' && $changedFields !== null && $snapshot !== null) {
116
                if ($before === $after || !in_array($map, $changedFields, true)) {
117
                    continue;
118
                }
119
            }
120
            
121 3
            $auditDetail = new $auditDetailClass();
122 3
            assert($auditDetail instanceof AuditDetailInterface);
123
            
124 3
            $auditDetail->setTable($model->getSource());
125 3
            $auditDetail->setPrimary($model->readAttribute('id'));
126 3
            $auditDetail->setEvent($event);
127 3
            $auditDetail->setColumn($column);
128 3
            $auditDetail->setMap($map);
129 3
            $auditDetail->setBefore($before);
130 3
            $auditDetail->setAfter($after);
131
            
132 3
            $auditDetailList[] = $auditDetail;
133
        }
134
        
135 3
        $audit->assign(['AuditDetailList' => $auditDetailList]);
136 3
        $save = $audit->save();
137 3
        foreach ($audit->getMessages() as $message) {
138 3
            $message->setField('Audit.' . $message->getField());
139 3
            $model->appendMessage($message);
140
        }
141
        
142 3
        self::$parentId = (!empty($model->getDirtyRelated())) ? $audit->getId() : null;
143 3
        return $save;
144
    }
145
    
146
    /**
147
     * Return true if data has been collected
148
     */
149
    protected function collectData(Model $model): bool
150
    {
151
        if ($model->hasSnapshotData()) {
152
            $this->snapshot = $model->getSnapshotData();
153
            $this->changedFields = $model->getChangedFields();
154
            return true;
155
        }
156
    
157
        $this->snapshot = null;
158
        $this->changedFields = null;
159
        return false;
160
    }
161
}
162