Test Failed
Push — master ( 2d3850...72bafb )
by Rafael
04:11
created

Table::getDefaultValue()   D

Complexity

Conditions 18
Paths 39

Size

Total Lines 40
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 40.6258

Importance

Changes 0
Metric Value
cc 18
eloc 34
nc 39
nop 1
dl 0
loc 40
ccs 20
cts 34
cp 0.5881
crap 40.6258
rs 4.8666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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