Completed
Push — master ( 63fc8b...d833c4 )
by Oscar
01:40
created

Row::relate()   D

Complexity

Conditions 20
Paths 55

Size

Total Lines 108
Code Lines 61

Duplication

Lines 5
Ratio 4.63 %

Importance

Changes 0
Metric Value
dl 5
loc 108
rs 4.7294
c 0
b 0
f 0
cc 20
eloc 61
nc 55
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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()->getName(),
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
            return $return;
77
        }
78
79
        //It's a localizable field
80
        $language = $this->getDatabase()->getAttribute(SimpleCrud::ATTR_LOCALE);
81
82
        if (!is_null($language)) {
83
            $localeName = "{$name}_{$language}";
84
85
            if (array_key_exists($localeName, $this->values)) {
86
                return $this->values[$localeName];
87
            }
88
        }
89
90
        //Load the relation
91
        $scheme = $this->getTable()->getScheme();
92
93
        if (isset($scheme['relations'][$name])) {
94
            $return = call_user_func([$this, $name])->run() ?: new NullValue();
95
            $this->relations[$name] = $return;
96
            return $return;
97
        }
98
99
        //Exists as a function
100
        if (method_exists($this, $name)) {
101
            $return = $this->$name();
102
            return $return;
103
        }
104
105
        throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
106
    }
107
108
    /**
109
     * Magic method to store properties.
110
     *
111
     * @param string $name
112
     * @param mixed  $value
113
     */
114
    public function __set($name, $value)
115
    {
116
        //It's a field
117 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...
118
            if ($this->values[$name] !== $value) {
119
                $this->changed = true;
120
            }
121
122
            return $this->values[$name] = $value;
123
        }
124
125
        //It's a localizable field
126
        $language = $this->getDatabase()->getAttribute(SimpleCrud::ATTR_LOCALE);
127
128
        if (!is_null($language)) {
129
            $localeName = "{$name}_{$language}";
130
131 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...
132
                if ($this->values[$localeName] !== $value) {
133
                    $this->changed = true;
134
                }
135
136
                return $this->values[$localeName] = $value;
137
            }
138
        }
139
140
        //It's a relation
141
        $table = $this->getTable();
142
        $scheme = $table->getScheme();
143
144
        if (!isset($scheme['relations'][$name])) {
145
            throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
146
        }
147
148
        $relation = $scheme['relations'][$name][0];
149
150
        //Check types
151
        if ($relation === Scheme::HAS_ONE) {
152
            if ($value !== null && !($value instanceof self)) {
153
                throw new SimpleCrudException(sprintf('Invalid value: %s must be a Row instance or null', $name));
154
            }
155
        } elseif (!($value instanceof RowCollection)) {
156
            throw new SimpleCrudException(sprintf('Invalid value: %s must be a RowCollection', $name));
157
        }
158
159
        $this->relations[$name] = $value;
160
    }
161
162
    /**
163
     * Magic method to check if a property is defined or not.
164
     *
165
     * @param string $name Property name
166
     *
167
     * @return bool
168
     */
169
    public function __isset($name)
