Completed
Push — master ( e58eb5...5b23f9 )
by James Ekow Abaka
02:57
created

RecordWrapper::initialize()   B

Complexity

Conditions 6
Paths 17

Size

Total Lines 25
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6.2488

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 25
ccs 17
cts 21
cp 0.8095
rs 8.439
cc 6
eloc 21
nc 17
nop 0
crap 6.2488
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
    protected $hasMany = [];
40
    protected $belongsTo = [];
41
    protected $manyHaveMany = [];
42
    protected $behaviours = [];
43
    protected $table;
44
    protected $schema;
45
    protected $modelData = [];
46
    private $quotedTable;
47
    private $unquotedTable;
48
    private $invalidFields;
49
    private $dynamicOperations;
50
    private $index = 0;
51
    private $dataSet = false;
52
    private $className;
53
    
54
    /**
55
     *
56
     * @var DriverAdapter
57
     */
58
    private $adapter;
59
    private $container;
60
    private $context;
61
    private $keys = [];
62
    private $initialized = false;
63
64 36
    protected function initialize() {
65 36
        if($this->initialized) return;
66 36
        $this->context = ORMContext::getInstance();
67 36
        $this->container = $this->context->getContainer();
68 36
        $this->adapter = $this->container->resolve(DriverAdapter::class);
69 36
        $table = $this->table ?? $this->context->getModelTable($this);
70 36
        $driver = $this->context->getDbContext()->getDriver();
71 36
        $this->adapter->setContext($this->context);
72 36
        $this->className = (new \ReflectionClass($this))->getName();
73 36
        if (is_string($table)) {
74
            //$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...
75 36
            $this->table = $this->unquotedTable = $table;
76
        } else {
77
            $this->table = $table['table'];
78
            $this->schema = $table['schema'];
79
        }
80 36
        $this->quotedTable = ($this->schema ? "{$driver->quoteIdentifier($this->schema)}." : "").$driver->quoteIdentifier($this->table);
81 36
        $this->unquotedTable = ($this->schema ? "{$this->schema}." : "").$this->table;
82 36
        $this->adapter->setModel($this, $this->quotedTable);
83 36
        foreach ($this->behaviours as $behaviour) {
84
            $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...
85
            $behaviourInstance->setModel($this);
86
        }
87 36
        $this->initialized = true;
88 36
    }
89
    
90
    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...
91
        $data = $this->getData();
92
        return $this->hasMultipleItems() ? $data : isset($data[0]) ? $data[0] : [];
93
    }
94
95
    /**
96
     * 
97
     * @return ModelDescription
98
     */
99 28
    public function getDescription() {
100 28
        $this->initialize();
101 28
        return $this->context->getCache()->read(
102 28
            (new \ReflectionClass($this))->getName().'::desc', function() {
103 28
                return $this->container->resolve(ModelDescription::class, ['model' => $this]);
104 28
            }
105
        );
106
    }
107
108
    /**
109
     * Return the number of items stored in the model or the query.
110
     * @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...
111
     */
112 14
    public function count() {
113 14
        if ($this->dataSet) {
114 14
            return count($this->getData());
115
        } else {
116
            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...
117
        }
118
    }
119
120
    /**
121
     * Retrieve an item stored in the record.
122
     * This method returns items that are directly stored in the model or lazy
123
     * loads related items. The key could be a field in the model's table or
124
     * the name of a related model.
125
     * @param string $key A key identifying the item to be retrieved.
126
     * @return mixed
127
     */
128 12
    private function retrieveItem($key) {
129 12
        $relationships = $this->getDescription()->getRelationships();
130 12
        $decamelizedKey = Text::deCamelize($key);
131 12
        if (isset($relationships[$key])) {
132 8
            return $this->fetchRelatedFields($relationships[$key]);
133
        }
134 4
        return isset($this->modelData[$decamelizedKey]) ? $this->modelData[$decamelizedKey] : null;
135
    }
136
137
    /**
138
     * @method
139
     * @param type $name
140
     * @param type $arguments
141
     * @return type
142
     */
143 34
    public function __call($name, $arguments) {
144 34
        $this->initialize();
145 34
        if ($this->dynamicOperations === null) {
146
            // Bind to existing instances
147 34
            $this->container->bind(RecordWrapper::class)->to($this);
148 34
            $this->dynamicOperations = $this->container->resolve(
149 34
                Operations::class, ['table' => $this->quotedTable, 'adapter' => $this->adapter]
150
            );
151
            // Unbind all bindings (necessary?)
152
        }
153 34
        return $this->dynamicOperations->perform($name, $arguments);
154
    }
155
156 8
    public function __set($name, $value) {
157 8
        $this->dataSet = true;
158 8
        $this->modelData[Text::deCamelize($name)] = $value;
159 8
    }
160
161 12
    public function __get($name) {
162 12
        return $this->retrieveItem($name);
163
    }
