Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.46% |
51 / 65 |
|
62.50% |
5 / 8 |
CRAP | |
0.00% |
0 / 1 |
Validator | |
78.46% |
51 / 65 |
|
62.50% |
5 / 8 |
28.29 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
4 | |||
loadSchemas | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
isValid | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getErrors | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 | |||
extractErrors | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 | |||
getCustomMessage | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
getOriginPointer | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getTranslatedPointer | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | namespace BO\Zmsentities\Schema; |
4 | |
5 | use Opis\JsonSchema\{Validator as OpisValidator, ValidationResult, Schema as OpisSchema}; |
6 | use Opis\JsonSchema\Resolvers\FormatResolver; |
7 | use Opis\JsonSchema\Errors\ValidationError as OpisValidationError; |
8 | |
9 | class Validator |
10 | { |
11 | protected $schemaObject; |
12 | protected $schemaData; |
13 | protected $locale; |
14 | protected $validator; |
15 | protected $validationResult; |
16 | |
17 | private static $schemasLoaded = false; |
18 | private static $validatorInstance = null; |
19 | |
20 | public function __construct($data, Schema $schemaObject, $locale) |
21 | { |
22 | $this->schemaData = $data; |
23 | $this->schemaObject = $schemaObject; |
24 | $this->locale = $locale; |
25 | |
26 | // Use static validator instance if available |
27 | if (self::$validatorInstance === null) { |
28 | self::$validatorInstance = new OpisValidator(); |
29 | $formats = self::$validatorInstance->parser()->getFormatResolver(); |
30 | $formats->registerCallable("array", "sameValues", function (array $data): bool { |
31 | return count($data) === 2 && $data[0] === $data[1]; |
32 | }); |
33 | } |
34 | $this->validator = self::$validatorInstance; |
35 | |
36 | // Load schemas only once for each process |
37 | if (!self::$schemasLoaded) { |
38 | $this->loadSchemas(); |
39 | self::$schemasLoaded = true; |
40 | } |
41 | |
42 | $schemaJson = json_decode(json_encode($schemaObject->toJsonObject())); |
43 | $data = json_decode(json_encode($data)); |
44 | $this->validationResult = $this->validator->validate($data, $schemaJson); |
45 | } |
46 | |
47 | private function loadSchemas() |
48 | { |
49 | $schemaPath = realpath(dirname(__FILE__) . '/../../../schema') . '/'; |
50 | $this->validator->resolver()->registerPrefix('schema://', $schemaPath); |
51 | $schemaFiles = glob($schemaPath . '*.json'); |
52 | |
53 | // TODO: Implement persistent caching for schema file reads to reduce redundant disk I/O and improve application performance. Not just for each process. |
54 | |
55 | foreach ($schemaFiles as $schemaFile) { |
56 | $schemaContent = file_get_contents($schemaFile); |
57 | $schemaName = 'schema://' . basename($schemaFile); |
58 | $this->validator->resolver()->registerRaw($schemaContent, $schemaName); |
59 | } |
60 | } |
61 | |
62 | public function isValid() |
63 | { |
64 | return $this->validationResult->isValid(); |
65 | } |
66 | |
67 | public function getErrors() |
68 | { |
69 | if ($this->validationResult->isValid()) { |
70 | return []; |
71 | } |
72 | |
73 | $errorsReducedList = []; |
74 | $error = $this->validationResult->error(); |
75 | |
76 | if ($error) { |
77 | $errorsReducedList = $this->extractErrors($error); |
78 | } |
79 | |
80 | return $errorsReducedList; |
81 | } |
82 | |
83 | private function extractErrors(OpisValidationError $error) |
84 | { |
85 | $errors = []; |
86 | |
87 | $errors[] = new OpisValidationError( |
88 | $error->keyword(), |
89 | $error->schema(), |
90 | $error->data(), |
91 | $this->getCustomMessage($error), |
92 | $error->args(), |
93 | [] |
94 | ); |
95 | |
96 | foreach ($error->subErrors() as $subError) { |
97 | if ($subError instanceof OpisValidationError) { |
98 | $errors = array_merge($errors, $this->extractErrors($subError)); |
99 | } |
100 | } |
101 | |
102 | return $errors; |
103 | } |
104 | |
105 | public function getCustomMessage(OpisValidationError $error) |
106 | { |
107 | $schemaData = $error->schema()->info()->data(); |
108 | if (is_object($schemaData)) { |
109 | $schemaData = (array) $schemaData; |
110 | } |
111 | $property = new \BO\Zmsentities\Helper\Property($schemaData); |
112 | |
113 | if ( |
114 | isset($property['x-locale'][$this->locale]->messages[$error->keyword()]) |
115 | && $property['x-locale'][$this->locale]->messages[$error->keyword()] !== null |
116 | ) { |
117 | return $property['x-locale'][$this->locale]->messages[$error->keyword()]->get(); |
118 | } |
119 | |
120 | return $error->message(); |
121 | } |
122 | |
123 | public static function getOriginPointer(OpisValidationError $error) |
124 | { |
125 | $dataInfo = $error->data(); |
126 | |
127 | if (empty($dataInfo->path())) { |
128 | return '/'; |
129 | } |
130 | |
131 | $pointer = '/' . implode('/', array_map('strval', $dataInfo->path())); |
132 | |
133 | return $pointer; |
134 | } |
135 | |
136 | public function getTranslatedPointer(OpisValidationError $error) |
137 | { |
138 | $schemaData = $error->schema()->info()->data(); |
139 | if (is_object($schemaData)) { |
140 | $schemaData = (array) $schemaData; |
141 | } |
142 | $property = new \BO\Zmsentities\Helper\Property($schemaData); |
143 | |
144 | if ( |
145 | isset($property['x-locale'][$this->locale]->pointer) |
146 | && $property['x-locale'][$this->locale]->pointer !== null |
147 | ) { |
148 | return $property['x-locale'][$this->locale]->pointer->get(self::getOriginPointer($error)); |
149 | } |
150 | |
151 | return self::getOriginPointer($error); |
152 | } |
153 | } |