DataOperations::validate()   A
last analyzed

Complexity

Conditions 5
Paths 12

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.4742

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 11
cts 15
cp 0.7332
rs 9.2088
c 0
b 0
f 0
cc 5
nc 12
nop 1
crap 5.4742
1
<?php
2
3
/*
4
 * The MIT License
5
 *
6
 * Copyright 2014-2018 James Ekow Abaka Ainooson
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 * THE SOFTWARE.
25
 */
26
27
namespace ntentan\nibii;
28
29
use ntentan\atiaa\Driver;
30
use ntentan\utils\Validator;
31
32
/**
33
 * Description of DataOperations.
34
 *
35
 * @author ekow
36
 */
37
class DataOperations
38
{
39
    /**
40
     * @var RecordWrapper
41
     */
42
    private $wrapper;
43
44
    /**
45
     * Private instance of driver adapter.
46
     *
47
     * @var DriverAdapter
48
     */
49
    private $adapter;
50
51
    /**
52
     * Copy of data to be manipulated in the operations.
53
     *
54
     * @var array
55
     */
56
    private $data;
57
58
    /**
59
     * Fields that contained errors after save or update operations were performed.
60
     *
61
     * @var array
62
     */
63
    private $invalidFields = [];
64
65
    /**
66
     * Set to true when the model holds multiple records.
67
     *
68
     * @var bool
69
     */
70
    private $hasMultipleData;
71
72
    /**
73
     * An instance of the atiaa driver.
74
     *
75
     * @var Driver
76
     */
77
    private $driver;
78
79
    /**
80
     * Used to indicate save operation is in save mode to create new items.
81
     */
82
    const MODE_SAVE = 0;
83
84
    /**
85
     * Used to indicate save operation is in update mode to update existing items.
86
     */
87
    const MODE_UPDATE = 1;
88
89
    /**
90
     * Create a new instance.
91
     *
92
     * @param \ntentan\nibii\RecordWrapper $wrapper
93
     * @param Driver                       $driver
94
     */
95 34
    public function __construct(RecordWrapper $wrapper, Driver $driver)
96
    {
97 34
        $this->wrapper = $wrapper;
98 34
        $this->adapter = $wrapper->getAdapter();
99 34
        $this->driver = $driver;
100 34
    }
101
102
    /**
103
     * Perform the model save command.
104
     *
105
     * @param bool $hasMultipleData
106
     *
107
     * @return bool
108
     */
109 10
    public function doSave(bool $hasMultipleData): bool
110
    {
111 10
        $this->hasMultipleData = $hasMultipleData;
112 10
        $invalidFields = [];
113 10
        $data = $this->wrapper->getData();
114
115 10
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
116 10
        $succesful = true;
117
118
        // Assign an empty array to force a validation error for empty models
119 10
        if (empty($data)) {
120 2
            $data = [[]];
121
        }
122
123 10
        $this->driver->beginTransaction();
124
125 10
        foreach ($data as $i => $datum) {
126 10
            $status = $this->saveRecord($datum, $primaryKey);
127 10
            $data[$i] = $datum;
128
129 10
            if (!$status['success']) {
130 4
                $succesful = false;
131 4
                $invalidFields[$i] = $status['invalid_fields'];
132 4
                $this->driver->rollback();
133 4
                break;
134
            }
135
        }
136
137 10
        if ($succesful) {
138 6
            $this->driver->commit();
139
        } else {
140 4
            $this->assignValue($this->invalidFields, $invalidFields);
141
        }
142
143 10
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
144
145 10
        return $succesful;
146
    }
147
148
    /**
149
     * @return bool|array
150
     */
151
    public function doValidate()
152
    {
153
        $record = $this->wrapper->getData()[0];
154
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
155
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
156
157
        return $this->validate($pkSet ? self::MODE_UPDATE : self::MODE_SAVE);
158
    }
159
160
    /**
161
     * Save an individual record.
162
     *
163
     * @param array $record     The record to be saved
164
     * @param array $primaryKey The primary keys of the record
165
     *
166
     * @return array
167
     */
168 10
    private function saveRecord(array &$record, array $primaryKey): array
169
    {
170
        $status = [
171 10
            'success'        => true,
172
            'pk_assigned'    => null,
173
            'invalid_fields' => [],
174
        ];
175
176
        // Determine if the primary key of the record is set.
177 10
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
178
179
        // Reset the data in the model to contain only the data to be saved
180 10
        $this->wrapper->setData($record);
181
182
        // Execute all callbacks on the model
183 10
        $this->wrapper->preSaveCallback();
184 10
        if ($pkSet) {
185 2
            $this->wrapper->preUpdateCallback();
186
        } else {
187 8
            $this->wrapper->preCreateCallback();
188
        }
189
190
        // Validate the data
191 10
        $validity = $this->validate($pkSet ? self::MODE_UPDATE : self::MODE_SAVE);
192
193
        // Exit if data is invalid
194 10
        if ($validity !== true) {
195 4
            $status['invalid_fields'] = $validity;
196 4
            $status['success'] = false;
197
198 4
            return $status;
199
        }
200
201 6
        $record = $this->wrapper->getData();
202 6
        $record = reset($record) === false ? [] : reset($record);
203
204
        // Save any relationships that are attached to the data
205 6
        $relationships = $this->wrapper->getDescription()->getRelationships();
206 6
        $relationshipsWithData = [];
207
208 6
        foreach ($relationships ?? [] as $model => $relationship) {
209 6
            if (isset($record[$model])) {
210
                $relationship->preSave($record, $record[$model]);
211
                $relationshipsWithData[$model] = $relationship;
212
            }
213
        }
214
215
        // Assign the data to the wrapper again
216 6
        $this->wrapper->setData($record);
217
218
        // Update or save the data and run post callbacks
219 6
        if ($pkSet) {
220 2
            $this->adapter->update($record);
221 2
            $this->wrapper->postUpdateCallback();
222
        } else {
223 4
            $this->adapter->insert($record);
224 4
            $keyValue = $this->driver->getLastInsertId();
225 4
            $this->wrapper->{$primaryKey[0]} = $keyValue;
226 4
            $this->wrapper->postCreateCallback($keyValue);
227
        }
228 6
        $this->wrapper->postSaveCallback();
229
230
        // Reset the data so it contains any modifications made by callbacks
231 6
        $record = $this->wrapper->getData()[0];
232 6
        foreach ($relationshipsWithData as $model => $relationship) {
233
            $relationship->postSave($record);
234
        }
235
236 6
        return $status;
237
    }
238
239
    /**
240
     * @param int $mode
241
     *
242
     * @return bool|array
243
     */
244 10
    private function validate(int $mode)
245
    {
246 10
        $validator = ORMContext::getInstance()->getModelValidatorFactory()->createModelValidator($this->wrapper, $mode);
247 10
        $mainValidatorErrors = [];
248 10
        $modelValidatorErrors = [];
249
250 10
        $data = $this->wrapper->toArray();
251
252 10
        if (!$validator->validate($data)) {
253 4
            $mainValidatorErrors = $validator->getInvalidFields();
254
        }
255
256 10
        if(!empty($this->wrapper->getValidationRules())) {
257
            $modelValidator = new Validator();
258
            $modelValidator->setRules($this->wrapper->getValidationRules());
259
            if(!$modelValidator->validate($data)) {
260
                $modelValidatorErrors = $modelValidator->getInvalidFields();
261
            }
262
        }
263
264 10
        $customValidatorErrors = $this->wrapper->onValidate($mainValidatorErrors);
265 10
        $errors = array_merge_recursive($mainValidatorErrors, $customValidatorErrors, $modelValidatorErrors);
266
267 10
        return empty($errors) ? true : $errors;
268
    }
269
270
    /**
271
     * @param string|array $primaryKey
272
     * @param array        $data
273
     *
274
     * @return bool
275
     */
276 10
    private function isPrimaryKeySet($primaryKey, array $data) : bool
277
    {
278 10
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
279
            return true;
280
        }
281 10
        foreach ($primaryKey as $keyField) {
282 10
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
283 8
                return false;
284
            }
285
        }
286
287 2
        return true;
288
    }
289
290
    /**
291
     * @param mixed $property
292
     * @param mixed $value
293
     */
294 4
    private function assignValue(&$property, $value) : void
295
    {
296 4
        if ($this->hasMultipleData) {
297
            $property = $value;
298
        } else {
299 4
            $property = $value[0];
300
        }
301 4
    }
302
303
    /**
304
     * @return array
305
     */
306
    public function getData() : array
307
    {
308
        return $this->data;
309
    }
310
311
    /**
312
     * @return array
313
     */
314 10
    public function getInvalidFields() : array
315
    {
316 10
        return $this->invalidFields;
317
    }
318
319
    /**
320
     * @param string $primaryKey
321
     * @param array  $data
322
     *
323
     * @return bool
324
     */
325
    public function isItemDeletable(string $primaryKey, array $data) : bool
326
    {
327
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
328
            return true;
329
        } else {
330
            return false;
331
        }
332
    }
333
}
334