170
    {
171
        $language = $this->getDatabase()->getAttribute(SimpleCrud::ATTR_LOCALE);
172
173
        if (!is_null($language) && isset($this->values["{$name}_{$language}"])) {
174
            return true;
175
        }
176
177
        return isset($this->values[$name]) || isset($this->relations[$name]);
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function toArray($recursive = true, array $bannedEntities = [])
184
    {
185
        if (!$recursive) {
186
            return $this->values;
187
        }
188
189
        $table = $this->getTable();
190
191
        if (!empty($bannedEntities) && in_array($table->getName(), $bannedEntities)) {
192
            return;
193
        }
194
195
        $bannedEntities[] = $table->getName();
196
        $data = $this->values;
197
198
        foreach ($this->relations as $name => $value) {
199
            if ($value !== null) {
200
                $data[$name] = $value->toArray(true, $bannedEntities);
201
            }
202
        }
203
204
        return $data;
205
    }
206
207
    public function edit(array $values)
208
    {
209
        foreach ($values as $name => $value) {
210
            $this->__set($name, $value);
211
        }
212
213
        return $this;
214
    }
215
216
    /**
217
     * Saves this row in the database.
218
     *
219
     * @return $this
220
     */
221
    public function save()
222
    {
223
        if ($this->changed) {
224
            if (empty($this->id)) {
225
                $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...
226
                    ->data($this->values, $this->values)
227
                    ->run();
228
            } else {
229
                $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...
230
                    ->data($this->values, $this->values)
231
                    ->byId($this->id)
232
                    ->limit(1)
233
                    ->run();
234
            }
235
236
            $this->table->cache($this);
237
        }
238
239
        return $this;
240
    }
241
242
    /**
243
     * Relate this row with other row and save the relation.
244
     *
245
     * @param Row $row
246
     *
247
     * @return $this
248
     */
249
    public function relate(Row $row)
0 ignored issues
show
Unused Code introduced by
The parameter $row 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...
250
    {
251
        $table = $this->getTable();
252
        $relations = $table->getScheme()['relations'];
253
        $rows = [];
0 ignored issues
show
Unused Code introduced by
$rows is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
254
255
        foreach (func_get_args() as $row) {
256
            $relationTable = $row->getTable();
257
258
            if (!isset($relations[$relationTable->getName()])) {
259
                throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->getName(), $relationTable->getName()));
260
            }
261
262
            $relation = $relations[$relationTable->getName()];
263
264
            if (!isset($relations[$relation[0]])) {
265
                $relations[$relation[0]] = [];
266
            }
267
268
            $relations[$relation[0]][] = [
269
                $relation,
270
                $relationTable,
271
                $row
272
            ];
273
        }
274
275
        if (isset($relations[Scheme::HAS_ONE])) {
276
            foreach ($relations[Scheme::HAS_ONE] as $r) {
277
                list($relation, $relationTable, $row) = $r;
278
279
                if ($row->id === null) {
280
                    $row->save();
281
                }
282
283
                $this->{$relation[1]} = $row->id;
284
                $this->relations[$relationTable->getName()] = $row;
285
            }
286
287
            $this->save();
288
289
            foreach ($relations[Scheme::HAS_ONE] as $r) {
290
                list($relation, $relationTable, $row) = $r;
291
292
                if ($table->getName() !== $relationTable->getName()) {
293
                    $cache = $row->getCache();
294
295
                    if (isset($cache[$table->getName()])) {
296
                        $cache[$table->getName()][] = $this;
297
                        $row->setCache($cache);
298
                    }
299
                }
300
            }
301
        }
302
303
        if (isset($relations[Scheme::HAS_MANY])) {
304
            if ($this->id === null) {
305
                $this->save();
306
            }
307
308
            foreach ($relations[Scheme::HAS_MANY] as $r) {
309
                list($relation, $relationTable, $row) = $r;
310
311
                $row->{$relation[1]} = $this->id;
312
                $row->save();
313
314
                if (isset($this->relations[$relationTable->getName()])) {
315
                    $this->relations[$relationTable->getName()][] = $row;
316
                }
317
318 View Code Duplication
                if ($table->getName() !== $relationTable->getName()) {
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...
319
                    $cache = $row->getCache();
320
                    $cache[$table->getName()] = $this;
321
                    $row->setCache($cache);
322
                }
323
            }
324
        }
325
326
        if (isset($relations[Scheme::HAS_MANY_TO_MANY])) {
327
            if ($this->id === null) {
328
                $this->save();
329
            }
330
331
            foreach ($relations[Scheme::HAS_MANY_TO_MANY] as $r) {
332
                list($relation, $relationTable, $row) = $r;
333
334
                $bridge = $this->getDatabase()->{$relation[1]};
335
336
                if ($row->id === null) {
337
                    $row->save();
338
                }
339
340
                $bridge
341
                    ->insert()
342
                    ->duplications()
343
                    ->data([
344
                        $relation[2] => $this->id,
345
                        $relation[3] => $row->id,
346
                    ])
347
                    ->run();
348
349
                if (isset($this->relations[$relationTable->getName()])) {
350
                    $this->relations[$relationTable->getName()][] = $row;
351
                }
352
            }
353
        }
