Completed
Push — master ( 997648...3e9ab2 )
by Oscar
01:59
created

Table::getFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace SimpleCrud;
4
5
use ArrayAccess;
6
use Closure;
7
8
/**
9
 * Manages a database table.
10
 *
11
 * @property Fields\Field $id
12
 */
13
class Table implements ArrayAccess
14
{
15
    private $name;
16
    private $db;
17
    private $row;
18
    private $rowCollection;
19
    private $cache = [];
20
    private $queriesModifiers = [];
21
    private $fields = [];
22
23
    /**
24
     * Constructor.
25
     *
26
     * @param SimpleCrud $db
27
     * @param string     $name
28
     */
29
    final public function __construct(SimpleCrud $db, $name)
30
    {
31
        $this->db = $db;
32
        $this->name = $name;
33
34
        $this->setRow(new Row($this));
35
        $this->setRowCollection(new RowCollection($this));
36
37
        $fieldFactory = $db->getFieldFactory();
38
39
        foreach (array_keys($this->getScheme()['fields']) as $name) {
40
            $this->fields[$name] = $fieldFactory->get($this, $name);
41
        }
42
43
        $this->init();
44
    }
45
46
    /**
47
     * Debug info.
48
     * 
49
     * @return array
50
     */
51
    public function __debugInfo()
52
    {
53
        return [
54
            'name' => $this->name,
55
            'fields' => $this->fields,
56
        ];
57
    }
58
59
    /**
60
     * Callback used to init the table.
61
     */
62
    protected function init()
63
    {
64
    }
65
66
    /**
67
     * Store a row in the cache.
68
     * 
69
     * @param Row $row
70
     */
71
    public function cache(Row $row)
72
    {
73
        $this->cache[$row->id] = $row;
74
    }
75
76
    /**
77
     * Clear the current cache.
78
     */
79
    public function clearCache()
80
    {
81
        $this->cache = [];
82
    }
83
84
    /**
85
     * Register a new query modifier.
86
     * 
87
     * @param string   $name
88
     * @param callable $modifier
89
     */
90
    public function addQueryModifier($name, callable $modifier)
91
    {
92
        if (!isset($this->queriesModifiers[$name])) {
93
            $this->queriesModifiers[$name] = [];
94
        }
95
96
        $this->queriesModifiers[$name][] = $modifier;
97
    }
98
99
    /**
100
     * Magic method to create queries related with this table.
101
     *
102
     * @param string $name
103
     * @param array  $arguments
104
     *
105
     * @throws SimpleCrudException
106
     *
107
     * @return Queries\Query|null
108
     */
109
    public function __call($name, $arguments)
110
    {
111
        $query = $this->getDatabase()->getQueryFactory()->get($this, $name);
112
113
        if (isset($this->queriesModifiers[$name])) {
114
            foreach ($this->queriesModifiers[$name] as $modifier) {
115
                $modifier($query);
116
            }
117
        }
118
119
        return $query;
120
    }
121
122
    /**
123
     * Magic method to get the Field instance of a table field
124
     *
125
     * @param string $name The field name
126
     *
127
     * @throws SimpleCrudException
128
     *
129
     * @return Fields\Field
130
     */
131
    public function __get($name)
132
    {
133
        if (!isset($this->fields[$name])) {
134
            throw new SimpleCrudException(sprintf('The field "%s" does not exist in the table "%s"', $name, $this->name));
135
        }
136
137
        return $this->fields[$name];
138
    }
139
140
    /**
141
     * Magic method to check if a field exists or not.
142
     *
143
     * @param string $name
144
     *
145
     * @return bool
146
     */
147
    public function __isset($name)
148
    {
149
        return isset($this->fields[$name]);
150
    }
151
152
    /**
153
     * Check if a row with a specific id exists.
154
     *
155
     * @see ArrayAccess
156
     *
157
     * @return bool
158
     */
159
    public function offsetExists($offset)
160
    {
161
        if (array_key_exists($offset, $this->cache)) {
162
            return !empty($this->cache[$offset]);
163
        }
164
165
        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...
166
            ->byId($offset)
167
            ->limit(1)
168
            ->run() === 1;
169
    }
170
171
    /**
172
     * Returns a row with a specific id.
173
     *
174
     * @see ArrayAccess
175
     *
176
     * @return Row|null
177
     */
178
    public function offsetGet($offset)
179
    {
180
        if (array_key_exists($offset, $this->cache)) {
181
            return $this->cache[$offset];
182
        }
183
184
        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...
185
            ->one()
186
            ->byId($offset)
187
            ->run();
188
    }
189
190
    /**
191
     * Store a row with a specific id.
192
     *
193
     * @see ArrayAccess
194
     */
195
    public function offsetSet($offset, $value)
196
    {
197
        //Insert on missing offset
198
        if ($offset === null) {
199
            $value['id'] = null;
200
201
            $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...
202
                ->data($value)
203
                ->run();
204
205
            return;
206
        }
207
208
        //Update if the element is cached
209
        if (isset($this->cache[$offset])) {
210
            $row = $this->cache[$offset];
211
212
            foreach ($value as $name => $val) {
213
                $row->$name = $val;
214
            }
215
216
            $row->save();
217
218
            return;
219
        }
220
221
        //Update if the element it's not cached
222
        if ($this->offsetExists($offset)) {
223
            $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...
224
                ->data($value)
225
                ->byId($offset)
226
                ->limit(1)
227
                ->run();
228
        }
229
    }
230
231
    /**
232
     * Remove a row with a specific id.
233
     *
234
     * @see ArrayAccess
235
     */
236
    public function offsetUnset($offset)
237
    {
238
        unset($this->cache[$offset]);
239
240
        $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...
241
            ->byId($offset)
242
            ->limit(1)
243
            ->run();
244
    }
245
246
    /**
247
     * Returns the table name.
248
     *
249
     * @return string
250
     */
251
    public function getName()
252
    {
253
        return $this->name;
254
    }
255
256
    /**
257
     * Returns the SimpleCrud instance associated with this table.
258
     *
259
     * @return SimpleCrud
260
     */
261
    public function getDatabase()
262
    {
263
        return $this->db;
264
    }
265
266
    /**
267
     * Returns all fields.
268
     *
269
     * @return Fields\Field[]
270
     */
271
    public function getFields()
272
    {
273
        return $this->fields;
274
    }
275
276
    /**
277
     * Returns the table scheme.
278
     *
279
     * @return array
280
     */
281
    public function getScheme()
282
    {
283
        return $this->db->getScheme()[$this->name];
284
    }
285
286
    /**
287
     * Returns an attribute.
288
     *
289
     * @param string $name
290
     *
291
     * @return null|mixed
292
     */
293
    public function getAttribute($name)
294
    {
295
        return $this->db->getAttribute($name);
296
    }
297
298
    /**
299
     * Defines the Row class used by this table.
300
     *
301
     * @param Row $row
302
     */
303
    public function setRow(Row $row)
304
    {
305
        $this->row = $row;
306
    }
307
308
    /**
309
     * Register a custom method to the row.
310
     *
311
     * @param string  $name
312
     * @param Closure $method
313
     *
314
     * @return self
315
     */
316
    public function setRowMethod($name, Closure $method)
317
    {
318
        $this->row->setMethod($name, $method);
319
320
        return $this;
321
    }
322
323
    /**
324
     * Defines the RowCollection class used by this table.
325
     *
326
     * @param RowCollection $rowCollection
327
     */
328
    public function setRowCollection(RowCollection $rowCollection)
329
    {
330
        $this->rowCollection = $rowCollection;
331
    }
332
333
    /**
334
     * Register a custom method to the rowCollections.
335
     *
336
     * @param string  $name
337
     * @param Closure $method
338
     *
339
     * @return self
340
     */
341
    public function setRowCollectionMethod($name, Closure $method)
342
    {
343
        $this->rowCollection->setMethod($name, $method);
344
345
        return $this;
346
    }
347
348
    /**
349
     * Creates a new row instance.
350
     *
351
     * @param array $data The values of the row
352
     *
353
     * @return Row
354
     */
355
    public function create(array $data = [])
356
    {
357 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...
358
            return $this->cache[$data['id']];
359
        }
360
361
        $row = clone $this->row;
362
363
        foreach ($data as $name => $value) {
364
            $row->$name = $value;
365
        }
366
367
        return $row;
368
    }
