Passed
Push — master ( 116340...b15bec )
by Bruno
07:47
created

Model::getRenderable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
crap 2
1
<?php declare(strict_types=1);
2
3
namespace Formularium;
4
5
use Formularium\Exception\Exception;
6
7
/**
8
 * Model class, representing a whole object.
9
 */
10
class Model
11
{
12
    /**
13
     * @var string
14
     */
15
    protected $name;
16
17
    /**
18
     * @var Field[]
19
     */
20
    protected $fields = [];
21
22
    /**
23
     * @var array
24
     */
25
    protected $renderable = [];
26
27
    /**
28
     * Model data being processed.
29
     * @var array
30
     */
31
    protected $_data = [];
32
33
    /**
34
     *
35
     * @param string $name
36
     * @throws Exception
37
     */
38 29
    protected function __construct(string $name = '')
39
    {
40 29
        $this->name = $name;
41 29
    }
42
43
    /**
44
     * Loads model from JSON file.
45
     *
46
     * @param array $struct
47
     * @return Model
48
     */
49 25
    public static function fromStruct(array $struct): Model
50
    {
51 25
        $m = new self('');
52 25
        $m->parseStruct($struct);
53 25
        return $m;
54
    }
55
56
    /**
57
     * Loads model from JSON file.
58
     *
59
     * @param string $name The JSON filename.
60
     * @return Model
61
     */
62
    public static function fromJSONFile(string $name): Model
63
    {
64
        $json = file_get_contents($name); // TODO: path
65
        if ($json === false) {
66
            throw new Exception('File not found');
67
        }
68
        return static::fromJSON($json);
69
    }
70
71
    /**
72
     * Loads model from JSON string
73
     *
74
     * @param string $json The JSON string.
75
     * @return Model
76
     */
77 5
    public static function fromJSON(string $json): Model
78
    {
79 5
        $data = \json_decode($json, true);
80 5
        if ($data === null) {
81 1
            throw new Exception('Invalid JSON format');
82
        }
83 4
        $m = new self('');
84 4
        $m->parseStruct($data);
85 1
        return $m;
86
    }
87
88
    /**
89
     * @param string $name
90
     * @param array[]|Field[] $fields
91
     * @return Model
92
     */
93
    public static function create(string $name, array $fields = []): Model
94
    {
95
        $m = new self($name);
96
        foreach ($fields as $fieldName => $fieldData) {
97
            if ($fieldData instanceof Field) {
98
                $m->fields[$fieldData->getName()] = $fieldData;
99
            } else {
100
                $m->fields[$fieldName] = Field::getFromData($fieldName, $fieldData);
101
            }
102
        }
103
        return $m;
104
    }
105
106 1
    public function getName(): string
107
    {
108 1
        return $this->name;
109
    }
110
111 1
    public function getFields(): array
112
    {
113 1
        return $this->fields;
114
    }
115
116 6
    public function getData(): array
117
    {
118 6
        return $this->_data;
119
    }
120
121
    public function getRenderables(): array
122
    {
123
        return $this->renderable;
124
    }
125
126
    /**
127
     * @param string $name
128
     * @param mixed $default
129
     * @return mixed
130
     */
131
    public function getRenderable(string $name, $default)
132
    {
133
        return $this->renderable[$name] ?? $default;
134
    }
135
136 16
    public function getField(string $name): Field
137
    {
138 16
        return $this->fields[$name];
139
    }
140
141
    public function appendField(Field $f): self
142
    {
143
        $this->fields[$f->getName()] = $f;
144
        return $this;
145
    }
146
147
    /**
148
     * @param Field[] $fields
149
     * @return self
150
     */
151
    public function appendFields(array $fields): self
152
    {
153
        foreach ($fields as $f) {
154
            $this->fields[$f->getName()] = $f;
155
        }
156
        return $this;
157
    }
158
159
    /**
160
     * Validates a set of data against this model.
161
     *
162
     * @param array $data A field name => data array.
163
     * @return array
164
     */
165 8
    public function validate(array $data): array
166
    {
167 8
        $this->_data = $data;
168 8
        $validate = [];
169 8
        $errors = [];
170
171
        // validate data
172 8
        foreach ($data as $name => $d) {
173
            // expected?
174 8
            if (!array_key_exists($name, $this->fields)) {
175 1
                $errors[$name] = "Field $name does not exist in this model";
176 1
                continue;
177
            }
178
179
            // call the datatype validator
180 8
            $field = $this->fields[$name];
181
            try {
182 8
                $validate[$name] = $field->getDatatype()->validate($d, $this);
183
            } catch (Exception $e) {
184
                $errors[$name] = $e->getMessage();
185
            }
186
187
            // call class validators.
188 8
            foreach ($field->getValidators() as $validatorName => $options) {
189
                // special case
190 8
                if ($validatorName === Datatype::REQUIRED) {
191 1
                    continue;
192
                }
193
194
                try {
195 8
                    $validate[$name] = ValidatorFactory::class($validatorName)::validate(
196 8
                        $validate[$name],
197 8
                        $options,
198 8
                        $field->getDatatype(),
199 8
                        $this
200
                    );
201 5
                } catch (Exception $e) {
202 5
                    $errors[$name] = $e->getMessage();
203
                }
204
            }
205
        }
206
207
        // now validate fields, since you may have some that were not in data.
208 8
        foreach ($this->fields as $name => $field) {
209
            // if in field list but not in data
210 8
            if (!array_key_exists($name, $data)) {
211
                // call class validators.
212 5
                foreach ($field->getValidators() as $validatorName => $options) {
213 5
                    if ($validatorName === Datatype::REQUIRED) {
214 1
                        if (!array_key_exists($name, $validate)
215 1
                            && !array_key_exists($name, $errors)
216
                        ) {
217
                            $errors[$name] = "Field $name is missing";
218
                        }
219 1
                        continue;
220
                    }
221
222
                    try {
223 5
                        $v = ValidatorFactory::class($validatorName)::validate(
0 ignored issues
show
Unused Code introduced by
The assignment to $v is dead and can be removed.
Loading history...
224 5
                            null,
225 5
                            $options,
226 5
                            $field->getDatatype(),
227 5
                            $this
228
                        );
229 3
                    } catch (Exception $e) {
230 3
                        $errors[$name] = $e->getMessage();
231
                    }
232
                }
233
            }
234
        }
235 8
        $this->_data = [];
236 8
        return ['validated' => $validate, 'errors' => $errors];
237
    }
238
239
    /**
240
     * Serializes this model to JSON.
241
     *
242
     * @return array
243
     */
244 1
    public function serialize(): array
245
    {
246 1
        $fields = array_map(
247
            function ($f) {
248
                return [
249 1
                    'datatype' => $f->getDatatype()->getName(),
250 1
                    'validators' => $f->getValidators(),
251 1
                    'renderable' => $f->getRenderables()
252
                ];
253 1
            },
254 1
            $this->fields
255
        );
256
        $model = [
257 1
            'name' => $this->name,
258 1
            'fields' => $fields
259
        ];
260 1
        return $model;
261
    }
262
263 1
    public function toJSON(): string
264
    {
265 1
        $t = json_encode($this->serialize());
266 1
        if (!$t) {
267
            throw new Exception('Cannot serialize');
268
        }
269 1
        return $t;
270
    }
271
272
    /**
273
     * Renders a readonly view of the model with given data.
274
     *
275
     * @param array $modelData
276
     * @return string
277
     */
278
    public function viewable(array $modelData): string
279
    {
280
        $this->_data = $modelData;
281
        $r = FrameworkComposer::viewable($this, $modelData);
282
        $this->_data = [];
283
        return $r;
284
    }
285
286
    /**
287
     * Renders a form view of the model with given data.
288
     *
289
     * @param array $modelData
290
     * @return string
291
     */
292
    public function editable(array $modelData = []): string
293
    {
294
        return FrameworkComposer::editable($this, $modelData);
295
    }
296
297 1
    public function getRandom(): array
298
    {
299 1
        $data = [];
300 1
        foreach ($this->fields as $f) {
301 1
            $data[$f->getName()] = $f->getDatatype()->getRandom();
302
        }
303 1
        return $data;
304
    }
305
306
    /**
307
     * Returns an array with the default values of each field
308
     *
309
     * @return array Field name => value
310
     */
311
    public function getDefault(): array
312
    {
313
        $data = [];
314
        foreach ($this->fields as $f) {
315
            $data[$f->getName()] = $f->getDatatype()->getDefault();
316
        }
317
        return $data;
318
    }
319
320
    /**
321
     * Parses struct
322
     *
323
     * @param array $data
324
     * @throws Exception
325
     * @return void
326
     */
327 29
    protected function parseStruct(array $data)
328
    {
329 29
        if (!array_key_exists('name', $data)) {
330 1
            throw new Exception('Missing name in model');
331
        }
332 28
        if (!array_key_exists('fields', $data)) {
333 1
            throw new Exception('Missing fields in model');
334
        }
335 27
        $this->name = $data['name'];
336 27
        foreach ($data['fields'] as $fieldName => $fieldData) {
337 27
            $this->fields[$fieldName] = Field::getFromData($fieldName, $fieldData);
338
        }
339 26
        if (array_key_exists('renderable', $data)) {
340
            if (!is_array($data['renderable'])) {
341
                throw new Exception('Model extension must be an array');
342
            }
343
            $this->renderable = $data['renderable'];
344
        }
345 26
    }
346
}
347