Passed
Push — develop ( cfc1cb...98f18c )
by Csaba
37s
created

SchemaValidator::replaceMode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
namespace Fathomminds\Rest\Schema;
3
4
use Fathomminds\Rest\Schema\TypeValidators\ValidatorFactory;
5
use Fathomminds\Rest\Exceptions\RestException;
6
7
class SchemaValidator
8
{
9
    protected $fields = [];
10
    protected $allowExtraneous = false;
11
    protected $requiredSchemaClass = null;
12
    private $updateMode = false;
13
    private $replaceMode = false;
14
15 57
    public function __construct($requiredSchemaClass = null)
16
    {
17 57
        $this->requiredSchemaClass = $requiredSchemaClass;
18 57
    }
19
20 19
    public function updateMode($updateMode = null)
21
    {
22 19
        if (is_bool($updateMode)) {
23 16
            $this->updateMode = $updateMode;
24
        }
25 19
        return $this->updateMode;
26
    }
27
28 18
    public function replaceMode($replaceMode = null)
29
    {
30 18
        if (is_bool($replaceMode)) {
31 15
            $this->replaceMode = $replaceMode;
32
        }
33 18
        return $this->replaceMode;
34
    }
35
36 19
    public function validate($resource)
37
    {
38 19
        $this->validateResourceType($resource);
39 19
        $extraneousCheck = [];
40 19
        if (!$this->allowExtraneous) {
41 17
            $extraneousCheck = $this->validateExtraneousFields($resource);
42
        }
43 19
        $errors = array_merge(
44 19
            $this->validateRequiredFields($resource),
45 19
            $extraneousCheck,
46 19
            $this->validateFieldTypes($resource)
47
        );
48 19
        if (!empty($errors)) {
49 9
            throw new RestException(
50 9
                'Invalid structure',
51
                [
52 9
                    'schema' => get_class($resource),
53 9
                    'errors' => $errors,
54
                ]
55
            );
56
        }
57 12
    }
58
59 45
    public function allowExtraneous($value)
60
    {
61 45
        $this->allowExtraneous = $value;
62 45
    }
63
64 19
    private function validateResourceType($resource)
65
    {
66 19
        $this->expectObject($resource);
67 19
        $this->objectHasSchemaMethod($resource);
68 19
        $this->objectIsValidSchemaClass($resource);
69 19
    }
70
71 20
    private function expectObject($resource)
72
    {
73 20
        if (gettype($resource) !== 'object') {
74 1
            throw new RestException(
75 1
                'Object expected',
76
                [
77 1
                    'schema' => static::class,
78 1
                    'type' => gettype($resource),
79
                ]
80
            );
81
        }
82 19
    }
83
84 19
    private function objectHasSchemaMethod($resource)
85
    {
86 19
        if (!method_exists($resource, 'schema')) {
87 1
            throw new RestException(
88 1
                'Object must be a correct RestSchema object',
89
                [
90 1
                    'schema' => static::class,
91 1
                    'type' => gettype($resource),
92
                ]
93
            );
94
        }
95 19
    }
96
97 19
    private function objectIsValidSchemaClass($resource)
98
    {
99 19
        if ($this->requiredSchemaClass === null) {
100 14
            return;
101
        }
102 9
        if (get_class($resource) !== $this->requiredSchemaClass) {
103 1
            throw new RestException(
104 1
                'Object must be an instance of the defined SchemaClass',
105
                [
106 1
                    'schema' => static::class,
107 1
                    'type' => gettype($resource),
108
                ]
109
            );
110
        }
111 8
    }
112
113 19
    private function validateRequiredFields($resource)
114
    {
115 19
        $errors = [];
116 19
        if ($this->updateMode()) {
117 2
            return $errors;
118
        }
119 17
        $missingFields = array_diff($this->getRequiredFields($resource), array_keys(get_object_vars($resource)));
120 17
        array_walk($missingFields, function($item) use (&$errors) {
121 5
            $errors[$item] = 'Missing required field';
122 17
        });
123 17
        return $errors;
124
    }
125
126 17
    private function validateExtraneousFields($resource)
127
    {
128 17
        $errors = [];
129 17
        $extraFields = array_diff(array_keys(get_object_vars($resource)), array_keys($resource->schema()));
130 17
        array_walk($extraFields, function($item) use (&$errors) {
131 1
            $errors[$item] = 'Extraneous field';
132 17
        });
133 17
        return $errors;
134
    }
135
136 19
    private function validateFieldTypes($resource)
137
    {
138 19
        $validatorFactory = new ValidatorFactory;
139 19
        $errors = [];
140 19
        foreach ($resource->schema() as $fieldName => $rules) {
141 19
            if (property_exists($resource, $fieldName)) {
142
                try {
143
                    $validatorFactory
144 16
                        ->create($rules, $this->updateMode(), $this->replaceMode())
145 16
                        ->validate($resource->{$fieldName});
146 3
                } catch (RestException $ex) {
147 3
                    $errors[$fieldName]['error'] = $ex->getMessage();
148 3
                    $errors[$fieldName]['details'] = $ex->getDetails();
149
                }
150
            }
151
        }
152 19
        return $errors;
153
    }
154
155 28
    private function filterFields($resource, $paramKey, $paramValue, $checkParamValue = true)
156
    {
157 28
        $fields = [];
158 28
        foreach ($resource->schema() as $fieldName => $params) {
159 28
            if (array_key_exists($paramKey, $params) && $this->isMatchedValue(
160 27
                $checkParamValue,
161 27
                $params[$paramKey],
162 27
                $paramValue
163
            )) {
164 27
                $fields[$fieldName] = $params;
165
            }
166
        }
167 28
        return $fields;
168
    }
169
170 27
    private function isMatchedValue($checkRequired, $value, $valueToMatch)
171
    {
172 27
        if (!$checkRequired) {
173 5
            return true;
174
        }
175 26
        return ($value == $valueToMatch);
176
    }
177
178 1
    public function getFields($resource)
179
    {
180 1
        return $resource->schema();
181
    }
182
183 17
    public function getRequiredFields($resource)
184
    {
185 17
        return array_keys($this->filterFields($resource, 'required', true));
186
    }
187
188 12
    public function getUniqueFields($resource)
189
    {
190 12
        return array_merge(
191 12
            array_keys($this->filterFields($resource, 'unique', true)),
192 12
            $this->getNestedUniqueFieldNames($resource)
193
        );
194
    }
195
196 15
    public function getSchemaFieldsWithDetails($resource)
197
    {
198 15
        return $this->filterFields($resource, 'type', 'schema');
199
    }
200
201 12
    private function getNestedUniqueFieldNames($resource) {
202 12
        $result = [];
203 12
        $schemaFields = $this->getSchemaFieldsWithDetails($resource);
204 12
        array_walk($schemaFields, function($fieldDetails, $fieldName) use (&$result, &$resource) {
205 7
            $nestedResourceClass = $fieldDetails['validator']['class'];
206 7
            $nestedResource = property_exists($resource, $fieldName)
207 2
                ? $resource->{$fieldName}
208 7
                : $nestedResourceClass::cast((object)[]);
209 7
            $nestedUniqueFields = (new SchemaValidator($nestedResourceClass))->getUniqueFields($nestedResource);
210 7
            array_walk($nestedUniqueFields, function($nestedFieldName) use (&$result, &$fieldName) {
211 7
                $result[] = $fieldName . '.' . $nestedFieldName;
212 7
            });
213 12
        });
214 12
        return $result;
215
    }
216
217 5
    public function getFieldsWithDefaults($resource)
218
    {
219 5
        return $this->filterFields($resource, 'default', null, false);
220
    }
221
}
222