Test Failed
Push — master ( 39a95c...bc6be4 )
by Rafael
03:44
created

Table   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Test Coverage

Coverage 37.5%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 109
c 3
b 0
f 0
dl 0
loc 271
ccs 45
cts 120
cp 0.375
rs 6.4799
wmc 54

12 Methods

Rating   Name   Duplication   Size   Complexity  
A checkStructure() 0 6 3
A __construct() 0 7 1
A getIdByName() 0 14 4
A getAllRecordsBy() 0 15 4
A getDefaultValues() 0 7 2
A getChecksFromTable() 0 3 1
A getIndexesFromTable() 0 3 1
A saveRecord() 0 6 2
C testData() 0 42 17
C getDefaultValue() 0 35 15
A getAllKeyValue() 0 9 2
A saveData() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Table, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Alxarafe. Development of PHP applications in a flash!
4
 * Copyright (C) 2018-2019 Alxarafe <[email protected]>
5
 */
6
7
namespace Alxarafe\Core\Base;
8
9
use Alxarafe\Core\Helpers\FormatUtils;
10
use Alxarafe\Core\Helpers\Schema;
11
use Alxarafe\Core\Helpers\SchemaDB;
12
use Alxarafe\Core\Providers\Database;
13
use Alxarafe\Core\Providers\FlashMessages;
14
use Alxarafe\Core\Providers\Translator;
15
16
/**
17
 * Class Table allows access to a table using an active record.
18
 * It is recommended to create a descendant for each table of the database, defining its tablename and structure.
19
 *
20
 * @property string $locked This field can exist or not (added here to avoid scrutinizer "property not exists")
21
 *
22
 * @package Alxarafe\Core\Base
23
 */
