Passed
Push — master ( f5421b...da5af7 )
by Bruno
06:35
created

Model::validate()   D

Complexity

Conditions 18
Paths 171

Size

Total Lines 67
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 47.9994

Importance

Changes 5
Bugs 3 Features 1
Metric Value
cc 18
eloc 42
c 5
b 3
f 1
nc 171
nop 1
dl 0
loc 67
ccs 23
cts 42
cp 0.5476
crap 47.9994
rs 4.275

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
    /**
24
     * Model data being processed.
25
     * @var array
26
     */
27
    protected $_data;
28
29
    /**
30
     *
31
     * @param string $name
32
     * @throws Exception
33
     */
34 9
    protected function __construct(string $name = '')
35
    {
36 9
        $this->name = $name;
37 9
    }
38
39
    /**
40
     * Loads model from JSON file.
41
     *
42
     * @param array $struct
43
     * @return Model
44
     */
45 5
    public static function fromStruct(array $struct): Model
46
    {
47 5
        $m = new self('');
48 5
        $m->parseStruct($struct);
49 5
        return $m;
50
    }
51
52
    /**
53
     * Loads model from JSON file.
54
     *
55
     * @param string $name The JSON filename.
56
     * @return Model
57
     */
58
    public static function fromJSONFile(string $name): Model
59
    {
60
        $json = file_get_contents($name); // TODO: path
61
        if ($json === false) {
62
            throw new Exception('File not found');
63
        }
64
        return static::fromJSON($json);
65
    }
66
67
    /**
68
     * Loads model from JSON string
69
     *
70
     * @param string $json The JSON string.
71
     * @return Model
72
     */
73 5
    public static function fromJSON(string $json): Model
74
    {
75 5
        $data = \json_decode($json, true);
76 5
        if ($data === null) {
77 1
            throw new Exception('Invalid JSON format');
78
        }
79 4
        $m = new self('');
80 4
        $m->parseStruct($data);
81 1
        return $m;
82
    }
83
84 1
    public function getName(): string
85
    {
86 1
        return $this->name;
87
    }
88
89 1
    public function getFields(): array
90
    {
91 1
        return $this->fields;
92
    }
93
94 2
    public function getData(): array
95
    {
96 2
        return $this->_data;
97
    }
98
99
    /**
100
     * Validates a set of data against this model.
101
     *
102
     * @param array $data A field name => data array.
103
     * @return array
104
     */
105 4
    public function validate(array $data): array
