Completed
Push — dev ( 15c1ec...2240d0 )
by James Ekow Abaka
01:47
created

DataOperations::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
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 30
    public function __construct(RecordWrapper $wrapper, Driver $driver)
96
    {
97 30
        $this->wrapper = $wrapper;
98 30
        $this->adapter = $wrapper->getAdapter();
99 30
        $this->driver = $driver;
100 30
    }
101
102
    /**
103
     * Perform the model save command.
104
     *
105
     * @param bool $hasMultipleData
106
     *
107
     * @return bool
108
     * @throws \ReflectionException
109
     * @throws \ntentan\atiaa\exceptions\ConnectionException
110
     * @throws exceptions\NibiiException
111
     */
112 10
    public function doSave(bool $hasMultipleData): bool
113
    {
114 10
        $this->hasMultipleData = $hasMultipleData;
115 10
        $invalidFields = [];
116 10
        $data = $this->wrapper->getData();
117
118 10
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
119 10
        $succesful = true;
120
121
        // Assign an empty array to force a validation error for empty models
122 10
        if (empty($data)) {
123 2
            $data = [[]];
124
        }
125
126 10
        $this->driver->beginTransaction();
127
128 10
        foreach ($data as $i => $datum) {
129 10
            $status = $this->saveRecord($datum, $primaryKey);
130 10
            $data[$i] = $datum;
131
132 10
            if (!$status['success']) {
133 4
                $succesful = false;
134 4
                $invalidFields[$i] = $status['invalid_fields'];
135 4
                $this->driver->rollback();
136 4
                break;
137
            }
138
        }
139
140 10
        if ($succesful) {
141 6
            $this->driver->commit();
142
        } else {
143 4
            $this->assignValue($this->invalidFields, $invalidFields);
144
        }
145
146 10
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
147
148 10
        return $succesful;
149
    }
150
151
    /**
152
     * @return bool|array
153
     */
154
    public function doValidate()
155
    {
156
        $record = $this->wrapper->getData()[0];
157
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
158
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
159
160
        return $this->validate($pkSet ? self::MODE_UPDATE : self::MODE_SAVE);
161
    }
162
163
    /**
164
     * Save an individual record.
165
     *
166
     * @param array $record The record to be saved
167
     * @param array $primaryKey The primary keys of the record
168
     *
169
     * @return array
170
     * @throws \ReflectionException
171
     * @throws \ntentan\atiaa\exceptions\ConnectionException
172
     * @throws exceptions\NibiiException
173
     */
174 10
    private function saveRecord(array &$record, array $primaryKey): array
175
    {
176
        $status = [
177 10
            'success'        => true,
178
            'pk_assigned'    => null,
179
            'invalid_fields' => [],
180
        ];
181
182
        // Determine if the primary key of the record is set.
183 10
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
184
185
        // Reset the data in the model to contain only the data to be saved
186 10
        $this->wrapper->setData($record);
187
188
        // Execute all callbacks on the model
189 10
        $this->wrapper->preSaveCallback();
190 10
        if ($pkSet) {
191 2
            $this->wrapper->preUpdateCallback();
192
        } else {
193 8
            $this->wrapper->preCreateCallback();
194
        }
195
196
        // Validate the data
197 10
        $validity = $this->validate($pkSet ? self::MODE_UPDATE : self::MODE_SAVE);
198
199
        // Exit if data is invalid
200 10
        if ($validity !== true) {
201 4
            $status['invalid_fields'] = $validity;
202 4
            $status['success'] = false;
203
204 4
            return $status;
205
        }
206
207 6
        $record = $this->wrapper->getData();
208 6
        $record = reset($record) === false ? [] : reset($record);
209
210
        // Save any relationships that are attached to the data
211 6
        $relationships = $this->wrapper->getDescription()->getRelationships();
212 6
        $relationshipsWithData = [];
213
214 6
        foreach ($relationships ?? [] as $model => $relationship) {
215 6
            if (isset($record[$model])) {
216
                $relationship->runSetup();
217
                $relationship->preSave($record, $record[$model]);
218
                $relationshipsWithData[$model] = $relationship;
219
            }
220
        }
221
222
        // Assign the data to the wrapper again
223 6
        $this->wrapper->setData($record);
224
225
        // Update or save the data and run post callbacks
226 6
        if ($pkSet) {
227 2
            $this->adapter->update($record);
228 2
            $this->wrapper->postUpdateCallback();
229
        } else {
230 4
            $this->adapter->insert($record);
231 4
            $keyValue = $this->driver->getLastInsertId();
232 4
            $this->wrapper->{$primaryKey[0]} = $keyValue;
233 4
            $this->wrapper->postCreateCallback($keyValue);
234
        }
235 6
        $this->wrapper->postSaveCallback();
236
237
        // Reset the data so it contains any modifications made by callbacks
238 6
        $record = $this->wrapper->getData()[0];
239 6
        foreach ($relationshipsWithData as $model => $relationship) {
240
            $relationship->postSave($record);
241
            $invalidRelatedFields = $relationship->getInvalidFields();
242
            if(!empty($invalidRelatedFields)) {
243
                $status['success'] = false;
244
                $status['invalid_fields'][$model] = $invalidRelatedFields;
245
            }
246
        }
247
248 6
        return $status;
249
    }
