Completed
Push — dev ( be8d0d...249ddf )
by James Ekow Abaka
01:51
created

DataOperations::doSave()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 38
ccs 22
cts 22
cp 1
rs 8.6897
c 0
b 0
f 0
cc 6
nc 12
nop 1
crap 6
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->runSetup();
211
                $relationship->preSave($record, $record[$model]);
212
                $relationshipsWithData[$model] = $relationship;
213
            }
214
        }
215
216
        // Assign the data to the wrapper again
217 6
        $this->wrapper->setData($record);
218
219
        // Update or save the data and run post callbacks
220 6
        if ($pkSet) {
221 2
            $this->adapter->update($record);
222 2
            $this->wrapper->postUpdateCallback();
223
        } else {
224 4
            $this->adapter->insert($record);
225 4
            $keyValue = $this->driver->getLastInsertId();
226 4
            $this->wrapper->{$primaryKey[0]} = $keyValue;
227 4
            $this->wrapper->postCreateCallback($keyValue);
228
        }
229 6
        $this->wrapper->postSaveCallback();
230
231
        // Reset the data so it contains any modifications made by callbacks
232 6
        $record = $this->wrapper->getData()[0];
233 6
        foreach ($relationshipsWithData as $model => $relationship) {
234
            $relationship->postSave($record);
235
        }
236
237 6
        return $status;
238
    }
239
240
    /**
241
     * @param int $mode
242
     *
243
     * @return bool|array
244
     */
245 10
    private function validate(int $mode)
246
    {
247 10
        $validator = ORMContext::getInstance()->getModelValidatorFactory()->createModelValidator($this->wrapper, $mode);
248 10
        $mainValidatorErrors = [];
249 10
        $modelValidatorErrors = [];
250
251 10
        $data = $this->wrapper->toArray();
252
253 10
        if (!$validator->validate($data)) {
254 4
            $mainValidatorErrors = $validator->getInvalidFields();
255
        }
256
257 10
        if(!empty($this->wrapper->getValidationRules())) {
258
            $modelValidator = new Validator();
259
            $modelValidator->setRules($this->wrapper->getValidationRules());
260
            if(!$modelValidator->validate($data)) {
261
                $modelValidatorErrors = $modelValidator->getInvalidFields();
262
            }
263
        }
264
265 10
        $customValidatorErrors = $this->wrapper->onValidate($mainValidatorErrors);
266 10
        $errors = array_merge_recursive($mainValidatorErrors, $customValidatorErrors, $modelValidatorErrors);
267
268 10
        return empty($errors) ? true : $errors;
269
    }
270
271
    /**
272
     * @param string|array $primaryKey
273
     * @param array        $data
274
     *
275
     * @return bool
276
     */
277 10
    private function isPrimaryKeySet($primaryKey, array $data) : bool
278
    {
279 10
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
280
            return true;
281
        }
282 10
        foreach ($primaryKey as $keyField) {
283 10
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
284 8
                return false;
285
            }
286
        }
287
288 2
        return true;
289
    }
290
291
    /**
292
     * @param mixed $property
293
     * @param mixed $value
294
     */
295 4
    private function assignValue(&$property, $value) : void
296
    {
297 4
        if ($this->hasMultipleData) {
298
            $property = $value;
299
        } else {
300 4
            $property = $value[0];
301
        }
302 4
    }
303
304
    /**
305
     * @return array
306
     */
307
    public function getData() : array
308
    {
309
        return $this->data;
310
    }
311
312
    /**
313
     * @return array
314
     */
315 10
    public function getInvalidFields() : array
316
    {
317 10
        return $this->invalidFields;
318
    }
319
320
    /**
321
     * @param string $primaryKey
322
     * @param array  $data
323
     *
324
     * @return bool
325
     */
326
    public function isItemDeletable(string $primaryKey, array $data) : bool
327
    {
328
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
329
            return true;
330
        } else {
331
            return false;
332
        }
333
    }
334
}
335