Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
99 / 99
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
InsertSchema
100.00% covered (success)
100.00%
99 / 99
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%
94 / 94
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 InsertSchema 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     * @param array<mixed,mixed> $input 検証する値を渡します
53     */
54    private function main(array $input): void
55    {
56        $this->arrayPath->down();
57
58        /** 検証対象の配列にのみ存在するフィールドを検出する処理 */
59        $inputKeys     = array_keys($input);
60        $definedKeys   = array_keys($this->schemaLoader->fieldLoaders);
61        $undefinedKeys = array_diff($inputKeys, $definedKeys);
62
63        foreach ($undefinedKeys as $undefinedKey) {
64            $this->arrayPath->setCurrentPath($undefinedKey);
65            $this->validateResults[] = new ValidateResult(
66                false,
67                ArrayPath::toString($this->arrayPath->getPaths()),
68                'Undefined key.'
69            );
70        }
71
72        foreach ($this->schemaLoader->fieldLoaders as $fieldLoader) {
73            $attributeValueValidates = $fieldLoader->fetchAttributes(ValueValidateInterface::class);
74            $attributeNest           = $fieldLoader->attributeNest;
75
76            $isValueValidateExecute = $attributeValueValidates !== [];
77            $isNestValidExecute     = $attributeNest           !== null;
78
79            $key = $fieldLoader->reflectionProperty->getName();
80            $this->arrayPath->setCurrentPath($key);
81
82            $keyValidator       = new Key($this->arrayPath);
83            $keyValidatorResult = $keyValidator->execute($key, $input);
84
85            if ($keyValidatorResult->failure()) {
86                $this->addValidateResults(...$keyValidatorResult->getValidateResults());
87                continue;
88            }
89
90            if ($isValueValidateExecute) {
91                // 値のバリデーション処理
92                $valueValidator       = new Value($this->arrayPath, $attributeValueValidates);
93                $valueValidatorResult = $valueValidator->execute($key, $input);
94
95                if ($valueValidatorResult->failure()) {
96                    $this->addValidateResults(...$valueValidatorResult->getValidateResults());
97                    // 値チェックに違反したら控えている処理は実行しない
98                    continue;
99                }
100
101                if (!$isNestValidExecute) {
102                    // 値チェックが成功 + ネストした値のバリデーションをしない
103                    continue;
104                }
105
106                // 値チェックが成功 + ネストした値のバリデーションをする
107                if (!\is_array($input[$key])) {
108                    /**
109                     * ここに到達するとき$input[$key]の値は値チェックで許可されたリテラル値
110                     * そのためネストした値のバリデーションは実行しないようにする
111                     * 例) null | object や null | array object といった属性の指定
112                     */
113                    $isNestValidExecute = false;
114                }
115            }
116
117            if ($isNestValidExecute) {
118                // ネストした値のバリデーション処理
119                /** @var Nest */
120                $nestInstance             = $attributeNest->newInstance();
121                $schemaLoader             = new SchemaLoader(new ReflectionClass($nestInstance->schemaClassName));
122                $nestValidator            = new self($schemaLoader);
123                $nestValidator->arrayPath = $this->arrayPath;
124
125                // ネストした値 = object or array object = inputは1次元または2次元配列を期待
126                if (!\is_array($input[$key])) {
127                    // 値がリテラル型だったとき
128                    $format         = 'Invalid value. Expect "%s" schema for array type';
129                    $mes            = \sprintf($format, $nestInstance->schemaClassName);
130                    $validateResult = new ValidateResult(
131                        false,
132                        ArrayPath::toString($this->arrayPath->getPaths()),
133                        $mes
134                    );
135                    $this->addValidateResults($validateResult);
136                    continue;
137                }
138
139                if ($nestInstance->type === Nest::TYPE_OBJECT) {
140                    // ネストした値がobject形式
141                    $object = $input[$key];
142
143                    if ($object === []) {
144                        // 値が空配列だったとき(ネストしたobject形式の配列を期待しているため、keyは必ず存在する)
145                        $format         = 'Invalid value. Expect "%s" schema for array type';
146                        $mes            = \sprintf($format, $nestInstance->schemaClassName);
147                        $validateResult = new ValidateResult(
148                            false,
149                            ArrayPath::toString($this->arrayPath->getPaths()),
150                            $mes
151                        );
152                        $this->addValidateResults($validateResult);
153                        continue;
154                    }
155
156                    $nestValidatorResult = $nestValidator->execute($object);
157
158                    if ($nestValidatorResult->failure()) {
159                        $this->addValidateResults(...$nestValidatorResult->getValidateResults());
160                    }
161                    continue;
162                }
163
164                // ネストした値がarray object形式
165                $objects = $input[$key];
166
167                $nestValidator->arrayPath->down();
168
169                foreach ($objects as $index => $object) {
170                    $nestValidator->arrayPath->setCurrentPath('[' . $index . ']');
171
172                    if (!\is_array($object)) {
173                        // 値がリテラル型だったとき
174                        $format         = 'Invalid value. Expect "%s" schema for array type';
175                        $mes            = \sprintf($format, $nestInstance->schemaClassName);
176                        $validateResult = new ValidateResult(
177                            false,
178                            ArrayPath::toString($nestValidator->arrayPath->getPaths()),
179                            $mes
180                        );
181                        $this->addValidateResults($validateResult);
182                        continue;
183                    }
184
185                    if ($object === []) {
186                        // 値が空配列だったとき(ネストしたobject形式の配列を期待しているため、keyは必ず存在する)
187                        $format         = 'Invalid value. Expect "%s" schema for array type';
188                        $mes            = \sprintf($format, $nestInstance->schemaClassName);
189                        $validateResult = new ValidateResult(
190                            false,
191                            ArrayPath::toString($this->arrayPath->getPaths()),
192                            $mes
193                        );
194                        $this->addValidateResults($validateResult);
195                        continue;
196                    }
197
198                    $nestValidatorResult = $nestValidator->execute($object);
199
200                    if ($nestValidatorResult->failure()) {
201                        $this->addValidateResults(...$nestValidatorResult->getValidateResults());
202                    }
203                }
204                $nestValidator->arrayPath->up();
205                continue;
206            }
207        }
208        $this->arrayPath->up();
209    }
210}