Completed
Push — master ( 75869c...fd9cc4 )
by James Ekow Abaka
01:42
created

RecordWrapper::expandArrayValue()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.3731

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 13
ccs 5
cts 7
cp 0.7143
rs 9.2
c 2
b 0
f 0
cc 4
eloc 8
nc 4
nop 5
crap 4.3731
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
    private $unquotedTable;
86
    private $invalidFields;
87
    private $dynamicOperations;
88
    private $index = 0;
89
    private $dataSet = false;
90
    private $className;
91
92
    /**
93
     * An instance of the driver adapter for interacting with the database.
94
     * @var DriverAdapter
95
     */
96
    private $adapter;
97
    private $context;
98
    private $keys = [];
99
    private $initialized = false;
100
101
    /**
102
     * Initialize the record wrapper and setup the adapters, drivers, tables and schemas.
103
     * @return void
104
     */
105 36
    protected function initialize(): void
106
    {
107 36
        if ($this->initialized) {
108 36
            return;
109
        }
110 36
        $this->context = ORMContext::getInstance();
111 36
        $this->adapter = $this->context->getDriverAdapter();
112 36
        $table = $this->table ?? $this->context->getModelTable($this);
113 36
        $driver = $this->context->getDbContext()->getDriver();
114 36
        $this->adapter->setContext($this->context);
115 36
        $this->className = (new \ReflectionClass($this))->getName();
116 36
        if (is_string($table)) {
117 36
            $this->table = $this->unquotedTable = $table;
118
        } else {
119
            $this->table = $table['table'];
120
            $this->schema = $table['schema'];
121
        }
122 36
        $this->quotedTable = ($this->schema ? "{$driver->quoteIdentifier($this->schema)}." : "") . $driver->quoteIdentifier($this->table);
123 36
        $this->unquotedTable = ($this->schema ? "{$this->schema}." : "") . $this->table;
124 36
        $this->adapter->setModel($this, $this->quotedTable);
125 36
        $this->initialized = true;
126 36
    }
127
128
    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...
129
    {
130
        $data = $this->getData();
131
        return $this->hasMultipleItems() ? $data : isset($data[0]) ? $data[0] : [];
132
    }
133
134
    /**
135
     * Return a description of the model wrapped by this wrapper.
136
     * @return ModelDescription
137
     */
138 28
    public function getDescription()
139
    {
140 28
        $this->initialize();
141 28
        return $this->context->getCache()->read(
142 28
                "{$this->className}::desc", function () {
143 28
                return $this->context->getModelDescription($this);
144 28
            }
145
        );
146
    }
147
148
    /**
149
     * Return the number of items stored in the model or matched by the query.
150
     * Depending on the state of the model, the count method will return different values. For models that have data
151
     * values set with calls to setData, this method returns the number of records that were added. On the other hand,
152
     * for models that do not have data set, this method queries the database to find out the number of records that
153
     * are either in the model, or for models that have been filtered, the number of records that match the filter.
154
     *
155
     * @param int|array|QueryParameters $query
156
     * @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...
157
     */
158 14
    public function count($query = null)
159
    {
160 14
        if ($this->dataSet) {
161 10
            return count($this->getData());
162
        }
163 4
        return $this->__call('count', [$query]);
164
    }
165
166
    /**
167
     * Retrieve an item stored in the record.
168
     * This method returns items that are directly stored in the model or lazy
169
     * loads related items. The key could be a field in the model's table or
170
     * the name of a related model.
171
     * @param string $key A key identifying the item to be retrieved.
172
     * @return mixed
173
     */
174 12
    private function retrieveItem($key)
175
    {
176 12
        $relationships = $this->getDescription()->getRelationships();
177 12
        $decamelizedKey = Text::deCamelize($key);
178 12
        if (isset($relationships[$key])) {
179 8
            return $this->fetchRelatedFields($relationships[$key]);
180
        }
181 4
        return isset($this->modelData[$decamelizedKey]) ? $this->modelData[$decamelizedKey] : null;
182
    }
183
184
    /**
185
     * @method
186
     * @param string $name
187
     * @param array $arguments
188
     * @return type
189
     */
190 34
    public function __call($name, $arguments)
191
    {
192 34
        $this->initialize();
193 34
        if ($this->dynamicOperations === null) {
194 34
            $this->dynamicOperations = new Operations($this, $this->quotedTable);
195
        }
196 34
        return $this->dynamicOperations->perform($name, $arguments);
197
    }
198
199 6
    public function __set($name, $value)
200
    {
201 6
        $this->dataSet = true;
202 6
        $this->modelData[Text::deCamelize($name)] = $value;
203 6
    }
204
205 12
    public function __get($name)
206
    {
207 12
        return $this->retrieveItem($name);
208
    }
209
210 10
    public function save()
211
    {
212 10
        $return = $this->__call('save', [$this->hasMultipleItems()]);
213 10
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
214 10
        return $return;
215
    }
216
217 18
    private function hasMultipleItems()
218
    {
219 18
        if (count($this->modelData) > 0) {
220 16
            return is_numeric(array_keys($this->modelData)[0]);
221
        } else {
222 2
            return false;
223
        }
224
    }
225
226 18
    public function getData()
