GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — dev ( cf5568...6a90d3 )
by James Ekow Abaka
01:49
created

RecordWrapper::getValidationRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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\nibii\exceptions\NibiiException;
30
use ntentan\utils\Text;
31
32
/**
33
 * An active record wrapper for database records.
34
 */
35
class RecordWrapper implements \ArrayAccess, \Countable, \Iterator
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
     * Extra validation rules to use over the model's inherent validation requirements.
81
     * @var array
82
     */
83
    protected $validationRules = [];
84
85
    /**
86
     * A quoted string of the table name used for building queries.
87
     *
88
     * @var string
89
     */
90
    private $quotedTable;
91
92
    /**
93
     * The raw table name without any quotes.
94
     *
95
     * @var string
96
     */
97
    private $unquotedTable;
98
99
    /**
100
     * An array of fields that contain validation errors after an attempted save.
101
     *
102
     * @var array
103
     */
104
    private $invalidFields;
105
106
    /**
107
     * An instance of the operations dispatcher.
108
     *
109
     * @var Operations
110
     */
111
    private $dynamicOperations;
112
113
    /**
114
     * Location of the RecordWrapper's internal iterator.
115
     *
116
     * @var int
117
     */
118
    private $index = 0;
119
120
    /**
121
     * This flag is set whenever data is manually put in the model with the setData method.
122
     *
123
     * @var bool
124
     */
125
    private $dataSet = false;
126
127
    /**
128
     * The name of the class for this model obtained through reflection.
129
     *
130
     * @var string
131
     */
132
    private $className;
133
134
    /**
135
     * An instance of the driver adapter for interacting with the database.
136
     *
137
     * @var DriverAdapter
138
     */
139
    private $adapter;
140
141
    /**
142
     * An instance of the ORMContext through which this model is operating.
143
     *
144
     * @var ORMContext
145
     */
146
    private $context;
147
148
    /**
149
     * Keys for the various fields when model is accessed as an associative array.
150
     *
151
     * @var array
152
     */
153
    private $keys = [];
154
155
    /**
156
     * This flag is set after the model has been properly initialized.
157
     * Useful after model is unserialized or accessed through the static interface.
158
     *
159
     * @var bool
160
     */
161
    private $initialized = false;
162
163
    /**
164
     * Initialize the record wrapper and setup the adapters, drivers, tables and schemas.
165
     * After initialization, this method sets the initialized flag.
166
     *
167
     * @return void
168
     * @throws NibiiException
169
     * @throws \ReflectionException
170
     * @throws \ntentan\atiaa\exceptions\ConnectionException
171
     */
172 32
    protected function initialize(): void
173
    {
174 32
        if ($this->initialized) {
175 32
            return;
176
        }
177 32
        $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...
178 32
        $this->adapter = $this->context->getDriverAdapter();
179 32
        $table = $this->table ?? $this->context->getModelTable($this);
180 32
        $driver = $this->context->getDbContext()->getDriver();
181 32
        $this->adapter->setContext($this->context);
182 32
        $this->className = (new \ReflectionClass($this))->getName();
183 32
        if (is_string($table)) {
184 32
            $this->table = $this->unquotedTable = $table;
185
        } else {
186
            $this->table = $table['table'];
187
            $this->schema = $table['schema'];
188
        }
189 32
        $this->quotedTable = ($this->schema ? "{$driver->quoteIdentifier($this->schema)}." : '').$driver->quoteIdentifier($this->table);
190 32
        $this->unquotedTable = ($this->schema ? "{$this->schema}." : '').$this->table;
191 32
        $this->adapter->setModel($this, $this->quotedTable);
192 32
        $this->initialized = true;
193 32
    }
194
195
    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...
196
    {
197
        $data = $this->getData();
198
        return $this->hasMultipleItems() ? $data : isset($data[0]) ? $data[0] : [];
199
    }
200
201
    /**
202
     * Return a description of the model wrapped by this wrapper.
203
     *
204
     * @return ModelDescription
205
     * @throws NibiiException
206
     * @throws \ReflectionException
207
     * @throws \ntentan\atiaa\exceptions\ConnectionException
208
     */
209 24
    public function getDescription() : ModelDescription
210
    {
211 24
        $this->initialize();
212
213 24
        return $this->context->getCache()->read(
214
            "{$this->className}::desc", function () {
215 24
                return $this->context->getModelDescription($this);
216 24
            }
217
        );
218
    }
219
220
    /**
221
     * Return the number of items stored in the model or matched by the query.
222
     * Depending on the state of the model, the count method will return different values. For models that have data
223
     * values set with calls to setData, this method returns the number of records that were added. On the other hand,
224
     * for models that do not have data set, this method queries the database to find out the number of records that
225
     * are either in the model, or for models that have been filtered, the number of records that match the filter.
226
     *
227
     * @param int|array|QueryParameters $query
228
     *
229
     * @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...
230
     * @throws NibiiException
231
     * @throws \ReflectionException
232
     * @throws \ntentan\atiaa\exceptions\ConnectionException
233
     */
