Skip to content

Commit 6103344

Browse files
authored
Laravel/Lumen regex support (#4)
* Adds new adapter option to turn off adding regex for integers by default * Also fixes non-static controller syntax for Lumen (class@method instead of class::method) * Reorganization of framework unit tests using a shared set of controller classes
1 parent a4fd84f commit 6103344

33 files changed

+639
-538
lines changed

docs/Configuration.md

+10
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ Configuration keys are available as constants on the `RoutingAdapterInterface` i
6161

6262
Right now these options are available:
6363

64+
**`OPTIONS_AUTO_REGEX`**
65+
---
66+
When enabled the adapter will automatically configure a `[0-9]+` regex for any path elements defined as integer.
67+
68+
Available for:
69+
* All adapters
70+
71+
default: `true`
72+
6473
**`OPTIONS_NAMESPACE`**
6574
---
6675
Specifies a base namespace for all controllers. If set this will be removed from the controller classes passed into the
@@ -88,6 +97,7 @@ use Symfony\Component\Cache\Simple\ArrayCache;
8897
];
8998

9099
$adapterOptions = [
100+
RoutingAdapterInterface::OPTIONS_AUTO_REGEX => false,
91101
RoutingAdapterInterface::OPTIONS_NAMESPACE => 'My\\App',
92102
];
93103

src/Adapters/LaravelRoutingAdapter.php

+34-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Foundation\Application;
66
use Illuminate\Routing\Router;
77
use OpenApi\Annotations\Operation;
8+
use OpenApi\Annotations\Parameter;
89
use Radebatz\OpenApi\Routing\RoutingAdapterInterface;
910

1011
/**
@@ -21,7 +22,10 @@ class LaravelRoutingAdapter implements RoutingAdapterInterface
2122
public function __construct(Application $app, array $options = [])
2223
{
2324
$this->app = $app;
24-
$this->options = $options + [self::OPTIONS_NAMESPACE => 'App\\Http\\Controllers\\'];
25+
$this->options = $options + [
26+
self::OPTIONS_AUTO_REGEX => true,
27+
self::OPTIONS_NAMESPACE => 'App\\Http\\Controllers\\',
28+
];
2529
}
2630

2731
/**
@@ -30,15 +34,36 @@ public function __construct(Application $app, array $options = [])
3034
public function register(Operation $operation, string $controller, array $parameters, array $custom): void
3135
{
3236
$path = $operation->path;
37+
38+
$where = [];
39+
/** @var Parameter $parameter */
40+
foreach ($parameters as $name => $parameter) {
41+
if (!$parameter['required']) {
42+
if (false !== strpos($path, $needle = "/{{$name}}")) {
43+
$path = str_replace("/{{$name}}", "/{{$name}?}", $path);
44+
}
45+
}
46+
47+
switch ($parameter['type']) {
48+
case 'regex':
49+
if ($pattern = $parameter['pattern']) {
50+
$where[$name] = $pattern;
51+
}
52+
break;
53+
54+
case 'integer':
55+
if ($this->options[self::OPTIONS_AUTO_REGEX]) {
56+
$where[$name] = '[0-9]+';
57+
}
58+
break;
59+
}
60+
}
61+
3362
$controller = str_replace('::__invoke', '', $controller);
3463
if ($namespace = $this->options[self::OPTIONS_NAMESPACE]) {
3564
$controller = str_replace($namespace, '', $controller);
3665
}
3766

38-
foreach ($parameters as $parameter) {
39-
// TODO
40-
}
41-
4267
/** @var Router $router */
4368
$router = $this->app->get('router');
4469

@@ -49,8 +74,10 @@ public function register(Operation $operation, string $controller, array $parame
4974
$action['as'] = $custom[static::X_NAME];
5075
}
5176

52-
$route = $router->addRoute(strtoupper($operation->method), $path, $action);
53-
$route->middleware($custom[static::X_MIDDLEWARE]);
77+
$router
78+
->addRoute(strtoupper($operation->method), $path, $action)
79+
->middleware($custom[static::X_MIDDLEWARE])
80+
->where($where);
5481
}
5582

5683
/**

src/Adapters/LumenRoutingAdapter.php

+30-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Laravel\Lumen\Application;
66
use Laravel\Lumen\Routing\Router;
77
use OpenApi\Annotations\Operation;
8+
use OpenApi\Annotations\Parameter;
89
use Radebatz\OpenApi\Routing\RoutingAdapterInterface;
910

1011
/**
@@ -21,7 +22,10 @@ class LumenRoutingAdapter implements RoutingAdapterInterface
2122
public function __construct(Application $app, array $options = [])
2223
{
2324
$this->app = $app;
24-
$this->options = $options + [self::OPTIONS_NAMESPACE => 'App\\Http\\Controllers\\'];
25+
$this->options = $options + [
26+
self::OPTIONS_AUTO_REGEX => true,
27+
self::OPTIONS_NAMESPACE => 'App\\Http\\Controllers\\',
28+
];
2529
}
2630

2731
/**
@@ -35,15 +39,37 @@ public function register(Operation $operation, string $controller, array $parame
3539
$controller = str_replace($namespace, '', $controller);
3640
}
3741

38-
foreach ($parameters as $parameter) {
39-
// TODO
42+
/** @var Parameter $parameter */
43+
foreach ($parameters as $name => $parameter) {
44+
if (!$parameter['required']) {
45+
if (false !== strpos($path, $needle = "/{{$name}}[/{")) {
46+
// multiple optional parameters
47+
$path = preg_replace("#/{{$name}}(\[?.*}\])#", "[/{{$name}}$1]", $path);
48+
} else {
49+
$path = str_replace("/{{$name}}", "[/{{$name}}]", $path);
50+
}
51+
}
52+
53+
switch ($parameter['type']) {
54+
case 'regex':
55+
if ($pattern = $parameter['pattern']) {
56+
$path = str_replace("{{$name}}", "{{$name}:$pattern}", $path);
57+
}
58+
break;
59+
60+
case 'integer':
61+
if ($this->options[self::OPTIONS_AUTO_REGEX]) {
62+
$path = str_replace("{{$name}}", "{{$name}:[0-9]+}", $path);
63+
}
64+
break;
65+
}
4066
}
4167

