Completed
Push — dev-master ( 0f2dab...d5eb58 )
by Vijay
03:06
created

Mapper::initValidation()   C

Complexity

Conditions 16
Paths 113

Size

Total Lines 60
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 36
nc 113
nop 0
dl 0
loc 60
rs 5.8945
c 0
b 0
f 0

How to fix   Long Method    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
namespace FFCMS\Mappers;
4
5
use FFMVC\Helpers;
6
use FFCMS\{Traits, Models};
7
8
/**
9
 * Base Database Mapper Class extends f3's DB\SQL\Mapper
10
 *
11
 * @author Vijay Mahrra <[email protected]>
12
 * @copyright (c) Copyright 2016 Vijay Mahrra
13
 * @license GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
14
 *
15
 * @see https://fatfreeframework.com/cursor
16
 * @see https://fatfreeframework.com/sql-mapper
17
 * @see https://github.com/Wixel/GUMP
18
 */
19
20
// abstract class Magic implements ArrayAccess
21
// abstract class Cursor extends \Magic implements \IteratorAggregate
22
// class Mapper extends \DB\Cursor
23
/**
24
 * @property string $key
25
 * @property string $value
26
 * @property string $created
27
 */
28
abstract class Mapper extends \DB\SQL\Mapper
29
{
30
    use Traits\Validation;
31
32
    /**
33
     * Fields and their visibility to clients, boolean or string of visible field name
34
     *
35
     * @var array $fieldsVisible
36
     */
37
    protected $fieldsVisible = [];
38
39
    /**
40
     * Fields that are editable to clients, boolean or string of visible field name
41
     *
42
     * @var array $fieldsEditable
43
     */
44
    protected $fieldsEditable = [];
45
46
    /**
47
     * @var object database class
48
     */
49
    protected $db;
50
51
    /**
52
     * @var string $table for the mapper - this string gets automatically quoted
53
     */
54
    protected $table;
55
56
    /**
57
     * @var string $mapperName name for the mapper
58
     */
59
    protected $mapperName;
60
61
    /**
62
     * @var string $uuid the fieldname used for the uuid
63
     */
64
    protected $uuidField = 'uuid';
65
66
    /**
67
     * @var boolean $valid the data after validation is valid?
68
     */
69
    protected $valid = null;
70
71
    /**
72
     * @var array $originalData the original data when object created/loaded
73
     */
74
    protected $originalData = [];
75
76
    /**
77
     * @var array $auditData data to write to audit log
78
     */
79
    protected $auditData = [];
80
81
    /**
82
     * @var boolean $initValidation automatically append validation settings for fields $this->validationRules/Default?
83
     */
84
    protected $initValidation = true;
85
86
    /**
87
     * initialize with array of params
88
     *
89
     */
90
    public function __construct()
91
    {
92
        $f3 = \Base::instance();
93
94
        // guess the table name from the class name if not specified as a class member
95
        $class = \UTF::instance()->substr(strrchr(get_class($this), '\\'),1);
96
        $this->table = $this->mapperName = empty($this->table) ? $f3->snakecase($class) : $this->table;
97
98
        parent::__construct(\Registry::get('db'), $this->table);
99
100
        $this->initValidation(); // automatically create validation settings from reading tables
101
        $this->setupHooks(); // setup hooks for before/after data changes in mapper
102
    }
103
104
    /**
105
     * Initialise automatic validation settings for fields
106
     */
107
    public function initValidation()
108
    {
109
        if (!$this->initValidation) {
110
            return;
111
        }
112
        
113
        // work out default validation rules from schema and cache them
114
        $validationRules   = [];
115
        foreach ($this->schema() as $field => $metadata) {
116
            if ('id' == $field)  {
117
                continue;
118
            }
119
120
            $validationRules[$field] = '';
121
            $rules   = [];
122
123
            if (empty($metadata['nullable']) || !empty($metadata['pkey'])) {
124
                // special case, id for internal use so we don't interfere with this
125
                $rules[] = 'required';
126
            }
127
128
            if (preg_match('/^(?<type>[^(]+)\(?(?<length>[^)]+)?/i', $metadata['type'], $matches)) {
129
                switch ($matches['type']) {
130
                    case 'char':
131
                    case 'varchar':
132
                        $rules[] = 'max_len,' . $matches['length'];
133
                        break;
134
135
                    case 'text':
136
                        $rules[] = 'max_len,65535';
137
                        break;
138
139
                    case 'int':
140
                        $rules[] = 'integer|min_numeric,0';
141
                        break;
142
143
                    case 'datetime':
144
                        $rules[] = 'date|min_len,0|max_len,19';
145
                        break;
146
147
                    default:
148
                        break;
149
                }
150
                $validationRules[$field] = empty($rules) ? '' : join('|', $rules);
151
            }
152
        }
153
154
        // set default validation rules
155
        foreach ($this->validationRules as $field => $rule) {
156
            if (empty($rule)) {
157
                continue;
158
            }
159
            $validationRules[$field] = empty($validationRules[$field]) ? $rule :
160
                                                $validationRules[$field] .  '|' . $rule;
161
        }
162
163
        // save default validation rules and filter rules in-case we add rules
164
        $this->validationRulesDefault = $this->validationRules = $validationRules;
165
        $this->filterRulesDefault = $this->filterRules;
166
    }
167
168
    /**
169
     * Initialise hooks for the mapper object actions
170
     */
171
    public function setupHooks()
172
    {
173
        // set original data when object loaded
174
        $this->onload(function($mapper){
175
            $mapper->originalData = $mapper->cast();
176
        });
177
178
        // filter data, set UUID and date created before insert
179
        $this->beforeinsert(function($mapper){
180
            $mapper->setUUID($mapper->uuidField);
181
            $mapper->copyFrom($mapper->filter());
182
            if (in_array('created', $mapper->fields()) && empty($mapper->created)) {
183
                $mapper->created = Helpers\Time::database();
184
            }
185
            return $mapper->validate();
186
        });
187
188
        // filter data, set updated field if present before update
189
        $this->beforeupdate(function($mapper){
190
            $mapper->copyFrom($mapper->filter());
191
            return $mapper->validate();
192
        });
193
194
        // write audit data after save
195
        $this->aftersave(function($mapper){
196
            if ('audit' == $mapper->mapperName) {
197
                return;
198
            }
199
            $data = array_merge([
200
                'event' => (empty($mapper->originalData) ? 'created-'  : 'updated-') . $mapper->mapperName,
201
                'old' => $mapper->originalData,
202
                'new' => $mapper->cast()
203
            ], $this->auditData);
204
            Models\Audit::instance()->write($data);
205
            $mapper->originalData = $data['new'];
206
            $mapper->auditData = [];
207
        });
208
209
        // write audit data after erase
210
        $this->aftererase(function($mapper){
211
            if ('audit' == $mapper->mapperName) {
212
                return;
213
            }
214
            Models\Audit::instance()->write(array_merge([
215
                'event' => 'deleted-' . $mapper->mapperName,
216
                'old' => $mapper->originalData,
217
                'new' => $mapper->cast()
218
            ], $this->auditData));
219
            $mapper->originalData = $mapper->auditData = [];
220
        });
221
    }
222
223
    /**
224
     * return string representation of class - json of data
225
     *
226
     * @param string
227
     */
228
    public function __toString(): string
229
    {
230
        return json_encode($this->cast(), JSON_PRETTY_PRINT);
231
    }
232
233
234
    /**
235
     * return array representation of class - json of data
236
     *
237
     * @param array
238
     */
239
    public function __toArray(): array
240
    {
241
        return $this->cast();
242
    }
243
244
    /**
245
     * Cast the mapper data to an array using only provided fields
246
     *
247
     * @param mixed string|array fields to return in response
248
     * @param array optional data optional data to use instead of fields
249
     * @return array $data
250
     */
251
    public function castFields($fields = null, array $data = []): array
252
    {
253 View Code Duplication
        if (!empty($fields)) {
254
            if (is_string($fields)) {
255
                $fields = preg_split("/[\s,]+/", strtolower($fields));
256
            } else if (!is_array($fields)) {
257
                $fields = [];
258
            }
259
        }
260
261
        if (empty($data) || !is_array($data)) {
262
            $data = $this->cast();
263
        }
264
265
        if (empty($fields)) {
266
            $fields = array_keys($data);
267
        }
268
269
        // remove fields not in the list
270
        foreach ($data as $k => $v) {
271
            if (!in_array($k, $fields)) {
272
                unset($data[$k]);
273
            }
274
        }
275
276
        $data['object'] = $this->table;
277
278
        return $data;
279
    }
280
281
    /**
282
     * Cast the mapper data to an array and modify (for external clients typically)
283
     * using the visible fields and names for export, converting dates to unixtime
284
     * optionally pass in a comma (or space)-separated list of fields or an array of fields
285
     *
286
     * @param mixed string|array fields to return in response
287
     * @param array optional data optional data to use instead of fields
288
     * @return array $data
289
     */
290
    public function exportArray($fields = null, array $data = []): array
291
    {
292 View Code Duplication
        if (!empty($fields)) {
293
            if (is_string($fields)) {
294
                $fields = preg_split("/[\s,]+/", strtolower($fields));
295
            } else if (!is_array($fields)) {
296
                $fields = [];
297
            }
298
        }
299
300
        if (empty($data) || !is_array($data)) {
301
            $data = $this->cast();
302
        }
303
304
        foreach ($data as $k => $v) {
305
            if (empty($this->fieldsVisible[$k])) {
306
                unset($data[$k]);
307
                continue;
308
            } elseif (true !== $this->fieldsVisible[$k]) {
309
                unset($data[$k]);
310
                $k = $this->fieldsVisible[$k];
311
                $data[$k] = $v;
312
            }
313
            // convert date to unix timestamp
314
            if ('updated' == $k || 'created' == $k || (
315
                strlen($v) == 19 && preg_match("/^[\d]{4}-[\d]{2}-[\d]{2}[\s]+[\d]{2}:[\d]{2}:[\d]{2}/", $v, $m))) {
316
                $time = strtotime($v);
317
                if ($time < 0) {
318
                    $time = 0;
319
                }
320
                $data[$k] = $time;
321
            }
322
            if (!empty($fields) && $k !== 'id' && $k !== 'object' && !in_array($k, $fields)) {
323
                unset($data[$k]);
324
            }
325
        }
326
327
        $data['object'] = $this->table;
328
329
        return $data;
330
    }
331
332
333
    /**
334
     * Convert the mapper object to format suitable for JSON
335
     *
336
     * @param boolean $unmodified cast as public (visible) data or raw db data?
337
     * @param mixed $fields optional string|array fields to include
338
     * @return string json-encoded data
339
     */
340
    public function exportJson(bool $unmodified = false, $fields = null): string
341
    {
342
        return json_encode(empty($unmodified) ? $this->castFields($fields) : $this->exportArray($fields), JSON_PRETTY_PRINT);
343
    }
344
345
346
    /**
347
     * Set a field (default named uuid) to a UUID value if one is not present.
348
     *
349
     * @param string $field the name of the field to check and set
350
     * @return null|string $uuid the new uuid generated
351
     */
352
    public function setUUID(string $field = 'uuid')
353
    {
354
        $db = \Registry::get('db');
355
        // a proper uuid is 36 characters
356
        if (in_array($field, $this->fields()) &&
357
            (empty($this->$field) || strlen($this->$field) < 36)) {
358
            $tmp = clone $this;
359
360
            do {
361
                $uuid = Helpers\Str::uuid();
362
            }
363
            while ($tmp->load([$db->quotekey($field) . ' = ?', $uuid]));
364
365
            unset($tmp);
366
            $this->$field = $uuid;
367
            return $uuid;
368
        }
369
        return empty($this->$field) ? null : $this->$field;
370
    }
371
372
373
    /**
374
     * Write data for audit logging
375
     *
376
     * @param $data array of data to audit log
377
     * @return array $this->auditData return the updated audit data for the mapper
378
     */
379
    public function audit(array $data = []): array
380
    {
381
        $this->auditData = array_merge($this->auditData, $data);
382
        return $this->auditData;
383
    }
384
385
}
386