Completed
Push — master ( a30571...7a668c )
by James Ekow Abaka
02:45
created

RecordWrapper::getRelationships()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 1
b 0
f 0
cc 1
eloc 5
nc 1
nop 0
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
 * Wraps a record from the database with data manipulation operations.
33
 * Wrapping a table with the record wrapper makes it possible to add, edit,
34
 * delete and query the underlying database. An MVC framework can use the
35
 * record wrapper as a base for its Model class.
36
 */
37
class RecordWrapper implements \ArrayAccess, \Countable, \Iterator
38
{
39
40
    protected $hasMany = [];
41
    protected $belongsTo = [];
42
    protected $manyHaveMany = [];
43
    protected $behaviours = [];
44
    protected $table;
45
    protected $schema;
46
    protected $modelData = [];
47
    private $quotedTable;
48
    private $unquotedTable;
49
    private $invalidFields;
50
    private $dynamicOperations;
51
    private $index = 0;
52
    private $dataSet = false;
53
    private $className;
54
55
    /**
56
     *
57
     * @var DriverAdapter
58
     */
59
    private $adapter;
60
    private $container;
61
    private $context;
62
    private $keys = [];
63
    private $initialized = false;
64
65
    /**
66
     * Initialize the record wrapper and setup the adapters, drivers, tables and schemas.
67
     *
68
     * @return void
69
     */
70 36
    protected function initialize(): void
71
    {
72 36
        if ($this->initialized) {
73 36
            return;
74
        }
75 36
        $this->context = ORMContext::getInstance();
76 36
        $this->adapter = $this->context->getDriverAdapter();
77 36
        $table = $this->table ?? $this->context->getModelTable($this);
78 36
        $driver = $this->context->getDbContext()->getDriver();
79 36
        $this->adapter->setContext($this->context);
80 36
        $this->className = (new \ReflectionClass($this))->getName();
81 36
        if (is_string($table)) {
82 36
            $this->table = $this->unquotedTable = $table;
83
        } else {
84
            $this->table = $table['table'];
85
            $this->schema = $table['schema'];
86
        }
87 36
        $this->quotedTable = ($this->schema ? "{$driver->quoteIdentifier($this->schema)}." : "") . $driver->quoteIdentifier($this->table);
88 36
        $this->unquotedTable = ($this->schema ? "{$this->schema}." : "") . $this->table;
89 36
        $this->adapter->setModel($this, $this->quotedTable);
90 36
        foreach ($this->behaviours as $behaviour) {
91
            $behaviourInstance = $this->getComponentInstance($behaviour);
0 ignored issues
show
Documentation Bug introduced by
The method getComponentInstance 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...
92
            $behaviourInstance->setModel($this);
93
        }
94 36
        $this->initialized = true;
95 36
    }
96
97
    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...
98
    {
99
        $data = $this->getData();
100
        return $this->hasMultipleItems() ? $data : isset($data[0]) ? $data[0] : [];
101
    }
102
103
    /**
104
     *
105
     * @return ModelDescription
106
     */
107 28
    public function getDescription()
108
    {
109 28
        $this->initialize();
110 28
        return $this->context->getCache()->read(
111 28
                "{$this->className}::desc", function () {
112 28
                return $this->context->getModelDescription($this);
113 28
            }
114
        );
115
    }
116
117
    /**
118
     * Return the number of items stored in the model or the query.
119
     * @return integer
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...
120
     */
121 14
    public function count()
122
    {
123 14
        if ($this->dataSet) {
124 14
            return count($this->getData());
125
        } else {
126
            return $this->__call('count', []);
0 ignored issues
show
Documentation introduced by
'count' is of type string, but the function expects a object<ntentan\nibii\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
array() is of type array, but the function expects a object<ntentan\nibii\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
        }
128
    }
129
130
    /**
131
     * Retrieve an item stored in the record.
132
     * This method returns items that are directly stored in the model or lazy
133
     * loads related items. The key could be a field in the model's table or
134
     * the name of a related model.
135
     * @param string $key A key identifying the item to be retrieved.
136
     * @return mixed
137
     */
138 12
    private function retrieveItem($key)
139
    {
140 12
        $relationships = $this->getDescription()->getRelationships();
141 12
        $decamelizedKey = Text::deCamelize($key);
142 12
        if (isset($relationships[$key])) {
143 8
            return $this->fetchRelatedFields($relationships[$key]);
144
        }
145 4
        return isset($this->modelData[$decamelizedKey]) ? $this->modelData[$decamelizedKey] : null;
146
    }
147
148
    /**
149
     * @method
150
     * @param type $name
151
     * @param type $arguments
152
     * @return type
153
     */
154 34
    public function __call($name, $arguments)
155
    {
156 34
        $this->initialize();
157 34
        if ($this->dynamicOperations === null) {
158 34
            $this->dynamicOperations = new Operations($this, $this->quotedTable);
159
        }
160 34
        return $this->dynamicOperations->perform($name, $arguments);
161
    }
162
163 8
    public function __set($name, $value)
164
    {
165 8
        $this->dataSet = true;
166 8
        $this->modelData[Text::deCamelize($name)] = $value;
167 8
    }
168
169 12
    public function __get($name)
170
    {
171 12
        return $this->retrieveItem($name);
172
    }
173
174 4
    private function expandArrayValue($array, $relationships, $depth, $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...
175
    {
176 4
        foreach ($relationships as $name => $relationship) {
177 4
            $array[$name] = $this->fetchRelatedFields($relationship, $index)->toArray($depth);
178
        }
179 4
        return $array;
180
    }
181
182 16
    public function toArray($depth = 0)
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...
183
    {
184 16
        $relationships = $this->getDescription()->getRelationships();
185 16
        $array = $this->modelData;
186 16
        if ($depth > 0) {
187 4
            if ($this->hasMultipleItems()) {
188
                foreach ($array as $i => $value) {
189
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $i);
190
                }
191
            } else {
192 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1);
193
            }
194
        }
195 16
        return $array;
196
    }
