Completed
Push — master ( 882132...509951 )
by James Ekow Abaka
02:54
created

DataOperations::isPrimaryKeySet()   B

Complexity

Conditions 8
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 8.1867

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
ccs 6
cts 7
cp 0.8571
rs 7.7777
cc 8
eloc 7
nc 4
nop 2
crap 8.1867
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);
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
        // Run preUpdate or preSave callbacks on models and behaviours
173 10
        if ($pkSet) {
174 2
            $this->wrapper->preUpdateCallback();
175 2
            $record = $this->wrapper->getData();
176 2
            $record = reset($record) === false ? [] : reset($record);
177
        } else {
178 8
            $this->wrapper->preSaveCallback();
179 8
            $record = $this->wrapper->getData();
180 8
            $record = reset($record) === false ? [] : reset($record);
181
        }
182
183
        // Validate the data
184 10
        $validity = $this->validate($record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE);
185
186
        // Exit if data is invalid
187 10
        if ($validity !== true) {
188 4
            $status['invalid_fields'] = $validity;
189 4
            $status['success'] = false;
190 4
            return $status;
191
        }
192
193
        // Save any relationships that are attached to the data
194 6
        $relationships = $this->wrapper->getDescription()->getRelationships();
195 6
        $presentRelationships = [];
196
197 6
        foreach ($relationships ?? [] as $model => $relationship) {
198 6
            if (isset($record[$model])) {
199
                $relationship->preSave($record, $record[$model]);
200 6
                $presentRelationships[$model] = $relationship;
201
            }
202
        }
203
204
        // Assign the data to the wrapper again
205 6
        $this->wrapper->setData($record);
206
207
        // Update or save the data and run post callbacks
208 6
        if ($pkSet) {
209 2
            $this->adapter->update($record);
210 2
            $this->wrapper->postUpdateCallback();
211
        } else {
212 4
            $this->adapter->insert($record);
213 4
            $keyValue = $this->driver->getLastInsertId();
214 4
            $this->wrapper->{$primaryKey[0]} = $keyValue;
215 4
            $this->wrapper->postSaveCallback($keyValue);
216
        }
217
218
        // Reset the data so it contains any modifications made by callbacks
219 6
        $record = $this->wrapper->getData()[0];
220 6
        foreach ($presentRelationships as $model => $relationship) {
221
            $relationship->postSave($record);
222
        }
223
224 6
        return $status;
225
    }
226
227
    /**
228
     * 
229
     * @param array $data
230
     * @param int $mode
231
     * @return bool|array
232
     */
233 10
    private function validate(array $data, int $mode)
234
    {
235 10
        $validator = ORMContext::getInstance()->getModelValidatorFactory()->createModelValidator($this->wrapper, $mode);
236 10
        $errors = [];
237
238 10
        if (!$validator->validate($data)) {
239 4
            $errors = $validator->getInvalidFields();
240
        }
241 10
        $errors = $this->wrapper->onValidate($errors);
242 10
        return empty($errors) ? true : $errors;
243
    }
244
245
    /**
246
     * 
247
     * @param string|array $primaryKey
248
     * @param array $data
249
     * @return bool
250
     */
251 10
    private function isPrimaryKeySet($primaryKey, array $data) : bool
252
    {
253 10
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
254
            return true;
255
        }
256 10
        foreach ($primaryKey as $keyField) {
257 10
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
258 10
                return false;
259
            }
260
        }
261 2
        return true;
262
    }
263
    
264
    /**
265
     * 
266
     * @param mixed $property
267
     * @param mixed $value
268
     */
269 4
    private function assignValue(&$property, $value) : void
270
    {
271 4
        if ($this->hasMultipleData) {
272
            $property = $value;
273
        } else {
274 4
            $property = $value[0];
275
        }
276 4
    }
277
278
    /**
279
     * 
280
     * @return array
281
     */
282
    public function getData() : array
283
    {
284
        return $this->data;
285
    }
286
287
    /**
288
     * 
289
     * @return array
290
     */
291 10
    public function getInvalidFields() : array
292
    {
293 10
        return $this->invalidFields;
294
    }
295
296
    /**
297
     * 
298
     * @param string $primaryKey
299
     * @param array $data
300
     * @return bool
301
     */
302
    public function isItemDeletable(string $primaryKey, array $data) : bool
303
    {
304
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
305
            return true;
306
        } else {
307
            return false;
308
        }
309
    }
310
311
}
312