24
class Table extends SimpleTable
25
{
26
    /**
27
     * Build a Table model. $table is the name of the table in the database.
28
     * $params is a parameters array:
29
     * - create is true if the table is to be created if it does not exist (false by default)
30
     * - idField is the name of the primary key (default id)
31
     * - nameField is the name of the descriptive field (name by default)
32
     *
33
     * @param string $tableName
34
     * @param array  $params
35
     */
36 33
    public function __construct(string $tableName, array $params = [])
37
    {
38 33
        parent::__construct($tableName, $params);
39 33
        $this->debugTool->startTimer($this->shortName, $this->shortName . ' Table Constructor');
40 33
        $create = $params['create'] ?? false;
41 33
        $this->checkStructure($create);
42 33
        $this->debugTool->stopTimer($this->shortName);
43 33
    }
44
45
    /**
46
     * Create a new table if it does not exist and it has been passed true as a parameter.
47
     *
48
     * This should check if there are differences between the defined in dbStructure and the physical table,
49
     * correcting the differences if true is passed as parameter.
50
     *
51
     * @param bool $create
52
     */
53 33
    public function checkStructure(bool $create = false): void
54
    {
55 33
        if (!$create || !Database::getInstance()->getDbEngine()->issetDbTableStructure($this->tableName)) {
56 1
            return;
57
        }
58 33
        SchemaDB::checkTableStructure($this->tableName);
59 33
    }
60
61
    /**
62
     * Perform a search of a record by the name, returning the id of the corresponding record, or '' if it is not found
63
     * or does not have a name field.
64
     *
65
     * @param string $name
66
     *
67
     * @return string
68
     */
69
    public function getIdByName(string $name): string
70
    {
71
        if ($this->nameField === '') {
72
            return '';
73
        }
74
75
        $nameField = Database::getInstance()->getSqlHelper()->quoteFieldName($this->nameField);
76
        $sql = "SELECT {$this->idField} AS id FROM {$this->getQuotedTableName()} WHERE {$nameField} = :name;";
77
        $data = Database::getInstance()->getDbEngine()->select($sql, ['name' => $name]);
78
        if (!empty($data) && count($data) > 0) {
79
            return $data[0]['id'];
80
        }
81
82
        return '';
83
    }
84
85
    /**
86
     * Get an array with all data.
87
     *
88
     * @param string $key
89
     * @param mixed  $value
90
     * @param string $comparison By default is '='
91
     * @param string $orderBy
92
     *
93
     * @return array
94
     */
95 70
    public function getAllRecordsBy(string $key, $value, string $comparison = '=', string $orderBy = ''): array
96
    {
97 70
        $fieldName = Database::getInstance()->getSqlHelper()->quoteFieldName($key);
98 70
        if (!empty($orderBy)) {
99 61
            $orderBy = " ORDER BY {$orderBy}";
100
        }
101 70
        if ($value === 'NULL') {
102 61
            $isNull = $comparison === '=' ? ' IS NULL' : ' IS NOT NULL';
103 61
            $sql = "SELECT * FROM {$this->getQuotedTableName()} WHERE {$fieldName}{$isNull}{$orderBy};";
104
        } else {
105 9
            $sql = "SELECT * FROM {$this->getQuotedTableName()} WHERE {$fieldName} {$comparison} :value{$orderBy};";
106
        }
107 70
        $vars = [];
108 70
        $vars['value'] = $value;
109 70
        return Database::getInstance()->getDbEngine()->select($sql, $vars);
110
    }
111
112
    /**
113
     * Return an array $key=>$value with all records on the table.
114
     *
115
     * @return array
116
     */
117
    public function getAllKeyValue()
118
    {
119
        $return = [];
120
        $sql = "SELECT {$this->idField}, {$this->nameField} FROM {$this->getQuotedTableName()};";
121
        $result = Database::getInstance()->getDbEngine()->select($sql);
122
        foreach ($result as $record) {
123
            $return[$record[$this->idField]] = $record[$this->nameField];
124
        }
125
        return $return;
126
    }
127
128
    /**
129
     * Return a list of key indexes.
130
     * Each final model that needed, must overwrite it.
131
     *
132
     * TODO: Why "*FromTable()" need to be overwrited on final model? Is not from model definition.
133
     *
134
     * @return array
135
     */
136
    public function getIndexesFromTable(): array
137
    {
138
        return Database::getInstance()->getSqlHelper()->getIndexes($this->tableName, true);
139
    }
140
141
    /**
142
     * Return a list of default values.
143
     * Each final model that needed, must overwrite it.
144
     *
145
     * @return array
146
     */
147 78
    public function getDefaultValues(): array
148
    {
149 78
        $items = [];
150 78
        foreach ($this->getStructure()['fields'] as $key => $valueData) {
151 78
            $items[$key] = $this->getDefaultValue($valueData);
152
        }
153 78
        return $items;
154
    }
155
156
    /**
157
     * Get default value data for this valueData.
158
     *
159
     * @param array $valueData
160
     *
161
     * @return bool|false|int|string
162
     */
163 78
    private function getDefaultValue(array $valueData)
164
    {
165 78
        $item = $valueData['default'] ?? '';
166 78
        if ($valueData['nullable'] === 'no') {
167 78
            switch ($valueData['type']) {
168 78
                case 'integer':
169 48
                case 'number':
170 48
                case 'email':
171 68
                    $item = 0;
172 68
                    break;
173 48
                case 'checkbox':
174
                    $item = false;
175
                    break;
176 48
                case 'date':
177
                    $item = FormatUtils::getFormattedDate();
178
                    break;
179 48
                case 'datetime':
180
                    $item = FormatUtils::getFormattedDateTime();
181
                    break;
182 48
                case 'time':
183
                    $item = FormatUtils::getFormattedTime();
184
                    break;
185 48
                case 'string':
186
                case 'text':
187
                case 'textarea':
188
                case 'blob':
189
                case 'data':
190
                case 'link':
191 48
                    $item = '';
192 48
                    break;
193
                default:
194
                    $item = $valueData['default'];
195
            }
196
        }
197 78
        return $item;
198
    }
199
200
    /**
201
     * TODO: Undocumented
202
     *
203
     * @return array
204
     */
205
    public function getChecksFromTable(): array
206
    {
207
        return Schema::getFromYamlFile($this->tableName, 'viewdata');
208
    }
209
210
    /**
211
     * Save the data to a record if pass the test and returns true/false based on the result.
212
     *
213
     * @param array $data
214
     *
215
     * @return bool
216
     */
217
    public function saveRecord(array $data): bool
218
    {
219
        if ($this->testData($data)) {
220
            return $this->saveData($data);
221
        }
222
        return false;
223
    }
224
225
    /**
226
     * TODO: Need revision.
227
     * Return true if all data is Ok.
228
     * Must check if any field does not meet the criteria set.
229
     * Should check even those fields that have not been passed in the data in case they are required.
230
     *
231
     * @param array $data
232
     *
233
     * @return bool
234
     */
235
    protected function testData(array $data): bool
236
    {
237
        $ok = true;
238
        foreach ($data as $tableName => $block) {   // Recorrer tablas
239
            foreach ($block as $blockId => $record) {            // Recorrer registros de la tabla (seguramente uno)
240
                foreach (Database::getInstance()->getDbEngine()->getDbTableStructure($tableName)['checks'] as $fieldName => $fieldStructure) {
241
                    $length = $fieldStructure['length'] ?? null;
242
                    if (isset($length) && $length > 0 && strlen($record[$fieldName]) > $length) {
243
                        $vars = ['%tableName%' => $tableName, '%fieldName%' => $fieldName, '%length%' => $length];
244
                        FlashMessages::getInstance()::setError(Translator::getInstance()->trans('tablename-fieldname-max-length', $vars));
245
                        $ok = false;
246
                    }
247
                    $min = $fieldStructure['min'] ?? null;
248
                    if (isset($min) && $min > (int) $record[$fieldName]) {
249
                        $vars = ['%tableName%' => $tableName, '%fieldName%' => $fieldName, '%min%' => $min];
250
                        FlashMessages::getInstance()::setError(Translator::getInstance()->trans('tablename-fieldname-exceeds-minimum', $vars));
251
                        $ok = false;
252
                    }
253
                    $max = $fieldStructure['max'] ?? null;
254
                    if (isset($max) && $max < (int) $record[$fieldName]) {
255
                        $vars = ['%tableName%' => $tableName, '%fieldName%' => $fieldName, '%max%' => $max];
256
                        FlashMessages::getInstance()::setError(Translator::getInstance()->trans('tablename-fieldname-exceeds-maximum', $vars));
257
                        $ok = false;
258
                    }
259
                    if (isset($fieldStructure['unique']) && ($fieldStructure['unique'] === 'yes')) {
260
                        $sql = "SELECT * FROM {$this->getQuotedTableName()} WHERE {$fieldName}='{$data[$tableName][$blockId][$fieldName]}';";
261
                        $bad = Database::getInstance()->getDbEngine()->select($sql);
262
                        if ($bad && count($bad) > 0) {
263
                            foreach ($bad as $badrecord) {
264
                                // TODO: Estoy utilizando 'id', pero tendría que ser el $this->idField del modelo correspondiente
265
                                if ($badrecord['id'] !== $data[$tableName][$blockId]['id']) {
266
                                    $vars = ['%tableName%' => $tableName, '%fieldName%' => $fieldName, '%value%' => $data[$tableName][$blockId][$fieldName], '%register%' => $badrecord['id']];
267
                                    FlashMessages::getInstance()::setError(Translator::getInstance()->trans('tablename-fieldname-register-duplicated', $vars));
268
                                    $ok = false;
269
                                }
270
                            }
271
                        }
272
                    }
273
                }
274
            }
275
        }
276
        return $ok;
277
    }
278
279
    /**
280
     * Try to save the data and return true/false based on the result.
281
     *
282
     * @param array $data
283
     *
284
     * @return bool
285
     */
286
    protected function saveData(array $data): bool
287
    {
288
        $ret = true;
289
        foreach ($data[$this->tableName] as $key => $value) {
290
            $this->load($key);
291
            $this->newData = $value;
292
            $ret &= $this->save();
293
        }
294
        return (bool) $ret;
295
    }
296
}
297