197
198 10
    public function save()
199
    {
200 10
        $return = $this->__call('save', [$this->hasMultipleItems()]);
0 ignored issues
show
Documentation introduced by
'save' is of type string, but the function expects a object<ntentan\nibii\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
array($this->hasMultipleItems()) is of type array<integer,boolean,{"0":"boolean"}>, but the function expects a object<ntentan\nibii\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
201 10
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
202 10
        return $return;
203
    }
204
205 18
    private function hasMultipleItems()
206
    {
207 18
        if (count($this->modelData) > 0) {
208 16
            return is_numeric(array_keys($this->modelData)[0]);
209
        } else {
210 2
            return false;
211
        }
212
    }
213
214 18
    public function getData()
215
    {
216 18
        $data = [];
217
218 18
        if (count($this->modelData) == 0) {
219 6
            $data = $this->modelData;
220 16
        } else if ($this->hasMultipleItems()) {
221 6
            $data = $this->modelData;
222 12
        } else if (count($this->modelData) > 0) {
223 12
            $data[] = $this->modelData;
224
        }
225
226 18
        return $data;
227
    }
228
229 26
    public function setData($data)
230
    {
231 26
        $this->dataSet = true;
232 26
        $this->modelData = $data;
233 26
    }
234
235
    public function mergeData($data)
236
    {
237
        foreach ($data as $key => $value) {
238
            $this->modelData[$key] = $value;
239
        }
240
        $this->dataSet = true;
241
    }
242
243 2
    public function offsetExists($offset)
244
    {
245 2
        return isset($this->modelData[$offset]);
246
    }
247
248 2
    public function offsetGet($offset)
249
    {
250 2
        if (is_numeric($offset)) {
251 2
            return $this->wrap($offset);
252
        } else {
253 2
            return $this->retrieveItem($offset);
254
        }
255
    }
256
257 2
    public function offsetSet($offset, $value)
258
    {
259 2
        $this->dataSet = true;
260 2
        $this->modelData[$offset] = $value;
261 2
    }
262
263
    public function offsetUnset($offset)
264
    {
265
        unset($this->modelData[$offset]);
266
    }
267
268 6
    private function wrap($offset)
269
    {
270 6
        $this->initialize();
271 6
        if (isset($this->modelData[$offset])) {
272 6
            $className = $this->className;
273 6
            $newInstance = new $className();
274 6
            $newInstance->initialize();
275 6
            $newInstance->setData($this->modelData[$offset]);
276 6
            return $newInstance;
277
        } else {
278
            return null;
279
        }
280
    }
281
282 4
    public function getInvalidFields()
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...
283
    {
284 4
        return $this->invalidFields;
285
    }
286
287
    public function getHasMany()
288
    {
289
        return $this->hasMany;
290
    }
291
292
    public function getBelongsTo()
293
    {
294
        return $this->belongsTo;
295
    }
296
297 4
    public function current()
298
    {
299 4
        return $this->wrap($this->keys[$this->index]);
300
    }
301
302
    public function key()
303
    {
304
        return $this->keys[$this->index];
305
    }
306
307 4
    public function next()
308
    {
309 4
        $this->index++;
310 4
    }
311
312 4
    public function rewind()
313
    {
314 4
        $this->keys = array_keys($this->modelData);
315 4
        $this->index = 0;
316 4
    }
317
318 4
    public function valid()
319
    {
320 4
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
321
    }
322
323 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...
324
    {
325 10
        return $errors;
326
    }
327
328 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...
329
    {
330 12
        if ($index === null) {
331 12
            $data = $this->modelData;
332
        } else {
333
            $data = $this->modelData[$index];
334
        }
335 12
        $model = $relationship->getModelInstance();
336 12
        if (empty($data)) {
337
            return $model;
338
        }
339 12
        $query = $relationship->prepareQuery($data);
340 12
        return $query ? $model->fetch($query) : $model;
341
    }
342
343 28
    public function getRelationships()
344
    {
345
        return [
346 28
            'HasMany' => $this->hasMany,
347 28
            'BelongsTo' => $this->belongsTo,
348 28
            'ManyHaveMany' => $this->manyHaveMany
349
        ];
350
    }
351
352
    public function usetField($field)
353
    {
354
        unset($this->modelData[$field]);
355
    }
356
357 8
    public function preSaveCallback()
358
    {
359
        
360 8
    }
361
362 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...
363
    {
364
        
365 4
    }
366
367 2
    public function preUpdateCallback()
368
    {
369
        
370 2
    }
371
372 2
    public function postUpdateCallback()
373
    {
374
        
375 2
    }
376
377 36
    public function getDBStoreInformation()
378
    {
379 36
        $this->initialize();
380
        return [
381 36
            'schema' => $this->schema,
382 36
            'table' => $this->table,
383 36
            'quoted_table' => $this->quotedTable,
384 36
            'unquoted_table' => $this->unquotedTable
385
        ];
386
    }
387
388
    /**
389
     *
390
     * @return DataAdapter
0 ignored issues
show
Documentation introduced by
Should the return type not be DriverAdapter?

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...
391
     */
392 36
    public function getAdapter()
393
    {
394 36
        $this->initialize();
395 36
        return $this->adapter;
396
    }
397
398
}
399