Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
98 / 98
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
UpdateSchema
100.00% covered (success)
100.00%
98 / 98
100.00% covered (success)
100.00%
4 / 4
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addValidateResults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 main
100.00% covered (success)
100.00%
93 / 93
100.00% covered (success)
100.00%
1 / 1
17
1<?php
2
3/**
4 * @license MIT
5 * @author hazuki3417<hazuki3417@gmail.com>
6 * @copyright 2022 hazuki3417 all rights reserved.
7 */
8
9namespace Selen\MongoDB\Validator;
10
11use ReflectionClass;
12use Selen\Data\ArrayPath;
13use Selen\MongoDB\Attribute\SchemaLoader;
14use Selen\MongoDB\Attributes\Nest;
15use Selen\MongoDB\Validator\Model\ValidateResult;
16use Selen\MongoDB\Validator\Model\ValidatorResult;
17
18class UpdateSchema implements SchemaValidatorInterface
19{
20    /** @var ArrayPath */
21    public $arrayPath;
22
23    /** @var Model\ValidateResult[] */
24    private $validateResults = [];
25
26    /** @var SchemaLoader */
27    private $schemaLoader;
28
29    public function __construct(SchemaLoader $schemaLoader)
30    {
31        $this->arrayPath    = new ArrayPath();
32        $this->schemaLoader = $schemaLoader;
33    }
34
35    /**
36     * 値の検証を実行します
37     *
38     * @param array<mixed,mixed> $input 検証する値を渡します
39     */
40    public function execute(array $input): ValidatorResult
41    {
42        $this->main($input);
43        return new ValidatorResult(...$this->validateResults);
44    }
45
46    private function addValidateResults(ValidateResult ...$validateResults): void
47    {
48        $this->validateResults = \array_merge($this->validateResults, $validateResults);
49    }
50
51    /**
52     * 値の検証を実行します
53     *
54     * @param array<mixed,mixed> $input 検証する値を渡します
55     */
56    private function main(array $input): void
57    {
58        $this->arrayPath->down();
59
60        /** 検証対象の配列にのみ存在するフィールドを検出する処理 */
61        $inputKeys     = array_keys($input);
62        $definedKeys   = array_keys($this->schemaLoader->fieldLoaders);
63        $undefinedKeys = array_diff($inputKeys, $definedKeys);
64
65        foreach ($undefinedKeys as $undefinedKey) {
66            $this->arrayPath->setCurrentPath($undefinedKey);
67            $this->validateResults[] = new ValidateResult(
68                false,
69                ArrayPath::toString($this->arrayPath->getPaths()),
70                'Undefined key.'
71            );
72        }
73
74        foreach ($input as $key => $value) {
75            $keyValue = [$key => $value];
76
77            if (!\array_key_exists($key, $this->schemaLoader->fieldLoaders)) {
78                // 入力側のkeyが定義側に存在しないとき
79                continue;
80            }
81
82            // 入力側のkeyが定義側に存在したとき
83            $fieldLoader = $this->schemaLoader->fieldLoaders[$key];
84
85            $attributeValueValidates = $fieldLoader->fetchAttributes(ValueValidateInterface::class);
86            $attributeNest           = $fieldLoader->attributeNest;
87
88            $isValueValidateExecute = $attributeValueValidates !== [];
89            $isNestValidExecute     = $attributeNest           !== null;
90
91            $key = $fieldLoader->reflectionProperty->getName();
92            $this->arrayPath->setCurrentPath($key);
93
94            if ($isValueValidateExecute) {
95                // 値のバリデーション処理
96                $valueValidator       = new Value($this->arrayPath, $attributeValueValidates);
97                $valueValidatorResult = $valueValidator->execute($key, $keyValue);
98
99                if ($valueValidatorResult->failure()) {
100                    $this->addValidateResults(...$valueValidatorResult->getValidateResults());
101                    // 値チェックに違反したら控えている処理は実行しない
102                    continue;
103                }
104
105                if (!$isNestValidExecute) {
106                    // 値チェックが成功 + ネストした値のバリデーションをしない
107                    continue;
108                }
109
110                // 値チェックが成功 + ネストした値のバリデーションをする
111                if (!\is_array($value)) {
112                    /**
113                     * ここに到達するとき$valueの値は値チェックで許可されたリテラル値
114                     * そのためネストした値のバリデーションは実行しないようにする
115                     * 例) null | object や null | array object といった属性の指定
116                     */
117                    $isNestValidExecute = false;
118                }
119            }
120
121            if ($isNestValidExecute) {
122                // ネストした値のバリデーション処理
123                /** @var Nest */
124                $nestInstance             = $attributeNest->newInstance();
125                $schemaLoader             = new SchemaLoader(new ReflectionClass($nestInstance->schemaClassName));
126                $nestValidator            = new self($schemaLoader);
127                $nestValidator->arrayPath = $this->arrayPath;
128
129                // ネストした値 = object or array object = inputは1次元または2次元配列を期待
130                if (!\is_array($value)) {
131                    // 値がリテラル型だったとき
132                    $format         = 'Invalid value. Expect "%s" schema for array type';
133                    $mes            = \sprintf($format, $nestInstance->schemaClassName);
134                    $validateResult = new ValidateResult(
135                        false,
136                        ArrayPath::toString($this->arrayPath->getPaths()),
137                        $mes
138                    );
139                    $this->addValidateResults($validateResult);
140                    continue;
141                }
142
143                if ($nestInstance->type === Nest::TYPE_OBJECT) {
144                    // ネストした値がobject形式
145                    $object = $value;
146
147                    if ($object === []) {
148                        // 値が空配列だったとき(ネストしたobject形式の配列を期待しているため、keyは必ず存在する)
149                        $format         = 'Invalid value. Expect "%s" schema for array type';
150                        $mes            = \sprintf($format, $nestInstance->schemaClassName);
151                        $validateResult = new ValidateResult(
152                            false,
153                            ArrayPath::toString($this->arrayPath->getPaths()),
154                            $mes
155                        );
156                        $this->addValidateResults($validateResult);
157                        continue;
158                    }
159
160                    $nestValidatorResult = $nestValidator->execute($object);
161
162                    if ($nestValidatorResult->failure()) {
163                        $this->addValidateResults(...$nestValidatorResult->getValidateResults());
164                    }
165                    continue;
166                }
167
168                // ネストした値がarray object形式
169                $objects = $value;
170
171                $nestValidator->arrayPath->down();
172
173                foreach ($objects as $index => $object) {
174                    $nestValidator->arrayPath->setCurrentPath('[' . $index . ']');
175
176                    if (!\is_array($object)) {
177                        // 値がリテラル型だったとき
178                        $format         = 'Invalid value. Expect "%s" schema for array type';
179                        $mes            = \sprintf($format, $nestInstance->schemaClassName);
180                        $validateResult = new ValidateResult(
181                            false,
182                            ArrayPath::toString($nestValidator->arrayPath->getPaths()),
183                            $mes
184                        );
185                        $this->addValidateResults($validateResult);
186                        continue;
187                    }
188
189                    if ($object === []) {
190                        // 値が空配列だったとき(ネストしたobject形式の配列を期待しているため、keyは必ず存在する)
191                        $format         = 'Invalid value. Expect "%s" schema for array type';
192                        $mes            = \sprintf($format, $nestInstance->schemaClassName);
193                        $validateResult = new ValidateResult(
194                            false,
195                            ArrayPath::toString($this->arrayPath->getPaths()),
196                            $mes
197                        );
198                        $this->addValidateResults($validateResult);
199                        continue;
200                    }
201
202                    $nestValidatorResult = $nestValidator->execute($object);
203
204                    if ($nestValidatorResult->failure()) {
205                        $this->addValidateResults(...$nestValidatorResult->getValidateResults());
206                    }
207                }
208                $nestValidator->arrayPath->up();
209                continue;
210            }
211        }
212        $this->arrayPath->up();
213    }
214}