Passed
Push — master ( 9794e5...97f672 )
by Dawid
02:48
created

extractControllerResponseValidator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2.0078

Importance

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