Completed
Push — master ( fe0e92...0c7bc3 )
by James Ekow Abaka
02:30
created

RecordWrapper   C

Complexity

Total Complexity 64

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 83.33%

Importance

Changes 14
Bugs 1 Features 0
Metric Value
wmc 64
c 14
b 1
f 0
lcom 2
cbo 8
dl 0
loc 320
ccs 140
cts 168
cp 0.8333
rs 5.8364

39 Methods

Rating   Name   Duplication   Size   Complexity  
B initialize() 0 24 6
A __debugInfo() 0 4 2
A getDescription() 0 8 1
A count() 0 7 2
A retrieveItem() 0 8 3
A createNew() 0 5 1
A __call() 0 12 2
A __callStatic() 0 3 1
A __set() 0 4 1
A __get() 0 3 1
A expandArrayValue() 0 6 2
A toArray() 0 14 4
A save() 0 5 1
A hasMultipleItems() 0 7 2
A getData() 0 13 4
A setData() 0 4 1
A mergeData() 0 6 2
A offsetExists() 0 3 1
A offsetGet() 0 7 2
A offsetSet() 0 4 1
A offsetUnset() 0 3 1
A wrap() 0 9 2
A getInvalidFields() 0 3 1
A getHasMany() 0 3 1
A getBelongsTo() 0 3 1
A current() 0 3 1
A key() 0 3 1
A next() 0 3 1
A rewind() 0 4 1
A valid() 0 3 2
A onValidate() 0 3 1
A fetchRelatedFields() 0 13 4
A getRelationships() 0 7 1
A usetField() 0 3 1
A preSaveCallback() 0 3 1
A postSaveCallback() 0 3 1
A preUpdateCallback() 0 3 1
A postUpdateCallback() 0 3 1
A getDBStoreInformation() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like RecordWrapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RecordWrapper, and based on these observations, apply Extract Interface, too.

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