-
Notifications
You must be signed in to change notification settings - Fork 445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow for custom ConfigureRoutes implementations #278
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ final class FastRoute | |
* @param class-string<RouteParser> $routeParser | ||
* @param class-string<DataGenerator> $dataGenerator | ||
* @param class-string<Dispatcher> $dispatcher | ||
* @param class-string<ConfigureRoutes> $routesConfiguration | ||
* @param class-string<ConfigureRoutes> $configureRoutes | ||
* @param class-string<GenerateUri> $uriGenerator | ||
* @param Cache|class-string<Cache>|null $cacheDriver | ||
* @param non-empty-string|null $cacheKey | ||
|
@@ -30,7 +30,7 @@ private function __construct( | |
private readonly string $routeParser, | ||
private readonly string $dataGenerator, | ||
private readonly string $dispatcher, | ||
private readonly string $routesConfiguration, | ||
private readonly string $configureRoutes, | ||
private readonly string $uriGenerator, | ||
private readonly Cache|string|null $cacheDriver, | ||
private readonly ?string $cacheKey, | ||
|
@@ -62,7 +62,7 @@ public function disableCache(): self | |
$this->routeParser, | ||
$this->dataGenerator, | ||
$this->dispatcher, | ||
$this->routesConfiguration, | ||
$this->configureRoutes, | ||
$this->uriGenerator, | ||
null, | ||
null, | ||
|
@@ -80,7 +80,7 @@ public function withCache(Cache|string $driver, string $cacheKey): self | |
$this->routeParser, | ||
$this->dataGenerator, | ||
$this->dispatcher, | ||
$this->routesConfiguration, | ||
$this->configureRoutes, | ||
$this->uriGenerator, | ||
$driver, | ||
$cacheKey, | ||
|
@@ -118,7 +118,24 @@ public function useCustomDispatcher(string $dataGenerator, string $dispatcher): | |
$this->routeParser, | ||
$dataGenerator, | ||
$dispatcher, | ||
$this->routesConfiguration, | ||
$this->configureRoutes, | ||
$this->uriGenerator, | ||
$this->cacheDriver, | ||
$this->cacheKey, | ||
); | ||
} | ||
|
||
/** | ||
* @param class-string<ConfigureRoutes> $configureRoutes | ||
*/ | ||
public function useCustomConfigureRoutes(string $configureRoutes): self | ||
{ | ||
return new self( | ||
$this->routeDefinitionCallback, | ||
$this->routeParser, | ||
$this->dataGenerator, | ||
$this->dispatcher, | ||
$configureRoutes, | ||
$this->uriGenerator, | ||
$this->cacheDriver, | ||
$this->cacheKey, | ||
|
@@ -133,29 +150,24 @@ public function withUriGenerator(string $uriGenerator): self | |
$this->routeParser, | ||
$this->dataGenerator, | ||
$this->dispatcher, | ||
$this->routesConfiguration, | ||
$this->configureRoutes, | ||
$uriGenerator, | ||
$this->cacheDriver, | ||
$this->cacheKey, | ||
); | ||
} | ||
|
||
/** @return ProcessedData */ | ||
private function buildConfiguration(): array | ||
public function processedConfiguration(): array | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is meant to be encapsulated, please don't make it public There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
/me nods along Is there a suggestion for how to inspect the processed routes, so that someone might (for example) dump a list of what paths map to what handlers? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are open issues about dumping that info and I was planning to introduce something that consolidates it in a standard structure for debugging. |
||
{ | ||
if ($this->processedConfiguration !== null) { | ||
return $this->processedConfiguration; | ||
} | ||
|
||
$loader = function (): array { | ||
$configuredRoutes = new $this->routesConfiguration( | ||
new $this->routeParser(), | ||
new $this->dataGenerator(), | ||
); | ||
|
||
($this->routeDefinitionCallback)($configuredRoutes); | ||
|
||
return $configuredRoutes->processedRoutes(); | ||
$configureRoutes = $this->configureRoutes(); | ||
($this->routeDefinitionCallback)($configureRoutes); | ||
return $configureRoutes->processedRoutes(); | ||
}; | ||
|
||
if ($this->cacheDriver === null) { | ||
|
@@ -171,13 +183,21 @@ private function buildConfiguration(): array | |
return $this->processedConfiguration = $cache->get($this->cacheKey, $loader); | ||
} | ||
|
||
public function configureRoutes(): ConfigureRoutes | ||
{ | ||
return new $this->configureRoutes( | ||
new $this->routeParser(), | ||
new $this->dataGenerator(), | ||
); | ||
} | ||
|
||
public function dispatcher(): Dispatcher | ||
{ | ||
return new $this->dispatcher($this->buildConfiguration()); | ||
return new $this->dispatcher($this->processedConfiguration()); | ||
} | ||
|
||
public function uriGenerator(): GenerateUri | ||
{ | ||
return new $this->uriGenerator($this->buildConfiguration()[2]); | ||
return new $this->uriGenerator($this->processedConfiguration()[2]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,6 @@ | |
* @phpstan-import-type ExtraParameters from DataGenerator | ||
* @phpstan-import-type RoutesForUriGeneration from GenerateUri | ||
* @phpstan-import-type ParsedRoutes from RouteParser | ||
* @final | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is has been added on purpose and we'll make this class final on v3 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If I may ask, for what purpose was it added? Making it final means something like CastRouteCollector cannot capture or modify information along the way, without (essentially) copying the entire RouteCollector class to replace it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The goal is to move the focus of people extending components away from the implementation details and give me flexibility on evolving the lib. We can introduce ways to reduce boilerplate in the downstream if that's helpful, I just prefer to favour composition over inheritance for that component. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After having tried my hand at it, composition-over-inheritance is difficult to implement with RouteCollector. After creating a decorator around it and modifying addRoute() to automatically add a route name, I realized that addGroup() calls $this internally, which means FastRoute\RouteCollector will use its own instance methods, and not the decorator instance methods. For example: use FastRoute\RouteCollector as RouteCollector;
use FastRoute\ConfigureRoutes;
class DecoRouteCollector implements ConfigureRoutes
public function __construct(
protected RouteCollector $fastRouteCollector,
) {
}
/**
* @inheritdoc
*/
public function addRoute(string|array $httpMethod, string $route, mixed $handler, array $extraParameters = []): void
{
if (is_string($handler)) {
$extraParameters[self::ROUTE_NAME] ??= $handler;
}
$this->fastRouteCollector->addRoute($httpMethod, $route, $handler, $extraParameters);
}
public function addGroup(string $prefix, callable $callback): void
{
// uses the FastRoute collector addRoute() under the hood,
// not the decorator addRoute() method.
$this->fastRouteCollector->addGroup($prefix, $callback);
}
} I think the easiest solution here still is to make RouteCollector non-final and allow extension. |
||
*/ | ||
class RouteCollector implements ConfigureRoutes | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace FastRoute\Test; | ||
|
||
use FastRoute\RouteCollector; | ||
|
||
class FakeRouteCollector extends RouteCollector | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not needed, as we just need to implement the interface. |
||
{ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having an instance that implements should also be helpful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That instance would be the FakeRouteCollector, referenced above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was multi-tasking and communicated poorly.
I meant to say having the ability to receive an instance. Your point on #280 makes a lot of sense, so we could postpone changes and enhance the design using closures in a separate pr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have posted an alternative solution that incorporates something like "real" DI instead at #282 -- let me know if you think that path (or some variation of it) is viable.