Completed
Push — master ( 532c3e...02516d )
by Oscar
02:27
created

Row   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 374
Duplicated Lines 0.8 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 67
Bugs 17 Features 10
Metric Value
wmc 54
c 67
b 17
f 10
lcom 1
cbo 4
dl 3
loc 374
rs 7.0642

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __debugInfo() 0 7 1
A getCache() 0 4 1
A setCache() 0 4 1
C __get() 0 26 7
C __set() 0 32 8
A __isset() 0 4 2
B toArray() 0 19 5
A edit() 0 8 2
C relate() 3 62 10
A __construct() 0 8 2
A save() 0 20 3
B unrelate() 0 53 7
B unrelateAll() 0 43 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Row 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 Row, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SimpleCrud;
4
5
use SimpleCrud\Scheme\Scheme;
6
7
/**
8
 * Stores the data of an table row.
9
 */
10
class Row extends AbstractRow
11
{
12
    private $values = [];
13
    private $relations = [];
14
    private $changed = false;
15
16
    /**
17
     * {@inheritdoc}
18
     */
19
    public function __construct(Table $table)
20
    {
21
        parent::__construct($table);
22
23
        foreach ($table->getScheme()['fields'] as $name => $field) {
24
            $this->values[$name] = null;
25
        }
26
    }
27
28
    /**
29
     * Debug info.
30
     * 
31
     * @return array
32
     */
33
    public function __debugInfo()
34
    {
35
        return [
36
            'table' => $this->getTable()->name,
37
            'values' => $this->values,
38
        ];
39
    }
40
41
    /**
42
     * Return the current cache.
43
     * 
44
     * @return array
45
     */
46
    public function getCache()
47
    {
48
        return $this->relations;
49
    }
50
51
    /**
52
     * Set a new cache.
53
     * 
54
     * @param array $relations
55
     */
56
    public function setCache(array $relations)
57
    {
58
        return $this->relations = $relations;
59
    }
60
61
    /**
62
     * Magic method to return properties or load them automatically.
63
     *
64
     * @param string $name
65
     */
66
    public function __get($name)
67
    {
68
        //It's a field
69
        if (array_key_exists($name, $this->values)) {
70
            return $this->values[$name];
71
        }
72
73
        //It's a relation
74
        if (array_key_exists($name, $this->relations)) {
75
            return $this->relations[$name] ?: new NullValue();
76
        }
77
78
        //Load the relation
79
        $scheme = $this->getTable()->getScheme();
80
81
        if (isset($scheme['relations'][$name])) {
82
            return ($this->relations[$name] = call_user_func([$this, $name])->run()) ?: new NullValue();
83
        }
84
85
        //Exists as a function
86
        if (method_exists($this, $name)) {
87
            return $this->$name();
88
        }
89
90
        throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
91
    }
92
93
    /**
94
     * Magic method to store properties.
95
     *
96
     * @param string $name
97
     * @param mixed  $value
98
     */
99
    public function __set($name, $value)
100
    {
101
        //It's a field
102
        if (array_key_exists($name, $this->values)) {
103
            if ($this->values[$name] !== $value) {
104
                $this->changed = true;
105
            }
106
107
            return $this->values[$name] = $value;
108
        }
109
110
        //It's a relation
111
        $table = $this->getTable();
112
        $scheme = $table->getScheme();
113
114
        if (!isset($scheme['relations'][$name])) {
115
            throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
116
        }
117
118
        $relation = $scheme['relations'][$name][0];
119
120
        //Check types
121
        if ($relation === Scheme::HAS_ONE) {
122
            if ($value !== null && !($value instanceof self)) {
123
                throw new SimpleCrudException(sprintf('Invalid value: %s must be a Row instance or null', $name));
124
            }
125
        } elseif (!($value instanceof RowCollection)) {
126
            throw new SimpleCrudException(sprintf('Invalid value: %s must be a RowCollection', $name));
127
        }
128
129
        $this->relations[$name] = $value;
130
    }
131
132
    /**
133
     * Magic method to check if a property is defined or not.
134
     *
135
     * @param string $name Property name
136
     *
137
     * @return bool
138
     */
139
    public function __isset($name)
140
    {
141
        return isset($this->values[$name]) || isset($this->relations[$name]);
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function toArray(array $bannedEntities = [])
148
    {
149
        $table = $this->getTable();
150
151
        if (!empty($bannedEntities) && in_array($table->name, $bannedEntities)) {
152
            return;
153
        }
154
155
        $bannedEntities[] = $table->name;
156
        $data = $this->values;
157
158
        foreach ($this->relations as $name => $value) {
159
            if ($value !== null) {
160
                $data[$name] = $value->toArray($bannedEntities);
161
            }
162
        }
163
164
        return $data;
165
    }
166
167
    public function edit(array $values)
168
    {
169
        foreach ($values as $name => $value) {
170
            $this->__set($name, $value);
171
        }
172
173
        return $this;
174
    }
175
176
    /**
177
     * Saves this row in the database.
178
     *
179
     * @return $this
180
     */
181
    public function save()
182
    {
183
        if ($this->changed) {
184
            if (empty($this->id)) {
185
                $this->id = $this->table->insert()
0 ignored issues
show
Documentation Bug introduced by
The method insert does not exist on object<SimpleCrud\Table>? 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...
186
                    ->data($this->values)
187
                    ->run();
188
            } else {
189
                $this->table->update()
0 ignored issues
show
Documentation Bug introduced by
The method update does not exist on object<SimpleCrud\Table>? 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...
190
                    ->data($this->values)
191
                    ->byId($this->id)
192
                    ->limit(1)
193
                    ->run();
194
            }
195
196
            $this->table->cache($this);
197
        }
198
199
        return $this;
200
    }
201
202
    /**
203
     * Relate this row with other row and save the relation.
204
     *
205
     * @param Row $row
206
     *
207
     * @return $this
208
     */
209
    public function relate(Row $row)
210
    {
211
        $table = $this->getTable();
212
        $relationTable = $row->getTable();
213
        $relations = $table->getScheme()['relations'];
214
215 View Code Duplication
        if (!isset($relations[$relationTable->name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->name, $relationTable->name));
217
        }
218
219
        $relation = $relations[$relationTable->name];
220
221
        if ($relation[0] === Scheme::HAS_ONE) {
222
            $row->relate($this);
223
224
            return $this;
225
        }
226
227
        if ($relation[0] === Scheme::HAS_MANY) {
228
            if ($this->id === null) {
229
                $this->save();
230
            }
231
232
            $row->{$relation[1]} = $this->id;
233
            $row->save();
234
235
            if (isset($this->relations[$relationTable->name])) {
236
                $this->relations[$relationTable->name][] = $row;
237
            }
238
239
            $cache = $row->getCache();
240
            $cache[$table->name] = $this;
241
            $row->setCache($cache);
242
243
            return $this;
244
        }
245
246
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
247
            $bridge = $this->getDatabase()->{$relation[1]};
248
249
            if ($row->id === null) {
250
                $row->save();
251
            }
252
253
            if ($this->id === null) {
254
                $this->save();
255
            }
256
257
            $bridge
258
                ->insert()
259
                ->duplications()
260
                ->data([
261
                    $relation[2] => $this->id,
262
                    $relation[3] => $row->id,
263
                ])
264
                ->run();
265
266
            if (isset($this->relations[$relationTable->name])) {
267
                $this->relations[$relationTable->name][] = $row;
268
            }
269
        }
270
    }
271
272
    /**
273
     * Unrelate this row with other row and save it.
274
     *
275
     * @param Row $row
276
     *
277
     * @return $this
278
     */
279
    public function unrelate(Row $row)
280
    {
281
        $table = $this->getTable();
282
        $relationTable = $row->getTable();
283
        $relations = $table->getScheme()['relations'];
284
285
        if (!isset($relations[$relationTable->name])) {
286
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->name, $relationTable->name));
287
        }
