Test Failed
Push — master ( d56733...42dbbd )
by Carlos
22:26 queued 14:13
created

JoinModel::getModelFields()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 16
c 1
b 0
f 0
nc 6
nop 0
dl 0
loc 33
rs 8.8333
1
<?php
2
/**
3
 * This file is part of FacturaScripts
4
 * Copyright (C) 2017-2024 Carlos Garcia Gomez <[email protected]>
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
namespace FacturaScripts\Core\Model\Base;
21
22
use FacturaScripts\Core\Base\DataBase;
23
use FacturaScripts\Core\Base\DataBase\DataBaseWhere;
24
use FacturaScripts\Core\Base\ToolBox;
25
use FacturaScripts\Core\Cache;
26
27
/**
28
 * The class from which all views of the model are inherited.
29
 * It allows the visualization of data of several tables of the database.
30
 * This type of model is only for reading data, it does not allow modification
31
 * or deletion of data directly.
32
 *
33
 * A main model ("master") must be indicated, which will be responsible for executing
34
 * the data modification actions. This means that when inserting, modifying or deleting,
35
 * only the operation on the indicated master model is performed.
36
 *
37
 * @author Jose Antonio Cuello Principal    <[email protected]>
38
 * @author Carlos García Gómez              <[email protected]>
39
 */
