Test Failed
Push — master ( 894c40...e5d2d2 )
by Julien
11:34
created

Blameable::notify()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 10
c 1
b 0
f 0
nc 6
nop 2
dl 0
loc 17
ccs 0
cts 1
cp 0
crap 56
rs 8.8333
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 Phalcon\Text;
17
use Zemit\Models\Audit;
18
use Zemit\Models\AuditDetail;
19
use Zemit\Models\Interfaces\AuditInterface;
20
use Zemit\Models\User;
21
22
/**
23
 * Zemit\Mvc\Model\Behavior\Blameable
24
 *
25
 * Allows to automatically update a model’s attribute saving the datetime when a
26
 * record is created or updated
27
 */
28
class Blameable extends Behavior
29
{
30
    use SkippableTrait;
31
    
32
    protected static ?int $parentId = null;
33
34
    protected ?array $snapshot = null;
35
36
    protected ?array $changedFields = null;
37
    
38
    protected string $auditClass = Audit::class;
39
40
    protected string $auditDetailClass = AuditDetail::class;
41
42
    protected string $userClass = User::class;
43
    
44
    public function __construct(?array $options = null)
45
    {
46
        parent::__construct($options);
0 ignored issues
show
Bug introduced by
It seems like $options can also be of type null; however, parameter $options of Phalcon\Mvc\Model\Behavior::__construct() 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

46
        parent::__construct(/** @scrutinizer ignore-type */ $options);
Loading history...
47
        $this->userClass = $options['userClass'] ?? $this->userClass;
48
        $this->auditClass = $options['auditClass'] ?? $this->auditClass;
49
        $this->auditDetailClass = $options['auditDetailClass'] ?? $this->auditDetailClass;
50
    }
51
    
52
    public function notify(string $type, ModelInterface $model)
53
    {
54
        if ($this->isEnabled()) {
55
            return;
56
        }
57
        
58
        // prevent auditing audit & audit_detail tables
59
        if ($model instanceof $this->auditClass || $model instanceof $this->auditDetailClass) {
60
            return;
61
        }
62
        
63
        switch ($type) {
64
            case 'afterCreate':
65
            case 'afterUpdate':
66
                return $this->createAudit($type, $model);
67
            case 'beforeUpdate':
68
                return $this->collectData($model);
69
        }
70
    }
71
    
72
    /**
73
     * Create new audit
74
     * Return true if the audit was created
75
     */
76
    public function createAudit(string $type, ModelInterface $model): bool
77
    {
78
        $event = lcfirst(Text::uncamelize(str_replace(['before', 'after'], ['', ''], $type)));
79
        
80
        $auditClass = $this->auditClass;
81
        $auditDetailClass = $this->auditDetailClass;
82
        
83
        $metaData = $model->getModelsMetaData();
84
        $columns = $metaData->getAttributes($model);
85
        $columnMap = $metaData->getColumnMap($model);
86
        $changedFields = $this->changedFields;
87
        $snapshot = $this->snapshot;
88
        
89
        $audit = new $auditClass();
90
        assert($audit instanceof AuditInterface);
91
        
92
        $audit->setModel(get_class($model));
93
        $audit->setTable($model->getSource());
94
        $audit->setPrimary($model->getId());
95
        $audit->setEvent($event);
96
        $audit->setColumns(json_encode($columnMap ?: $columns));
97
        $audit->setBefore($snapshot ? json_encode($snapshot) : null);
98
        $audit->setAfter(json_encode($model->toArray()));
0 ignored issues
show
Bug introduced by
The method toArray() does not exist on Phalcon\Mvc\ModelInterface. It seems like you code against a sub-type of Phalcon\Mvc\ModelInterface such as Phalcon\Mvc\Model or Zemit\Models\Setting or Zemit\Models\Category or Zemit\Models\Audit or Zemit\Models\UserGroup or Zemit\Models\User or Zemit\Models\Field or Zemit\Models\Page or Zemit\Models\Log or Zemit\Models\File or Zemit\Models\Role or Zemit\Models\GroupRole or Zemit\Models\Template or Zemit\Models\AuditDetail or Zemit\Models\UserType or Zemit\Models\Post or Zemit\Models\PostCategory or Zemit\Models\Session or Zemit\Models\TranslateField or Zemit\Models\GroupType or Zemit\Models\Translate or Zemit\Models\Email or Zemit\Models\Data or Zemit\Models\Group or Zemit\Models\Lang or Zemit\Models\EmailFile or Zemit\Models\TranslateTable or Zemit\Models\SiteLang or Zemit\Models\UserRole or Zemit\Models\Flag or Zemit\Models\Menu or Zemit\Models\Site or Zemit\Models\Type or Zemit\Models\Channel or Zemit\Models\Meta. ( Ignorable by Annotation )

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

98
        $audit->setAfter(json_encode($model->/** @scrutinizer ignore-call */ toArray()));
Loading history...
99
        $audit->setParentId(self::$parentId);
100
        
101
        $auditDetailList = [];
102
        
103
        foreach ($columns as $column) {
104
            $map = empty($columnMap) ? $column : $columnMap[$column];
105
            $before = $snapshot[$map] ?? null;
106
            $after = $model->readAttribute($map);
0 ignored issues
show
Bug introduced by
The method readAttribute() does not exist on Phalcon\Mvc\ModelInterface. It seems like you code against a sub-type of Phalcon\Mvc\ModelInterface such as Phalcon\Mvc\Model or Zemit\Models\Setting or Zemit\Models\Category or Zemit\Models\Audit or Zemit\Models\UserGroup or Zemit\Models\User or Zemit\Models\Field or Zemit\Models\Page or Zemit\Models\Log or Zemit\Models\File or Zemit\Models\Role or Zemit\Models\GroupRole or Zemit\Models\Template or Zemit\Models\AuditDetail or Zemit\Models\UserType or Zemit\Models\Post or Zemit\Models\PostCategory or Zemit\Models\Session or Zemit\Models\TranslateField or Zemit\Models\GroupType or Zemit\Models\Translate or Zemit\Models\Email or Zemit\Models\Data or Zemit\Models\Group or Zemit\Models\Lang or Zemit\Models\EmailFile or Zemit\Models\TranslateTable or Zemit\Models\SiteLang or Zemit\Models\UserRole or Zemit\Models\Flag or Zemit\Models\Menu or Zemit\Models\Site or Zemit\Models\Type or Zemit\Models\Channel or Zemit\Models\Meta. ( Ignorable by Annotation )

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

106
            /** @scrutinizer ignore-call */ 
107
            $after = $model->readAttribute($map);
Loading history...
107
            // skip unchanged
108
            if ($event === 'update' && $changedFields !== null && $snapshot !== null) {
109
                if ($before === $after || !in_array($map, $changedFields, true)) {
110
                    continue;
111
                }
112
            }
113
            
114
            /** @var AuditDetail $auditDetail */
115
            $auditDetail = new $auditDetailClass();
116
            $auditDetail->setModel(get_class($model));
117
            $auditDetail->setTable($model->getSource());
118
            $auditDetail->setPrimary($model->getId());
119
            $auditDetail->setEvent($event);
120
            $auditDetail->setColumn($column);
121
            $auditDetail->setMap($map);
122
            $auditDetail->setBefore($before);
123
            $auditDetail->setAfter($after);
124
            $auditDetailList[] = $auditDetail;
125
        }
126
        
127
        $audit->AuditDetailList = $auditDetailList;
0 ignored issues
show
Bug introduced by
Accessing AuditDetailList on the interface Zemit\Models\Interfaces\AuditInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
128
        $save = $audit->save();
129
        foreach ($audit->getMessages() as $message) {
130
            $message->setField('Audit.' . $message->getField());
131
            $model->appendMessage($message);
132
        }
133
        
134
        self::$parentId = (!empty($model->hasDirtyRelated())) ? $audit->getId() : null;
0 ignored issues
show
Bug introduced by
The method hasDirtyRelated() does not exist on Phalcon\Mvc\ModelInterface. It seems like you code against a sub-type of Phalcon\Mvc\ModelInterface such as Phalcon\Mvc\Model or Zemit\Models\Setting or Zemit\Models\Category or Zemit\Models\Audit or Zemit\Models\UserGroup or Zemit\Models\User or Zemit\Models\Field or Zemit\Models\Page or Zemit\Models\Log or Zemit\Models\File or Zemit\Models\Role or Zemit\Models\GroupRole or Zemit\Models\Template or Zemit\Models\AuditDetail or Zemit\Models\UserType or Zemit\Models\Post or Zemit\Models\PostCategory or Zemit\Models\Session or Zemit\Models\TranslateField or Zemit\Models\GroupType or Zemit\Models\Translate or Zemit\Models\Email or Zemit\Models\Data or Zemit\Models\Group or Zemit\Models\Lang or Zemit\Models\EmailFile or Zemit\Models\TranslateTable or Zemit\Models\SiteLang or Zemit\Models\UserRole or Zemit\Models\Flag or Zemit\Models\Menu or Zemit\Models\Site or Zemit\Models\Type or Zemit\Models\Channel or Zemit\Models\Meta. ( Ignorable by Annotation )

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

134
        self::$parentId = (!empty($model->/** @scrutinizer ignore-call */ hasDirtyRelated())) ? $audit->getId() : null;
Loading history...
135
        return $save;
136
    }
137
    
138
    /**
139
     * Return true if data has been collected
140
     */
141
    protected function collectData(ModelInterface $model): bool
142
    {
143
        if ($model->hasSnapshotData()) {
0 ignored issues
show
Bug introduced by
The method hasSnapshotData() does not exist on Phalcon\Mvc\ModelInterface. It seems like you code against a sub-type of Phalcon\Mvc\ModelInterface such as Phalcon\Mvc\Model or Zemit\Models\Setting or Zemit\Models\Category or Zemit\Models\Audit or Zemit\Models\UserGroup or Zemit\Models\User or Zemit\Models\Field or Zemit\Models\Page or Zemit\Models\Log or Zemit\Models\File or Zemit\Models\Role or Zemit\Models\GroupRole or Zemit\Models\Template or Zemit\Models\AuditDetail or Zemit\Models\UserType or Zemit\Models\Post or Zemit\Models\PostCategory or Zemit\Models\Session or Zemit\Models\TranslateField or Zemit\Models\GroupType or Zemit\Models\Translate or Zemit\Models\Email or Zemit\Models\Data or Zemit\Models\Group or Zemit\Models\Lang or Zemit\Models\EmailFile or Zemit\Models\TranslateTable or Zemit\Models\SiteLang or Zemit\Models\UserRole or Zemit\Models\Flag or Zemit\Models\Menu or Zemit\Models\Site or Zemit\Models\Type or Zemit\Models\Channel or Zemit\Models\Meta. ( Ignorable by Annotation )

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

143
        if ($model->/** @scrutinizer ignore-call */ hasSnapshotData()) {
Loading history...
144
            $this->snapshot = $model->getSnapshotData();
0 ignored issues
show
Bug introduced by
The method getSnapshotData() does not exist on Phalcon\Mvc\ModelInterface. It seems like you code against a sub-type of Phalcon\Mvc\ModelInterface such as Phalcon\Mvc\Model or Zemit\Models\Setting or Zemit\Models\Category or Zemit\Models\Audit or Zemit\Models\UserGroup or Zemit\Models\User or Zemit\Models\Field or Zemit\Models\Page or Zemit\Models\Log or Zemit\Models\File or Zemit\Models\Role or Zemit\Models\GroupRole or Zemit\Models\Template or Zemit\Models\AuditDetail or Zemit\Models\UserType or Zemit\Models\Post or Zemit\Models\PostCategory or Zemit\Models\Session or Zemit\Models\TranslateField or Zemit\Models\GroupType or Zemit\Models\Translate or Zemit\Models\Email or Zemit\Models\Data or Zemit\Models\Group or Zemit\Models\Lang or Zemit\Models\EmailFile or Zemit\Models\TranslateTable or Zemit\Models\SiteLang or Zemit\Models\UserRole or Zemit\Models\Flag or Zemit\Models\Menu or Zemit\Models\Site or Zemit\Models\Type or Zemit\Models\Channel or Zemit\Models\Meta. ( Ignorable by Annotation )

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

144
            /** @scrutinizer ignore-call */ 
145
            $this->snapshot = $model->getSnapshotData();
Loading history...
145
            $this->changedFields = $model->getChangedFields();
0 ignored issues
show
Bug introduced by
The method getChangedFields() does not exist on Phalcon\Mvc\ModelInterface. It seems like you code against a sub-type of Phalcon\Mvc\ModelInterface such as Phalcon\Mvc\Model or Zemit\Models\Setting or Zemit\Models\Category or Zemit\Models\Audit or Zemit\Models\UserGroup or Zemit\Models\User or Zemit\Models\Field or Zemit\Models\Page or Zemit\Models\Log or Zemit\Models\File or Zemit\Models\Role or Zemit\Models\GroupRole or Zemit\Models\Template or Zemit\Models\AuditDetail or Zemit\Models\UserType or Zemit\Models\Post or Zemit\Models\PostCategory or Zemit\Models\Session or Zemit\Models\TranslateField or Zemit\Models\GroupType or Zemit\Models\Translate or Zemit\Models\Email or Zemit\Models\Data or Zemit\Models\Group or Zemit\Models\Lang or Zemit\Models\EmailFile or Zemit\Models\TranslateTable or Zemit\Models\SiteLang or Zemit\Models\UserRole or Zemit\Models\Flag or Zemit\Models\Menu or Zemit\Models\Site or Zemit\Models\Type or Zemit\Models\Channel or Zemit\Models\Meta. ( Ignorable by Annotation )

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

145
            /** @scrutinizer ignore-call */ 
146
            $this->changedFields = $model->getChangedFields();
Loading history...
146
            return true;
147
        }
148
    
149
        $this->snapshot = null;
150
        $this->changedFields = null;
151
        return false;
152
    }
153
}
154