288
289
        $relation = $relations[$relationTable->name];
290
291
        if ($relation[0] === Scheme::HAS_ONE) {
292
            if ($this->{$relation[1]} === $row->id) {
293
                $this->{$relation[1]} = null;
294
            }
295
296
            $this->relations[$relationTable->name] = new NullValue();
297
298
            $cache = $row->getCache();
299
300
            if (isset($cache[$table->name])) {
301
                unset($cache[$table->name][$this->id]);
302
                $row->setCache($cache);
303
            }
304
305
            return $this->save();
306
        }
307
308
        if ($relation[0] === Scheme::HAS_MANY) {
309
            $row->unrelate($this);
310
311
            return $this;
312
        }
313
314
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
315
            $bridge = $this->getDatabase()->{$relation[1]};
316
317
            $bridge
318
                ->delete()
319
                ->by($relation[2], $this->id)
320
                ->by($relation[3], $row->id)
321
                ->run();
322
323
            unset($this->relations[$relation[1]]);
324
            unset($this->relations[$relationTable->name][$row->id]);
325
326
            $cache = $row->getCache();
327
            unset($cache[$relation[1]]);
328
            unset($cache[$table->name][$this->id]);
329
            $row->setCache($cache);
330
        }
331
    }
332
333
    /**
334
     * Unrelate this row with all rows of a specific table.
335
     *
336
     * @param Table $relationTable
337
     *
338
     * @return $this
339
     */
340
    public function unrelateAll(Table $relationTable)
341
    {
342
        $table = $this->getTable();
343
        $relations = $table->getScheme()['relations'];
344
345
        if (!isset($relations[$relationTable->name])) {
346
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->name, $relationTable->name));
347
        }
348
349
        $relation = $relations[$relationTable->name];
350
351
        if ($relation[0] === Scheme::HAS_ONE) {
352
            $this->{$relation[1]} = null;
353
            $this->relations[$relationTable->name] = new NullValue();
354
355
            return $this->save();
356
        }
357
358
        if ($relation[0] === Scheme::HAS_MANY) {
359
            $relationTable->update()
0 ignored issues
show
Documentation Bug introduced by
The method update does not exist on object<SimpleCrud\Table>? 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...
360
                ->data([
361
                    $relation[1] => null,
362
                ])
363
                ->by($relation[1], $this->id)
364
                ->run();
365
366
            $this->relations[$relationTable->name] = $relationTable->createCollection();
367
368
            return $this;
369
        }
370
371
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
372
            $bridge = $this->getDatabase()->{$relation[1]};
373
374
            $bridge
375
                ->delete()
376
                ->by($relation[2], $this->id)
377
                ->run();
378
379
            $this->relations[$bridge->name] = $bridge->createCollection();
380
            $this->relations[$relationTable->name] = $relationTable->createCollection();
381
        }
382
    }
383
}
384