106
    {
107 4
        $this->_data = $data;
108 4
        $validate = [];
109 4
        $errors = [];
110 4
        foreach ($data as $name => $d) {
111
            // expected?
112 4
            if (!array_key_exists($name, $this->fields)) {
113
                $errors[$name] = "Field $name does not exist in this model";
114
                continue;
115
            }
116
117 4
            $field = $this->fields[$name];
118
119
            // must be filled?
120 4
            if ($field->getValidators()[Datatype::FILLED] ?? false) {
121 1
                if (empty($d)) {
122 1
                    $errors[$name] = "Field $name is empty";
123 1
                    continue;
124
                }
125
            }
126
127 4
            if ($field->getValidators()[Datatype::REQUIRED_WITH] ?? false) {
128
                $expectedFields = $field->getValidators()[Datatype::REQUIRED_WITH];
129
                $found = false;
130
                foreach ($expectedFields as $ef) {
131
                    if (array_key_exists($data, $ef)) {
132
                        $found = true;
133
                    }
134
                }
135
                if (!$found) {
136
                    $errors[$name] = "Field $name is required when at least one of fields " . join(',', $expectedFields . ' are present');
0 ignored issues
show
Bug introduced by
$expectedFields . ' are present' of type string is incompatible with the type array expected by parameter $pieces of join(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

136
                    $errors[$name] = "Field $name is required when at least one of fields " . join(',', /** @scrutinizer ignore-type */ $expectedFields . ' are present');
Loading history...
137
                    continue;
138
                }
139
            }
140
141 4
            if ($field->getValidators()[Datatype::REQUIRED_WITH_ALL] ?? false) {
142
                $expectedFields = $field->getValidators()[Datatype::REQUIRED_WITH_ALL];
143
                $found = true;
144
                foreach ($expectedFields as $ef) {
145
                    if (!array_key_exists($data, $ef)) {
146
                        $found = false;
147
                        break;
148
                    }
149
                }
150
                if (!$found) {
151
                    $errors[$name] = "Field $name is required when at all fields " . join(',', $expectedFields . ' are present');
152
                    continue;
153
                }
154
            }
155
156
            try {
157 4
                $validate[$name] = $field->getDatatype()->validate($d, $field, $this);
158 2
            } catch (Exception $e) {
159 4
                $errors[$name] = $e->getMessage();
160
            }
161
        }
162 4
        foreach ($this->fields as $name => $field) {
163 4
            if (($field->getValidators()[Datatype::REQUIRED] ?? false)
164 4
                && !array_key_exists($name, $validate)
165 4
                && !array_key_exists($name, $errors)
166
            ) {
167 4
                $errors[$name] = "Field $name is missing";
168
            }
169
        }
170 4
        $this->_data = [];
171 4
        return ['validated' => $validate, 'errors' => $errors];
172
    }
173
174
    /**
175
     * Serializes this model to JSON.
176
     *
177
     * @return array
178
     */
179
    public function serialize(): array
180
    {
181
        $fields = array_map(
182
            function ($f) {
183
                return [
184
                    'datatype' => $f->getDatatype()->getName(),
185
                    'validators' => $f->getValidators(),
186
                    'extensions' => $f->getExtensions()
187
                ];
188
            },
189
            $this->fields
190
        );
191
        $model = [
192
            'name' => $this->name,
193
            'fields' => $fields
194
        ];
195
        return $model;
196
    }
197
198
    public function toJSON(): string
199
    {
200
        $t = json_encode($this->serialize());
201
        if (!$t) {
202
            throw new Exception('Cannot serialize');
203
        }
204
        return $t;
205
    }
206
207
    /**
208
     * Renders a readonly view of the model with given data.
209
     *
210
     * @param array $modelData
211
     * @return string
212
     */
213
    public function viewable(array $modelData): string
214
    {
215
        return FrameworkComposer::viewable($this, $modelData);
216
    }
217
218
    /**
219
     * Renders a form view of the model with given data.
220
     *
221
     * @param array $modelData
222
     * @return string
223
     */
224
    public function editable(array $modelData = []): string
225
    {
226
        return FrameworkComposer::editable($this, $modelData);
227
    }
228
229 1
    public function getRandom(): array
230
    {
231 1
        $data = [];
232 1
        foreach ($this->fields as $f) {
233 1
            $data[$f->getName()] = $f->getDatatype()->getRandom();
234
        }
235 1
        return $data;
236
    }
237
238
    /**
239
     * Returns an array with the default values of each field
240
     *
241
     * @return array Field name => value
242
     */
243
    public function getDefault(): array
244
    {
245
        $data = [];
246
        foreach ($this->fields as $f) {
247
            $data[$f->getName()] = $f->getDatatype()->getDefault();
248
        }
249
        return $data;
250
    }
251
252
    /**
253
     * Parses struct
254
     *
255
     * @param array $data
256
     * @throws Exception
257
     * @return void
258
     */
259 9
    protected function parseStruct(array $data)
260
    {
261 9
        if (!array_key_exists('name', $data)) {
262 1
            throw new Exception('Missing name in model');
263
        }
264 8
        if (!array_key_exists('fields', $data)) {
265 1
            throw new Exception('Missing fields in model');
266
        }
267 7
        $this->name = $data['name'];
268 7
        foreach ($data['fields'] as $fieldName => $fieldData) {
269 7
            $this->fields[$fieldName] = Field::getFromData($fieldName, $fieldData);
270
        }
271 6
    }
272
}
273