Completed
Push — master ( a640fb...f80c47 )
by James Ekow Abaka
02:10
created

RecordWrapper::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 1
b 0
f 0
cc 1
eloc 2
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
 * 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->container = $this->context->getContainer();
77 36
        $this->adapter = $this->container->resolve(DriverAdapter::class);
78 36
        $table = $this->table ?? $this->context->getModelTable($this);
79 36
        $driver = $this->context->getDbContext()->getDriver();
80 36
        $this->adapter->setContext($this->context);
81 36
        $this->className = (new \ReflectionClass($this))->getName();
82 36
        if (is_string($table)) {
83
            //$this->quotedTable = $driver->quoteIdentifier($table);
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
84 36
            $this->table = $this->unquotedTable = $table;
85
        } else {
86
            $this->table = $table['table'];
87
            $this->schema = $table['schema'];
88
        }
89 36
        $this->quotedTable = ($this->schema ? "{$driver->quoteIdentifier($this->schema)}." : "") . $driver->quoteIdentifier($this->table);
90 36
        $this->unquotedTable = ($this->schema ? "{$this->schema}." : "") . $this->table;
91 36
        $this->adapter->setModel($this, $this->quotedTable);
92 36
        foreach ($this->behaviours as $behaviour) {
93
            $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...
94
            $behaviourInstance->setModel($this);
95
        }
96 36
        $this->initialized = true;
97 36
    }
98
99
    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...
100
    {
101
        $data = $this->getData();
102
        return $this->hasMultipleItems() ? $data : isset($data[0]) ? $data[0] : [];
103
    }
104
105
    /**
106
     * 
107
     * @return ModelDescription
108
     */
109 28
    public function getDescription()
110
    {
111 28
        $this->initialize();
112 28
        return $this->context->getCache()->read(
113 28
                        (new \ReflectionClass($this))->getName() . '::desc', function() {
114 28
                    return $this->container->resolve(ModelDescription::class, ['model' => $this]);
115 28
                }
116
        );
117
    }
118
119
    /**
120
     * Return the number of items stored in the model or the query.
121
     * @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...
122
     */
123 14
    public function count()
124
    {
125 14
        if ($this->dataSet) {
126 14
            return count($this->getData());
127
        } else {
128
            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...
129
        }
130
    }
131
132
    /**
133
     * Retrieve an item stored in the record.
134
     * This method returns items that are directly stored in the model or lazy
135
     * loads related items. The key could be a field in the model's table or
136
     * the name of a related model.
137
     * @param string $key A key identifying the item to be retrieved.
138
     * @return mixed
139
     */
140 12
    private function retrieveItem($key)
141
    {
142 12
        $relationships = $this->getDescription()->getRelationships();
143 12
        $decamelizedKey = Text::deCamelize($key);
144 12
        if (isset($relationships[$key])) {
145 8
            return $this->fetchRelatedFields($relationships[$key]);
146
        }
147 4
        return isset($this->modelData[$decamelizedKey]) ? $this->modelData[$decamelizedKey] : null;
148
    }
149
150
    /**
151
     * @method
152
     * @param type $name
153
     * @param type $arguments
154
     * @return type
155
     */
156 34
    public function __call($name, $arguments)
157
    {
158 34
        $this->initialize();
159 34
        if ($this->dynamicOperations === null) {
160
            // Bind to existing instances
161 34
            $this->container->bind(RecordWrapper::class)->to($this);
162 34
            $this->dynamicOperations = $this->container->resolve(
163 34
                    Operations::class, ['table' => $this->quotedTable, 'adapter' => $this->adapter]
164
            );
165
            // Unbind all bindings (necessary?)
166
        }
167 34
        return $this->dynamicOperations->perform($name, $arguments);
168
    }
169
170 8
    public function __set($name, $value)
171
    {
172 8
        $this->dataSet = true;
173 8
        $this->modelData[Text::deCamelize($name)] = $value;
174 8
    }
175
176 12
    public function __get($name)
177
    {
178 12
        return $this->retrieveItem($name);
179
    }
180
181 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...
182
    {
183 4
        foreach ($relationships as $name => $relationship) {
184 4
            $array[$name] = $this->fetchRelatedFields($relationship, $index)->toArray($depth);
185
        }
186 4
        return $array;
187
    }
188
189 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...
190
    {
191 16
        $relationships = $this->getDescription()->getRelationships();
192 16
        $array = $this->modelData;
193 16
        if ($depth > 0) {
194 4
            if ($this->hasMultipleItems()) {
195
                foreach ($array as $i => $value) {
196
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $i);
197
                }
198
            } else {
199 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1);
200
            }
201
        }
202 16
        return $array;
203
    }
204
205 10
    public function save()