250
251
    /**
252
     * @param int $mode
253
     *
254
     * @return bool|array
255
     */
256 10
    private function validate(int $mode)
257
    {
258 10
        $validator = ORMContext::getInstance()->getModelValidatorFactory()->createModelValidator($this->wrapper, $mode);
259 10
        $mainValidatorErrors = [];
260 10
        $modelValidatorErrors = [];
261
262 10
        $data = $this->wrapper->toArray();
263
264 10
        if (!$validator->validate($data)) {
265 4
            $mainValidatorErrors = $validator->getInvalidFields();
266
        }
267
268 10
        if(!empty($this->wrapper->getValidationRules())) {
269
            $modelValidator = new Validator();
270
            $modelValidator->setRules($this->wrapper->getValidationRules());
271
            if(!$modelValidator->validate($data)) {
272
                $modelValidatorErrors = $modelValidator->getInvalidFields();
273
            }
274
        }
275
276 10
        $customValidatorErrors = $this->wrapper->onValidate($mainValidatorErrors);
277 10
        $errors = array_merge_recursive($mainValidatorErrors, $customValidatorErrors, $modelValidatorErrors);
278
279 10
        return empty($errors) ? true : $errors;
280
    }
281
282
    /**
283
     * @param string|array $primaryKey
284
     * @param array        $data
285
     *
286
     * @return bool
287
     */
288 10
    private function isPrimaryKeySet($primaryKey, array $data) : bool
289
    {
290 10
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
291
            return true;
292
        }
293 10
        foreach ($primaryKey as $keyField) {
294 10
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
295 8
                return false;
296
            }
297
        }
298
299 2
        return true;
300
    }
301
302
    /**
303
     * @param mixed $property
304
     * @param mixed $value
305
     */
306 4
    private function assignValue(&$property, $value) : void
307
    {
308 4
        if ($this->hasMultipleData) {
309
            $property = $value;
310
        } else {
311 4
            $property = $value[0];
312
        }
313 4
    }
314
315
    /**
316
     * @return array
317
     */
318
    public function getData() : array
319
    {
320
        return $this->data;
321
    }
322
323
    /**
324
     * @return array
325
     */
326 10
    public function getInvalidFields() : array
327
    {
328 10
        return $this->invalidFields;
329
    }
330
331
    /**
332
     * @param string $primaryKey
333
     * @param array  $data
334
     *
335
     * @return bool
336
     */
337
    public function isItemDeletable(string $primaryKey, array $data) : bool
338
    {
339
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
340
            return true;
341
        } else {
342
            return false;
343
        }
344
    }
345
}
346