Completed
Push — dev ( 6df47d...65a8c9 )
by James Ekow Abaka
12:02
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 2015 ekow.
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
    private $wrapper;
40
41
    /**
42
     * Private instance of driver adapter
43
     *
44
     * @var DriverAdapter
45
     */
46
    private $adapter;
47
48
    /**
49
     * Copy of data to be manipulated in the operations.
50
     * @var array
51
     */
52
    private $data;
53
54
    /**
55
     * Fields that contained errors after save or update operations were performed.
56
     * @var array
57
     */
58
    private $invalidFields = [];
59
60
    /**
61
     * Set to true when the model holds multiple records.
62
     * @var bool
63
     */
64
    private $hasMultipleData;
65
66
    /**
67
     * An instance of the atiaa driver.
68
     * @var Driver
69
     */
70
    private $driver;
71
72
    /**
73
     * Used to indicate save operation is in save mode to create new items.
74
     */
75
    const MODE_SAVE = 0;
76
    
77
    /**
78
     * Used to indicate save operation is in update mode to update existing items.
79
     */
80
    const MODE_UPDATE = 1;
81
82
    /**
83
     * Create a new instance
84
     * 
85
     * @param \ntentan\nibii\RecordWrapper $wrapper
86
     * @param Driver $driver
87
     */
88 34
    public function __construct(RecordWrapper $wrapper, Driver $driver)
89
    {
90 34
        $this->wrapper = $wrapper;
91 34
        $this->adapter = $wrapper->getAdapter();
92 34
        $this->driver = $driver;
93 34
    }
94
95
    /**
96
     * Perform the model save command
97
     * 
98
     * @param bool $hasMultipleData
99
     * @return bool
100
     */
101 10
    public function doSave(bool $hasMultipleData): bool
102
    {
103 10
        $this->hasMultipleData = $hasMultipleData;
104 10
        $invalidFields = [];
105 10
        $data = $this->wrapper->getData();
106
107 10
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
108 10
        $succesful = true;
109
110
        // Assign an empty array to force a validation error for empty models
111 10
        if (empty($data)) {
112 2
            $data = [[]];
113
        }
114
115 10
        $this->driver->beginTransaction();
116
117 10
        foreach ($data as $i => $datum) {
118 10
            $status = $this->saveRecord($datum, $primaryKey);
119 10
            $data[$i] = $datum;
120
121 10
            if (!$status['success']) {
122 4
                $succesful = false;
123 4
                $invalidFields[$i] = $status['invalid_fields'];
124 4
                $this->driver->rollback();
125 10
                break;
126
            }
127
        }
128
129 10
        if ($succesful) {
130 6
            $this->driver->commit();
131
        } else {
132 4
            $this->assignValue($this->invalidFields, $invalidFields);
133
        }
134
135 10
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
136 10
        return $succesful;
137
    }
138
139
    /**
140
     * 
141
     * @return bool|array
142
     */
143
    public function doValidate()
144
    {
145
        $record = $this->wrapper->getData()[0];
146
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
147
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
148
        return $this->validate($record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE);
0 ignored issues
show
Unused Code introduced by
The call to DataOperations::validate() has too many arguments starting with $pkSet ? \ntentan\nibii\...taOperations::MODE_SAVE.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
149
    }
150
151
    /**
152
     * Save an individual record.
153
     * 
154
     * @param array $record The record to be saved
155
     * @param array $primaryKey The primary keys of the record
156
     * @return array
157
     */
158 10
    private function saveRecord(array &$record, array $primaryKey): array
