Completed
Push — dev-master ( d862d4...0f2dab )
by Vijay
02:55
created

Mapper::exportJson()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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