Completed
Push — master ( ed6a4c...a3a62e )
by James Ekow Abaka
03:18
created

RecordWrapper::getAdapter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 3
nc 1
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
    
55
    /**
56
     *
57
     * @var DriverAdapter
58
     */
59
    private $adapter;
60
    private $container;
61
    private $context;
62
    private $keys = [];
63
    private $initialized = false;
64
65 37
    private function initialize() {
66 37
        if($this->initialized) return;
67 37
        $this->context = ORMContext::getInstance();
68 37
        $this->container = $this->context->getContainer();
69 37
        $this->adapter = $this->container->resolve(DriverAdapter::class);
70 37
        $table = $this->context->getModelTable($this);
71 37
        $driver = $this->context->getDbContext()->getDriver();
72 37
        $this->adapter->setContext($this->context);
73 37
        if (is_string($table)) {
74 36
            $this->quotedTable = $driver->quoteIdentifier($table);
75 36
            $this->table = $this->unquotedTable = $table;
76
        } else {
77 1
            $this->quotedTable = (isset($table['schema']) ? "{$driver->quoteIdentifier($table["schema"])}." : "").$driver->quoteIdentifier($table["table"]);
78 1
            $this->unquotedTable = (isset($table['schema']) ? "{$table['schema']}." : "").$table['table'];
79 1
            $this->table = $table['table'];
80 1
            $this->schema = $table['schema'];
81
        }
82 37
        $this->adapter->setModel($this, $this->quotedTable);
83 37
        foreach ($this->behaviours as $behaviour) {
84
            $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...
85
            $behaviourInstance->setModel($this);
86
        }
87 37
        $this->initialized = true;
88 37
    }
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 : $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
     * Create a new instance of this Model
139
     * @return \ntentan\nibii\RecordWrapper
140
     */
141 35
    public static function createNew() {
142 35
        $instance = ORMContext::getInstance()->getContainer()->resolve(get_called_class());
143 35
        $instance->initialize();
144 35
        return $instance;
145
    }
146
147
    /**
148
     * @method
149
     * @param type $name
150
     * @param type $arguments
151
     * @return type
152
     */
153 35
    public function __call($name, $arguments) {
154 35
        $this->initialize();
155 35
        if ($this->dynamicOperations === null) {
156
            // Bind to existing instances
157 35
            $this->container->bind(RecordWrapper::class)->to($this);
158 35
            $this->dynamicOperations = $this->container->resolve(
159 35
                Operations::class, ['table' => $this->quotedTable, 'adapter' => $this->adapter]
160
            );
161
            // Unbind all bindings (necessary?)
162
        }
163 35
        return $this->dynamicOperations->perform($name, $arguments);
164
    }
165
166 27
    public static function __callStatic($name, $arguments) {
167 27
        return call_user_func_array([self::createNew(), $name], $arguments);
168
    }
169
170 8
    public function __set($name, $value) {
171 8
        $this->dataSet = true;
172 8
        $this->modelData[Text::deCamelize($name)] = $value;
173 8
    }
174
175 12
    public function __get($name) {
176 12
        return $this->retrieveItem($name);
177
    }
178
179 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...
180 4
        foreach ($relationships as $name => $relationship) {
181 4
            $array[$name] = $this->fetchRelatedFields($relationship, $index)->toArray($depth);
182
        }
183 4
        return $array;
184
    }
185
186 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...
187 16
        $relationships = $this->getDescription()->getRelationships();
188 16
        $array = $this->modelData;
189 16
        if ($depth > 0) {
190 4
            if ($this->hasMultipleItems()) {
191
                foreach ($array as $i => $value) {
192
                    $array[$i] = $this->expandArrayValue($value, $relationships, $depth - 1, $i);
193
                }
194
            } else {
195 4
                $array = $this->expandArrayValue($array, $relationships, $depth - 1);
196
            }
197
        }
198 16
        return $array;
199
    }
200
201 10
    public function save() {
202 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...
203 6
        $this->invalidFields = $this->dynamicOperations->getInvalidFields();
204 6
        return $return;
205
    }
