Completed
Push — master ( 73b6ea...6deb59 )
by James Ekow Abaka
02:32
created

RecordWrapper::hasMultipleItems()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
rs 9.4285
ccs 4
cts 4
cp 1
cc 2
eloc 5
nc 2
nop 0
crap 2
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
    use \ntentan\panie\ComponentContainerTrait;
40
41
    protected $hasMany = [];
42
    protected $belongsTo = [];
43
    protected $manyHaveMany = [];
44
    protected $behaviours = [];
45
    protected $table;
46
    protected $schema;
47
    private $quotedTable;
48
    private $unquotedTable;
49
    private $modelData = [];
50
    private $invalidFields;
51
    private $dynamicOperations;
52
    private $index = 0;
53
    private $dataSet = false;
54
    protected $adapter;
55
    private $container;
56
    private $context;
57
    private $keys = [];
58
59 37
    public function __construct(DriverAdapter $adapter, ORMContext $context) {
60 37
        $table = $context->getModelTable($this);
61 37
        $driver = $context->getDbContext()->getDriver();
62 37
        $adapter->setContext($context);
63 37
        $this->container = $context->getContainer();
64 37
        $this->context = $context;
65 37
        if (is_string($table)) {
66 36
            $this->quotedTable = $driver->quoteIdentifier($table);
67 36
            $this->table = $this->unquotedTable = $table;
68
        } else {
69 1
            $this->quotedTable = (isset($table['schema']) ? "{$driver->quoteIdentifier($table["schema"])}." : "").$driver->quoteIdentifier($table["table"]);
70 1
            $this->unquotedTable = (isset($table['schema']) ? "{$table['schema']}." : "").$table['table'];
71 1
            $this->table = $table['table'];
72 1
            $this->schema = $table['schema'];
73
        }
74 37
        $this->adapter = $adapter;
75 37
        $this->adapter->setModel($this, $this->quotedTable);
0 ignored issues
show
Unused Code introduced by
The call to DriverAdapter::setModel() has too many arguments starting with $this->quotedTable.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
76 37
        foreach ($this->behaviours as $behaviour) {
77
            $behaviourInstance = $this->getComponentInstance($behaviour);
0 ignored issues
show
Bug introduced by
The call to getComponentInstance() misses a required argument $component.

This check looks for function calls that miss required arguments.

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