Completed
Push — master ( 798733...6762cf )
by James Ekow Abaka
01:47
created

DataOperations   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 79.57%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 36
c 7
b 0
f 0
lcom 1
cbo 6
dl 0
loc 275
ccs 74
cts 93
cp 0.7957
rs 8.8

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B doSave() 0 38 6
A doValidate() 0 7 2
A validate() 0 11 3
B isPrimaryKeySet() 0 12 8
A assignValue() 0 8 2
A getData() 0 4 1
A getInvalidFields() 0 4 1
A isItemDeletable() 0 8 2
C saveRecord() 0 68 10
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
     * 
84
     * @param \ntentan\nibii\RecordWrapper $wrapper
85
     * @param Driver $driver
86
     */
87 34
    public function __construct(RecordWrapper $wrapper, Driver $driver)
88
    {
89 34
        $this->wrapper = $wrapper;
90 34
        $this->adapter = $wrapper->getAdapter();
91 34
        $this->driver = $driver;
92 34
    }
93
94
    /**
95
     * 
96
     * @param bool $hasMultipleData
97
     * @return bool
98
     */
99 10
    public function doSave(bool $hasMultipleData): bool
100
    {
101 10
        $this->hasMultipleData = $hasMultipleData;
102 10
        $invalidFields = [];
103 10
        $data = $this->wrapper->getData();
104
105 10
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
106 10
        $succesful = true;
107
108
        // Assign an empty array to force a validation error for empty models
109 10
        if (empty($data)) {
110 2
            $data = [[]];
111
        }
112
113 10
        $this->driver->beginTransaction();
114
115 10
        foreach ($data as $i => $datum) {
116 10
            $status = $this->saveRecord($datum, $primaryKey);
117 10
            $data[$i] = $datum;
118
119 10
            if (!$status['success']) {
120 8
                $succesful = false;
121 8
                $invalidFields[$i] = $status['invalid_fields'];
122 8
                $this->driver->rollback();
123 10
                break;
124
            }
125
        }
126
127 10
        if ($succesful) {
128 2
            $this->driver->commit();
129
        } else {
130 8
            $this->assignValue($this->invalidFields, $invalidFields);
131
        }
132
133 10
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
134
135 10
        return $succesful;
136
    }
137
138
    /**
139
     * 
140
     * @return bool|array
141
     */
142
    public function doValidate()
143
    {
144
        $record = $this->wrapper->getData()[0];
145
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
146
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
147
        return $this->validate($record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE);
148
    }
149
150
    /**
151
     * Save an individual record.
152
     * 
153
     * @param array $record The record to be saved
154
     * @param array $primaryKey The primary keys of the record
155
     * @return array
156
     */
157 10
    private function saveRecord(array &$record, array $primaryKey): array
158
    {
159
        $status = [
160 10
            'success' => true,
161
            'pk_assigned' => null,
162
            'invalid_fields' => []
163
        ];
164
165
        // Determine if the primary key of the record is set.
166 10
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
167
168
        // Reset the data in the model to contain only the data to be saved
169 10
        $this->wrapper->setData($record);
170
171
        // Run preUpdate or preSave callbacks on models and behaviours
172 10
        if ($pkSet) {
173 2
            $this->wrapper->preUpdateCallback();
174 2
            $record = $this->wrapper->getData();
175 2
            $record = reset($record) === false ? [] : reset($record);
176
        } else {
177 8
            $this->wrapper->preSaveCallback();
178 8
            $record = $this->wrapper->getData();
179 8
            $record = reset($record) === false ? [] : reset($record);
180
        }
181
182
        // Validate the data
183 10
        $validity = $this->validate($record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE);
184
185
        // Exit if data is invalid
186 10
        if ($validity !== true) {
187 8
            $status['invalid_fields'] = $validity;
188 8
            $status['success'] = false;
189 8
            return $status;
190
        }
191
192
        // Save any relationships that are attached to the data
193 2
        $relationships = $this->wrapper->getDescription()->getRelationships();
194 2
        $presentRelationships = [];
195
196 2
        foreach ($relationships ?? [] as $model => $relationship) {
197 2
            if (isset($record[$model])) {
198
                $relationship->preSave($record, $record[$model]);
199 2
                $presentRelationships[$model] = $relationship;
200
            }
201
        }
202
203
        // Assign the data to the wrapper again
204 2
        $this->wrapper->setData($record);
205
206
        // Update or save the data and run post callbacks
207 2
        if ($pkSet) {
208 2
            $this->adapter->update($record);
209 2
            $this->wrapper->postUpdateCallback();
210
        } else {
211
            $this->adapter->insert($record);
212
            $keyValue = $this->driver->getLastInsertId();
213
            $this->wrapper->{$primaryKey[0]} = $keyValue;
214
            $this->wrapper->postSaveCallback($keyValue);
215
        }
216
217
        // Reset the data so it contains any modifications made by callbacks
218 2
        $record = $this->wrapper->getData()[0];
219 2
        foreach ($presentRelationships as $model => $relationship) {
220
            $relationship->postSave($record);
221
        }
222
223 2
        return $status;
224
    }
225
226
    /**
227
     * 
228
     * @param array $data
229
     * @param int $mode
230
     * @return bool|array
231
     */
232 10
    private function validate(array $data, int $mode)
233
    {
234 10
        $validator = ORMContext::getInstance()->getModelValidatorFactory()->createModelValidator($this->wrapper, $mode);
235 10
        $errors = [];
236
237 10
        if (!$validator->validate($data)) {
238 8
            $errors = $validator->getInvalidFields();
239
        }
240 10
        $errors = $this->wrapper->onValidate($errors);
241 10
        return empty($errors) ? true : $errors;
242
    }
243
244
    /**
245
     * 
246
     * @param string|array $primaryKey
247
     * @param array $data
248
     * @return bool
249
     */
250 10
    private function isPrimaryKeySet($primaryKey, array $data) : bool
251
    {
252 10
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
253
            return true;
254
        }
255 10
        foreach ($primaryKey as $keyField) {
256 10
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
257 10
                return false;
258
            }
259
        }
260 2
        return true;
261
    }
262
    
263
    /**
264
     * 
265
     * @param mixed $property
266
     * @param mixed $value
267
     */
268 8
    private function assignValue(&$property, $value) : void
269
    {
270 8
        if ($this->hasMultipleData) {
271
            $property = $value;
272
        } else {
273 8
            $property = $value[0];
274
        }
275 8
    }
276
277
    /**
278
     * 
279
     * @return array
280
     */
281
    public function getData() : array
282
    {
283
        return $this->data;
284
    }
285
286
    /**
287
     * 
288
     * @return array
289
     */
290 10
    public function getInvalidFields() : array
291
    {
292 10
        return $this->invalidFields;
293
    }
294
295
    /**
296
     * 
297
     * @param string $primaryKey
298
     * @param array $data
299
     * @return bool
300
     */
301
    public function isItemDeletable(string $primaryKey, array $data) : bool
302
    {
303
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
304
            return true;
305
        } else {
306
            return false;
307
        }
308
    }
309
310
}
311