354
355
        return $this;
356
    }
357
358
    /**
359
     * Unrelate this row with other row and save it.
360
     *
361
     * @param Row $row
362
     *
363
     * @return $this
364
     */
365
    public function unrelate(Row $row)
366
    {
367
        $table = $this->getTable();
368
        $relationTable = $row->getTable();
369
        $relations = $table->getScheme()['relations'];
370
371
        if (!isset($relations[$relationTable->getName()])) {
372
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->getName(), $relationTable->getName()));
373
        }
374
375
        $relation = $relations[$relationTable->getName()];
376
377
        if ($relation[0] === Scheme::HAS_ONE) {
378
            $row->unrelate($this);
379
380
            return $this;
381
        }
382
383
        if ($relation[0] === Scheme::HAS_MANY) {
384
            if ($row->{$relation[1]} === $this->id) {
385
                $row->{$relation[1]} = null;
386
                $row->save();
387
            }
388
389
            if (isset($this->relations[$relationTable->getName()])) {
390
                unset($this->relations[$relationTable->getName()][$row->id]);
391
            }
392
393 View Code Duplication
            if ($table->getName() !== $relationTable->getName()) {
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...
394
                $cache = $row->getCache();
395
                $cache[$table->getName()] = new NullValue();
396
                $row->setCache($cache);
397
            }
398
399
            return $this;
400
        }
401
402
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
403
            $bridge = $this->getDatabase()->{$relation[1]};
404
405
            $bridge
406
                ->delete()
407
                ->by($relation[2], $this->id)
408
                ->by($relation[3], $row->id)
409
                ->run();
410
411
            unset($this->relations[$relation[1]]);
412
            unset($this->relations[$relationTable->getName()][$row->id]);
413
414
            $cache = $row->getCache();
415
            unset($cache[$relation[1]]);
416
            unset($cache[$table->getName()][$this->id]);
417
            $row->setCache($cache);
418
        }
419
    }
420
421
    /**
422
     * Unrelate this row with all rows of a specific table.
423
     *
424
     * @param Table $relationTable
425
     *
426
     * @return $this
427
     */
428
    public function unrelateAll(Table $relationTable)
429
    {
430
        $table = $this->getTable();
431
        $relations = $table->getScheme()['relations'];
432
433
        if (!isset($relations[$relationTable->getName()])) {
434
            throw new SimpleCrudException(sprintf('Invalid relation: %s - %s', $table->getName(), $relationTable->getName()));
435
        }
436
437
        $relation = $relations[$relationTable->getName()];
438
439
        if ($relation[0] === Scheme::HAS_ONE) {
440
            $this->{$relation[1]} = null;
441
            $this->relations[$relationTable->getName()] = new NullValue();
442
443
            return $this->save();
444
        }
445
446
        if ($relation[0] === Scheme::HAS_MANY) {
447
            $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...
448
                ->data([
449
                    $relation[1] => null,
450
                ])
451
                ->by($relation[1], $this->id)
452
                ->run();
453
454
            $this->relations[$relationTable->getName()] = $relationTable->createCollection();
455
456
            return $this;
457
        }
458
459
        if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
460
            $bridge = $this->getDatabase()->{$relation[1]};
461
462
            $bridge
463
                ->delete()
464
                ->by($relation[2], $this->id)
465
                ->run();
466
467
            $this->relations[$bridge->getName()] = $bridge->createCollection();
468
            $this->relations[$relationTable->getName()] = $relationTable->createCollection();
469
        }
470
    }
471
}
472