4268
/** @var Router $router */
4369
$router = $this->app->router;
4470

4571
$action = [
46-
'uses' => $controller
72+
'uses' => str_replace('::', '@', $controller),
4773
];
4874
if ($custom[static::X_NAME]) {
4975
$action['as'] = $custom[static::X_NAME];

src/Adapters/SilexRoutingAdapter.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class SilexRoutingAdapter implements RoutingAdapterInterface
1919
public function __construct(Application $app, array $options = [])
2020
{
2121
$this->app = $app;
22+
$this->options = $options + [
23+
self::OPTIONS_AUTO_REGEX => true,
24+
];
2225
}
2326

2427
/**
@@ -47,7 +50,9 @@ public function register(Operation $operation, string $controller, array $parame
4750
break;
4851

4952
case 'integer':
50-
$controller->assert($name, '[0-9]+');
53+
if ($this->options[self::OPTIONS_AUTO_REGEX]) {
54+
$controller->assert($name, '[0-9]+');
55+
}
5156
break;
5257
}
5358
}

src/Adapters/SlimRoutingAdapter.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ class SlimRoutingAdapter implements RoutingAdapterInterface
1515
/** @var App $app */
1616
protected $app;
1717

18+
/** @var array */
19+
protected $options = [];
20+
1821
public function __construct(App $app, array $options = [])
1922
{
2023
$this->app = $app;
24+
$this->options = $options + [
25+
self::OPTIONS_AUTO_REGEX => true,
26+
];
2127
}
2228

2329
/**
@@ -46,7 +52,9 @@ public function register(Operation $operation, string $controller, array $parame
4652
break;
4753

4854
case 'integer':
49-
$path = str_replace("{{$name}}", "{{$name}:[0-9]+}", $path);
55+
if ($this->options[self::OPTIONS_AUTO_REGEX]) {
56+
$path = str_replace("{{$name}}", "{{$name}:[0-9]+}", $path);
57+
}
5058
break;
5159
}
5260
}

src/RoutingAdapterInterface.php

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface RoutingAdapterInterface
1414
public const X_MIDDLEWARE = 'middleware';
1515

1616
public const OPTIONS_NAMESPACE = 'namespace';
17+
public const OPTIONS_AUTO_REGEX = 'autoregex';
1718

1819
/**
1920
* Register a route.

tests/Frameworks/Fixtures/Laravel/InvokeController.php renamed to tests/Frameworks/Fixtures/InvokeController.php

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
<?php declare(strict_types=1);
22

3-
namespace Radebatz\OpenApi\Routing\Tests\Frameworks\Fixtures\Laravel;
4-
5-
use Illuminate\Http\Request;
6-
use Illuminate\Routing\Controller;
3+
namespace Radebatz\OpenApi\Routing\Tests\Frameworks\Fixtures;
74

85
/**
96
* @OAX\Controller(
107
* prefix="foo",
118
* @OA\Response(response="403", description="Not allowed")
129
* )
1310
*/
14-
class InvokeController extends Controller
11+
class InvokeController
1512
{
1613
/**
1714
* @OA\Get(
18-
* path="/invoke",
15+
* path="/invoke/{name}",
1916
* x={
2017
* "name": "invoke"
2118
* },
2219
* @OA\Response(response="200", description="All good")
2320
* )
2421
*/
25-
public function __invoke(Request $request)
22+
public function __invoke($name)
2623
{
2724
return 'invoke';
2825
}

tests/Frameworks/Fixtures/Laravel/NamedRouteController.php

-29
This file was deleted.

tests/Frameworks/Fixtures/Lumen/NamedRouteController.php

-22
This file was deleted.

tests/Frameworks/Fixtures/Slim/MiddlewareController.php renamed to tests/Frameworks/Fixtures/MiddlewareController.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php declare(strict_types=1);
22

3-
namespace Radebatz\OpenApi\Routing\Tests\Frameworks\Fixtures\Slim;
3+
namespace Radebatz\OpenApi\Routing\Tests\Frameworks\Fixtures;
44

55
class MiddlewareController
66
{
@@ -14,7 +14,7 @@ class MiddlewareController
1414
* @OA\Response(response="200", description="All good")
1515
* )
1616
*/
17-
public static function mw($request, $response)
17+
public function mw($request, $response)
1818
{
1919
return $response->write('MW!');
2020
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Radebatz\OpenApi\Routing\Tests\Frameworks\Fixtures;
4+
5+
class NamedRouteController
6+
{
7+
/**
8+
* @OA\Get(
9+
* path="/getya",
10+
* x={
11+
* "name": "getya"
12+
* },
13+
* @OA\Response(response="200", description="All good")
14+
* )
15+
*/
16+
public function getya()
17+
{
18+
return 'Get ya';
19+
}
20+
21+
/**
22+
* @OA\Get(
23+
* path="/static_getya",
24+
* x={
25+
* "name": "static_getya"
26+
* },
27+
* @OA\Response(response="200", description="All good")
28+
* )
29+
*/
30+
public static function static_getya()
31+
{
32+
return 'Get ya';
33+
}
34+
}

0 commit comments

Comments
 (0)