Passed
Push — master ( 7a0aab...f61a34 )
by Dawid
02:57
created

determineGlobalNonExistingSchemaFiles()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

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