Completed
Push — master ( f61a34...afd70b )
by Dawid
02:55
created

DataCollector::getKnownRouteResponseSchemas()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiechu\SymfonyCommonsBundle\Service;
6
7
use Doctrine\Common\Annotations\Reader;
8
use Spiechu\SymfonyCommonsBundle\Annotation\Controller\ControllerAnnotationExtractorTrait;
9
use Spiechu\SymfonyCommonsBundle\Annotation\Controller\ResponseSchemaValidator;
10
use Spiechu\SymfonyCommonsBundle\Event\ApiVersion\ApiVersionSetEvent;
11
use Spiechu\SymfonyCommonsBundle\Event\ApiVersion\Events as ApiVersionEvents;
12
use Spiechu\SymfonyCommonsBundle\Event\ResponseSchemaCheck\CheckResult;
13
use Spiechu\SymfonyCommonsBundle\Event\ResponseSchemaCheck\Events as ResponseSchemaCheckEvents;
14
use Spiechu\SymfonyCommonsBundle\EventListener\GetMethodOverrideListener;
15
use Spiechu\SymfonyCommonsBundle\EventListener\RequestSchemaValidatorListener;
16
use Spiechu\SymfonyCommonsBundle\Service\SchemaValidator\ValidationViolation;
17
use Spiechu\SymfonyCommonsBundle\Twig\DataCollectorExtension;
18
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\HttpFoundation\Response;
21
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
22
use Symfony\Component\HttpKernel\DataCollector\DataCollector as BaseDataCollector;
23
use Symfony\Component\Routing\Route;
24
use Symfony\Component\Routing\RouterInterface;
25
26
class DataCollector extends BaseDataCollector implements EventSubscriberInterface
27
{
28
    use ControllerAnnotationExtractorTrait;
29
30
    public const COLLECTOR_NAME = 'spiechu_symfony_commons.data_collector';
31
32
    protected const DATA_GLOBAL_RESPONSE_SCHEMAS = 'global_response_schemas';
33
    protected const DATA_GLOBAL_NON_EXISTING_SCHEMA_FILES = 'global_non_existing_schema_files';
34
35
    protected const DATA_GET_METHOD_OVERRIDE = 'get_method_override';
36
37
    protected const DATA_KNOWN_ROUTE_RESPONSE_SCHEMAS = 'known_route_response_schemas';
38
    protected const DATA_VALIDATION_RESULT = 'validation_result';
39
40
    protected const DATA_API_VERSION_SET = 'api_version_set';
41
42
    /**
43
     * @var RouterInterface
44
     */
45
    protected $router;
46
47
    /**
48
     * @var Reader
49
     */
50
    protected $reader;
51
52
    /**
53
     * @var ControllerResolverInterface
54
     */
55
    protected $controllerResolver;
56
57
    /**
58
     * @var DataCollectorExtension
59
     */
60
    protected $dataCollectorExtension;
61
62
    /**
63
     * @param RouterInterface             $router
64
     * @param Reader                      $reader
65
     * @param ControllerResolverInterface $controllerResolver
66
     * @param DataCollectorExtension      $dataCollectorExtension
67
     */
68 18
    public function __construct(
69
        RouterInterface $router,
70
        Reader $reader,
71
        ControllerResolverInterface $controllerResolver,
72
        DataCollectorExtension $dataCollectorExtension
73
    ) {
74 18
        $this->router = $router;
75 18
        $this->reader = $reader;
76 18
        $this->controllerResolver = $controllerResolver;
77 18
        $this->dataCollectorExtension = $dataCollectorExtension;
78 18
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 16
    public function collect(Request $request, Response $response, \Exception $exception = null): void
84
    {
85 16
        $this->data[static::DATA_KNOWN_ROUTE_RESPONSE_SCHEMAS] = $request->attributes->get(RequestSchemaValidatorListener::ATTRIBUTE_RESPONSE_SCHEMAS);
86 16
        $this->data[static::DATA_GET_METHOD_OVERRIDE] = $request->attributes->get(GetMethodOverrideListener::ATTRIBUTE_REQUEST_GET_METHOD_OVERRIDE);
87
88 16
        $this->extractRoutesData();
89 16
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 18
    public function getName(): string
95
    {
96 18
        return static::COLLECTOR_NAME;
97
    }
98
99
    /**
100
     * Forward compatibility with Symfony 3.4.
101
     */
102
    public function reset(): void
103
    {
104
        $this->data = [];
105
    }
106
107
    /**
108
     * @return array
109
     */
110
    public function getGlobalResponseSchemas(): array
111
    {
112
        return $this->data[static::DATA_GLOBAL_RESPONSE_SCHEMAS];
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 8
    public static function getSubscribedEvents(): array
119
    {
120
        return [
121 8
            ResponseSchemaCheckEvents::CHECK_RESULT => ['onCheckResult', 100],
122 8
            ApiVersionEvents::API_VERSION_SET => ['onApiVersionSet', 100],
123
        ];
124
    }
125
126
    /**
127
     * @param CheckResult $checkResult
128
     */
129 6
    public function onCheckResult(CheckResult $checkResult): void
130
    {
131 6
        $this->data[static::DATA_VALIDATION_RESULT] = $checkResult->getValidationResult();
132 6
    }
133
134
    /**
135
     * @param ApiVersionSetEvent $apiVersionSetEvent
136
     */
137 9
    public function onApiVersionSet(ApiVersionSetEvent $apiVersionSetEvent): void
138
    {
139 9
        $this->data[static::DATA_API_VERSION_SET] = $apiVersionSetEvent->getApiVersion();
140 9
    }
141
142
    /**
143
     * @return array
144
     */
145 12
    public function getKnownRouteResponseSchemas(): array
146
    {
147 12
        return empty($this->data[static::DATA_KNOWN_ROUTE_RESPONSE_SCHEMAS]) ? [] : $this->data[static::DATA_KNOWN_ROUTE_RESPONSE_SCHEMAS];
148
    }
149
150
    /**
151
     * @return int
152
     */
153
    public function getKnownRouteResponseSchemaNumber(): int
154
    {
155 12
        return array_reduce($this->getKnownRouteResponseSchemas(), function (int $counter, array $formatSchemas) {
156 2
            return $counter + \count($formatSchemas);
157 12
        }, 0);
158
    }
159
160
    /**
161
     * @return int
162
     */
163 10
    public function getAllPotentialErrorsCount(): int
164
    {
165 10
        return \count($this->getValidationErrors()) + $this->getGlobalNonExistingSchemaFiles();
166
    }
167
168
    /**
169
     * @return bool
170
     */
171 13
    public function responseWasChecked(): bool
172
    {
173 13
        return array_key_exists(static::DATA_VALIDATION_RESULT, $this->data);
174
    }
175
176
    /**
177
     * @return bool
178
     */
179 10
    public function apiVersionWasSet(): bool
180
    {
181 10
        return array_key_exists(static::DATA_API_VERSION_SET, $this->data);
182
    }
183
184
    /**
185
     * @return null|string
186
     */
187 10
    public function getApiVersion(): ?string
188
    {
189 10
        return $this->apiVersionWasSet() ? $this->data[static::DATA_API_VERSION_SET] : null;
190
    }
191
192
    /**
193
     * @return ValidationViolation[]
194
     */
195 10
    public function getValidationErrors(): array
196
    {
197 10
        if (!$this->responseWasChecked()) {
198 10
            return [];
199
        }
200
201
        return $this->data[static::DATA_VALIDATION_RESULT]->getViolations();
202
    }
203
204
    /**
205
     * @return bool
206
     */
207 1
    public function isGetMethodWasOverridden(): bool
208
    {
209 1
        return !empty($this->data[static::DATA_GET_METHOD_OVERRIDE]);
210
    }
211
212
    /**
213
     * @return null|string
214
     */
215 1
    public function getGetMethodOverriddenTo(): ?string
216
    {
217 1
        return $this->isGetMethodWasOverridden() ? $this->data[static::DATA_GET_METHOD_OVERRIDE] : null;
218
    }
219
220 16
    protected function extractRoutesData(): void
221
    {
222 16
        $this->data[static::DATA_GLOBAL_RESPONSE_SCHEMAS] = [];
223 16
        $this->data[static::DATA_GLOBAL_NON_EXISTING_SCHEMA_FILES] = 0;
224
225
        /** @var Route $route */
226
        /** @var string $controllerDefinition */
227
        /** @var ResponseSchemaValidator $responseSchemaValidator */
228 16
        foreach ($this->getRouteCollectionGenerator() as $name => [$route, $controllerDefinition, $responseSchemaValidator]) {
229 14
            $annotationSchemas = $responseSchemaValidator->getSchemas();
230
231 14
            $this->data[static::DATA_GLOBAL_RESPONSE_SCHEMAS][] = [
232 14
                'path' => $route->getPath(),
233 14
                'name' => $name,
234 14
                'controller' => $controllerDefinition,
235 14
                'response_schemas' => $annotationSchemas,
236
            ];
237
238 14
            $this->determineGlobalNonExistingSchemaFiles($annotationSchemas);
239
        }
240 16
    }
241
242
    /**
243
     * @param array $annotationSchemas
244
     */
245 14
    protected function determineGlobalNonExistingSchemaFiles(array $annotationSchemas)
246
    {
247
        /** @var array $schemas */
248 14
        foreach ($annotationSchemas as $schemas) {
249 14
            foreach ($schemas as $schema) {
250 14
                if (!$this->dataCollectorExtension->schemaFileExists($schema)) {
251 14
                    ++$this->data[static::DATA_GLOBAL_NON_EXISTING_SCHEMA_FILES];
252
                }
253
            }
254
        }
255 14
    }
256
257
    /**
258
     * @throws \Exception
259
     *
260
     * @return \Generator string $name => [Route $route, string $controllerDefinition, ResponseSchemaValidator $methodAnnotation]
261
     */
262 16
    protected function getRouteCollectionGenerator(): \Generator
263
    {
264 16
        foreach ($this->router->getRouteCollection() as $name => $route) {
265 16
            if (empty($controllerDefinition = $route->getDefault('_controller'))) {
266
                continue;
267
            }
268
269 16
            $methodAnnotation = $this->extractControllerResponseValidator($controllerDefinition);
270 16
            if (!$methodAnnotation instanceof ResponseSchemaValidator) {
271 16
                continue;
272
            }
273
274 14
            yield $name => [$route, $controllerDefinition, $methodAnnotation];
275
        }
276 16
    }
277
278
    /**
279
     * @param string $controllerDefinition
280
     *
281
     * @throws \Exception
282
     *
283
     * @return null|ResponseSchemaValidator
284
     */
285 16
    protected function extractControllerResponseValidator(string $controllerDefinition): ?ResponseSchemaValidator
286
    {
287 16
        $resolvedController = $this->controllerResolver->getController(new Request(
288 16
            [],
289 16
            [],
290
            [
291 16
                '_controller' => $controllerDefinition,
292
            ]
293
        ));
294
295 16
        if (!\is_callable($resolvedController)) {
296
            return null;
297
        }
298
299 16
        return $this->getMethodAnnotationFromController(/* @scrutinizer ignore-type */$resolvedController, ResponseSchemaValidator::class);
300
    }
301
302
    /**
303
     * @return int
304
     */
305 10
    protected function getGlobalNonExistingSchemaFiles(): int
306
    {
307 10
        return empty($this->data[static::DATA_GLOBAL_NON_EXISTING_SCHEMA_FILES]) ? 0 : \count($this->data[static::DATA_GLOBAL_NON_EXISTING_SCHEMA_FILES]);
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313 16
    protected function getAnnotationReader(): Reader
314
    {
315 16
        return $this->reader;
316
    }
317
}
318