Completed
Push — master ( ad3b69...02c092 )
by John
09:08 queued 05:48
created

OpenApiRouteLoader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php declare(strict_types = 1);
2
/*
3
 * This file is part of the KleijnWeb\SwaggerBundle package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace KleijnWeb\SwaggerBundle\Routing;
10
11
use KleijnWeb\PhpApi\Descriptions\Description\Operation;
12
use KleijnWeb\PhpApi\Descriptions\Description\Parameter;
13
use KleijnWeb\PhpApi\Descriptions\Description\Repository;
14
use KleijnWeb\PhpApi\Descriptions\Description\Schema\ScalarSchema;
15
use KleijnWeb\PhpApi\Descriptions\Description\Schema\Schema;
16
use KleijnWeb\SwaggerBundle\EventListener\Request\RequestMeta;
17
use Symfony\Component\Config\Loader\Loader;
18
use Symfony\Component\Routing\Route;
19
use Symfony\Component\Routing\RouteCollection;
20
21
/**
22
 * @author John Kleijn <[email protected]>
23
 */
24
class OpenApiRouteLoader extends Loader
25
{
26
    /**
27
     * @var array
28
     */
29
    private $descriptions = [];
30
31
    /**
32
     * @var Repository
33
     */
34
    private $repository;
35
36
    /**
37
     * @param Repository $repository
38
     */
39
    public function __construct(Repository $repository)
40
    {
41
        $this->repository = $repository;
42
    }
43
44
    /**
45
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
46
     *
47
     * @param mixed  $resource
48
     * @param string $type
49
     *
50
     * @return bool
51
     */
52
    public function supports($resource, $type = null)
53
    {
54
        return 'swagger' === $type;
55
    }
56
57
    /**
58
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
59
     *
60
     * @param mixed $resource
61
     * @param null  $type
62
     *
63
     * @return RouteCollection
64
     */
65
    public function load($resource, $type = null): RouteCollection
66
    {
67
        $resource = (string)$resource;
68
        if (in_array($resource, $this->descriptions)) {
69
            throw new \RuntimeException("Resource '$resource' was already loaded");
70
        }
71
72
        $description = $this->repository->get($resource);
73
74
        $routes           = new RouteCollection();
75
        $router           = $description->getExtension('router') ?: 'swagger.controller';
76
        $routerController = $description->getExtension('router-controller');
77
78
        foreach ($description->getPaths() as $pathItem) {
79
            $relativePath = ltrim($pathItem->getPath(), '/');
80
            $resourceName = strpos($relativePath, '/')
81
                ? substr($relativePath, 0, strpos($relativePath, '/'))
82
                : $relativePath;
83
84
            $routerController = $pathItem->getExtension('router-controller') ?: $routerController;
85
86
            foreach ($pathItem->getOperations() as $operation) {
87
                $controllerKey = $this->resolveControllerKey(
88
                    $operation,
89
                    $resourceName,
90
                    $router,
91
                    $routerController
92
                );
93
                $defaults      = [
94
                    '_controller'               => $controllerKey,
95
                    RequestMeta::ATTRIBUTE_URI  => $resource,
96
                    RequestMeta::ATTRIBUTE_PATH => $pathItem->getPath()
97
                ];
98
99
                $route = new Route($pathItem->getPath(), $defaults, $this->resolveRequirements($operation));
100
                $route->setMethods($operation->getMethod());
101
                $routes->add($this->createRouteId($resource, $pathItem->getPath(), $controllerKey), $route);
102
            }
103
        }
104
105
        $this->descriptions[] = $resource;
106
107
        return $routes;
108
    }
109
110
    /**
111
     * @param Operation $operation
112
     *
113
     * @return array
114
     */
115
    private function resolveRequirements(Operation $operation): array
116
    {
117
        $requirements = [];
118
119
        foreach ($operation->getParameters() as $parameter) {
120
            if ($parameter->getIn() === Parameter::IN_PATH
121
                && ($schema = $parameter->getSchema()) instanceof ScalarSchema
122
            ) {
123
                switch ($schema->getType()) {
124
                    case Schema::TYPE_INT:
125
                        $requirements[$parameter->getName()] = '\d+';
126
                        break;
127
                    case Schema::TYPE_STRING:
128
                        if ($pattern = $schema->getPattern()) {
129
                            $requirements[$parameter->getName()] = $pattern;
130
                        } elseif ($enum = $schema->getEnum()) {
131
                            $requirements[$parameter->getName()] = '(' . implode('|', $enum) . ')';
132
                        }
133
                        break;
134
                    default:
135
                        //NOOP
136
                }
137
            }
138
        }
139
140
        return $requirements;
141
    }
142
143
    /**
144
     * @param Operation   $operation
145
     * @param string      $resourceName
146
     * @param string      $router
147
     * @param string|null $routerController
148
     *
149
     * @return string
150
     */
151
    private function resolveControllerKey(
152
        Operation $operation,
153
        string $resourceName,
154
        string $router,
155
        string $routerController = null
156
    ): string
157
    {
158
        $operationName = $operation->getMethod();
159
        $diKey         = "$router.$resourceName";
160
161
        if (0 !== strpos($operation->getId(), '/')) {
162
            if (false !== strpos($operation->getId(), ':')) {
163
                return $operation->getId();
164
            }
165
            $operationName = $operation->getId();
166
        }
167
168
        if ($controller = $operation->getExtension('router-controller')) {
169
            $diKey = $controller;
170
        } elseif ($routerController) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $routerController of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
171
            $diKey = $routerController;
172
        }
173
174
        if ($controllerMethod = $operation->getExtension('router-controller-method')) {
175
            $operationName = $controllerMethod;
176
        }
177
178
        return "$diKey:$operationName";
179
    }
180
181
    /**
182
     * @param string $resource
183
     * @param string $path
184
     *
185
     * @param string $controllerKey
186
     *
187
     * @return string
188
     */
189
    private function createRouteId(string $resource, string $path, string $controllerKey): string
190
    {
191
        list(, $operationName) = explode(':', $controllerKey);
192
        $fileName       = pathinfo($resource, PATHINFO_FILENAME);
193
        $normalizedPath = strtolower(trim(preg_replace('/\W+/', '.', $path), '.'));
194
        $routeName      = "swagger.{$fileName}.$normalizedPath.$operationName";
195
196
        return $routeName;
197
    }
198
}
199