Completed
Push — master ( 7f591d...75938c )
by James Ekow Abaka
21:31 queued 19:26
created

RecordWrapper::toArray()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.1755

Importance

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