Process::validate()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 16
ccs 8
cts 8
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace roaresearch\yii2\workflow\models;
4
5
use roaresearch\yii2\rmdb\models\Entity;
6
use yii\{base\InvalidConfigException, db\ActiveQuery};
7
8
/**
9
 * Model class for process which change stages depending on a worklow
10
 *
11
 * @property int workflowId
12
 * @property Workflow workflow
13
 * @property WorkLog[] $workLogs
14
 * @property WorkLog $activeWorkLog
15
 */
16
abstract class Process extends Entity
17
{
18
    /**
19
     * @var WorkLog model used internally to create the initial WorkLog
20
     * @see hasInitialWorkLog()
21
     */
22
    private $initialWorkLog;
23
24
    /**
25
     * @var bool Whether or not autogenerate the initial worklog. It only works
26
     * on new records.
27
     * @see hasInitialWorkLog()
28
     */
29
    protected $autogenerateInitialWorklog = true;
30
31
    /**
32
     * @return string full class name of the class to be used for the relation
33
     * `getWorkflow()`.
34
     */
35
    public function workflowClass(): string
36
    {
37
        return Workflow::class;
38
    }
39
40
    /**
41
     * @return string full class name of the class to be used to store the
42
     * assignment records.
43
     */
44
    abstract protected function assignmentClass(): string;
45
46
    /**
47
     * @return string full class name of the class to be used to store the
48
     * WorkLog records.
49
     */
50
    abstract protected function workLogClass(): string;
51
52
    /**
53
     * @return int the id of the workflow this process belongs to.
54
     */
55
    abstract public function getWorkflowId(): int;
56
57
    /**
58
     * Determines if the current process record has the need of an initial
59
     * worklog. If thats the case it autogenerates the initial worklog in case
60
     * its not already autogenerated.
61
     *
62
     * @return bool whether the initial worklog was autogenerated.
63
     */
64 5
    private function hasInitialWorkLog(): bool
65
    {
66 5
        if (!$this->autogenerateInitialWorklog || !$this->isNewRecord) {
67 3
            return false;
68
        }
69 3
        if (null === $this->initialWorkLog) {
70 3
            $this->initialWorkLog = $this->ensureWorkLog([
71 3
                'scenario' => WorkLog::SCENARIO_INITIAL,
72
            ]);
73
        }
74
75 3
        return true;
76
    }
77
78
    /**
79
     * @inheritdoc
80
     */
81 5
    public function load($data, $formName = null)
82
    {
83 5
        if ($this->hasInitialWorklog()) {
84 3
            $logLoad = $this->initialWorkLog->load($data, $formName);
85
86 3
            return parent::load($data, $formName) || $logLoad;
87
        }
88
89 2
        return parent::load($data, $formName);
90
    }
91
92
    /**
93
     * Whether the user is asigned to the process.
94
     *
95
     * @param ?int $userId
96
     * @return bool
97
     */
98 3
    public function userAssigned(?int $userId): bool
99
    {
100 3
        return !$this->getAssignments()->exists() // no one is assigned
101
            || $this->getAssignments()
102
                ->andWhere(['user_id' => $userId])
103 3
                ->exists();
104
    }
105
106
    /**
107
     * @inheritdoc
108
     */
109 5
    public function validate($attributeNames = null, $clearErrors = true)
110
    {
111 5
        $parentValidate = parent::validate($attributeNames, $clearErrors);
112 5
        if ($this->hasInitialWorklog()) {
113
            if (
114 3
                $this->initialWorkLog->validate($attributeNames, $clearErrors)
115
            ) {
116 2
                return $parentValidate;
117
            }
118
119 2
            $this->addErrors($this->initialWorkLog->getErrors());
120
121 2
            return false;
122
        }
123
124 3
        return $parentValidate;
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130 4
    public function transactions()
131
    {
132
        return [
133 4
            self::SCENARIO_DEFAULT => self::OP_INSERT,
134
        ];
135
    }
136
137
    /**
138
     * @inheritdoc
139
     */
140 3
    public function afterSave($insert, $changedAttributes)
141
    {
142 3
        parent::afterSave($insert, $changedAttributes);
143
144 3
        if ($insert) {
145 2
            $this->initialWorkLog->process_id = $this->id;
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on roaresearch\yii2\workflow\models\Process. Since you implemented __get, consider adding a @property annotation.
Loading history...
146 2
            $this->initialWorkLog->save(false); // skip validation
147
        }
148 3
    }
149
150
    /**
151
     * @inheritdoc
152
     */
153 18
    public function init()
154
    {
155 18
        if (!is_subclass_of($this->workLogClass(), WorkLog::class)) {
156
            throw new InvalidConfigException(
157
                static::class . '::workLogClass() must extend '
158
                    . WorkLog::class
159
            );
160
        }
161 18
        if (!is_subclass_of($this->assignmentClass(), Assignment::class)) {
162
            throw new InvalidConfigException(
163
                static::class . '::assignmentClass() must extend '
164
                    . Assignment::class
165
            );
166
        }
167 18
        parent::init();
168 18
    }
169
170
    /**
171
     * @return Workflow
172
     */
173
    public function getWorkflow(): Workflow
174
    {
175
        $workflowClass = $this->workflowClass();
176
177
        return $workflowClass::findOne($this->getWorkflowId());
178
    }
179
180
    /**
181
     * @return ActiveQuery
182
     */
183 3
    public function getAssignments(): ActiveQuery
184
    {
185 3
        return $this->hasMany($this->assignmentClass(), [
186 3
            'process_id' => 'id',
187 3
        ])->inverseOf('process');
188
    }
189
190
    /**
191
     * @return ActiveQuery
192
     */
193 6
    public function getWorkLogs(): ActiveQuery
194
    {
195 6
        return $this->hasMany($this->workLogClass(), [
196 6
            'process_id' => 'id',
197 6
        ])->inverseOf('process');
198
    }
199
200
    /**
201
     * @return ActiveQuery
202
     * @see https://dev.mysql.com/doc/refman/5.7/en/example-maximum-column-group-row.html
203
     */
204 6
    public function getActiveWorkLog(): ActiveQuery
205
    {
206 6
        $query = $this->getWorkLogs()->alias('activeWorkLog');
207 6
        $query->multiple = false;
208 6
        $workLogClass = $this->workLogClass();
209
210 6
        return $query->andWhere([
211 6
            'activeWorkLog.id' => $workLogClass::find()
212 6
                ->alias('WorkLogGroupWise')
213 6
                ->select(['MAX(WorkLogGroupWise.id)'])
214 6
                ->andWhere('activeWorkLog.process_id = WorkLogGroupWise.process_id')
215
        ]);
216
    }
217
218
    /**
219
     * Adds record to the WorkLog effectively transitioning the stage of the
220
     * process.
221
     *
222
     * @param array|WorkLog the WorkLog the process will transit to or an array
0 ignored issues
show
Bug introduced by
The type roaresearch\yii2\workflow\models\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
223
     * to create said WorkLog.
224
     * @param bool $runValidation
225
     */
226
    public function flow(&$workLog, bool $runValidation = true)
227
    {
228
        $workLog = $this->ensureWorkLog($workLog);
229
        $workLog->scenario = WorkLog::SCENARIO_FLOW;
230
231
        return $workLog->save($runValidation);
232
    }
233
234
    /**
235
     * Ensures that the provided parameter is either an array to create a valid
236
     * instance of the `workLogClass()` class.
237
     *
238
     * @param array|WorkLog $workLog
239
     * @return WorkLog extending the class defined in `workLogClass()`
240
     */
241 3
    private function ensureWorkLog($workLog): WorkLog
242
    {
243 3
        $workLogClass = $this->workLogClass();
244 3
        if (is_array($workLog)) {
245 3
            $workLog = new $workLogClass($workLog);
246
        } elseif (!$workLog instanceof $workLogClass) {
247
            throw new InvalidParamException(
0 ignored issues
show
Bug introduced by
The type roaresearch\yii2\workflo...s\InvalidParamException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
248
                "Parameter must be an instance of {$workLogClass} or an "
249
                    . 'array to configure an instance'
250
            );
251
        }
252
253 3
        $workLog->process_id = $this->id;
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on roaresearch\yii2\workflow\models\Process. Since you implemented __get, consider adding a @property annotation.
Loading history...
254 3
        $workLog->populateRelation('process', $this);
255
256 3
        return $workLog;
257
    }
258
}
259