Test Failed
Push — main ( 8846ad...255e1c )
by Rafael
05:35
created

Table   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 385
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 385
rs 9.36
c 0
b 0
f 0
wmc 38

18 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 4 1
A getData() 0 12 3
A insertRecord() 0 8 1
A __call() 0 13 3
B save() 0 24 8
A getNameField() 0 3 1
A __set() 0 3 1
A getIdField() 0 3 1
A load() 0 3 1
A updateRecord() 0 4 1
A __get() 0 3 1
A checkStructure() 0 5 4
A __construct() 0 11 1
A getIdByName() 0 13 4
A getAllRecords() 0 4 1
A getStructure() 0 3 1
A getDataArray() 0 6 3
A newRecord() 0 8 2
1
<?php
2
/**
3
 * Alxarafe. Development of PHP applications in a flash!
4
 * Copyright (C) 2018 Alxarafe <[email protected]>
5
 */
6
7
namespace Alxarafe\Core\Base;
8
9
use Alxarafe\Core\Helpers\Utils;
0 ignored issues
show
Bug introduced by
The type Alxarafe\Core\Helpers\Utils was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Alxarafe\Core\Singletons\Config;
11
use Alxarafe\Database\Engine;
12
use Alxarafe\Database\Schema;
13
use DebugBar\DebugBarException;
14
15
/**
16
 * Class Table allows access to a table using an active record.
17
 * It is recommended to create a descendant for each table of the database,
18
 * defining its tablename and structure.
19
 */
