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

RecordWrapper::initialize()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 22
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5.0342

Importance

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