234 4
    public function count($query = null)
235
    {
236 4
        if ($this->dataSet) {
237 4
            return count($this->getData());
238
        }
239
240
        return $this->__call('count', [$query]);
241
    }
242
243
    /**
244
     * Retrieve an item stored in the record.
245
     * This method returns items that are directly stored in the model, or lazy loads related items if needed.
246
     * The key could be a field in the model's table or the name of a related model.
247
     *
248
     * @param string $key A key identifying the item to be retrieved.
249
     *
250
     * @return mixed
251
     * @throws NibiiException
252
     * @throws \ReflectionException
253
     * @throws \ntentan\atiaa\exceptions\ConnectionException
254
     */
255 8
    private function retrieveItem($key)
256
    {
257 8
        if ($this->hasMultipleItems()) {
258
            throw new NibiiException('Current model object state contains multiple items. Please index with a numeric key to select a specific item first.');
259
        }
260 8
        $relationships = $this->getDescription()->getRelationships();
261 8
        $decamelizedKey = Text::deCamelize($key);
262 8
        if (isset($relationships[$decamelizedKey]) && !isset($this->modelData[$decamelizedKey])) {
263 4
            $this->modelData[$decamelizedKey] = $this->fetchRelatedFields($relationships[$decamelizedKey]);
264 4
            return $this->modelData[$decamelizedKey];
265
        }
266
267 4
        return isset($this->modelData[$decamelizedKey]) ? $this->modelData[$decamelizedKey] : null;
268
    }
269
270
    /**
271
     * Calls dynamic methods.
272
     *
273
     * @param string $name
274
     * @param array $arguments
275
     *
276
     * @return type
277
     * @throws NibiiException
278
     * @throws \ReflectionException
279
     * @throws \ntentan\atiaa\exceptions\ConnectionException
280
     */
281 30
    public function __call($name, $arguments)
282
    {
283 30
        $this->initialize();
284 30
        if ($this->dynamicOperations === null) {
285 30
            $this->dynamicOperations = new Operations($this, $this->quotedTable);
286
        }
287
288 30
        return $this->dynamicOperations->perform($name, $arguments);
289
    }
290
291
    /**
292
     * Set a value for a field in the model.
293
     *
294
     * @param string $name
295
     * @param mixed  $value
296
     */
297 8
    public function __set($name, $value)
298
    {
299 8
        $this->dataSet = true;
300 8
        $this->modelData[Text::deCamelize($name)] = $value;
301 8
    }
302
303 8
    public function __get($name)
304
    {
305 8
        return $this->retrieveItem($name);
306
    }
307
308 10
    public function save()
309
    {
310 10
        $return = $this->__call('save', [$this->hasMultipleItems()]);
311 10
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
312
313 10
        return $return;
314
    }
315
316 22
    private function hasMultipleItems()
317
    {
318 22
        if (count($this->modelData) > 0) {
319 20
            return is_numeric(array_keys($this->modelData)[0]);
320
        } else {
321 2
            return false;
322
        }
323
    }
324
325 14
    public function getData()
326
    {
327 14
        $data = [];
328
329 14
        if (count($this->modelData) == 0) {
330 2
            $data = $this->modelData;
331 12
        } elseif ($this->hasMultipleItems()) {
332 2
            $data = $this->modelData;
333 12
        } elseif (count($this->modelData) > 0) {
334 12
            $data[] = $this->modelData;
335
        }
336
337 14
        return $data;
338
    }
339
340 22
    public function setData($data)
341
    {
342 22
        $this->dataSet = is_array($data) ? true : false;
343 22
        $this->modelData = $data;
344 22
    }
345
346
    public function mergeData($data)
347
    {
348
        foreach ($data as $key => $value) {
349
            $this->modelData[$key] = $value;
350
        }
351
        $this->dataSet = true;
352
    }
353
354 2
    public function offsetExists($offset)
355
    {
356 2
        return isset($this->modelData[$offset]);
357
    }
358
359 2
    public function offsetGet($offset)
360
    {
361 2
        if (is_numeric($offset)) {
362 2
            return $this->wrap($offset);
363
        } else {
364 2
            return $this->retrieveItem($offset);
365
        }
366
    }
367
368 2
    public function offsetSet($offset, $value)
369
    {
370 2
        $this->dataSet = true;
371 2
        $this->modelData[$offset] = $value;
372 2
    }
373
374
    public function offsetUnset($offset)
375
    {
376
        unset($this->modelData[$offset]);
377
    }
378
379 4
    private function wrap($offset)
380
    {
381 4
        $this->initialize();
382 4
        if (isset($this->modelData[$offset])) {
383 4
            $className = $this->className;
384 4
            $newInstance = new $className();
385 4
            $newInstance->initialize();
386 4
            $newInstance->setData($this->modelData[$offset]);
387
388 4
            return $newInstance;
389
        } else {
390
            return;
391
        }
392
    }
393
394 4
    public function getInvalidFields()