227
    {
228 18
        $data = [];
229
230 18
        if (count($this->modelData) == 0) {
231 2
            $data = $this->modelData;
232 16
        } else if ($this->hasMultipleItems()) {
233 6
            $data = $this->modelData;
234 12
        } else if (count($this->modelData) > 0) {
235 12
            $data[] = $this->modelData;
236
        }
237
238 18
        return $data;
239
    }
240
241 26
    public function setData($data)
242
    {
243 26
        $this->dataSet = $data ? true : false;
244 26
        $this->modelData = $data;
245 26
    }
246
247
    public function mergeData($data)
248
    {
249
        foreach ($data as $key => $value) {
250
            $this->modelData[$key] = $value;
251
        }
252
        $this->dataSet = true;
253
    }
254
255 2
    public function offsetExists($offset)
256
    {
257 2
        return isset($this->modelData[$offset]);
258
    }
259
260 2
    public function offsetGet($offset)
261
    {
262 2
        if (is_numeric($offset)) {
263 2
            return $this->wrap($offset);
264
        } else {
265 2
            return $this->retrieveItem($offset);
266
        }
267
    }
268
269 2
    public function offsetSet($offset, $value)
270
    {
271 2
        $this->dataSet = true;
272 2
        $this->modelData[$offset] = $value;
273 2
    }
274
275
    public function offsetUnset($offset)
276
    {
277
        unset($this->modelData[$offset]);
278
    }
279
280 6
    private function wrap($offset)
281
    {
282 6
        $this->initialize();
283 6
        if (isset($this->modelData[$offset])) {
284 6
            $className = $this->className;
285 6
            $newInstance = new $className();
286 6
            $newInstance->initialize();
287 6
            $newInstance->setData($this->modelData[$offset]);
288 6
            return $newInstance;
289
        } else {
290
            return null;
291
        }
292
    }
293
294 4
    public function getInvalidFields()
295
    {
296 4
        return $this->invalidFields;
297
    }
298
299
    public function getHasMany()
300
    {
301
        return $this->hasMany;
302
    }
303
304
    public function getBelongsTo()
305
    {
306
        return $this->belongsTo;
307
    }
308
309 4
    public function current()
310
    {
311 4
        return $this->wrap($this->keys[$this->index]);
312
    }
313
314
    public function key()
315
    {
316
        return $this->keys[$this->index];
317
    }
318
319 4
    public function next()
320
    {
321 4
        $this->index++;
322 4
    }
323
324 4
    public function rewind()
325
    {
326 4
        $this->keys = array_keys($this->modelData);
327 4
        $this->index = 0;
328 4
    }
329
330 4
    public function valid()
331
    {
332 4
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
333
    }
334
335 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...
336
    {
337 10
        return $errors;
338
    }
339
340 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...
341
    {
342 12
        if ($index === null) {
343 12
            $data = $this->modelData;
344
        } else {
345
            $data = $this->modelData[$index];
346
        }
347 12
        $model = $relationship->getModelInstance();
348 12
        if (empty($data)) {
349
            return $model;
350
        }
351 12
        $query = $relationship->prepareQuery($data);
352 12
        return $query ? $model->fetch($query) : $model;
353
    }
354
355 28
    public function getRelationships()
356
    {
357
        return [
358 28
            'HasMany' => $this->hasMany,
359 28
            'BelongsTo' => $this->belongsTo,
360 28
            'ManyHaveMany' => $this->manyHaveMany
361
        ];
362
    }
363
364
    public function usetField($field)
365
    {
366
        unset($this->modelData[$field]);
367
    }
368
369 8
    public function preSaveCallback()
370
    {
371
        
372 8
    }
373
374
    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...
375
    {
376
        
377
    }
378
379 2
    public function preUpdateCallback()
380
    {
381
        
382 2
    }
383
384 2
    public function postUpdateCallback()
385
    {
386
        
387 2
    }
388
389 36
    public function getDBStoreInformation()
390
    {
391 36
        $this->initialize();
392
        return [
393 36
            'schema' => $this->schema,
394 36
            'table' => $this->table,
395 36
            'quoted_table' => $this->quotedTable,
396 36
            'unquoted_table' => $this->unquotedTable
397
        ];
398
    }
399
400
    /**
401
     *
402
     * @return DriverAdapter
403
     */
404 36
    public function getAdapter()
405
    {
406 36
        $this->initialize();
407 36
        return $this->adapter;
408
    }
409
410 4
    private function expandArrayValue($array, $relationships, $depth, $expandOnly = [], $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...
411
    {
412 4
        if(empty($expandOnly)){
413 4
            foreach ($relationships as $name => $relationship) {
414 4
                $array[$name] = $this->fetchRelatedFields($relationship, $index)->toArray($depth);
415
            }
416
        } else {
417
            foreach ($expandOnly as $name) {
418
                $array[$name] = $this->fetchRelatedFields($relationships[$name], $index)->toArray($depth, $expandOnly);
419
            }
420
        }
421 4
        return $array;
422
    }
423
424 16
    public function toArray($depth = 0, $expandOnly = [])
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...
425
    {
426 16
        $relationships = $this->getDescription()->getRelationships();
427 16
        $array = $this->modelData;
428 16
        if ($depth > 0) {
429 4
            if ($this->hasMultipleItems()) {
430
                foreach ($array as $i => $value) {
431
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $expandOnly, $i);
432
                }
433
            } else {
434 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1, $expandOnly);
435
            }
436
        }
437 16
        return $array;
438
    }
439
440
}
441