Completed
Push — master ( 02516d...659273 )
by Oscar
02:37
created

Row   C

Complexity

Total Complexity 59

Size/Duplication

Total Lines 400
Duplicated Lines 4.25 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 67
Bugs 17 Features 11
Metric Value
wmc 59
c 67
b 17
f 11
lcom 1
cbo 5
dl 17
loc 400
rs 6.1904

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __debugInfo() 0 7 1
A getCache() 0 4 1
A setCache() 0 4 1
A __construct() 0 8 2
D __get() 0 37 9
C __set() 14 47 11
A __isset() 0 4 2
B toArray() 0 19 5
A edit() 0 8 2
A save() 0 20 3
C relate() 3 62 10
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
        //It's a localizable field
79
        $language = $this->getDatabase()->getAttribute(SimpleCrud::ATTR_LOCALE);
80
81
        if (!is_null($language)) {
82
            $localeName = "{$name}_{$language}";
83
84
            if (array_key_exists($localeName, $this->values)) {
85
                return $this->values[$localeName];
86
            }
87
        }
88
89
        //Load the relation
90
        $scheme = $this->getTable()->getScheme();
91
92
        if (isset($scheme['relations'][$name])) {
93
            return ($this->relations[$name] = call_user_func([$this, $name])->run()) ?: new NullValue();
94
        }
95
96
        //Exists as a function
97
        if (method_exists($this, $name)) {
98
            return $this->$name();
99
        }
100
101
        throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
102
    }
103
104
    /**
105
     * Magic method to store properties.
106
     *
107
     * @param string $name
108
     * @param mixed  $value
109
     */
110
    public function __set($name, $value)
111
    {
112
        //It's a field
113 View Code Duplication
        if (array_key_exists($name, $this->values)) {
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...
114
            if ($this->values[$name] !== $value) {
115
                $this->changed = true;
116
            }
117
118
            return $this->values[$name] = $value;
119
        }
120
121
        //It's a localizable field
122
        $language = $this->getDatabase()->getAttribute(SimpleCrud::ATTR_LOCALE);
123
124
        if (!is_null($language)) {
125
            $localeName = "{$name}_{$language}";
126
127 View Code Duplication
            if (array_key_exists($localeName, $this->values)) {
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...
128
                if ($this->values[$localeName] !== $value) {
129
                    $this->changed = true;
130
                }
131
132
                return $this->values[$localeName] = $value;
133
            }
134
        }
135
136
        //It's a relation
137
        $table = $this->getTable();
138
        $scheme = $table->getScheme();
139
140
        if (!isset($scheme['relations'][$name])) {
141
            throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
142
        }
143
144
        $relation = $scheme['relations'][$name][0];
145
146
        //Check types
147
        if ($relation === Scheme::HAS_ONE) {
148
            if ($value !== null && !($value instanceof self)) {
149
                throw new SimpleCrudException(sprintf('Invalid value: %s must be a Row instance or null', $name));
150
            }
151
        } elseif (!($value instanceof RowCollection)) {
152
            throw new SimpleCrudException(sprintf('Invalid value: %s must be a RowCollection', $name));
153
        }
154
155
        $this->relations[$name] = $value;
156
    }
157
158
    /**
159
     * Magic method to check if a property is defined or not.
160
     *
161
     * @param string $name Property name
162
     *
163
     * @return bool
164
     */
165
    public function __isset($name)
166
    {
167
        return isset($this->values[$name]) || isset($this->relations[$name]);
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    public function toArray(array $bannedEntities = [])
174
    {
175
        $table = $this->getTable();
176
177
        if (!empty($bannedEntities) && in_array($table->name, $bannedEntities)) {
178
            return;
179
        }
180
181
        $bannedEntities[] = $table->name;
182
        $data = $this->values;
183
184
        foreach ($this->relations as $name => $value) {
185
            if ($value !== null) {
186
                $data[$name] = $value->toArray($bannedEntities);
187
            }
188
        }
189
190
        return $data;
191
    }
192
193
    public function edit(array $values)
194
    {
195
        foreach ($values as $name => $value) {
196
            $this->__set($name, $value);
197
        }
198
199
        return $this;
200
    }
201
202
    /**
203
     * Saves this row in the database.
204
     *
205
     * @return $this
206
     */
207
    public function save()
208
    {
209
        if ($this->changed) {
210
            if (empty($this->id)) {
211
                $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...
212
                    ->data($this->values)
213
                    ->run();
214
            } else {
215
                $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...
216
                    ->data($this->values)
217
                    ->byId($this->id)
218
                    ->limit(1)
219
                    ->run();
220
            }
221
222
            $this->table->cache($this);
223
        }
224
225
        return $this;
226
    }
227
228
    /**
229
     * Relate this row with other row and save the relation.
230
     *
231
     * @param Row $row
232
     *
233
     * @return $this
234
     */
235
    public function relate(Row $row)
236
    {
237
        $table = $this->getTable();
238
        $relationTable = $row->getTable();
239
        $relations = $table->getScheme()['relations'];
240
241 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...
242
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->name, $relationTable->name));
243
        }
