Completed
Pull Request — master (#5)
by James Ekow Abaka
32:52 queued 31:13
created

DataOperations::saveRecord()   B

Complexity

Conditions 9
Paths 50

Size

Total Lines 70

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 9.0164

Importance

Changes 0
Metric Value
dl 0
loc 70
ccs 32
cts 34
cp 0.9412
rs 7.0989
c 0
b 0
f 0
cc 9
nc 50
nop 2
crap 9.0164

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
31
/**
32
 * Description of DataOperations.
33
 *
34
 * @author ekow
35
 */
36
class DataOperations
37
{
38
    /**
39
     * @var RecordWrapper
40
     */
41
    private $wrapper;
42
43
    /**
44
     * Private instance of driver adapter.
45
     *
46
     * @var DriverAdapter
47
     */
48
    private $adapter;
49
50
    /**
51
     * Copy of data to be manipulated in the operations.
52
     *
53
     * @var array
54
     */
55
    private $data;
56
57
    /**
58
     * Fields that contained errors after save or update operations were performed.
59
     *
60
     * @var array
61
     */
62
    private $invalidFields = [];
63
64
    /**
65
     * Set to true when the model holds multiple records.
66
     *
67
     * @var bool
68
     */
69
    private $hasMultipleData;
70
71
    /**
72
     * An instance of the atiaa driver.
73
     *
74
     * @var Driver
75
     */
76
    private $driver;
77
78
    /**
79
     * Used to indicate save operation is in save mode to create new items.
80
     */
81
    const MODE_SAVE = 0;
82
83
    /**
84
     * Used to indicate save operation is in update mode to update existing items.
85
     */
86
    const MODE_UPDATE = 1;
87
88
    /**
89
     * Create a new instance.
90
     *
91
     * @param \ntentan\nibii\RecordWrapper $wrapper
92
     * @param Driver                       $driver
93
     */
94 34
    public function __construct(RecordWrapper $wrapper, Driver $driver)
95
    {
96 34
        $this->wrapper = $wrapper;
97 34
        $this->adapter = $wrapper->getAdapter();
98 34
        $this->driver = $driver;
99 34
    }
100
101
    /**
102
     * Perform the model save command.
103
     *
104
     * @param bool $hasMultipleData
105
     *
106
     * @return bool
107
     */
108 10
    public function doSave(bool $hasMultipleData): bool
109
    {
110 10
        $this->hasMultipleData = $hasMultipleData;
111 10
        $invalidFields = [];
112 10
        $data = $this->wrapper->getData();
113
114 10
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
115 10
        $succesful = true;
116
117
        // Assign an empty array to force a validation error for empty models
118 10
        if (empty($data)) {
119 2
            $data = [[]];
120
        }
121
122 10
        $this->driver->beginTransaction();
123
124 10
        foreach ($data as $i => $datum) {
125 10
            $status = $this->saveRecord($datum, $primaryKey);
126 10
            $data[$i] = $datum;
127
128 10
            if (!$status['success']) {
129 4
                $succesful = false;
130 4
                $invalidFields[$i] = $status['invalid_fields'];
131 4
                $this->driver->rollback();
132 10
                break;
133
            }
134
        }
135
136 10
        if ($succesful) {
137 6
            $this->driver->commit();
138
        } else {
139 4
            $this->assignValue($this->invalidFields, $invalidFields);
140
        }
141
142 10
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
143
144 10
        return $succesful;
145
    }
146
147
    /**
148
     * @return bool|array
149
     */
150
    public function doValidate()
151
    {
152
        $record = $this->wrapper->getData()[0];
153
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
154
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
155
156
        return $this->validate($pkSet ? self::MODE_UPDATE : self::MODE_SAVE);
157
    }
158
159
    /**
160
     * Save an individual record.
161
     *
162
     * @param array $record     The record to be saved
163
     * @param array $primaryKey The primary keys of the record
164
     *
165
     * @return array
166
     */
167 10
    private function saveRecord(array &$record, array $primaryKey): array
168
    {
169
        $status = [
170 10
            'success'        => true,
171
            'pk_assigned'    => null,
172
            'invalid_fields' => [],
173
        ];
174
175
        // Determine if the primary key of the record is set.
176 10
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
177
178
        // Reset the data in the model to contain only the data to be saved
179 10
        $this->wrapper->setData($record);
180
181
        // Execute all callbacks on the model
182 10
        $this->wrapper->preSaveCallback();
183 10
        if ($pkSet) {
184 2
            $this->wrapper->preUpdateCallback();
185
        } else {
186 8
            $this->wrapper->preCreateCallback();
187
        }
188
189
        // Validate the data
190 10
        $validity = $this->validate($pkSet ? self::MODE_UPDATE : self::MODE_SAVE);
191
192
        // Exit if data is invalid
193 10
        if ($validity !== true) {
194 4
            $status['invalid_fields'] = $validity;
195 4
            $status['success'] = false;
196
197 4
            return $status;
198
        }
199
200 6
        $record = $this->wrapper->getData();
201 6
        $record = reset($record) === false ? [] : reset($record);
202
203
        // Save any relationships that are attached to the data
204 6
        $relationships = $this->wrapper->getDescription()->getRelationships();
205 6
        $relationshipsWithData = [];
206
207 6
        foreach ($relationships ?? [] as $model => $relationship) {
208 6
            if (isset($record[$model])) {
209
                $relationship->preSave($record, $record[$model]);
210 6
                $relationshipsWithData[$model] = $relationship;
211
            }
212
        }
213
214
        // Assign the data to the wrapper again
215 6
        $this->wrapper->setData($record);
216
217
        // Update or save the data and run post callbacks
218 6
        if ($pkSet) {
219 2
            $this->adapter->update($record);
220 2
            $this->wrapper->postUpdateCallback();
221
        } else {
222 4
            $this->adapter->insert($record);
223 4
            $keyValue = $this->driver->getLastInsertId();
224 4
            $this->wrapper->{$primaryKey[0]} = $keyValue;
225 4
            $this->wrapper->postCreateCallback($keyValue);
226
        }
227 6
        $this->wrapper->postSaveCallback();
228
229
        // Reset the data so it contains any modifications made by callbacks
230 6
        $record = $this->wrapper->getData()[0];
231 6
        foreach ($relationshipsWithData as $model => $relationship) {
232
            $relationship->postSave($record);
233
        }
234
235 6
        return $status;
236
    }
237
238
    /**
239
     * @param int $mode
240
     *
241
     * @return bool|array
242
     */
243 10
    private function validate(int $mode)
244
    {
245 10
        $validator = ORMContext::getInstance()->getModelValidatorFactory()->createModelValidator($this->wrapper, $mode);
246 10
        $mainValidatorErrors = [];
247
248 10
        if (!$validator->validate($this->wrapper->toArray())) {
249 4
            $mainValidatorErrors = $validator->getInvalidFields();
250
        }
251 10
        $customValidatorErrors = $this->wrapper->validate();
252 10
        $errors = array_merge_recursive($mainValidatorErrors, $customValidatorErrors);
253
254 10
        return empty($errors) ? true : $errors;
255
    }
256
257
    /**
258
     * @param string|array $primaryKey
259
     * @param array        $data
260
     *
261
     * @return bool
262
     */
263 10
    private function isPrimaryKeySet($primaryKey, array $data) : bool
264
    {
265 10
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
266
            return true;
267
        }
268 10
        foreach ($primaryKey as $keyField) {
269 10
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
270 10
                return false;
271
            }
272
        }
273
274 2
        return true;
275
    }
276
277
    /**
278
     * @param mixed $property
279
     * @param mixed $value
280
     */
281 4
    private function assignValue(&$property, $value) : void
282
    {
283 4
        if ($this->hasMultipleData) {
284
            $property = $value;
285
        } else {
286 4
            $property = $value[0];
287
        }
288 4
    }
289
290
    /**
291
     * @return array
292
     */
293
    public function getData() : array
294
    {
295
        return $this->data;
296
    }
297
298
    /**
299
     * @return array
300
     */
301 10
    public function getInvalidFields() : array
302
    {
303 10
        return $this->invalidFields;
304
    }
305
306
    /**
307
     * @param string $primaryKey
308
     * @param array  $data
309
     *
310
     * @return bool
311
     */
312
    public function isItemDeletable(string $primaryKey, array $data) : bool
313
    {
314
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
315
            return true;
316
        } else {
317
            return false;
318
        }
319
    }
320
}
321