20
abstract class Table
21
{
22
    /**
23
     * The database engine.
24
     *
25
     * @var Engine
26
     */
27
    public static Engine $engine;
28
29
    /**
30
     * A Config instance.
31
     *
32
     * @var Config
33
     */
34
    public static Config $config;
35
36
    /**
37
     * It's the name of the table without the database prefix
38
     * @var string
39
     */
40
    public string $name;
41
42
    /**
43
     * It is the name of the table including the database prefix.
44
     *
45
     * @var string
46
     */
47
    public string $tableName;
48
49
    /**
50
     * Value of the main index for the active record.
51
     * When a record is loaded, this field will contain its id and will be the
52
     * one that will be used for in the WHERE clause of the UPDATE.
53
     * If it does not exist in file it will contain ''.
54
     *
55
     * @var string
56
     */
57
    protected string $id;
58
59
    /**
60
     * It is the name of the main id field. By default, 'id'
61
     *
62
     * @var string
63
     */
64
    protected string $idField;
65
66
    /**
67
     * It is the name of the field name. By default 'name'.
68
     * TODO: See if it may not exist, in which case, null or ''?
69
     *
70
     * @var string
71
     */
72
    protected $nameField;
73
74
    /**
75
     * It contains the data previous to the modification of the current record
76
     *
77
     * @var array
78
     */
79
    protected array $oldData;
80
81
    /**
82
     * Contains the new data of the current record.
83
     * It will start when loading a record and will be used when making a save.
84
     *
85
     * @var array
86
     */
87
    protected array $newData;
88
89
    /**
90
     * Build a Table model. $tableName is the name of the table in the database.
91
     * $params is a parameters array:
92
     * - create is true if the table is to be created if it does not exist (false by default)
93
     * - idField is the name of the primary key (default id)
94
     * - nameField is the name of the descriptive field (name by default)
95
     *
96
     * @param string $tableName
97
     * @param array  $params
98
     *
99
     * @throws DebugBarException
100
     */
101
    public function __construct(string $tableName, array $params = [])
102
    {
103
        $this->name = $tableName;
104
        $this->tableName = Engine::getTablename($tableName);
105
        $this->idField = $params['idField'] ?? 'id';
106
        $this->nameField = $params['nameField'] ?? 'name';
107
        $create = $params['create'] ?? false;
108
109
        Schema::checkStructure($tableName, $create);
110
                // $this->setStructure();
111
                $this->checkStructure($create);
112
    }
113
114
    /**
115
     * Create a new table if it does not exist and it has been passed true as a parameter.
116
     *
117
     * This should check if there are differences between the defined in bbddStructure
118
     * and the physical table, correcting the differences if true is passed as parameter.
119
     *
120
     * @param bool $create
121
     *
122
     * @throws DebugBarException
123
     */
124
    public function checkStructure(bool $create = false)
125
    {
126
        if (isset(Schema::$bbddStructure[$this->name])) {
127
            if ($create && !Schema::tableExists($this->name)) {
128
                Schema::createTable($this->name);
129
            }
130
        }
131
    }
132
133
    /**
134
     * Execute a magic method of the setField or getField style
135
     *
136
     * @param string $method
137
     * @param array  $params
138
     *
139
     * @return string
140
     */
141
    public function __call(string $method, array $params): string
142
    {
143
        $command = substr($method, 0, 3); // set or get
144
        $field = Utils::camelToSnake(substr($method, 3)); // What follows set or get
145
        switch ($command) {
146
            case 'set':
147
                return $this->newData[$field] = $params[0] ?? '';
148
            case 'get':
149
                return $this->newData[$field];
150
            default:
151
                dump("Review $method in $this->tableName. Error collecting the '$command/$field' attribute");
152
                dump($params);
153
                throw /** @scrutinizer ignore-call */ Exception('Program halted!');
154
        }
155
    }
156
157
    /**
158
     * It allows access to a field of the record using the attribute.
159
     * To access the name field, we should use $this->getName(), but thanks to
160
     * this, we can also use $this->name.
161
     *
162
     * @param string $property
163
     *
164
     * @return string
165
     */
166
    public function __get(string $property): string
167
    {
168
        return $this->newData[$property] ?? '';
169
    }
170
171
    /**
172
     * Allows you to assign value to a field in the record using the attribute.
173
     * De esta forma simulamos del comportamientod e FacturaScript.
174
     * To assign a value to the name field, we should use $this->setName('Pepe'),
175
     * but thanks to this, we can also use $this->name='Pepe'.
176
     *
177
     * @param string $property
178
     * @param string $value
179
     *
180
     * @return void
181
     */
182
    public function __set(string $property, string $value): void
183
    {
184
        $this->newData[$property] = $value;
185
    }
186
187
    /**
188
     * Returns the name of the main key field of the table (PK-Primary Key). By default, it will be id.
189
     *
190
     * @return string
191
     */
192
    public function getIdField(): string
193
    {
194
        return $this->idField;
195
    }
196
197
    /**
198
     * Returns the name of the identification field of the record. By default, it
199
     * will be "name".
200
     *
201
     * @return string
202
     */
203
    public function getNameField(): string
204
    {
205
        return $this->nameField;
206
    }
207
208
    /**
209
     * Returns a new instance of the table with the requested record.
210
     * As a previous step, a getData of the current instance is made, so both
211
     * will point to the same record.
212
     * Makes a getData and returns a new instance of the model.
213
     *
214
     * @param string $id
215
     *
216
     * @return Table
217
     */
218
    public function get(string $id): Table
219
    {
220
        $this->getData($id);
221
        return $this;
222
    }
223
224
    /**
225
     * This method is private. Use load instead.
226
     * Establishes a record as an active record.
227
     * If found, the $id will be in $this->id and the data in $this->newData.
228
     * If it is not found, $this->id will contain '' and $this->newData will
229
     * contain the data by default.
230
     *
231
     * @param string $id
232
     *
233
     * @return bool
234
     */
235
    private function getData(string $id): bool
236
    {
237
        $sql = "SELECT * FROM $this->tableName WHERE $this->idField='$id'";
238
        $data = self::$engine->select($sql);
239
        if (!isset($data) || count($data) == 0) {
240
            $this->newRecord();
241
            return false;
242
        }
243
        $this->newData = $data[0];
244
        $this->oldData = $this->newData;
245
        $this->id = $this->newData[$this->idField];
246
        return true;
247
    }
248
249
    /**
250
     * Sets the active record in a new record.
251
     * Note that changes made to the current active record will be lost.
252
     */
253
    private function newRecord()
254
    {
255
        $this->id = '';
256
        $this->newData = [];
257
        foreach (Schema::$bbddStructure[$this->tableName]['fields'] as $key => $value) {
258
            $this->newData[$key] = $value['default'] ?? '';
259
        }
260
        $this->oldData = $this->newData;
261
    }
262
263
    /**
264
     * Return an array with the current active record.
265
     * If an $id is indicated, it searches to change the active record before
266
     * returning the value.
267
     * Warning: If an $id is set, any unsaved data will be lost when searching
268
     * for the new record.
269
     *
270
     * @param string|null $id
271
     *
272
     * @return array
273
     */
274
    public function getDataArray(?string $id = null): array
275
    {
276
        if (isset($id) && ($id != $this->id)) {
277
            $this->getData($id);
278
        }
279
        return $this->newData;
280
    }
281
282
    /**
283
     * Establishes a record as an active record.
284
     * If found, the $id will be in $this->id and the data in $this->newData.
285
     * If it is not found, $this->id will contain '' and $this->newData will
286
     * contain the data by default.
287
     *
288
     * @param string $id
289
     *
290
     * @return bool
291
     */
292
    public function load(string $id): bool
293
    {
294
        return $this->getData($id);
295
    }
296
297
    /**
298
     * Saves the changes made to the active record.
299
     *
300
     * @return boolean
301
     */
302
    public function save(): bool
303
    {
304
        // We create separate arrays with the modified fields
305
        $fields = $values = $assigns = [];
306
        foreach ($this->newData as $field => $data) {
307
            // The first condition is to prevent nulls from becoming empty strings
308
            if ((!isset($this->oldData[$field]) && isset($this->newData['field'])) || $this->newData[$field] != $this->oldData[$field]) {
309
                $fields[] = $field;
310
                $values[] = "'$data'";
311
                $assigns[] = "$field='$data'";
312
            }
313
        }
314
315
        // If there are no modifications, we leave without error.
316
        if (count($fields) == 0) {
317
            return true;
318
        }
319
320
        // Insert or update the data as appropriate (insert if $this->id == '')
321
        $ret = ($this->id == '') ? $this->insertRecord($fields, $values) : $this->updateRecord($assigns);
322
        if ($ret) {
323
            $this->oldData = $this->newData;
324
        }
325
        return $ret;
326
    }
327
328
    /**
329
     * Insert a new record.
330
     * $fields is an array of fields and $values an array with the values for
331
     * each field in the same order.
332
     *
333
     * @param array $fields
334
     * @param array $values
335
     *
336
     * @return bool
337
     */
338
    private function insertRecord(array $fields, array $values): bool
339
    {
340
        $fieldList = implode(',', $fields);
341
        $valueList = implode(',', $values);
342
        $ret = self::$engine->exec("INSERT INTO  $this->tableName ($fieldList) VALUES ($valueList)");
343
        // Asigna el valor de la clave primaria del registro recién insertado
344
        $this->id = $this->newData[$this->idField] ?? self::$engine->getLastInserted();
345
        return $ret;
346
    }
347
348
    /**
349
     * Update the modified fields in the active record.
350
     * $data is an array of assignments of type field=value.
351
     *
352
     * @param array $data
353
     *
354
     * @return bool
355
     */
356
    private function updateRecord(array $data): bool
357
    {
358
        $value = implode(',', $data);
359
        return self::$engine->exec("UPDATE $this->tableName SET $value WHERE $this->idField='$this->id'");
360
    }
361
362
    /**
363
     * Returns the structure of the normalized table
364
     *
365
     * @return array
366
     */
367
    public function getStructure(): array
368
    {
369
        return Schema::$bbddStructure[$this->tableName];
370
    }
371
372
    /**
373
     * Get an array with all data
374
     *
375
     * @return array
376
     */
377
    public function getAllRecords(): array
378
    {
379
        $sql = "SELECT * FROM  $this->tableName";
380
        return self::$engine->select($sql);
381
    }
382
383
    /**
384
     * Perform a search of a record by the name, returning the id of the
385
     * corresponding record, or '' if it is not found or does not have a
386
     * name field.
387
     *
388
     * @param string $name
389
     *
390
     * @return string
391
     */
392
    public function getIdByName(string $name): string
393
    {
394
        if ($this->nameField == '') {
395
            return '';
396
        }
397
398
        $sql = "SELECT $this->idField AS id FROM $this->tableName WHERE $this->nameField='$name'";
399
        $data = self::$engine->select($sql);
400
        if (!empty($data) && count($data) > 0) {
401
            return $data[0]['id'];
402
        }
403
404
        return '';
405
    }
406
}
407