Passed
Branch master (1c67ec)
by Tarmo
02:30
created

ApiDocDescriber   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 197
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 197
ccs 64
cts 64
cp 1
rs 10
c 0
b 0
f 0
wmc 18

7 Methods

Rating   Name   Duplication   Size   Complexity  
A describe() 0 9 3
A getRouteModels() 0 50 1
A __construct() 0 5 1
A routeFilter() 0 19 4
A isRouteSupported() 0 5 3
A isRestApiDocDisabled() 0 7 4
A routeFilterMethod() 0 17 2
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/Rest/Describer/ApiDocDescriber.php
5
 *
6
 * @author  TLe, Tarmo Leppänen <[email protected]>
7
 */
8
namespace App\Rest\Describer;
9
10
use App\Annotation\RestApiDoc;
11
use App\Rest\Doc\RouteModel;
12
use Doctrine\Common\Annotations\AnnotationReader;
13
use EXSyst\Component\Swagger\Swagger;
14
use Nelmio\ApiDocBundle\Describer\DescriberInterface;
15
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
16
use Symfony\Component\Routing\Route;
17
use Symfony\Component\Routing\RouteCollection;
18
use Symfony\Component\Routing\RouterInterface;
19
20
/**
21
 * Class ApiDocDescriber
22
 *
23
 * @package App\Rest\Describer
24
 * @author  TLe, Tarmo Leppänen <[email protected]>
25
 */