244
245
        $relation = $relations[$relationTable->name];
246
247
        if ($relation[0] === Scheme::HAS_ONE) {
248
            $row->relate($this);
249
250
            return $this;
251
        }
252
253
        if ($relation[0] === Scheme::HAS_MANY) {
254
            if ($this->id === null) {
255
                $this->save();
256
            }
257
258
            $row->{$relation[1]} = $this->id;
259
            $row->save();
260
261
            if (isset($this->relations[$relationTable->name])) {
262
                $this->relations[$relationTable->name][] = $row;
263
            }
264
265
            $cache = $row->getCache();
266
            $cache[$table->name] = $this;
267
            $row->setCache($cache);
268
269
            return $this;
270
        }
271
272
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
273
            $bridge = $this->getDatabase()->{$relation[1]};
274
275
            if ($row->id === null) {
276
                $row->save();
277
            }
278
279
            if ($this->id === null) {
280
                $this->save();
281
            }
282
283
            $bridge
284
                ->insert()
285
                ->duplications()
286
                ->data([
287
                    $relation[2] => $this->id,
288
                    $relation[3] => $row->id,
289
                ])
290
                ->run();
291
292
            if (isset($this->relations[$relationTable->name])) {
293
                $this->relations[$relationTable->name][] = $row;
294
            }
295
        }
296
    }
297
298
    /**
299
     * Unrelate this row with other row and save it.
300
     *
301
     * @param Row $row
302
     *
303
     * @return $this
304
     */
305
    public function unrelate(Row $row)
306
    {
307
        $table = $this->getTable();
308
        $relationTable = $row->getTable();
309
        $relations = $table->getScheme()['relations'];
310
311
        if (!isset($relations[$relationTable->name])) {
312
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->name, $relationTable->name));
313
        }
314
315
        $relation = $relations[$relationTable->name];
316
317
        if ($relation[0] === Scheme::HAS_ONE) {
318
            if ($this->{$relation[1]} === $row->id) {
319
                $this->{$relation[1]} = null;
320
            }
321
322
            $this->relations[$relationTable->name] = new NullValue();
323
324
            $cache = $row->getCache();
325
326
            if (isset($cache[$table->name])) {
327
                unset($cache[$table->name][$this->id]);
328
                $row->setCache($cache);
329
            }
330
331
            return $this->save();
332
        }
333
334
        if ($relation[0] === Scheme::HAS_MANY) {
335
            $row->unrelate($this);
336
337
            return $this;
338
        }
339
340
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
341
            $bridge = $this->getDatabase()->{$relation[1]};
342
343
            $bridge
344
                ->delete()
345
                ->by($relation[2], $this->id)
346
                ->by($relation[3], $row->id)
347
                ->run();
348
349
            unset($this->relations[$relation[1]]);
350
            unset($this->relations[$relationTable->name][$row->id]);
351
352
            $cache = $row->getCache();
353
            unset($cache[$relation[1]]);
354
            unset($cache[$table->name][$this->id]);
355
            $row->setCache($cache);
356
        }
357
    }
358
359
    /**
360
     * Unrelate this row with all rows of a specific table.
361
     *
362
     * @param Table $relationTable
363
     *
364
     * @return $this
365
     */
366
    public function unrelateAll(Table $relationTable)
367
    {
368
        $table = $this->getTable();
369
        $relations = $table->getScheme()['relations'];
370
371
        if (!isset($relations[$relationTable->name])) {
372
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->name, $relationTable->name));
373
        }
374
375
        $relation = $relations[$relationTable->name];
376
377
        if ($relation[0] === Scheme::HAS_ONE) {
378
            $this->{$relation[1]} = null;
379
            $this->relations[$relationTable->name] = new NullValue();
380
381
            return $this->save();
382
        }
383
384
        if ($relation[0] === Scheme::HAS_MANY) {
385
            $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...
386
                ->data([
387
                    $relation[1] => null,
388
                ])
389
                ->by($relation[1], $this->id)
390
                ->run();
391
392
            $this->relations[$relationTable->name] = $relationTable->createCollection();
393
394
            return $this;
395
        }
396
397
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
398
            $bridge = $this->getDatabase()->{$relation[1]};
399
400
            $bridge
401
                ->delete()
402
                ->by($relation[2], $this->id)
403
                ->run();
404
405
            $this->relations[$bridge->name] = $bridge->createCollection();
406
            $this->relations[$relationTable->name] = $relationTable->createCollection();
407
        }
408
    }
409
}
410