Completed
Push — master ( 0eeb04...4002ae )
by Oscar
02:31
created

Row::save()   D

Complexity

Conditions 18
Paths 30

Size

Total Lines 90
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 24
Bugs 1 Features 2
Metric Value
c 24
b 1
f 2
dl 0
loc 90
rs 4.7996
cc 18
eloc 53
nc 30
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
        $defaults = [];
24
25
        foreach ($table->getScheme()['fields'] as $name => $field) {
26
            $defaults[$name] = $field['default'];
27
        }
28
29
        $this->init($defaults);
30
    }
31
32
    /**
33
     * Clear the current cache.
34
     */
35
    public function clearCache()
36
    {
37
        $this->relations = [];
38
    }
39
40
    /**
41
     * Initialize the row with the data from database.
42
     * 
43
     * @param array $values
44
     * @param array $relations
45
     */
46
    public function init(array $values, array $relations = [])
47
    {
48
        $this->values = $values;
49
        $this->relations = $relations;
50
        $this->changed = false;
51
    }
52
53
    /**
54
     * Magic method to return properties or load them automatically.
55
     *
56
     * @param string $name
57
     */
58
    public function __get($name)
59
    {
60
        //It's a field
61
        if (array_key_exists($name, $this->values)) {
62
            return $this->values[$name];
63
        }
64
65
        //It's a relation
66
        if (array_key_exists($name, $this->relations)) {
67
            return $this->relations[$name] ?: new NullValue();
68
        }
69
70
        //Load the relation
71
        $scheme = $this->getTable()->getScheme();
72
73
        if (isset($scheme['relations'][$name])) {
74
            return ($this->relations[$name] = call_user_func([$this, $name])->run()) ?: new NullValue();
75
        }
76
77
        throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
78
    }
79
80
    /**
81
     * Magic method to store properties.
82
     *
83
     * @param string $name
84
     * @param mixed  $value
85
     */
86
    public function __set($name, $value)
87
    {
88
        //It's a field
89
        if (array_key_exists($name, $this->values)) {
90
            if ($this->values[$name] !== $value) {
91
                $this->changed = true;
92
            }
93
94
            return $this->values[$name] = $value;
95
        }
96
97
        //It's a relation
98
        $table = $this->getTable();
99
        $scheme = $table->getScheme();
100
101
        if (!isset($scheme['relations'][$name])) {
102
            throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
103
        }
104
105
        $relation = $scheme['relations'][$name][0];
106
107
        //Check types
108
        if ($relation === Scheme::HAS_ONE) {
109
            if ($value !== null && !($value instanceof self)) {
110
                throw new SimpleCrudException(sprintf('Invalid value: %s must be a Row instance or null', $name));
111
            }
112
        } elseif (!($value instanceof RowCollection)) {
113
            throw new SimpleCrudException(sprintf('Invalid value: %s must be a RowCollection', $name));
114
        }
115
116
        $this->relations[$name] = $value;
117
    }
118
119
    /**
120
     * Magic method to check if a property is defined or not.
121
     *
122
     * @param string $name Property name
123
     *
124
     * @return bool
125
     */
126
    public function __isset($name)
127
    {
128
        return isset($this->values[$name]) || isset($this->relations[$name]);
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function toArray(array $bannedEntities = [])
135
    {
136
        $table = $this->getTable();
137
138
        if (!empty($bannedEntities) && in_array($table->name, $bannedEntities)) {
139
            return;
140
        }
141
142
        $bannedEntities[] = $table->name;
143
        $data = $this->values;
144
145
        foreach ($this->relations as $name => $value) {
146
            if ($value !== null) {
147
                $data[$name] = $value->toArray($bannedEntities);
148
            }
149
        }
150
151
        return $data;
152
    }
153
154
    /**
155
     * Saves this row in the database.
156
     *
157
     * @param bool|array $relations Set true to save the relations with other entities
158
     *
159
     * @return $this
160
     */
161
    public function save($relations = false)
162
    {
163
        if ($relations === true) {
164
            $relations = array_keys($this->relations);
165
        }
166
167
        if ($relations) {
168
            $scheme = $this->getTable()->getScheme()['relations'];
169
170
            foreach ($relations as $name) {
0 ignored issues
show
Bug introduced by
The expression $relations of type boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
171
                if (!array_key_exists($name, $this->relations)) {
172
                    continue;
173
                }
174
175
                if (!isset($scheme[$name])) {
176
                    throw new SimpleCrudException(sprintf('Invalid relation: %s', $name));
177
                }
178
179
                $relation = $scheme[$name];
180
181
                if ($relation[0] === Scheme::HAS_ONE) {
182
                    $this->{$relation[1]} = ($this->relations[$name] === null) ? null : $this->relations[$name]->save()->id;
183
                }
184
            }
185
        }
186
187
        if (!$this->changed) {
188
            return $this;
189
        }
190
191
        if (empty($this->id)) {
192
            $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...
193
                ->data($this->values)
194
                ->run();
195
        } else {
196
            $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...
197
                ->data($this->values)
198
                ->byId($this->id)
199
                ->limit(1)
200
                ->run();
201
        }
202
203
        if ($relations) {
204
            $scheme = $this->getTable()->getScheme()['relations'];
205
206
            foreach ($relations as $name) {
0 ignored issues
show
Bug introduced by
The expression $relations of type boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
207
                if (!array_key_exists($name, $this->relations)) {
208
                    continue;
209
                }
210
211
                if (!isset($scheme[$name])) {
212
                    throw new SimpleCrudException(sprintf('Invalid relation: %s', $name));
213
                }
214
215
                $relation = $scheme[$name];
216
217
                if ($relation[0] === Scheme::HAS_MANY) {
218
                    foreach ($this->relations[$name] as $row) {
219
                        $row->{$relation[1]} = $this->id;
220
                        $row->save();
221
                    }
222
223
                    continue;
224
                }
225
226
                if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
227
                    $bridge = $this->getDatabase()->{$relation[1]};
228
229
                    $bridge
230
                        ->delete()
231
                        ->by($relation[2], $this->id)
232
                        ->run();
233
234
                    foreach ($this->relations[$name] as $row) {
235
                        $bridge
236
                            ->insert()
237
                            ->data([
238
                                $relation[2] => $this->id,
239
                                $relation[3] => $row->id,
240
                            ])
241
                            ->run();
242
                    }
243
                }
244
            }
245
        }
246
247
        $this->table->cache($this);
248
249
        return $this;
250
    }
251
}
252