369
370
    /**
371
     * Creates a new rowCollection instance.
372
     *
373
     * @param array $data Rows added to this collection
374
     *
375
     * @return RowCollection
376
     */
377
    public function createCollection(array $data = [])
378
    {
379
        $rowCollection = clone $this->rowCollection;
380
381
        foreach ($data as $row) {
382
            $rowCollection[] = $row;
383
        }
384
385
        return $rowCollection;
386
    }
387
388
    /**
389
     * Default data converter/validator from database.
390
     *
391
     * @param array $data The values before insert to database
392
     * @param bool  $new  True for inserts, false for updates
393
     */
394
    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...
395
    {
396
        return $data;
397
    }
398
399
    /**
400
     * Default data converter from database.
401
     *
402
     * @param array $data The database format values
403
     */
404
    public function dataFromDatabase(array $data)
405
    {
406
        return $data;
407
    }
408
409
    /**
410
     * Prepares the data from the result of a database selection.
411
     *
412
     * @param array $data
413
     *
414
     * @return Row
415
     */
416
    public function createFromDatabase(array $data)
417
    {
418
        //Get from cache
419 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...
420
            return $this->cache[$data['id']];
421
        }
422
423
        foreach ($this->fields as $name => $field) {
424
            $data[$name] = $field->dataFromDatabase($data[$name]);
425
        }
426
427
        if (!is_array($data = $this->dataFromDatabase(array_intersect_key($data, $this->fields)))) {
428
            throw new SimpleCrudException('Data not valid');
429
        }
430
431
        $row = $this->create($data);
432
433
        $this->cache($row);
434
435
        return $row;
436
    }
437
438
    /**
439
     * Prepares the data before save into database (used by update and insert).
440
     *
441
     * @param array $data
442
     * @param bool  $new
443
     *
444
     * @return array
445
     */
446
    public function prepareDataToDatabase(array $data, $new)
447
    {
448
        if (!is_array($data = $this->dataToDatabase($data, $new))) {
449
            throw new SimpleCrudException('Data not valid');
450
        }
451
452
        if (array_diff_key($data, $this->fields)) {
453
            throw new SimpleCrudException('Invalid fields');
454
        }
455
456
        //Transform data before save to database
457
        foreach ($data as $key => &$value) {
458
            $value = $this->fields[$key]->dataToDatabase($value);
459
        }
460
461
        return $data;
462
    }
463
}
464