26
class ApiDocDescriber implements DescriberInterface
27
{
28
    /**
29
     * @var RouteCollection
30
     */
31
    private $routeCollection;
32
33
    /**
34
     * @var Rest
35
     */
36
    private $rest;
37
38
    /**
39
     * @var AnnotationReader
40
     */
41
    private $annotationReader;
42
43
    /**
44
     * @var Swagger $api
45
     */
46
    private $api;
47
48
    /**
49
     * @param RouterInterface $router
50
     * @param Rest            $rest
51
     *
52
     * @throws \Doctrine\Common\Annotations\AnnotationException
53
     */
54 2
    public function __construct(RouterInterface $router, Rest $rest)
55
    {
56 2
        $this->routeCollection = $router->getRouteCollection();
57 2
        $this->rest = $rest;
58 2
        $this->annotationReader = new AnnotationReader();
59 2
    }
60
61
    /**
62
     * @param Swagger $api
63
     *
64
     * @throws \ReflectionException
65
     * @throws \UnexpectedValueException
66
     * @throws \Psr\Container\ContainerExceptionInterface
67
     * @throws \Psr\Container\NotFoundExceptionInterface
68
     * @throws \Twig_Error_Loader
69
     * @throws \Twig_Error_Runtime
70
     * @throws \Twig_Error_Syntax
71
     */
72 2
    public function describe(Swagger $api): void
73
    {
74 2
        $this->api = $api;
75
76 2
        foreach ($this->getRouteModels() as $routeModel) {
77 2
            $path = $api->getPaths()->get($routeModel->getRoute()->getPath());
78
79 2
            if ($path->hasOperation($routeModel->getHttpMethod())) {
80 2
                $this->rest->createDocs($path->getOperation($routeModel->getHttpMethod()), $routeModel);
81
            }
82
        }
83 2
    }
84
85
    /**
86
     * @return RouteModel[]
87
     *
88
     * @throws \ReflectionException
89
     */
90
    private function getRouteModels(): array
91
    {
92
        /**
93
         * Simple filter lambda function to filter out all but Method class
94
         *
95
         * @param $annotation
96
         *
97
         * @return bool
98
         */
99 2
        $annotationFilterMethod = function ($annotation): bool {
100 2
            return $annotation instanceof Method;
101 2
        };
102
103
        /**
104
         * Simple filter lambda function to filter out all but Method class
105
         *
106
         * @param $annotation
107
         *
108
         * @return bool
109
         */
110 2
        $annotationFilterRoute = function ($annotation): bool {
111 2
            return $annotation instanceof \Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
112 2
        };
113
114 2
        $iterator = function (Route $route) use ($annotationFilterMethod, $annotationFilterRoute): RouteModel {
115 2
            [$controller, $method] = \explode('::', $route->getDefault('_controller'));
116
117 2
            $reflection = new \ReflectionMethod($controller, $method);
118 2
            $methodAnnotations = $this->annotationReader->getMethodAnnotations($reflection);
119 2
            $controllerAnnotations = $this->annotationReader->getClassAnnotations($reflection->getDeclaringClass());
120
121
            /** @var Method $httpMethodAnnotation */
122 2
            $httpMethodAnnotation = \array_values(\array_filter($methodAnnotations, $annotationFilterMethod))[0];
123
124
            /** @var \Sensio\Bundle\FrameworkExtraBundle\Configuration\Route $routeAnnotation */
125 2
            $routeAnnotation = \array_values(\array_filter($controllerAnnotations, $annotationFilterRoute))[0];
126
127 2
            $routeModel = new RouteModel();
128 2
            $routeModel->setController($controller);
129 2
            $routeModel->setMethod($method);
130 2
            $routeModel->setHttpMethod(\mb_strtolower($httpMethodAnnotation->getMethods()[0]));
131 2
            $routeModel->setBaseRoute($routeAnnotation->getPath());
132 2
            $routeModel->setRoute($route);
133 2
            $routeModel->setMethodAnnotations($methodAnnotations);
134 2
            $routeModel->setControllerAnnotations($controllerAnnotations);
135
136 2
            return $routeModel;
137 2
        };
138
139 2
        return \array_map($iterator, \array_filter($this->routeCollection->all(), [$this, 'routeFilter']));
140
    }
141
142
    /**
143
     * @param Route $route
144
     *
145
     * @return bool
146
     *
147
     * @throws \ReflectionException
148
     */
149 2
    private function routeFilter(Route $route): bool
150
    {
151 2
        $output = false;
152
153 2
        if (!$route->hasDefault('_controller') || \mb_strrpos($route->getDefault('_controller'), '::')) {
154 2
            $output = true;
155
        }
156
157 2
        if ($output) {
158 2
            [$controller] = \explode('::', $route->getDefault('_controller'));
159
160 2
            $reflection = new \ReflectionClass($controller);
161
162 2
            $annotations = $this->annotationReader->getClassAnnotations($reflection);
163
164 2
            $this->isRestApiDocDisabled($route, $annotations, $output);
165
        }
166
167 2
        return $this->routeFilterMethod($route, $output);
168
    }
169
170
    /**
171
     * @param Route $route
172
     * @param array $annotations
173
     * @param bool  $disabled
174
     */
175 2
    private function isRestApiDocDisabled(Route $route, array $annotations, bool &$disabled)
176
    {
177 2
        foreach ($annotations as $annotation) {
178 2
            if ($annotation instanceof RestApiDoc && $annotation->disabled) {
179 2
                $disabled = false;
180
181 2
                $this->api->getPaths()->remove($route->getPath());
182
            }
183
        }
184 2
    }
185
186
    /**
187
     * @param Route $route
188
     * @param bool  $output
189
     *
190
     * @return bool
191
     *
192
     * @throws \ReflectionException
193
     */
194 2
    private function routeFilterMethod(Route $route, bool $output): bool
195
    {
196 2
        if ($output) {
197 2
            [$controller, $method] = \explode('::', $route->getDefault('_controller'));
198
199 2
            $reflection = new \ReflectionMethod($controller, $method);
200
201 2
            $annotations = $this->annotationReader->getMethodAnnotations($reflection);
202
203 2
            $supported = [];
204
205 2
            \array_map($this->isRouteSupported($supported), $annotations);
206
207 2
            $output = \count($supported) === 2;
208
        }
209
210 2
        return $output;
211
    }
212
213
    /**
214
     * @param array $supported
215
     *
216
     * @return \Closure
217
     */
218
    private function isRouteSupported(array &$supported): \Closure
219
    {
220 2
        return function ($annotation) use (&$supported) {
221 2
            if ($annotation instanceof RestApiDoc || $annotation instanceof Method) {
222 2
                $supported[] = true;
223
            }
224 2
        };
225
    }
226
}
227