Passed
Push — master ( 57f4f6...f7a67d )
by Bruno
10:20
created

Model::appendField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
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 $extensions;
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 15
    protected function __construct(string $name = '')
39
    {
40 15
        $this->name = $name;
41 15
    }
42
43
    /**
44
     * Loads model from JSON file.
45
     *
46
     * @param array $struct
47
     * @return Model
48
     */
49 11
    public static function fromStruct(array $struct): Model
50
    {
51 11
        $m = new self('');
52 11
        $m->parseStruct($struct);
53 11
        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
     * Loads model from JSON string
90
     *
91
     * @param string $json The JSON string.
92
     * @return Model
93
     */
94
    public static function create(string $name): Model
95
    {
96
        $m = new self($name);
97
        return $m;
98
    }
99
100 1
    public function getName(): string
101
    {
102 1
        return $this->name;
103
    }
104
105 1
    public function getFields(): array
106
    {
107 1
        return $this->fields;
108
    }
109
110 6
    public function getData(): array
111
    {
112 6
        return $this->_data;
113
    }
114
115
    public function getExtensions(): array
116
    {
117
        return $this->extensions;
118
    }
119
120
    /**
121
     * @param string $name
122
     * @param mixed $default
123
     * @return mixed
124
     */
125
    public function getExtension(string $name, $default)
126
    {
127
        return $this->extensions[$name] ?? $default;
128
    }
129
130 2
    public function getField(string $name): Field
131
    {
132 2
        return $this->fields[$name];
133
    }
134
135
    public function appendField(Field $f): self
136
    {
137
        $this->fields[$f->name] = $f;
0 ignored issues
show
Bug introduced by
The property name is declared protected in Formularium\Field and cannot be accessed from this context.
Loading history...
138
        return $this;
139
    }
140
141
    /**
142
     * @param Field[] $fields
143
     * @return self
144
     */
145
    public function appendFields(array $fields): self
146
    {
147
        foreach ($fields as $f) {
148
            $this->fields[$f->name] = $f;
0 ignored issues
show
Bug introduced by
The property name is declared protected in Formularium\Field and cannot be accessed from this context.
Loading history...
149
        }
150
        return $this;
151
    }
152
153
    /**
154
     * Validates a set of data against this model.
155
     *
156
     * @param array $data A field name => data array.
157
     * @return array
158
     */
159 8
    public function validate(array $data): array
160
    {
161 8
        $this->_data = $data;
162 8
        $validate = [];
163 8
        $errors = [];
164
165
        // validate data
166 8
        foreach ($data as $name => $d) {
167
            // expected?
168 8
            if (!array_key_exists($name, $this->fields)) {
169 1
                $errors[$name] = "Field $name does not exist in this model";
170 1
                continue;
171
            }
172
173
            // call the datatype validator
174 8
            $field = $this->fields[$name];
175
            try {
176 8
                $validate[$name] = $field->getDatatype()->validate($d, $field, $this);
177 2
            } catch (Exception $e) {
178 2
                $errors[$name] = $e->getMessage();
179
            }
180
181
            // call class validators.
182 8
            foreach ($field->getValidators() as $validatorName => $_) {
183 8
                if (mb_strpos($validatorName, '\\') === false) {
184 3
                    continue;
185
                }
186
                try {
187 5
                    $v = Validator::factory($validatorName);
188 5
                    $validate[$name] = $v->validate($validate[$name], $field, $this);
189 3
                } catch (Exception $e) {
190 8
                    $errors[$name] = $e->getMessage();
191
                }
192
            }
193
        }
194
195 8
        foreach ($this->fields as $name => $field) {
196
            // check REQUIRED.
197 8
            if (($field->getValidators()[Datatype::REQUIRED] ?? false)
198 8
                && !array_key_exists($name, $validate)
199 8
                && !array_key_exists($name, $errors)
200
            ) {
201 1
                $errors[$name] = "Field $name is missing";
202
            }
203
204
            // if in field list but not in data
205 8
            if (!array_key_exists($name, $data)) {
206
                // call class validators.
207 5
                foreach ($field->getValidators() as $validatorName => $_) {
208 5
                    if (mb_strpos($validatorName, '\\') === false) {
209 1
                        continue;
210
                    }
211
                    try {
212 4
                        $v = Validator::factory($validatorName);
213 4
                        $v->validate(null, $field, $this);
214 2
                    } catch (Exception $e) {
215 8
                        $errors[$name] = $e->getMessage();
216
                    }
217
                }
218
            }
219
        }
220 8
        $this->_data = [];
221 8
        return ['validated' => $validate, 'errors' => $errors];
222
    }
223
224
    /**
225
     * Serializes this model to JSON.
226
     *
227
     * @return array
228
     */
229 1
    public function serialize(): array
230
    {
231 1
        $fields = array_map(
232
            function ($f) {
233
                return [
234 1
                    'datatype' => $f->getDatatype()->getName(),
235 1
                    'validators' => $f->getValidators(),
236 1
                    'extensions' => $f->getExtensions()
237
                ];
238 1
            },
239 1
            $this->fields
240
        );
241
        $model = [
242 1
            'name' => $this->name,
243 1
            'fields' => $fields
244
        ];
245 1
        return $model;
246
    }
247
248 1
    public function toJSON(): string
249
    {
250 1
        $t = json_encode($this->serialize());
251 1
        if (!$t) {
252
            throw new Exception('Cannot serialize');
253
        }
254 1
        return $t;
255
    }
256
257
    /**
258
     * Renders a readonly view of the model with given data.
259
     *
260
     * @param array $modelData
261
     * @return string
262
     */
263
    public function viewable(array $modelData): string
264
    {
265
        $this->_data = $modelData;
266
        $r = FrameworkComposer::viewable($this, $modelData);
267
        $this->_data = [];
268
        return $r;
269
    }
270
271
    /**
272
     * Renders a form view of the model with given data.
273
     *
274
     * @param array $modelData
275
     * @return string
276
     */
277
    public function editable(array $modelData = []): string
278
    {
279
        return FrameworkComposer::editable($this, $modelData);
280
    }
281
282 1
    public function getRandom(): array
283
    {
284 1
        $data = [];
285 1
        foreach ($this->fields as $f) {
286 1
            $data[$f->getName()] = $f->getDatatype()->getRandom();
287
        }
288 1
        return $data;
289
    }
290
291
    /**
292
     * Returns an array with the default values of each field
293
     *
294
     * @return array Field name => value
295
     */
296
    public function getDefault(): array
297
    {
298
        $data = [];
299
        foreach ($this->fields as $f) {
300
            $data[$f->getName()] = $f->getDatatype()->getDefault();
301
        }
302
        return $data;
303
    }
304
305
    /**
306
     * Parses struct
307
     *
308
     * @param array $data
309
     * @throws Exception
310
     * @return void
311
     */
312 15
    protected function parseStruct(array $data)
313
    {
314 15
        if (!array_key_exists('name', $data)) {
315 1
            throw new Exception('Missing name in model');
316
        }
317 14
        if (!array_key_exists('fields', $data)) {
318 1
            throw new Exception('Missing fields in model');
319
        }
320 13
        $this->name = $data['name'];
321 13
        foreach ($data['fields'] as $fieldName => $fieldData) {
322 13
            $this->fields[$fieldName] = Field::getFromData($fieldName, $fieldData);
323
        }
324 12
        if (array_key_exists('extensions', $data)) {
325
            if (!is_array($data['extensions'])) {
326
                throw new Exception('Model extension must be an array');
327
            }
328
            $this->extensions = $data['extensions'];
329
        }
330 12
    }
331
}
332