Skip to content

Commit 833d18d

Browse files
committed
feat: support explicit bindings for router
1 parent 9da19f8 commit 833d18d

File tree

2 files changed

+79
-13
lines changed

2 files changed

+79
-13
lines changed

src/router/src/Middleware/SubstituteBindings.php

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Hypervel\Router\Contracts\UrlRoutable;
1616
use Hypervel\Router\Exceptions\BackedEnumCaseNotFoundException;
1717
use Hypervel\Router\Exceptions\UrlRoutableNotFoundException;
18+
use Hypervel\Router\Router;
1819
use Psr\Container\ContainerInterface;
1920
use Psr\Http\Message\ResponseInterface;
2021
use Psr\Http\Message\ServerRequestInterface;
@@ -25,8 +26,15 @@
2526

2627
class SubstituteBindings implements MiddlewareInterface
2728
{
28-
public function __construct(protected ContainerInterface $container)
29-
{
29+
/**
30+
* All of the resolved definitions by dispatched routes.
31+
*/
32+
protected array $resolvedDefinitions = [];
33+
34+
public function __construct(
35+
protected ContainerInterface $container,
36+
protected Router $router
37+
) {
3038
}
3139

3240
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
@@ -38,13 +46,11 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
3846
return $handler->handle($request);
3947
}
4048

41-
if (strpos($dispatched->handler->route, '{') === false) {
49+
if (! $params = $dispatched->params) {
4250
return $handler->handle($request);
4351
}
4452

4553
$definitions = $this->getDefinitions($dispatched->handler->callback);
46-
$params = $dispatched->params;
47-
4854
$dispatched->params = $this->substituteBindings($definitions, $params);
4955

5056
return $handler->handle($request);
@@ -71,11 +77,14 @@ protected function getDefinitions(array|Closure|string $callback): array
7177
*/
7278
protected function getClosureDefinitions(Closure $callback): array
7379
{
74-
if (! $this->container->has(ClosureDefinitionCollectorInterface::class)) {
75-
return [];
80+
$signature = spl_object_hash($callback);
81+
if ($definitions = $this->resolvedDefinitions[$signature] ?? null) {
82+
return $definitions;
7683
}
7784

78-
return $this->container->get(ClosureDefinitionCollectorInterface::class)->getParameters($callback);
85+
return $this->resolvedDefinitions[$signature] = $this->container->has(ClosureDefinitionCollectorInterface::class)
86+
? $this->container->get(ClosureDefinitionCollectorInterface::class)->getParameters($callback)
87+
: [];
7988
}
8089

8190
/**
@@ -86,7 +95,12 @@ protected function getMethodDefinitions(array $callback): array
8695
$controller = $callback[0];
8796
$action = $callback[1];
8897

89-
return $this->container->get(MethodDefinitionCollectorInterface::class)->getParameters($controller, $action);
98+
$signature = "{$controller}::{$action}";
99+
if ($definitions = $this->resolvedDefinitions[$signature] ?? null) {
100+
return $definitions;
101+
}
102+
103+
return $this->resolvedDefinitions[$signature] = $this->container->get(MethodDefinitionCollectorInterface::class)->getParameters($controller, $action);
90104
}
91105

92106
/**
@@ -100,7 +114,10 @@ protected function substituteBindings(array $definitions, array $params): array
100114
if (! array_key_exists($name, $params)) {
101115
continue;
102116
}
103-
117+
if ($binding = $this->router->getExplicitBinding($name)) {
118+
$params[$name] = $binding($params[$name]);
119+
continue;
120+
}
104121
if ($binding = $this->resolveBinding($definition, $params, $name)) {
105122
$params[$name] = $binding;
106123
}
@@ -113,7 +130,7 @@ protected function substituteBindings(array $definitions, array $params): array
113130
* @throws ModelNotFoundException
114131
* @throws BackedEnumCaseNotFoundException
115132
*/
116-
protected function resolveBinding(ReflectionType $definition, array $params, string $name)
133+
protected function resolveBinding(ReflectionType $definition, array $params, string $name): mixed
117134
{
118135
$class = $definition->getName();
119136

@@ -122,12 +139,17 @@ protected function resolveBinding(ReflectionType $definition, array $params, str
122139
}
123140

124141
if (is_a($class, Model::class, true)) {
125-
return $this->resolveModel($class, $params[$name]);
142+
return $this->resolveModel(
143+
$this->router->getModelBinding($name) ?: $class,
144+
$params[$name]
145+
);
126146
}
127147

128148
if (is_a($class, BackedEnum::class, true)) {
129149
return $this->resolveBackedEnum($class, $params[$name]);
130150
}
151+
152+
return null;
131153
}
132154

133155
/**

src/router/src/Router.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
use Closure;
88
use Hyperf\Context\ApplicationContext;
9+
use Hyperf\Database\Model\Model;
910
use Hyperf\HttpServer\Router\DispatcherFactory;
11+
use Hyperf\HttpServer\Router\RouteCollector;
1012
use RuntimeException;
1113

1214
/**
@@ -16,6 +18,20 @@ class Router
1618
{
1719
protected string $serverName = 'http';
1820

21+
/**
22+
* Customized route parameters for model bindings.
23+
*
24+
* @var array<string, class-string>
25+
*/
26+
protected array $modelBindings = [];
27+
28+
/**
29+
* Customized route parameters for explicit bindings.
30+
*
31+
* @var array<string, Closure>
32+
*/
33+
protected array $explicitBindings = [];
34+
1935
public function __construct(protected DispatcherFactory $dispatcherFactory)
2036
{
2137
}
@@ -59,12 +75,40 @@ protected function registerRouteFile(string $routeFile): Closure
5975
return fn () => require $routeFile;
6076
}
6177

62-
public function getRouter()
78+
public function getRouter(): RouteCollector
6379
{
6480
return $this->dispatcherFactory
6581
->getRouter($this->serverName);
6682
}
6783

84+
public function model(string $param, string $modelClass): void
85+
{
86+
if (! class_exists($modelClass)) {
87+
throw new RuntimeException("Model class `{$modelClass}` does not exist.");
88+
}
89+
90+
if (! is_subclass_of($modelClass, Model::class)) {
91+
throw new RuntimeException("Model class `{$modelClass}` must be a subclass of `Model`.");
92+
}
93+
94+
$this->modelBindings[$param] = $modelClass;
95+
}
96+
97+
public function bind(string $param, Closure $callback): void
98+
{
99+
$this->explicitBindings[$param] = $callback;
100+
}
101+
102+
public function getModelBinding(string $param): ?string
103+
{
104+
return $this->modelBindings[$param] ?? null;
105+
}
106+
107+
public function getExplicitBinding(string $param): ?Closure
108+
{
109+
return $this->explicitBindings[$param] ?? null;
110+
}
111+
68112
public static function __callStatic(string $name, array $arguments)
69113
{
70114
return ApplicationContext::getContainer()

0 commit comments

Comments
 (0)