206
207 18
    private function hasMultipleItems() {
208 18
        if (count($this->modelData) > 0) {
209 16
            return is_numeric(array_keys($this->modelData)[0]);
210
        } else {
211 2
            return false;
212
        }
213
    }
214
215 18
    public function getData() {
216 18
        $data = [];
217
218 18
        if (count($this->modelData) == 0) {
219 6
            $data = $this->modelData;
220 16
        } else if ($this->hasMultipleItems()) {
221 6
            $data = $this->modelData;
222 12
        } else if (count($this->modelData) > 0) {
223 12
            $data[] = $this->modelData;
224
        }
225
226 18
        return $data;
227
    }
228
229 27
    public function setData($data) {
230 27
        $this->dataSet = true;
231 27
        $this->modelData = $data;
232 27
    }
233
234
    public function mergeData($data) {
235
        foreach ($data as $key => $value) {
236
            $this->modelData[$key] = $value;
237
        }
238
        $this->dataSet = true;
239
    }
240
241 2
    public function offsetExists($offset) {
242 2
        return isset($this->modelData[$offset]);
243
    }
244
245 2
    public function offsetGet($offset) {
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 2
        $this->dataSet = true;
255 2
        $this->modelData[$offset] = $value;
256 2
    }
257
258
    public function offsetUnset($offset) {
259
        unset($this->modelData[$offset]);
260
    }
261
262 6
    private function wrap($offset) {
263 6
        if (isset($this->modelData[$offset])) {
264 6
            $newInstance = $this->createNew();
265 6
            $newInstance->setData($this->modelData[$offset]);
266 6
            return $newInstance;
267
        } else {
268
            return null;
269
        }
270
    }
271
272
    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...
273
        return $this->invalidFields;
274
    }
275
276
    public function getHasMany() {
277
        return $this->hasMany;
278
    }
279
280
    public function getBelongsTo() {
281
        return $this->belongsTo;
282
    }
283
284 4
    public function current() {
285 4
        return $this->wrap($this->keys[$this->index]);
286
    }
287
288
    public function key() {
289
        return $this->keys[$this->index];
290
    }
291
292 4
    public function next() {
293 4
        $this->index++;
294 4
    }
295
296 4
    public function rewind() {
297 4
        $this->keys = array_keys($this->modelData);
298 4
        $this->index = 0;
299 4
    }
300
301 4
    public function valid() {
302 4
        return isset($this->keys[$this->index]) && isset($this->modelData[$this->keys[$this->index]]);
303
    }
304
305 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...
306 10
        return $errors;
307
    }
308
309 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...
310 12
        if ($index === null) {
311 12
            $data = $this->modelData;
312
        } else {
313
            $data = $this->modelData[$index];
314
        }
315 12
        $model = $relationship->getModelInstance();
316 12
        if (empty($data)) {
317
            return $model;
318
        }
319 12
        $query = $relationship->prepareQuery($data);
320 12
        return $query ? $model->fetch($query) : $model;
321
    }
322
323 28
    public function getRelationships() {
324
        return [
325 28
            'HasMany' => $this->hasMany,
326 28
            'BelongsTo' => $this->belongsTo,
327 28
            'ManyHaveMany' => $this->manyHaveMany
328
        ];
329
    }
330
331
    public function usetField($field) {
332
        unset($this->modelData[$field]);
333
    }
334
335 8
    public function preSaveCallback() {
336
        
337 8
    }
338
339 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...
340
        
341 4
    }
342
343 2
    public function preUpdateCallback() {
344
        
345 2
    }
346
347 2
    public function postUpdateCallback() {
348
        
349 2
    }
350
351 37
    public function getDBStoreInformation() {
352 37
        $this->initialize();
353
        return [
354 37
            'schema' => $this->schema,
355 37
            'table' => $this->table,
356 37
            'quoted_table' => $this->quotedTable,
357 37
            'unquoted_table' => $this->unquotedTable
358
        ];
359
    }
360
    
361
    /**
362
     * 
363
     * @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...
364
     */
365
    public function getAdapter() {
366
        $this->initialize();
367
        return $this->adapter;
368
    }
369
370
}
371