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

Table::setCollection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace SimpleCrud;
4
5
use ArrayAccess;
6
7
/**
8
 * Manages a database table.
9
 */
10
class Table implements ArrayAccess
11
{
12
    private $db;
13
    private $row;
14
    private $collection;
15
    private $cache = [];
16
17
    public $name;
18
    public $fields = [];
19
20
    /**
21
     * Constructor.
22
     *
23
     * @param SimpleCrud $db
24
     * @param string     $name
25
     */
26
    final public function __construct(SimpleCrud $db, $name)
27
    {
28
        $this->db = $db;
29
        $this->name = $name;
30
31
        $this->setRow(new Row($this));
32
        $this->setCollection(new RowCollection($this));
33
34
        $fieldFactory = $db->getFieldFactory();
35
36
        foreach (array_keys($this->getScheme()['fields']) as $name) {
37
            $this->fields[$name] = $fieldFactory->get($this, $name);
38
        }
39
40
        $this->init();
41
    }
42
43
    /**
44
     * Debug info.
45
     * 
46
     * @return array
47
     */
48
    public function __debugInfo()
49
    {
50
        return [
51
            'name' => $this->name,
52
            'fields' => $this->fields,
53
        ];
54
    }
55
56
    /**
57
     * Callback used to init the table.
58
     */
59
    protected function init()
60
    {
61
    }
62
63
    /**
64
     * Store a row in the cache.
65
     * 
66
     * @param int $id
0 ignored issues
show
Bug introduced by
There is no parameter named $id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
67
     * @param Row $Row
0 ignored issues
show
Bug introduced by
There is no parameter named $Row. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
68
     */
69
    public function cache(Row $row)
70
    {
71
        $this->cache[$row->id] = $row;
72
    }
73
74
    /**
75
     * Clear the current cache.
76
     */
77
    public function clearCache()
78
    {
79
        $this->cache = [];
80
    }
81
82
    /**
83
     * Magic method to create queries related with this table.
84
     *
85
     * @param string $name
86
     * @param array  $arguments
87
     *
88
     * @throws SimpleCrudException
89
     *
90
     * @return Queries\Query|null
91
     */
92
    public function __call($name, $arguments)
93
    {
94
        return $this->getDatabase()->getQueryFactory()->get($this, $name);
95
    }
96
97
    /**
98
     * Check if a row with a specific id exists.
99
     *
100
     * @see ArrayAccess
101
     *
102
     * @return bool
103
     */
104
    public function offsetExists($offset)
105
    {
106
        if (array_key_exists($offset, $this->cache)) {
107
            return !empty($this->cache[$offset]);
108
        }
109
110
        return $this->count()
0 ignored issues
show
Documentation Bug introduced by
The method count 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...
111
            ->byId($offset)
112
            ->limit(1)
113
            ->run() === 1;
114
    }
115
116
    /**
117
     * Returns a row with a specific id.
118
     *
119
     * @see ArrayAccess
120
     *
121
     * @return Row|null
122
     */
123
    public function offsetGet($offset)
124
    {
125
        if (array_key_exists($offset, $this->cache)) {
126
            return $this->cache[$offset];
127
        }
128
129
        return $this->cache[$offset] = $this->select()
0 ignored issues
show
Documentation Bug introduced by
The method select 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...
130
            ->one()
131
            ->byId($offset)
132
            ->run();
133
    }
134
135
    /**
136
     * Store a row with a specific id.
137
     *
138
     * @see ArrayAccess
139
     */
140
    public function offsetSet($offset, $value)
141
    {
142
        //Insert on missing offset
143
        if ($offset === null) {
144
            $value['id'] = null;
145
146
            $this->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...
147
                ->data($value)
148
                ->run();
149
150
            return;
151
        }
152
153
        //Update if the element is cached
154
        if (isset($this->cache[$offset])) {
155
            $row = $this->cache[$offset];
156
157
            foreach ($value as $name => $val) {
158
                $row->$name = $val;
159
            }
160
161
            $row->save();
162
163
            return;
164
        }
165
166
        //Update if the element it's not cached
167
        if ($this->offsetExists($offset)) {
168
            $this->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...
169
                ->data($value)
170
                ->byId($offset)
171
                ->limit(1)
172
                ->run();
173
        }
174
    }
175
176
    /**
177
     * Remove a row with a specific id.
178
     *
179
     * @see ArrayAccess
180
     */
181
    public function offsetUnset($offset)
182
    {
183
        unset($this->cache[$offset]);
184
185
        $this->delete()
0 ignored issues
show
Documentation Bug introduced by
The method delete 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
            ->byId($offset)
187
            ->limit(1)
188
            ->run();
189
    }
190
191
    /**
192
     * Returns the SimpleCrud instance associated with this table.
193
     *
194
     * @return SimpleCrud
195
     */
196
    public function getDatabase()
197
    {
198
        return $this->db;
199
    }
200
201
    /**
202
     * Returns the table scheme.
203
     *
204
     * @return array
205
     */
206
    public function getScheme()
207
    {
208
        return $this->db->getScheme()[$this->name];
209
    }
210
211
    /**
212
     * Returns an attribute.
213
     *
214
     * @param string $name
215
     *
216
     * @return null|mixed
217
     */
218
    public function getAttribute($name)
219
    {
220
        return $this->db->getAttribute($name);
221
    }
222
223
    /**
224
     * Defines the Row class used by this table.
225
     *
226
     * @param Row $row
227
     */
228
    public function setRow(Row $row)
229
    {
230
        $this->row = $row;
231
    }
232
233
    /**
234
     * Defines the RowCollection class used by this table.
235
     *
236
     * @param RowCollection $collection
237
     */
238
    public function setCollection(RowCollection $collection)
239
    {
240
        $this->collection = $collection;
241
    }
242
243
    /**
244
     * Creates a new row instance.
245
     *
246
     * @param array $data The values of the row
247
     *
248
     * @return Row
249
     */
250
    public function create(array $data = [])
251
    {
252 View Code Duplication
        if (isset($data['id']) && isset($this->cache[$data['id']]) && is_object($this->cache[$data['id']])) {
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...
253
            return $this->cache[$data['id']];
254
        }
255
256
        $row = clone $this->row;
257
258
        foreach ($data as $name => $value) {
259
            $row->$name = $value;
260
        }
261
262
        return $row;
263
    }
264
265
    /**
266
     * Creates a new rowCollection instance.
267
     *
268
     * @param array $data Rows added to this collection
269
     *
270
     * @return RowCollection
271
     */
272
    public function createCollection(array $data = [])
273
    {
274
        $collection = clone $this->collection;
275
276
        foreach ($data as $row) {
277
            $collection[] = $row;
278
        }
279
280
        return $collection;
281
    }
282
283
    /**
284
     * Default data converter/validator from database.
285
     *
286
     * @param array $data The values before insert to database
287
     * @param bool  $new  True for inserts, false for updates
288
     */
289
    public function dataToDatabase(array $data, $new)
0 ignored issues
show
Unused Code introduced by
The parameter $new 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...
290
    {
291
        return $data;
292
    }
293
294
    /**
295
     * Default data converter from database.
296
     *
297
     * @param array $data The database format values
298
     */
299
    public function dataFromDatabase(array $data)
300
    {
301
        return $data;
302
    }
303
304
    /**
305
     * Prepares the data from the result of a database selection.
306
     *
307
     * @param array $data
308
     *
309
     * @return array
310
     */
311
    public function createFromDatabase(array $data)
312
    {
313
        //Get from cache
314 View Code Duplication
        if (isset($this->cache[$data['id']]) && is_object(isset($this->cache[$data['id']]))) {
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...
315
            return $this->cache[$data['id']];
316
        }
317
318
        if (!is_array($row = $this->dataFromDatabase(array_intersect_key($data, $this->fields)))) {
319
            throw new SimpleCrudException('Data not valid');
320
        }
321
322
        foreach ($this->fields as $name => $field) {
323
            $row[$name] = $field->dataFromDatabase($row[$name]);
324
        }
325
326
        $row = $this->create($row);
327
328
        $this->cache($row);
329
330
        return $row;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $row; (SimpleCrud\Row) is incompatible with the return type documented by SimpleCrud\Table::createFromDatabase of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
331
    }
332
333
    /**
334
     * Prepares the data before save into database (used by update and insert).
335
     *
336
     * @param array $data
337
     * @param bool  $new
338
     *
339
     * @return array
340
     */
341
    public function prepareDataToDatabase(array $data, $new)
342
    {
343
        if (!is_array($data = $this->dataToDatabase($data, $new))) {
344
            throw new SimpleCrudException('Data not valid');
345
        }
346
347
        if (array_diff_key($data, $this->fields)) {
348
            throw new SimpleCrudException('Invalid fields');
349
        }
350
351
        //Transform data before save to database
352
        foreach ($data as $key => &$value) {
353
            $value = $this->fields[$key]->dataToDatabase($value);
354
        }
355
356
        return $data;
357
    }
358
}
359