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

Table::prepareDataToDatabase()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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