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

RecordWrapper::postSaveCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 1
nc 1
nop 1
crap 1
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\utils\Text;
30
31
/**
32
 * An active record wrapper for database records.
33
 */
34
class RecordWrapper implements \ArrayAccess, \Countable, \Iterator
35
{
36
37
    /**
38
     * An associative array of models to which this model has a one to may relationship.
39
     *
40
     * @var array
41
     */
42
    protected $hasMany = [];
43
44
    /**
45
     * An associative array of models which have a one-to-many relationship with this model.
46
     *
47
     * @var array
48
     */
49
    protected $belongsTo = [];
50
51
    /**
52
     * An associative array of models with which this model has a many to many relationship.
53
     *
54
     * @var array
55
     */
56
    protected $manyHaveMany = [];
57
58
    /**
59
     * The name of the database table.
60
     *
61
     * @var string
62
     */
63
    protected $table;
64
65
    /**
66
     * The name of the schema to which this table belongs.
67
     *
68
     * @var string
69
     */
70
    protected $schema;
71
72
    /**
73
     * Temporary data held in the model object.
74
     *
75
     * @var array
76
     */
77
    protected $modelData = [];
78
79
    /**
80
     * A quoted string of the table name used for building queries.
81
     *
82
     * @var string
83
     */
84
    private $quotedTable;
85
86
    /**
87
     * The raw table name without any quotes.
88
     *
89
     * @var string
90
     */
91
    private $unquotedTable;
92
93
    /**
94
     * An array of fields that contain validation errors after an attempted save.
95
     *
96
     * @var array
97
     */
98
    private $invalidFields;
99
100
    /**
101
     * @var
102
     */
103
    private $dynamicOperations;
104
    private $index = 0;
105
    private $dataSet = false;
106
    private $className;
107
108
    /**
109
     * An instance of the driver adapter for interacting with the database.
110
     * @var DriverAdapter
111
     */
112
    private $adapter;
113
    private $context;
114
    private $keys = [];
115
    private $initialized = false;
116
117
    /**
118
     * Initialize the record wrapper and setup the adapters, drivers, tables and schemas.
119
     * @return void
120
     */
121 36
    protected function initialize(): void
122
    {
123 36
        if ($this->initialized) {
124 36
            return;
125
        }
126 36
        $this->context = ORMContext::getInstance();
127 36
        $this->adapter = $this->context->getDriverAdapter();
128 36
        $table = $this->table ?? $this->context->getModelTable($this);
129 36
        $driver = $this->context->getDbContext()->getDriver();
130 36
        $this->adapter->setContext($this->context);
131 36
        $this->className = (new \ReflectionClass($this))->getName();
132 36
        if (is_string($table)) {
133 36
            $this->table = $this->unquotedTable = $table;
134
        } else {
135
            $this->table = $table['table'];
136
            $this->schema = $table['schema'];
137
        }
138 36
        $this->quotedTable = ($this->schema ? "{$driver->quoteIdentifier($this->schema)}." : "") . $driver->quoteIdentifier($this->table);
139 36
        $this->unquotedTable = ($this->schema ? "{$this->schema}." : "") . $this->table;
140 36
        $this->adapter->setModel($this, $this->quotedTable);
141 36
        $this->initialized = true;
142 36
    }
143
144
    public function __debugInfo()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
145
    {
146
        $data = $this->getData();
147
        return $this->hasMultipleItems() ? $data : isset($data[0]) ? $data[0] : [];
148
    }
149
150
    /**
151
     * Return a description of the model wrapped by this wrapper.
152
     * @return ModelDescription
153
     */
154 28
    public function getDescription()
155
    {
156 28
        $this->initialize();
157 28
        return $this->context->getCache()->read(
158 28
            "{$this->className}::desc", function () {
159 28
            return $this->context->getModelDescription($this);
160 28
        }
161
        );
162
    }
163
164
    /**
165
     * Return the number of items stored in the model or matched by the query.
166
     * Depending on the state of the model, the count method will return different values. For models that have data
167
     * values set with calls to setData, this method returns the number of records that were added. On the other hand,
168
     * for models that do not have data set, this method queries the database to find out the number of records that
169
     * are either in the model, or for models that have been filtered, the number of records that match the filter.
170
     *
171
     * @param int|array|QueryParameters $query
172
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|type?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
173
     */
174 14
    public function count($query = null)
175
    {
176 14
        if ($this->dataSet) {
177 14
            return count($this->getData());
178
        }
179
        return $this->__call('count', [$query]);
180
    }
181
182
    /**
183
     * Retrieve an item stored in the record.
184
     * This method returns items that are directly stored in the model or lazy loads related items. The key could be a
185
     * field in the model's table or the name of a related model.
186
     *
187
     * @param string $key A key identifying the item to be retrieved.
188
     * @return mixed
189
     */
190 12
    private function retrieveItem($key)
191
    {
192 12
        $relationships = $this->getDescription()->getRelationships();
193 12
        $decamelizedKey = Text::deCamelize($key);
194 12
        if (isset($relationships[$key])) {
195 8
            return $this->fetchRelatedFields($relationships[$key]);
196
        }
197 4
        return isset($this->modelData[$decamelizedKey]) ? $this->modelData[$decamelizedKey] : null;
198
    }
199
200
    /**
201
     * Calls dynamic methods.
202
     *
203
     * @method
204
     * @param string $name
205
     * @param array $arguments
206
     * @return type
207
     */
208 34
    public function __call($name, $arguments)
209
    {
210 34
        $this->initialize();
211 34
        if ($this->dynamicOperations === null) {
212 34
            $this->dynamicOperations = new Operations($this, $this->quotedTable);
213
        }
214 34
        return $this->dynamicOperations->perform($name, $arguments);
215
    }
216
217
    /**
218
     * Set a value for a field in the model.
219
     *
220
     * @param string $name
221
     * @param mixed $value
222
     */
223 8
    public function __set($name, $value)
224
    {
225 8
        $this->dataSet = true;
226 8
        $this->modelData[Text::deCamelize($name)] = $value;
227 8
    }
228
229 12
    public function __get($name)
230
    {
231 12
        return $this->retrieveItem($name);
232
    }
233
234 10
    public function save()
235
    {
236 10
        $return = $this->__call('save', [$this->hasMultipleItems()]);
237 10
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
238 10
        return $return;
239
    }
240
241 18
    private function hasMultipleItems()
242
    {
243 18
        if (count($this->modelData) > 0) {
244 16
            return is_numeric(array_keys($this->modelData)[0]);
245
        } else {
246 2
            return false;
247
        }
248
    }
249
250 18
    public function getData()
251
    {
252 18
        $data = [];
253
254 18
        if (count($this->modelData) == 0) {
255 6
            $data = $this->modelData;
256 16
        } else if ($this->hasMultipleItems()) {
257 6
            $data = $this->modelData;
258 12
        } else if (count($this->modelData) > 0) {
259 12
            $data[] = $this->modelData;
260
        }
261
262 18
        return $data;
263
    }
264
265 26
    public function setData($data)
266
    {
267 26
        $this->dataSet = is_array($data) ? true : false;
268 26
        $this->modelData = $data;
269 26
    }
270
271
    public function mergeData($data)
272
    {
273
        foreach ($data as $key => $value) {
274
            $this->modelData[$key] = $value;
275
        }
276
        $this->dataSet = true;
277
    }
278
279 2
    public function offsetExists($offset)
280
    {
281 2
        return isset($this->modelData[$offset]);
282
    }
283
284 2
    public function offsetGet($offset)
285
    {
286 2
        if (is_numeric($offset)) {
287 2
            return $this->wrap($offset);
288
        } else {
289 2
            return $this->retrieveItem($offset);
290
        }
291
    }
292
293 2
    public function offsetSet($offset, $value)
294
    {
295 2
        $this->dataSet = true;
296 2
        $this->modelData[$offset] = $value;
297 2
    }
298
299
    public function offsetUnset($offset)
300
    {
301
        unset($this->modelData[$offset]);
302
    }
303
304 6
    private function wrap($offset)
305
    {
306 6
        $this->initialize();
307 6
        if (isset($this->modelData[$offset])) {
308 6
            $className = $this->className;
309 6
            $newInstance = new $className();
310 6
            $newInstance->initialize();
311 6
            $newInstance->setData($this->modelData[$offset]);
312 6
            return $newInstance;
313
        } else {
314
            return null;
315
        }
316
    }
317
318 4
    public function getInvalidFields()
319
    {
320 4
        return $this->invalidFields;
321
    }
322
323
    public function getHasMany()
324
    {
325
        return $this->hasMany;
326
    }
327
328
    public function getBelongsTo()
329
    {
330
        return $this->belongsTo;
331
    }
332
333 4
    public function current()
334
    {
335 4
        return $this->wrap($this->keys[$this->index]);
336
    }
337
338
    public function key()
339
    {
340
        return $this->keys[$this->index];
341
    }
342
343 4
    public function next()
344
    {
345 4
        $this->index++;
346 4
    }
347
348 4
    public function rewind()
349
    {
350 4
        $this->keys = array_keys($this->modelData);
351 4
        $this->index = 0;
352 4
    }
353
354 4
    public function valid()
355
    {
356 4
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
357
    }
358
359 10
    public function onValidate($errors)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
360
    {
361 10
        return $errors;
362
    }
363
364 12
    private function fetchRelatedFields($relationship, $index = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
365
    {
366 12
        if ($index === null) {
367 12
            $data = $this->modelData;
368
        } else {
369
            $data = $this->modelData[$index];
370
        }
371 12
        $model = $relationship->getModelInstance();
372 12
        if (empty($data)) {
373
            return $model;
374
        }
375 12
        $query = $relationship->prepareQuery($data);
376 12
        return $query ? $model->fetch($query) : $model;
377
    }
378
379 28
    public function getRelationships()
380
    {
381
        return [
382 28
            'HasMany' => $this->hasMany,
383 28
            'BelongsTo' => $this->belongsTo,
384 28
            'ManyHaveMany' => $this->manyHaveMany
385
        ];
386
    }
387
388
    public function usetField($field)
389
    {
390
        unset($this->modelData[$field]);
391
    }
392
393 8
    public function preSaveCallback()
394
    {
395
396 8
    }
397
398 4
    public function postSaveCallback($id)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
399
    {
400
401 4
    }
402
403 2
    public function preUpdateCallback()
404
    {
405
406 2
    }
407
408 2
    public function postUpdateCallback()
409
    {
410
411 2
    }
412
413 36
    public function getDBStoreInformation()
414
    {
415 36
        $this->initialize();
416
        return [
417 36
            'schema' => $this->schema,
418 36
            'table' => $this->table,
419 36
            'quoted_table' => $this->quotedTable,
420 36
            'unquoted_table' => $this->unquotedTable
421
        ];
422
    }
423
424
    /**
425
     *
426
     * @return DriverAdapter
427
     */
428 36
    public function getAdapter()
429
    {
430 36
        $this->initialize();
431 36
        return $this->adapter;
432
    }
433
434 4
    private function expandArrayValue($array, $relationships, $depth, $expandableModels = [], $index = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
435
    {
436 4
        if (empty($expandableModels)) {
437 4
            foreach ($relationships as $name => $relationship) {
438 4
                $array[$name] = $this->fetchRelatedFields($relationship, $index)->toArray($depth);
439
            }
440
        } else {
441
            foreach ($expandableModels as $name) {
442
                $array[$name] = $this->fetchRelatedFields($relationships[$name], $index)->toArray($depth, $expandableModels);
443
            }
444
        }
445 4
        return $array;
446
    }
447
448 16
    public function toArray($depth = 0, $expandableModels = [])
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
449
    {
450 16
        $relationships = $this->getDescription()->getRelationships();
451 16
        $array = $this->modelData;
452 16
        if ($depth > 0) {
453 4
            if ($this->hasMultipleItems()) {
454
                foreach ($array as $i => $value) {
455
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $expandableModels, $i);
456
                }
457
            } else {
458 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1, $expandableModels);
459
            }
460
        }
461 16
        return $array;
462
    }
463
464
}
465