164
165 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...
166 4
        foreach ($relationships as $name => $relationship) {
167 4
            $array[$name] = $this->fetchRelatedFields($relationship, $index)->toArray($depth);
168
        }
169 4
        return $array;
170
    }
171
172 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...
173 16
        $relationships = $this->getDescription()->getRelationships();
174 16
        $array = $this->modelData;
175 16
        if ($depth > 0) {
176 4
            if ($this->hasMultipleItems()) {
177
                foreach ($array as $i => $value) {
178
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $i);
179
                }
180
            } else {
181 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1);
182
            }
183
        }
184 16
        return $array;
185
    }
186
187 10
    public function save() {
188 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...
189 10
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
190 10
        return $return;
191
    }
192
193 18
    private function hasMultipleItems() {
194 18
        if (count($this->modelData) > 0) {
195 16
            return is_numeric(array_keys($this->modelData)[0]);
196
        } else {
197 2
            return false;
198
        }
199
    }
200
201 18
    public function getData() {
202 18
        $data = [];
203
204 18
        if (count($this->modelData) == 0) {
205 6
            $data = $this->modelData;
206 16
        } else if ($this->hasMultipleItems()) {
207 6
            $data = $this->modelData;
208 12
        } else if (count($this->modelData) > 0) {
209 12
            $data[] = $this->modelData;
210
        }
211
212 18
        return $data;
213
    }
214
215 26
    public function setData($data) {
216 26
        $this->dataSet = true;
217 26
        $this->modelData = $data;
218 26
    }
219
220
    public function mergeData($data) {
221
        foreach ($data as $key => $value) {
222
            $this->modelData[$key] = $value;
223
        }
224
        $this->dataSet = true;
225
    }
226
227 2
    public function offsetExists($offset) {
228 2
        return isset($this->modelData[$offset]);
229
    }
230
231 2
    public function offsetGet($offset) {
232 2
        if (is_numeric($offset)) {
233 2
            return $this->wrap($offset);
234
        } else {
235 2
            return $this->retrieveItem($offset);
236
        }
237
    }
238
239 2
    public function offsetSet($offset, $value) {
240 2
        $this->dataSet = true;
241 2
        $this->modelData[$offset] = $value;
242 2
    }
243
244
    public function offsetUnset($offset) {
245
        unset($this->modelData[$offset]);
246
    }
247
248 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...
249 6
        $this->initialize();
250 6
        if (isset($this->modelData[$offset])) {
251 6
            $newInstance = $this->container->resolve($this->className);
252 6
            $newInstance->initialize();
253 6
            $newInstance->setData($this->modelData[$offset]);
254 6
            return $newInstance;
255
        } else {
256
            return null;
257
        }
258
    }
259
260 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...
261 4
        return $this->invalidFields;
262
    }
263
264
    public function getHasMany() {
265
        return $this->hasMany;
266
    }
267
268
    public function getBelongsTo() {
269
        return $this->belongsTo;
270
    }
271
272 4
    public function current() {
273 4
        return $this->wrap($this->keys[$this->index]);
274
    }
275
276
    public function key() {
277
        return $this->keys[$this->index];
278
    }
279
280 4
    public function next() {
281 4
        $this->index++;
282 4
    }
283
284 4
    public function rewind() {
285 4
        $this->keys = array_keys($this->modelData);
286 4
        $this->index = 0;
287 4
    }
288
289 4
    public function valid() {
290 4
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
291
    }
292
293 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...
294 10
        return $errors;
295
    }
296
297 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...
298 12
        if ($index === null) {
299 12
            $data = $this->modelData;
300
        } else {
301
            $data = $this->modelData[$index];
302
        }
303 12
        $model = $relationship->getModelInstance();
304 12
        if (empty($data)) {
305
            return $model;
306
        }
307 12
        $query = $relationship->prepareQuery($data);
308 12
        return $query ? $model->fetch($query) : $model;
309
    }
310
311 28
    public function getRelationships() {
312
        return [
313 28
            'HasMany' => $this->hasMany,
314 28
            'BelongsTo' => $this->belongsTo,
315 28
            'ManyHaveMany' => $this->manyHaveMany
316
        ];
317
    }
318
319
    public function usetField($field) {
320
        unset($this->modelData[$field]);
321
    }
322
323 8
    public function preSaveCallback() {
324
        
325 8
    }
326
327 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...
328
        
329 4
    }
330
331 2
    public function preUpdateCallback() {
332
        
333 2
    }
334
335 2
    public function postUpdateCallback() {
336
        
337 2
    }
338
339 36
    public function getDBStoreInformation() {
340 36
        $this->initialize();
341
        return [
342 36
            'schema' => $this->schema,
343 36
            'table' => $this->table,
344 36
            'quoted_table' => $this->quotedTable,
345 36
            'unquoted_table' => $this->unquotedTable
346
        ];
347
    }
348
    
349
    /**
350
     * 
351
     * @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...
352
     */
353
    public function getAdapter() {
354
        $this->initialize();
355
        return $this->adapter;
356
    }
357
358
}
359