Completed
Push — master ( 30ef78...8f8c1f )
by Niels
16s queued 10s
created

RouteLoader   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 178
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 54
dl 0
loc 178
rs 10
c 0
b 0
f 0
wmc 13

8 Methods

Rating   Name   Duplication   Size   Complexity  
A isValidRequestMethod() 0 13 1
A addDefaultRoutes() 0 10 1
A supports() 0 3 1
A __construct() 0 3 1
A load() 0 16 2
A parseOperation() 0 32 3
A parsePathItem() 0 14 3
A createRouteName() 0 5 1
1
<?php
2
3
/*
4
 * This file is part of the OpenapiBundle package.
5
 *
6
 * (c) Niels Nijens <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Nijens\OpenapiBundle\Routing;
13
14
use Nijens\OpenapiBundle\Controller\CatchAllController;
15
use Nijens\OpenapiBundle\Json\JsonPointer;
16
use Nijens\OpenapiBundle\Json\SchemaLoaderInterface;
17
use stdClass;
18
use Symfony\Component\Config\Loader\Loader;
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\Routing\Route;
21
use Symfony\Component\Routing\RouteCollection;
22
23
/**
24
 * Loads the paths from an OpenAPI specification as routes.
25
 *
26
 * @author Niels Nijens <[email protected]>
27
 */
28
class RouteLoader extends Loader
29
{
30
    /**
31
     * @var string
32
     */
33
    const TYPE = 'openapi';
34
35
    /**
36
     * @var SchemaLoaderInterface
37
     */
38
    private $schemaLoader;
39
40
    /**
41
     * Constructs a new RouteLoader instance.
42
     *
43
     * @param SchemaLoaderInterface $schemaLoader
44
     */
45
    public function __construct(SchemaLoaderInterface $schemaLoader)
46
    {
47
        $this->schemaLoader = $schemaLoader;
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function supports($resource, $type = null)
54
    {
55
        return self::TYPE === $type;
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function load($resource, $type = null): RouteCollection
62
    {
63
        $schema = $this->schemaLoader->load($resource);
64
65
        $jsonPointer = new JsonPointer($schema);
66
67
        $routeCollection = new RouteCollection();
68
69
        $paths = get_object_vars($jsonPointer->get('/paths'));
70
        foreach ($paths as $path => $pathItem) {
71
            $this->parsePathItem($jsonPointer, $resource, $routeCollection, $path, $pathItem);
72
        }
73
74
        $this->addDefaultRoutes($routeCollection, $resource);
75
76
        return $routeCollection;
77
    }
78
79
    /**
80
     * Parses a path item of the OpenAPI specification for a route.
81
     *
82
     * @param JsonPointer     $jsonPointer
83
     * @param string          $resource
84
     * @param RouteCollection $collection
85
     * @param string          $path
86
     * @param stdClass        $pathItem
87
     */
88
    private function parsePathItem(
89
        JsonPointer $jsonPointer,
90
        string $resource,
91
        RouteCollection $collection,
92
        string $path,
93
        stdClass $pathItem
94
    ): void {
95
        $operations = get_object_vars($pathItem);
96
        foreach ($operations as $requestMethod => $operation) {
97
            if ($this->isValidRequestMethod($requestMethod) === false) {
98
                return;
99
            }
100
101
            $this->parseOperation($jsonPointer, $resource, $collection, $path, $requestMethod, $operation);
102
        }
103
    }
104
105
    /**
106
     * Parses an operation of the OpenAPI specification for a route.
107
     *
108
     * @param JsonPointer     $jsonPointer
109
     * @param string          $resource
110
     * @param RouteCollection $collection
111
     * @param string          $path
112
     * @param string          $requestMethod
113
     * @param stdClass        $operation
114
     */
115
    private function parseOperation(
116
        JsonPointer $jsonPointer,
117
        string $resource,
118
        RouteCollection $collection,
119
        string $path,
120
        string $requestMethod,
121
        stdClass $operation
122
    ): void {
123
        $defaults = array();
124
        $options = array(
125
            'openapi_resource' => $resource,
126
        );
127
128
        if (isset($operation->{'x-symfony-controller'})) {
129
            $defaults['_controller'] = $operation->{'x-symfony-controller'};
130
        }
131
132
        if (isset($operation->requestBody->content->{'application/json'})) {
133
            $options['openapi_json_request_validation_pointer'] = sprintf(
134
                '/paths/%s/%s/requestBody/content/%s/schema',
135
                $jsonPointer->escape($path),
136
                $requestMethod,
137
                $jsonPointer->escape('application/json')
138
            );
139
        }
140
141
        $route = new Route($path, $defaults, array(), $options);
142
        $route->setMethods($requestMethod);
143
144
        $collection->add(
145
            $this->createRouteName($path, $requestMethod),
146
            $route
147
        );
148
    }
149
150
    /**
151
     * Returns true when the provided request method is a valid request method in the OpenAPI specification.
152
     *
153
     * @param string $requestMethod
154
     *
155
     * @return bool
156
     */
157
    private function isValidRequestMethod(string $requestMethod): bool
158
    {
159
        return in_array(
160
            strtoupper($requestMethod),
161
            array(
162
                Request::METHOD_GET,
163
                Request::METHOD_PUT,
164
                Request::METHOD_POST,
165
                Request::METHOD_DELETE,
166
                Request::METHOD_OPTIONS,
167
                Request::METHOD_HEAD,
168
                Request::METHOD_PATCH,
169
                Request::METHOD_TRACE,
170
            )
171
        );
172
    }
173
174
    /**
175
     * Creates a route name based on the path and request method.
176
     *
177
     * @param string $path
178
     * @param string $requestMethod
179
     *
180
     * @return string
181
     */
182
    private function createRouteName(string $path, string $requestMethod): string
183
    {
184
        return sprintf('%s_%s',
185
            trim(preg_replace('/[^a-zA-Z0-9]+/', '_', $path), '_'),
186
            $requestMethod
187
        );
188
    }
189
190
    /**
191
     * Adds a catch-all route to handle responses for non-existing routes.
192
     *
193
     * @param RouteCollection $collection
194
     * @param string          $resource
195
     */
196
    private function addDefaultRoutes(RouteCollection $collection, string $resource)
197
    {
198
        $catchAllRoute = new Route(
199
            '/{catchall}',
200
            array('_controller' => CatchAllController::CONTROLLER_REFERENCE),
201
            array('catchall' => '.+'),
202
            array('openapi_resource' => $resource)
203
        );
204
205
        $collection->add('catch_all', $catchAllRoute);
206
    }
207
}
208