Test Failed
Push — main ( e26c51...cb447d )
by Rafael
10:44
created

Table::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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