Passed
Push — master ( f360f0...1bc594 )
by Bruno
06:10
created

Model::validate()   C

Complexity

Conditions 15
Paths 156

Size

Total Lines 63
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 15

Importance

Changes 7
Bugs 4 Features 1
Metric Value
cc 15
eloc 36
c 7
b 4
f 1
nc 156
nop 1
dl 0
loc 63
ccs 34
cts 34
cp 1
crap 15
rs 5.4499

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 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 1
    public function getName(): string
89
    {
90 1
        return $this->name;
91
    }
92
93 1
    public function getFields(): array
94
    {
95 1
        return $this->fields;
96
    }
97
98 6
    public function getData(): array
99
    {
100 6
        return $this->_data;
101
    }
102
103
    public function getExtensions(): array
104
    {
105
        return $this->extensions;
106
    }
107
108
    /**
109
     * @param string $name
110
     * @param mixed $default
111
     * @return mixed
112
     */
113
    public function getExtension(string $name, $default)
114
    {
115
        return $this->extensions[$name] ?? $default;
116
    }
117
118 2
    public function getField(string $name): Field
119
    {
120 2
        return $this->fields[$name];
121
    }
122
123
    /**
124
     * Validates a set of data against this model.
125
     *
126
     * @param array $data A field name => data array.
127
     * @return array
128
     */
129 8
    public function validate(array $data): array
130
    {
131 8
        $this->_data = $data;
132 8
        $validate = [];
133 8
        $errors = [];
134
135
        // validate data
136 8
        foreach ($data as $name => $d) {
137
            // expected?
138 8
            if (!array_key_exists($name, $this->fields)) {
139 1
                $errors[$name] = "Field $name does not exist in this model";
140 1
                continue;
141
            }
142
143
            // call the datatype validator
144 8
            $field = $this->fields[$name];
145
            try {
146 8
                $validate[$name] = $field->getDatatype()->validate($d, $field, $this);
147 2
            } catch (Exception $e) {
148 2
                $errors[$name] = $e->getMessage();
149
            }
150
151
            // call class validators.
152 8
            foreach ($field->getValidators() as $validatorName => $_) {
153 8
                if (mb_strpos($validatorName, '\\') === false) {
154 3
                    continue;
155
                }
156
                try {
157 5
                    $v = Validator::factory($validatorName);
158 5
                    $validate[$name] = $v->validate($validate[$name], $field, $this);
159 3
                } catch (Exception $e) {
160 8
                    $errors[$name] = $e->getMessage();
161
                }
162
            }
163
        }
164
165 8
        foreach ($this->fields as $name => $field) {
166
            // check REQUIRED.
167 8
            if (($field->getValidators()[Datatype::REQUIRED] ?? false)
168 8
                && !array_key_exists($name, $validate)
169 8
                && !array_key_exists($name, $errors)
170
            ) {
171 1
                $errors[$name] = "Field $name is missing";
172
            }
173
174
            // if in field list but not in data
175 8
            if (!array_key_exists($name, $data)) {
176
                // call class validators.
177 5
                foreach ($field->getValidators() as $validatorName => $_) {
178 5
                    if (mb_strpos($validatorName, '\\') === false) {
179 1
                        continue;
180
                    }
181
                    try {
182 4
                        $v = Validator::factory($validatorName);
183 4
                        $v->validate(null, $field, $this);
184 2
                    } catch (Exception $e) {
185 8
                        $errors[$name] = $e->getMessage();
186
                    }
187
                }
188
            }
189
        }
190 8
        $this->_data = [];
191 8
        return ['validated' => $validate, 'errors' => $errors];
192
    }
193
194
    /**
195
     * Serializes this model to JSON.
196
     *
197
     * @return array
198
     */
199 1
    public function serialize(): array
200
    {
201 1
        $fields = array_map(
202
            function ($f) {
203
                return [
204 1
                    'datatype' => $f->getDatatype()->getName(),
205 1
                    'validators' => $f->getValidators(),
206 1
                    'extensions' => $f->getExtensions()
207
                ];
208 1
            },
209 1
            $this->fields
210
        );
211
        $model = [
212 1
            'name' => $this->name,
213 1
            'fields' => $fields
214
        ];
215 1
        return $model;
216
    }
217
218 1
    public function toJSON(): string
219
    {
220 1
        $t = json_encode($this->serialize());
221 1
        if (!$t) {
222
            throw new Exception('Cannot serialize');
223
        }
224 1
        return $t;
225
    }
226
227
    /**
228
     * Renders a readonly view of the model with given data.
229
     *
230
     * @param array $modelData
231
     * @return string
232
     */
233
    public function viewable(array $modelData): string
234
    {
235
        return FrameworkComposer::viewable($this, $modelData);
236
    }
237
238
    /**
239
     * Renders a form view of the model with given data.
240
     *
241
     * @param array $modelData
242
     * @return string
243
     */
244
    public function editable(array $modelData = []): string
245
    {
246
        return FrameworkComposer::editable($this, $modelData);
247
    }
248
249 1
    public function getRandom(): array
250
    {
251 1
        $data = [];
252 1
        foreach ($this->fields as $f) {
253 1
            $data[$f->getName()] = $f->getDatatype()->getRandom();
254
        }
255 1
        return $data;
256
    }
257
258
    /**
259
     * Returns an array with the default values of each field
260
     *
261
     * @return array Field name => value
262
     */
263
    public function getDefault(): array
264
    {
265
        $data = [];
266
        foreach ($this->fields as $f) {
267
            $data[$f->getName()] = $f->getDatatype()->getDefault();
268
        }
269
        return $data;
270
    }
271
272
    /**
273
     * Parses struct
274
     *
275
     * @param array $data
276
     * @throws Exception
277
     * @return void
278
     */
279 15
    protected function parseStruct(array $data)
280
    {
281 15
        if (!array_key_exists('name', $data)) {
282 1
            throw new Exception('Missing name in model');
283
        }
284 14
        if (!array_key_exists('fields', $data)) {
285 1
            throw new Exception('Missing fields in model');
286
        }
287 13
        $this->name = $data['name'];
288 13
        foreach ($data['fields'] as $fieldName => $fieldData) {
289 13
            $this->fields[$fieldName] = Field::getFromData($fieldName, $fieldData);
290
        }
291 12
        if (array_key_exists('extensions', $data)) {
292
            if (!is_array($data['extensions'])) {
293
                throw new Exception('Model extension must be an array');
294
            }
295
            $this->extensions = $data['extensions'];
296
        }
297 12
    }
298
}
299