Completed
Push — master ( 9817c8...2b2ac0 )
by Oscar
05:39
created

Row::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 13
Bugs 1 Features 1
Metric Value
c 13
b 1
f 1
dl 0
loc 8
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
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
     * Clear the current cache.
43
     */
44
    public function clearCache()
45
    {
46
        $this->relations = [];
47
    }
48
49
    /**
50
     * Magic method to return properties or load them automatically.
51
     *
52
     * @param string $name
53
     */
54
    public function __get($name)
55
    {
56
        //It's a field
57
        if (array_key_exists($name, $this->values)) {
58
            return $this->values[$name];
59
        }
60
61
        //It's a relation
62
        if (array_key_exists($name, $this->relations)) {
63
            return $this->relations[$name] ?: new NullValue();
64
        }
65
66
        //Load the relation
67
        $scheme = $this->getTable()->getScheme();
68
69
        if (isset($scheme['relations'][$name])) {
70
            return ($this->relations[$name] = call_user_func([$this, $name])->run()) ?: new NullValue();
71
        }
72
73
        //Exists as a function
74
        if (method_exists($this, $name)) {
75
            return $this->$name();
76
        }
77
78
        throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
79
    }
80
81
    /**
82
     * Magic method to store properties.
83
     *
84
     * @param string $name
85
     * @param mixed  $value
86
     */
87
    public function __set($name, $value)
88
    {
89
        //It's a field
90
        if (array_key_exists($name, $this->values)) {
91
            if ($this->values[$name] !== $value) {
92
                $this->changed = true;
93
            }
94
95
            return $this->values[$name] = $value;
96
        }
97
98
        //It's a relation
99
        $table = $this->getTable();
100
        $scheme = $table->getScheme();
101
102
        if (!isset($scheme['relations'][$name])) {
103
            throw new SimpleCrudException(sprintf('Undefined property "%s"', $name));
104
        }
105
106
        $relation = $scheme['relations'][$name][0];
107
108
        //Check types
109
        if ($relation === Scheme::HAS_ONE) {
110
            if ($value !== null && !($value instanceof self)) {
111
                throw new SimpleCrudException(sprintf('Invalid value: %s must be a Row instance or null', $name));
112
            }
113
        } elseif (!($value instanceof RowCollection)) {
114
            throw new SimpleCrudException(sprintf('Invalid value: %s must be a RowCollection', $name));
115
        }
116
117
        $this->relations[$name] = $value;
118
    }
119
120
    /**
121
     * Magic method to check if a property is defined or not.
122
     *
123
     * @param string $name Property name
124
     *
125
     * @return bool
126
     */
127
    public function __isset($name)
128
    {
129
        return isset($this->values[$name]) || isset($this->relations[$name]);
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function toArray(array $bannedEntities = [])
136
    {
137
        $table = $this->getTable();
138
139
        if (!empty($bannedEntities) && in_array($table->name, $bannedEntities)) {
140
            return;
141
        }
142
143
        $bannedEntities[] = $table->name;
144
        $data = $this->values;
145
146
        foreach ($this->relations as $name => $value) {
147
            if ($value !== null) {
148
                $data[$name] = $value->toArray($bannedEntities);
149
            }
150
        }
151
152
        return $data;
153
    }
154
155
    /**
156
     * Saves this row in the database.
157
     *
158
     * @param bool|array $relations Set true to save the relations with other entities
159
     *
160
     * @return $this
161
     */
162
    public function save($relations = false)
163
    {
164
        if ($relations === true) {
165
            $relations = array_keys($this->relations);
166
        }
167
168
        if ($relations) {
169
            $scheme = $this->getTable()->getScheme()['relations'];
170
171
            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...
172
                if (!array_key_exists($name, $this->relations)) {
173
                    continue;
174
                }
175
176
                if (!isset($scheme[$name])) {
177
                    throw new SimpleCrudException(sprintf('Invalid relation: %s', $name));
178
                }
179
180
                $relation = $scheme[$name];
181
182
                if ($relation[0] === Scheme::HAS_ONE) {
183
                    $this->{$relation[1]} = ($this->relations[$name] === null) ? null : $this->relations[$name]->save()->id;
184
                }
185
            }
186
        }
187
188
        if ($this->changed) {
189
            if (empty($this->id)) {
190
                $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...
191
                    ->data($this->values)
192
                    ->run();
193
            } else {
194
                $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...
195
                    ->data($this->values)
196
                    ->byId($this->id)
197
                    ->limit(1)
198
                    ->run();
199
            }
200
201
            $this->table->cache($this);
202
        }
203
204
        if ($relations) {
205
            $scheme = $this->getTable()->getScheme()['relations'];
206
207
            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...
208
                if (!array_key_exists($name, $this->relations)) {
209
                    continue;
210
                }
211
212
                if (!isset($scheme[$name])) {
213
                    throw new SimpleCrudException(sprintf('Invalid relation: %s', $name));
214
                }
215
216
                $relation = $scheme[$name];
217
218
                if ($relation[0] === Scheme::HAS_MANY) {
219
                    foreach ($this->relations[$name] as $row) {
220
                        $row->{$relation[1]} = $this->id;
221
                        $row->save();
222
                    }
223
224
                    continue;
225
                }
226
227
                if ($relation[0] === Scheme::HAS_MANY_TO_MANY) {
228
                    $bridge = $this->getDatabase()->{$relation[1]};
229
230
                    $bridge
231
                        ->delete()
232
                        ->by($relation[2], $this->id)
233
                        ->run();
234
235
                    foreach ($this->relations[$name] as $row) {
236
                        $bridge
237
                            ->insert()
238
                            ->data([
239
                                $relation[2] => $this->id,
240
                                $relation[3] => $row->id,
241
                            ])
242
                            ->run();
243
                    }
244
                }
245
            }
246
        }
247
248
        return $this;
249
    }
250
}
251