206
    {
207 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...
208 10
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
209 10
        return $return;
210
    }
211
212 18
    private function hasMultipleItems()
213
    {
214 18
        if (count($this->modelData) > 0) {
215 16
            return is_numeric(array_keys($this->modelData)[0]);
216
        } else {
217 2
            return false;
218
        }
219
    }
220
221 18
    public function getData()
222
    {
223 18
        $data = [];
224
225 18
        if (count($this->modelData) == 0) {
226 6
            $data = $this->modelData;
227 16
        } else if ($this->hasMultipleItems()) {
228 6
            $data = $this->modelData;
229 12
        } else if (count($this->modelData) > 0) {
230 12
            $data[] = $this->modelData;
231
        }
232
233 18
        return $data;
234
    }
235
236 26
    public function setData($data)
237
    {
238 26
        $this->dataSet = true;
239 26
        $this->modelData = $data;
240 26
    }
241
242
    public function mergeData($data)
243
    {
244
        foreach ($data as $key => $value) {
245
            $this->modelData[$key] = $value;
246
        }
247
        $this->dataSet = true;
248
    }
249
250 2
    public function offsetExists($offset)
251
    {
252 2
        return isset($this->modelData[$offset]);
253
    }
254
255 2
    public function offsetGet($offset)
256
    {
257 2
        if (is_numeric($offset)) {
258 2
            return $this->wrap($offset);
259
        } else {
260 2
            return $this->retrieveItem($offset);
261
        }
262
    }
263
264 2
    public function offsetSet($offset, $value)
265
    {
266 2
        $this->dataSet = true;
267 2
        $this->modelData[$offset] = $value;
268 2
    }
269
270
    public function offsetUnset($offset)
271
    {
272
        unset($this->modelData[$offset]);
273
    }
274
275 6
    private function wrap($offset)
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...
276
    {
277 6
        $this->initialize();
278 6
        if (isset($this->modelData[$offset])) {
279 6
            $newInstance = $this->container->resolve($this->className);
280 6
            $newInstance->initialize();
281 6
            $newInstance->setData($this->modelData[$offset]);
282 6
            return $newInstance;
283
        } else {
284
            return null;
285
        }
286
    }
287
288 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...
289
    {
290 4
        return $this->invalidFields;
291
    }
292
293
    public function getHasMany()
294
    {
295
        return $this->hasMany;
296
    }
297
298
    public function getBelongsTo()
299
    {
300
        return $this->belongsTo;
301
    }
302
303 4
    public function current()
304
    {
305 4
        return $this->wrap($this->keys[$this->index]);
306
    }
307
308
    public function key()
309
    {
310
        return $this->keys[$this->index];
311
    }
312
313 4
    public function next()
314
    {
315 4
        $this->index++;
316 4
    }
317
318 4
    public function rewind()
319
    {
320 4
        $this->keys = array_keys($this->modelData);
321 4
        $this->index = 0;
322 4
    }
323
324 4
    public function valid()
325
    {
326 4
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
327
    }
328
329 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...
330
    {
331 10
        return $errors;
332
    }
333
334 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...
335
    {
336 12
        if ($index === null) {
337 12
            $data = $this->modelData;
338
        } else {
339
            $data = $this->modelData[$index];
340
        }
341 12
        $model = $relationship->getModelInstance();
342 12
        if (empty($data)) {
343
            return $model;
344
        }
345 12
        $query = $relationship->prepareQuery($data);
346 12
        return $query ? $model->fetch($query) : $model;
347
    }
348
349 28
    public function getRelationships()
350
    {
351
        return [
352 28
            'HasMany' => $this->hasMany,
353 28
            'BelongsTo' => $this->belongsTo,
354 28
            'ManyHaveMany' => $this->manyHaveMany
355
        ];
356
    }
357
358
    public function usetField($field)
359
    {
360
        unset($this->modelData[$field]);
361
    }
362
363 8
    public function preSaveCallback()
364
    {
365
        
366 8
    }
367
368 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...
369
    {
370
        
371 4
    }
372
373 2
    public function preUpdateCallback()
374
    {
375
        
376 2
    }
377
378 2
    public function postUpdateCallback()
379
    {
380
        
381 2
    }
382
383 36
    public function getDBStoreInformation()
384
    {
385 36
        $this->initialize();
386
        return [
387 36
            'schema' => $this->schema,
388 36
            'table' => $this->table,
389 36
            'quoted_table' => $this->quotedTable,
390 36
            'unquoted_table' => $this->unquotedTable
391
        ];
392
    }
393
394
    /**
395
     * 
396
     * @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...
397
     */
398
    public function getAdapter()
399
    {
400
        $this->initialize();
401
        return $this->adapter;
402
    }
403
404
}
405