395
    {
396 4
        return $this->invalidFields;
397
    }
398
399
    public function getHasMany()
400
    {
401
        return $this->hasMany;
402
    }
403
404
    public function getBelongsTo()
405
    {
406
        return $this->belongsTo;
407
    }
408
409 2
    public function current()
410
    {
411 2
        return $this->wrap($this->keys[$this->index]);
412
    }
413
414
    public function key()
415
    {
416
        return $this->keys[$this->index];
417
    }
418
419 2
    public function next()
420
    {
421 2
        $this->index++;
422 2
    }
423
424 2
    public function rewind()
425
    {
426 2
        $this->keys = array_keys($this->modelData);
427 2
        $this->index = 0;
428 2
    }
429
430 2
    public function valid()
431
    {
432 2
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
433
    }
434
435
    /**
436
     * A custom validator for the record wrapper.
437
     *
438
     * @return mixed
439
     */
440 10
    public function onValidate($invalidFields) : array 
0 ignored issues
show
Unused Code introduced by
The parameter $invalidFields 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...
441
    {
442 10
        return [];
443
    }
444
445 8
    private function fetchRelatedFields(Relationship $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...
446
    {
447 8
        $data = $index ? $this->modelData[$index] : $this->modelData;
448 8
        if (empty($data)) {
449
            return null;
450
        }        
451 8
        $name = $relationship->getOptions()['name'];
452 8
        if(isset($data[$name]))
453
        {
454
            return $data[$name];
455
        }
456 8
        $query = $relationship->prepareQuery($data);
457
458 8
        if($query) {
459 8
            $model = $relationship->getModelInstance();
460 8
            return $model->fetch($query);
0 ignored issues
show
Documentation Bug introduced by
The method fetch does not exist on object<ntentan\nibii\RecordWrapper>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
461
        } else {
462
            return null;
463
        }
464
465
        //return $query ? $model->fetch($query) : $model;
466
    }
467
468 24
    public function getRelationships()
469
    {
470
        return [
471 24
            'HasMany'      => $this->hasMany,
472 24
            'BelongsTo'    => $this->belongsTo,
473 24
            'ManyHaveMany' => $this->manyHaveMany,
474
        ];
475
    }
476
477
    public function usetField($field)
478
    {
479
        unset($this->modelData[$field]);
480
    }
481
482
    /**
483
     * Callback for when a record is either added or modified.
484
     */
485 10
    public function preSaveCallback()
486
    {
487 10
    }
488
489
    /**
490
     * Callback for when a record has been added or modified.
491
     *
492
     * @param $id
493
     */
494 6
    public function postSaveCallback()
495
    {
496 6
    }
497
498
    /**
499
     * Callback for when a new record is about to be created.
500
     */
501 8
    public function preCreateCallback()
502
    {
503 8
    }
504
505
    /**
506
     * Callback for when a new record has been created.
507
     * This callback can be most useful for obtaining the primary key of a newly created record.
508
     *
509
     * @param $id
510
     */
511 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...
512
    {
513 4
    }
514
515
    /**
516
     * Callback for when a record is about to be updated.
517
     */
518 2
    public function preUpdateCallback()
519
    {
520 2
    }
521
522
    /**
523
     * Callback for when a record has been updated.
524
     */
525 2
    public function postUpdateCallback()
526
    {
527 2
    }
528
529 32
    public function getDBStoreInformation()
530
    {
531 32
        $this->initialize();
532
533
        return [
534 32
            'schema'         => $this->schema,
535 32
            'table'          => $this->table,
536 32
            'quoted_table'   => $this->quotedTable,
537 32
            'unquoted_table' => $this->unquotedTable,
538
        ];
539
    }
540
541
    /**
542
     * @return DriverAdapter
543
     */
544 32
    public function getAdapter()
545
    {
546 32
        $this->initialize();
547 32
        return $this->adapter;
548
    }
549
550 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...
551
    {
552 4
        $expandableModels = empty($expandableModels) ? array_keys($relationships) : $expandableModels;
553 4
        foreach ($expandableModels as $name) {
554 4
            $value = $this->fetchRelatedFields($relationships[$name], $index);
555 4
            if($value) {
556 4
                $array[$name] = $value->toArray($depth, $expandableModels);
557
            }
558
        }
559 4
        return $array;
560
    }
561
562 10
    public function getValidationRules() : array
563
    {
564 10
        return $this->validationRules;
565
    }
566
567 20
    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...
568
    {
569 20
        $relationships = $this->getDescription()->getRelationships();
570 20
        $array = $this->modelData;
571 20
        foreach ($relationships as $model => $relationship) {
572 20
            unset($array[$model]);
573
        }
574 20
        if (!empty($array) && $depth > 0) {
575 4
            if ($this->hasMultipleItems()) {
576
                foreach ($array as $i => $value) {
577
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $expandableModels, $i);
578
                }
579
            } else {
580 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1, $expandableModels);
581
            }
582
        }
583
584 20
        return $array;
585
    }
586
}
587