40
abstract class JoinModel
41
{
42
    /**
43
     * It provides direct access to the database.
44
     *
45
     * @var DataBase
46
     */
47
    protected static $dataBase;
48
49
    /**
50
     * Master model
51
     *
52
     * @var ModelClass
53
     */
54
    protected $masterModel;
55
56
    /**
57
     * List of values for record view
58
     *
59
     * @var array
60
     */
61
    private $values = [];
62
63
    /**
64
     * List of tables required for the execution of the view.
65
     */
66
    abstract protected function getTables(): array;
67
68
    /**
69
     * List of fields or columns to select clausule
70
     */
71
    abstract protected function getFields(): array;
72
73
    /**
74
     * List of tables related to from clausule
75
     */
76
    abstract protected function getSQLFrom(): string;
77
78
    /**
79
     * Constructor and class initializer.
80
     *
81
     * @param array $data
82
     */
83
    public function __construct(array $data = [])
84
    {
85
        if (self::$dataBase === null) {
86
            self::$dataBase = new DataBase();
87
        }
88
89
        if (empty($data)) {
90
            $this->clear();
91
        } else {
92
            $this->loadFromData($data);
93
        }
94
    }
95
96
    /**
97
     * Return model view field value
98
     *
99
     * @param string $name
100
     *
101
     * @return mixed
102
     */
103
    public function __get($name)
104
    {
105
        if (!isset($this->values[$name])) {
106
            $this->values[$name] = null;
107
        }
108
109
        return $this->values[$name];
110
    }
111
112
    /**
113
     * Check if exits value to property
114
     *
115
     * @param string $name
116
     *
117
     * @return bool
118
     */
119
    public function __isset($name)
120
    {
121
        return array_key_exists($name, $this->values);
122
    }
123
124
    /**
125
     * Set value to model view field
126
     *
127
     * @param string $name
128
     * @param mixed $value
129
     */
130
    public function __set($name, $value)
131
    {
132
        $this->values[$name] = $value;
133
    }
134
135
    /**
136
     * Load data for the indicated where.
137
     *
138
     * @param DataBaseWhere[] $where filters to apply to model records.
139
     * @param array $order fields to use in the sorting. For example ['code' => 'ASC']
140
     * @param int $offset
141
     * @param int $limit
142
     *
143
     * @return static[]
144
     */
145
    public function all(array $where, array $order = [], int $offset = 0, int $limit = 0): array
146
    {
147
        $result = [];
148
        if ($this->checkTables()) {
149
            $sql = 'SELECT ' . $this->fieldsList() . ' FROM ' . $this->getSQLFrom()
150
                . DataBaseWhere::getSQLWhere($where) . $this->getGroupBy() . $this->getOrderBy($order);
151
            foreach (self::$dataBase->selectLimit($sql, $limit, $offset) as $row) {
152
                $result[] = new static($row);
153
            }
154
        }
155
156
        return $result;
157
    }
158
159
    /**
160
     * Reset the values of all model properties.
161
     */
162
    public function clear()
163
    {
164
        foreach (array_keys($this->getFields()) as $field) {
165
            $this->values[$field] = null;
166
        }
167
    }
168
169
    /**
170
     * Returns the number of records that meet the condition.
171
     *
172
     * @param DataBaseWhere[] $where filters to apply to records.
173
     *
174
     * @return int
175
     */
176
    public function count(array $where = []): int
177
    {
178
        $groupFields = $this->getGroupFields();
179
        if (!empty($groupFields)) {
180
            $groupFields .= ', ';
181
        }
182
183
        // buscamos en caché
184
        $cacheKey = 'join-model-' . md5($this->getSQLFrom()) . '-count';
185
        if (empty($where)) {
186
            $count = Cache::get($cacheKey);
187
            if (is_numeric($count)) {
188
                return $count;
189
            }
190
        }
191
192
        $sql = 'SELECT ' . $groupFields . 'COUNT(*) count_total'
193
            . ' FROM ' . $this->getSQLFrom()
194
            . DataBaseWhere::getSQLWhere($where)
195
            . $this->getGroupBy();
196
197
        $data = self::$dataBase->select($sql);
198
        $count = count($data);
199
        $final = $count == 1 ? (int)$data[0]['count_total'] : $count;
200
201
        // guardamos en caché
202
        if (empty($where)) {
203
            Cache::set($cacheKey, $final);
204
        }
205
206
        return $final;
207
    }
208
209
    /**
210
     * Remove the model master data from the database.
211
     *
212
     * @return bool
213
     */
214
    public function delete(): bool
215
    {
216
        if (isset($this->masterModel)) {
217
            $primaryColumn = $this->masterModel->primaryColumn();
218
            $this->masterModel->{$primaryColumn} = $this->primaryColumnValue();
219
            return $this->masterModel->delete();
220
        }
221
222
        return false;
223
    }
224
225
    /**
226
     * Returns true if the model data is stored in the database.
227
     *
228
     * @return bool
229
     */
230
    public function exists(): bool
231
    {
232
        return isset($this->masterModel) ? $this->masterModel->exists() : $this->count() > 0;
233
    }
234
235
    public function getModelFields(): array
236
    {
237
        $fields = [];
238
        foreach ($this->getFields() as $key => $field) {
239
            $fields[$key] = [
240
                'name' => $field,
241
                'type' => ''
242
            ];
243
244
            // si contiene paréntesis, saltamos
245
            if (false !== strpos($field, '(')) {
246
                continue;
247
            }
248
249
            // extraemos el nombre de la tabla
250
            $arrayField = explode('.', $field);
251
            if (false === is_array($arrayField) && false === isset($arrayField[0])) {
252
                continue;
253
            }
254
255
            // comprobamos si existe la tabla
256
            if (false === in_array($arrayField[0], $this->getTables())) {
257
                continue;
258
            }
259
260
            // consultamos la información de la tabla para obtener el tipo
261
            $columns = self::$dataBase->getColumns($arrayField[0]);
262
            if (isset($columns[$arrayField[1]])) {
263
                $fields[$key]['type'] = $columns[$arrayField[1]]['type'];
264
            }
265
        }
266
267
        return $fields;
268
    }
269
270
    /**
271
     * Fill the class with the registry values
272
     * whose primary column corresponds to the value $cod, or according to the condition
273
     * where indicated, if value is not reported in $cod.
274
     * Initializes the values of the class if there is no record that
275
     * meet the above conditions.
276
     * Returns True if the record exists and False otherwise.
277
     *
278
     * @param string $cod
279
     * @param array $where
280
     * @param array $orderby
281
     *
282
     * @return bool
283
     */
284
    public function loadFromCode($cod, array $where = [], array $orderby = []): bool
285
    {
286
        if (!$this->loadFilterWhere($cod, $where)) {
287
            $this->clear();
288
            return false;
289
        }
290
291
        $sql = 'SELECT ' . $this->fieldsList()
292
            . ' FROM ' . $this->getSQLFrom()
293
            . DataBaseWhere::getSQLWhere($where)
294
            . $this->getGroupBy()
295
            . $this->getOrderBy($orderby);
296
297
        $data = self::$dataBase->selectLimit($sql, 1);
298
        if (empty($data)) {
299
            $this->clear();
300
            return false;
301
        }
302
303
        $this->loadFromData($data[0]);
304
        return true;
305
    }
306
307
    /**
308
     * Gets the value from model view cursor of the master model primary key.
309
     *
310
     * @return mixed
311
     */
312
    public function primaryColumnValue()
313
    {
314
        if (isset($this->masterModel)) {
315
            $primaryColumn = $this->masterModel->primaryColumn();
316
            return $this->{$primaryColumn};
317
        }
318
319
        return null;
320
    }
321
322
    public function totalSum(string $field, array $where = []): float
323
    {
324
        // buscamos en caché
325
        $cacheKey = 'join-model-' . md5($this->getSQLFrom()) . '-' . $field . '-total-sum';
326
        if (empty($where)) {
327
            $count = Cache::get($cacheKey);
328
            if (is_numeric($count)) {
329
                return $count;
330
            }
331
        }
332
333
        // obtenemos el nombre completo del campo
334
        $fields = $this->getFields();
335
        $field = $fields[$field] ?? $field;
336
337
        $sql = false !== strpos($field, '(') ?
338
            'SELECT ' . $field . ' AS total_sum' . ' FROM ' . $this->getSQLFrom() . DataBaseWhere::getSQLWhere($where) :
339
            'SELECT SUM(' . $field . ') AS total_sum' . ' FROM ' . $this->getSQLFrom() . DataBaseWhere::getSQLWhere($where);
340
341
        $data = self::$dataBase->select($sql);
342
        $sum = count($data) == 1 ? (float)$data[0]['total_sum'] : 0.0;
343
344
        // guardamos en caché
345
        if (empty($where)) {
346
            Cache::set($cacheKey, $sum);
347
        }
348
349
        return $sum;
350
    }
351
352
    /**
353
     * Returns the url where to see / modify the data.
354
     *
355
     * @param string $type
356
     * @param string $list
357
     *
358
     * @return string
359
     */
360
    public function url(string $type = 'auto', string $list = 'List'): string
361
    {
362
        if (isset($this->masterModel)) {
363
            $primaryColumn = $this->masterModel->primaryColumn();
364
            $this->masterModel->{$primaryColumn} = $this->primaryColumnValue();
365
            return $this->masterModel->url($type, $list);
366
        }
367
368
        return '';
369
    }
370
371
    /**
372
     * Check list of tables required.
373
     *
374
     * @return bool
375
     */
376
    private function checkTables(): bool
377
    {
378
        foreach ($this->getTables() as $tableName) {
379
            if (!self::$dataBase->tableExists($tableName)) {
380
                return false;
381
            }
382
        }
383
384
        return true;
385
    }
386
387
    /**
388
     * Convert the list of fields into a string to use as a select clause
389
     *
390
     * @return string
391
     */
392
    private function fieldsList(): string
393
    {
394
        $result = '';
395
        $comma = '';
396
        foreach ($this->getFields() as $key => $value) {
397
            $result = $result . $comma . $value . ' ' . $key;
398
            $comma = ',';
399
        }
400
        return $result;
401
    }
402
403
    /**
404
     * Return Group By clausule
405
     *
406
     * @return string
407
     */
408
    private function getGroupBy(): string
409
    {
410
        $fields = $this->getGroupFields();
411
        return empty($fields) ? '' : ' GROUP BY ' . $fields;
412
    }
413
414
    /**
415
     * Return Group By fields
416
     *
417
     * @return string
418
     */
419
    protected function getGroupFields(): string
420
    {
421
        return '';
422
    }
423
424
    /**
425
     * Convert an array of filters order by in string.
426
     *
427
     * @param array $order
428
     *
429
     * @return string
430
     */
431
    private function getOrderBy(array $order): string
432
    {
433
        $result = '';
434
        $coma = ' ORDER BY ';
435
        foreach ($order as $key => $value) {
436
            $result .= $coma . $key . ' ' . $value;
437
            $coma = ', ';
438
        }
439
        return $result;
440
    }
441
442
    /**
443
     * If a value is reported for the PK create a database where for
444
     * the master key of the master model.
445
     *
446
     * @param string $cod
447
     * @param array $where
448
     *
449
     * @return bool
450
     */
451
    private function loadFilterWhere($cod, array &$where): bool
452
    {
453
        // If there is no search by code we use the where informed
454
        if (empty($cod)) {
455
            return true;
456
        }
457
458
        // If dont define master model cant load from code
459
        if (!isset($this->masterModel)) {
460
            return false;
461
        }
462
463
        // Search primary key from field list
464
        $primaryColumn = $this->masterModel->primaryColumn();
465
        foreach ($this->getFields() as $field => $sqlField) {
466
            if ($field == $primaryColumn) {
467
                $where = [new DataBaseWhere($sqlField, $cod)];
468
                return true;
469
            }
470
        }
471
472
        // The PK field is not defined in the field list. No posible search by PK
473
        return false;
474
    }
475
476
    /**
477
     * Assign the values of the $data array to the model view properties.
478
     *
479
     * @param array $data
480
     */
481
    protected function loadFromData(array $data)
482
    {
483
        foreach ($data as $field => $value) {
484
            $this->values[$field] = $value;
485
        }
486
    }
487
488
    /**
489
     * Sets the master model for data operations
490
     *
491
     * @param ModelClass $model
492
     */
493
    protected function setMasterModel($model)
494
    {
495
        $this->masterModel = $model;
496
    }
497
498
    /**
499
     * @return ToolBox
500
     * @deprecated since version 2023.1
501
     */
502
    protected function toolBox(): ToolBox
503
    {
504
        return new ToolBox();
0 ignored issues
show
Deprecated Code introduced by
The class FacturaScripts\Core\Base\ToolBox has been deprecated: since version 2024.5 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

504
        return /** @scrutinizer ignore-deprecated */ new ToolBox();
Loading history...
505
    }
506
}
507