159
    {
160
        $status = [
161 10
            'success' => true,
162
            'pk_assigned' => null,
163
            'invalid_fields' => []
164
        ];
165
166
        // Determine if the primary key of the record is set.
167 10
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
168
169
        // Reset the data in the model to contain only the data to be saved
170 10
        $this->wrapper->setData($record);
171
172
173
        // Execute all callbacks on the model
174 10
        $this->wrapper->preSaveCallback();
175 10
        if ($pkSet) {
176 2
            $this->wrapper->preUpdateCallback();
177
        } else {
178 8
            $this->wrapper->preCreateCallback();
179
        }
180
181
        // Validate the data
182 10
        $validity = $this->validate($pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE);
183
184
        // Exit if data is invalid
185 10
        if ($validity !== true) {
186 4
            $status['invalid_fields'] = $validity;
187 4
            $status['success'] = false;
188 4
            return $status;
189
        }
190
191 6
        $record = $this->wrapper->getData();
192 6
        $record = reset($record) === false ? [] : reset($record);
193
194
        // Save any relationships that are attached to the data
195 6
        $relationships = $this->wrapper->getDescription()->getRelationships();
196 6
        $relationshipsWithData = [];
197
198 6
        foreach ($relationships ?? [] as $model => $relationship) {
199 6
            if (isset($record[$model])) {
200
                $relationship->preSave($record, $record[$model]);
201 6
                $relationshipsWithData[$model] = $relationship;
202
            }
203
        }
204
205
        // Assign the data to the wrapper again
206 6
        $this->wrapper->setData($record);
207
208
        // Update or save the data and run post callbacks
209 6
        if ($pkSet) {
210 2
            $this->adapter->update($record);
211 2
            $this->wrapper->postUpdateCallback();
212
        } else {
213 4
            $this->adapter->insert($record);
214 4
            $keyValue = $this->driver->getLastInsertId();
215 4
            $this->wrapper->{$primaryKey[0]} = $keyValue;
216 4
            $this->wrapper->postCreateCallback($keyValue);
217
        }
218 6
        $this->wrapper->postSaveCallback();
219
220
        // Reset the data so it contains any modifications made by callbacks
221 6
        $record = $this->wrapper->getData()[0];
222 6
        foreach ($relationshipsWithData as $model => $relationship) {
223
            $relationship->postSave($record);
224
        }
225
226 6
        return $status;
227
    }
228
229
    /**
230
     * 
231
     * @param array $data
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
232
     * @param int $mode
233
     * @return bool|array
234
     */
235 10
    private function validate(int $mode)
236
    {
237 10
        $validator = ORMContext::getInstance()->getModelValidatorFactory()->createModelValidator($this->wrapper, $mode);
238 10
        $mainValidatorErrors = [];
239
240 10
        if (!$validator->validate($this->wrapper->toArray())) {
241 4
            $mainValidatorErrors = $validator->getInvalidFields();
242
        }
243 10
        $customValidatorErrors = $this->wrapper->validate($mainValidatorErrors);
0 ignored issues
show
Unused Code introduced by
The call to RecordWrapper::validate() has too many arguments starting with $mainValidatorErrors.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
244 10
        $errors = array_merge_recursive($mainValidatorErrors, $customValidatorErrors);
245 10
        return empty($errors) ? true : $errors;
246
    }
247
248
    /**
249
     * 
250
     * @param string|array $primaryKey
251
     * @param array $data
252
     * @return bool
253
     */
254 10
    private function isPrimaryKeySet($primaryKey, array $data) : bool
255
    {
256 10
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
257
            return true;
258
        }
259 10
        foreach ($primaryKey as $keyField) {
260 10
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
261 10
                return false;
262
            }
263
        }
264 2
        return true;
265
    }
266
    
267
    /**
268
     * 
269
     * @param mixed $property
270
     * @param mixed $value
271
     */
272 4
    private function assignValue(&$property, $value) : void
273
    {
274 4
        if ($this->hasMultipleData) {
275
            $property = $value;
276
        } else {
277 4
            $property = $value[0];
278
        }
279 4
    }
280
281
    /**
282
     * 
283
     * @return array
284
     */
285
    public function getData() : array
286
    {
287
        return $this->data;
288
    }
289
290
    /**
291
     * 
292
     * @return array
293
     */
294 10
    public function getInvalidFields() : array
295
    {
296 10
        return $this->invalidFields;
297
    }
298
299
    /**
300
     * 
301
     * @param string $primaryKey
302
     * @param array $data
303
     * @return bool
304
     */
305
    public function isItemDeletable(string $primaryKey, array $data) : bool
306
    {
307
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
308
            return true;
309
        } else {
310
            return false;
311
        }
312
    }
313
314
}
315