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

RecordWrapper::postCreateCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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\utils\Text;
30
31
/**
32
 * An active record wrapper for database records.
33
 */
34
class RecordWrapper implements \ArrayAccess, \Countable, \Iterator
35
{
36
    /**
37
     * An associative array of models to which this model has a one to may relationship.
38
     *
39
     * @var array
40
     */
41
    protected $hasMany = [];
42
43
    /**
44
     * An associative array of models which have a one-to-many relationship with this model.
45
     *
46
     * @var array
47
     */
48
    protected $belongsTo = [];
49
50
    /**
51
     * An associative array of models with which this model has a many to many relationship.
52
     *
53
     * @var array
54
     */
55
    protected $manyHaveMany = [];
56
57
    /**
58
     * The name of the database table.
59
     *
60
     * @var string
61
     */
62
    protected $table;
63
64
    /**
65
     * The name of the schema to which this table belongs.
66
     *
67
     * @var string
68
     */
69
    protected $schema;
70
71
    /**
72
     * Temporary data held in the model object.
73
     *
74
     * @var array
75
     */
76
    protected $modelData = [];
77
78
    /**
79
     * A quoted string of the table name used for building queries.
80
     *
81
     * @var string
82
     */
83
    private $quotedTable;
84
85
    /**
86
     * The raw table name without any quotes.
87
     *
88
     * @var string
89
     */
90
    private $unquotedTable;
91
92
    /**
93
     * An array of fields that contain validation errors after an attempted save.
94
     *
95
     * @var array
96
     */
97
    private $invalidFields;
98
99
    /**
100
     * An instance of the operations dispatcher.
101
     *
102
     * @var Operations
103
     */
104
    private $dynamicOperations;
105
106
    /**
107
     * Location of the RecordWrapper's internal iterator.
108
     *
109
     * @var int
110
     */
111
    private $index = 0;
112
113
    /**
114
     * This flag is set whenever data is manually put in the model with the setData method.
115
     *
116
     * @var bool
117
     */
118
    private $dataSet = false;
119
120
    /**
121
     * The name of the class for this model obtained through reflection.
122
     *
123
     * @var string
124
     */
125
    private $className;
126
127
    /**
128
     * An instance of the driver adapter for interacting with the database.
129
     *
130
     * @var DriverAdapter
131
     */
132
    private $adapter;
133
134
    /**
135
     * An instance of the ORMContext through which this model is operating.
136
     *
137
     * @var ORMContext
138
     */
139
    private $context;
140
141
    /**
142
     * Keys for the various fields when model is accessed as an associative array.
143
     *
144
     * @var array
145
     */
146
    private $keys = [];
147
148
    /**
149
     * This flag is set after the model has been properly initialized.
150
     * Useful after model is unserialized or accessed through the static interface.
151
     *
152
     * @var bool
153
     */
154
    private $initialized = false;
155
156
    /**
157
     * Initialize the record wrapper and setup the adapters, drivers, tables and schemas.
158
     * After initialization, this method sets the initialized flag.
159
     *
160
     * @return void
161
     */
162 36
    protected function initialize(): void
163
    {
164 36
        if ($this->initialized) {
165 36
            return;
166
        }
167 36
        $this->context = ORMContext::getInstance();
0 ignored issues
show
Documentation Bug introduced by
It seems like \ntentan\nibii\ORMContext::getInstance() of type object<self> is incompatible with the declared type object<ntentan\nibii\ORMContext> of property $context.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
168 36
        $this->adapter = $this->context->getDriverAdapter();
169 36
        $table = $this->table ?? $this->context->getModelTable($this);
170 36
        $driver = $this->context->getDbContext()->getDriver();
171 36
        $this->adapter->setContext($this->context);
172 36
        $this->className = (new \ReflectionClass($this))->getName();
173 36
        if (is_string($table)) {
174 36
            $this->table = $this->unquotedTable = $table;
175
        } else {
176
            $this->table = $table['table'];
177
            $this->schema = $table['schema'];
178
        }
179 36
        $this->quotedTable = ($this->schema ? "{$driver->quoteIdentifier($this->schema)}." : '').$driver->quoteIdentifier($this->table);
180 36
        $this->unquotedTable = ($this->schema ? "{$this->schema}." : '').$this->table;
181 36
        $this->adapter->setModel($this, $this->quotedTable);
182 36
        $this->initialized = true;
183 36
    }
184
185
    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...
186
    {
187
        $data = $this->getData();
188
189
        return $this->hasMultipleItems() ? $data : isset($data[0]) ? $data[0] : [];
190
    }
191
192
    /**
193
     * Return a description of the model wrapped by this wrapper.
194
     *
195
     * @return ModelDescription
196
     */
197 28
    public function getDescription() : ModelDescription
198
    {
199 28
        $this->initialize();
200
201 28
        return $this->context->getCache()->read(
202
            "{$this->className}::desc", function () {
203 28
                return $this->context->getModelDescription($this);
204 28
            }
205
        );
206
    }
207
208
    /**
209
     * Return the number of items stored in the model or matched by the query.
210
     * Depending on the state of the model, the count method will return different values. For models that have data
211
     * values set with calls to setData, this method returns the number of records that were added. On the other hand,
212
     * for models that do not have data set, this method queries the database to find out the number of records that
213
     * are either in the model, or for models that have been filtered, the number of records that match the filter.
214
     *
215
     * @param int|array|QueryParameters $query
216
     *
217
     * @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...
218
     */
219 14
    public function count($query = null)
220
    {
221 14
        if ($this->dataSet) {
222 14
            return count($this->getData());
223
        }
224
225
        return $this->__call('count', [$query]);
226
    }
227
228
    /**
229
     * Retrieve an item stored in the record.
230
     * This method returns items that are directly stored in the model or lazy loads related items. The key could be a
231
     * field in the model's table or the name of a related model.
232
     *
233
     * @param string $key A key identifying the item to be retrieved.
234
     *
235
     * @return mixed
236
     */
237 12
    private function retrieveItem($key)
238
    {
239 12
        $relationships = $this->getDescription()->getRelationships();
240 12
        $decamelizedKey = Text::deCamelize($key);
241 12
        if (isset($relationships[$key])) {
242 8
            return $this->fetchRelatedFields($relationships[$key]);
243
        }
244
245 4
        return isset($this->modelData[$decamelizedKey]) ? $this->modelData[$decamelizedKey] : null;
246
    }
247
248
    /**
249
     * Calls dynamic methods.
250
     *
251
     * @param string $name
252
     * @param array  $arguments
253
     *
254
     * @throws exceptions\NibiiException
255
     *
256
     * @return type
257
     */
258 34
    public function __call($name, $arguments)
259
    {
260 34
        $this->initialize();
261 34
        if ($this->dynamicOperations === null) {
262 34
            $this->dynamicOperations = new Operations($this, $this->quotedTable);
263
        }
264
265 34
        return $this->dynamicOperations->perform($name, $arguments);
266
    }
267
268
    /**
269
     * Set a value for a field in the model.
270
     *
271
     * @param string $name
272
     * @param mixed  $value
273
     */
274 8
    public function __set($name, $value)
275
    {
276 8
        $this->dataSet = true;
277 8
        $this->modelData[Text::deCamelize($name)] = $value;
278 8
    }
279
280 12
    public function __get($name)
281
    {
282 12
        return $this->retrieveItem($name);
283
    }
284
285 10
    public function save()
286
    {
287 10
        $return = $this->__call('save', [$this->hasMultipleItems()]);
288 10
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
289
290 10
        return $return;
291
    }
292
293 18
    private function hasMultipleItems()
294
    {
295 18
        if (count($this->modelData) > 0) {
296 16
            return is_numeric(array_keys($this->modelData)[0]);
297
        } else {
298 2
            return false;
299
        }
300
    }
301
302 18
    public function getData()
303
    {
304 18
        $data = [];
305
306 18
        if (count($this->modelData) == 0) {
307 6
            $data = $this->modelData;
308 16
        } elseif ($this->hasMultipleItems()) {
309 6
            $data = $this->modelData;
310 12
        } elseif (count($this->modelData) > 0) {
311 12
            $data[] = $this->modelData;
312
        }
313
314 18
        return $data;
315
    }
316
317 26
    public function setData($data)
318
    {
319 26
        $this->dataSet = is_array($data) ? true : false;
320 26
        $this->modelData = $data;
321 26
    }
322
323
    public function mergeData($data)
324
    {
325
        foreach ($data as $key => $value) {
326
            $this->modelData[$key] = $value;
327
        }
328
        $this->dataSet = true;
329
    }
330
331 2
    public function offsetExists($offset)
332
    {
333 2
        return isset($this->modelData[$offset]);
334
    }
335
336 2
    public function offsetGet($offset)
337
    {
338 2
        if (is_numeric($offset)) {
339 2
            return $this->wrap($offset);
340
        } else {
341 2
            return $this->retrieveItem($offset);
342
        }
343
    }
344
345 2
    public function offsetSet($offset, $value)
346
    {
347 2
        $this->dataSet = true;
348 2
        $this->modelData[$offset] = $value;
349 2
    }
350
351
    public function offsetUnset($offset)
352
    {
353
        unset($this->modelData[$offset]);
354
    }
355
356 6
    private function wrap($offset)
357
    {
358 6
        $this->initialize();
359 6
        if (isset($this->modelData[$offset])) {
360 6
            $className = $this->className;
361 6
            $newInstance = new $className();
362 6
            $newInstance->initialize();
363 6
            $newInstance->setData($this->modelData[$offset]);
364
365 6
            return $newInstance;
366
        } else {
367
            return;
368
        }
369
    }
370
371 4
    public function getInvalidFields()
372
    {
373 4
        return $this->invalidFields;
374
    }
375
376
    public function getHasMany()
377
    {
378
        return $this->hasMany;
379
    }
380
381
    public function getBelongsTo()
382
    {
383
        return $this->belongsTo;
384
    }
385
386 4
    public function current()
387
    {
388 4
        return $this->wrap($this->keys[$this->index]);
389
    }
390
391
    public function key()
392
    {
393
        return $this->keys[$this->index];
394
    }
395
396 4
    public function next()
397
    {
398 4
        $this->index++;
399 4
    }
400
401 4
    public function rewind()
402
    {
403 4
        $this->keys = array_keys($this->modelData);
404 4
        $this->index = 0;
405 4
    }
406
407 4
    public function valid()
408
    {
409 4
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
410
    }
411
412
    /**
413
     * A custom validator for the record wrapper.
414
     *
415
     * @return mixed
416
     */
417 10
    public function validate()
418
    {
419 10
        return [];
420
    }
421
422 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...
423
    {
424 12
        if ($index === null) {
425 12
            $data = $this->modelData;
426
        } else {
427
            $data = $this->modelData[$index];
428
        }
429 12
        $model = $relationship->getModelInstance();
430 12
        if (empty($data)) {
431
            return $model;
432
        }
433 12
        $query = $relationship->prepareQuery($data);
434
435 12
        return $query ? $model->fetch($query) : $model;
436
    }
437
438 28
    public function getRelationships()
439
    {
440
        return [
441 28
            'HasMany'      => $this->hasMany,
442 28
            'BelongsTo'    => $this->belongsTo,
443 28
            'ManyHaveMany' => $this->manyHaveMany,
444
        ];
445
    }
446
447
    public function usetField($field)
448
    {
449
        unset($this->modelData[$field]);
450
    }
451
452
    /**
453
     * Callback for when a record is either added or modified.
454
     */
455 10
    public function preSaveCallback()
456
    {
457 10
    }
458
459
    /**
460
     * Callback for when a record has been added or modified.
461
     *
462
     * @param $id
463
     */
464 6
    public function postSaveCallback()
465
    {
466 6
    }
467
468
    /**
469
     * Callback for when a new record is about to be created.
470
     */
471 8
    public function preCreateCallback()
472
    {
473 8
    }
474
475
    /**
476
     * Callback for when a new record has been created.
477
     * This callback can be most useful for obtaining the primary key of a newly created record.
478
     *
479
     * @param $id
480
     */
481 4
    public function postCreateCallback($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...
482
    {
483 4
    }
484
485
    /**
486
     * Callback for when a record is about to be updated.
487
     */
488 2
    public function preUpdateCallback()
489
    {
490 2
    }
491
492
    /**
493
     * Callback for when a record has been updated.
494
     */
495 2
    public function postUpdateCallback()
496
    {
497 2
    }
498
499 36
    public function getDBStoreInformation()
500
    {
501 36
        $this->initialize();
502
503
        return [
504 36
            'schema'         => $this->schema,
505 36
            'table'          => $this->table,
506 36
            'quoted_table'   => $this->quotedTable,
507 36
            'unquoted_table' => $this->unquotedTable,
508
        ];
509
    }
510
511
    /**
512
     * @return DriverAdapter
513
     */
514 36
    public function getAdapter()
515
    {
516 36
        $this->initialize();
517
518 36
        return $this->adapter;
519
    }
520
521 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...
522
    {
523 4
        if (empty($expandableModels)) {
524 4
            foreach ($relationships as $name => $relationship) {
525 4
                $array[$name] = $this->fetchRelatedFields($relationship, $index)->toArray($depth);
526
            }
527
        } else {
528
            foreach ($expandableModels as $name) {
529
                $array[$name] = $this->fetchRelatedFields($relationships[$name], $index)->toArray($depth, $expandableModels);
530
            }
531
        }
532
533 4
        return $array;
534
    }
535
536 24
    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...
537
    {
538 24
        $relationships = $this->getDescription()->getRelationships();
539 24
        $array = $this->modelData;
540 24
        if ($depth > 0) {
541 4
            if ($this->hasMultipleItems()) {
542
                foreach ($array as $i => $value) {
543
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $expandableModels, $i);
544
                }
545
            } else {
546 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1, $expandableModels);
547
            }
548
        }
549
550 24
        return $array;
551
    }
552
}
553