Skip to content
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 attributes to not override #90

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/Bridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
*/
class Bridge
{
public static function create(?ContainerInterface $container = null): App
{
public static function create(
?ContainerInterface $container = null,
bool $prioritiseAttributesOverParams = false
): App {
$container = $container ?: new Container;

$callableResolver = new InvokerCallableResolver($container);
Expand All @@ -33,13 +35,16 @@ public static function create(?ContainerInterface $container = null): App

$container->set(App::class, $app);

$controllerInvoker = static::createControllerInvoker($container);
$controllerInvoker = static::createControllerInvoker($container, $prioritiseAttributesOverParams);
$app->getRouteCollector()->setDefaultInvocationStrategy($controllerInvoker);

return $app;
}

private static function createControllerInvoker(ContainerInterface $container): ControllerInvoker
/**
* Create an invoker with the default resolvers.
*/
protected static function createInvoker(ContainerInterface $container): Invoker
{
$resolvers = [
// Inject parameters by name first
Expand All @@ -50,8 +55,14 @@ private static function createControllerInvoker(ContainerInterface $container):
new DefaultValueResolver,
];

$invoker = new Invoker(new ResolverChain($resolvers), $container);
return new Invoker(new ResolverChain($resolvers), $container);
}

return new ControllerInvoker($invoker);
/**
* Create a controller invoker with the default resolvers.
*/
protected static function createControllerInvoker(ContainerInterface $container, bool $prioritiseAttributesOverParams): ControllerInvoker
{
return new ControllerInvoker(self::createInvoker($container), $prioritiseAttributesOverParams);
}
}
35 changes: 30 additions & 5 deletions src/ControllerInvoker.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ class ControllerInvoker implements InvocationStrategyInterface
/** @var InvokerInterface */
private $invoker;

public function __construct(InvokerInterface $invoker)
/** @var bool Whether attributes should override parameters */
protected $prioritiseAttributesOverParams;

public function __construct(InvokerInterface $invoker, bool $prioritiseAttributesOverParams)
{
$this->invoker = $invoker;
$this->prioritiseAttributesOverParams = $prioritiseAttributesOverParams;
}

/**
Expand All @@ -24,7 +28,6 @@ public function __construct(InvokerInterface $invoker)
* @param ServerRequestInterface $request The request object.
* @param ResponseInterface $response The response object.
* @param array $routeArguments The route's placeholder arguments
* @return ResponseInterface|string The response from the callable.
*/
public function __invoke(
callable $callable,
Expand All @@ -34,21 +37,43 @@ public function __invoke(
): ResponseInterface {
// Inject the request and response by parameter name
$parameters = [
'request' => self::injectRouteArguments($request, $routeArguments),
'request' => self::injectRouteArguments($request, $routeArguments, $this->prioritiseAttributesOverParams),
'response' => $response,
];
// Inject the route arguments by name
$parameters += $routeArguments;
// Inject the attributes defined on the request
$parameters += $request->getAttributes();

return $this->invoker->call($callable, $parameters);
return $this->processResponse($this->invoker->call($callable, $parameters));
}

private static function injectRouteArguments(ServerRequestInterface $request, array $routeArguments): ServerRequestInterface
/**
* Allow for child classes to process the response.
*
* @param ResponseInterface|string $response The response from the callable.
* @return ResponseInterface|string The processed response
*/
protected function processResponse($response)
{
return $response;
}

/**
* Inject route arguments into the request.
*
* @param array $routeArguments
*/
protected static function injectRouteArguments(
ServerRequestInterface $request,
array $routeArguments,
bool $prioritiseAttributesOverParams
): ServerRequestInterface {
$requestWithArgs = $request;
foreach ($routeArguments as $key => $value) {
if ($prioritiseAttributesOverParams && $request->getAttribute($key) !== null) {
continue;
}
$requestWithArgs = $requestWithArgs->withAttribute($key, $value);
}
return $requestWithArgs;
Expand Down
20 changes: 20 additions & 0 deletions tests/RoutingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,26 @@ public function injects_route_placeholder_over_request_attribute()
$this->assertEquals('Hello matt', (string) $response->getBody());
}

/**
* @test
*/
public function prefers_request_attribute_over_route_placeholder_if_configured()
{
$app = Bridge::create(null, true);
$app->add(function (ServerRequestInterface $request, RequestHandlerInterface $next) {
return $next->handle($request->withAttribute('name', 'Bob'));
});
$app->get('/{name}', function ($name, $request, $response) {
$response->getBody()->write('Hello ' . $name . ', from ' . $request->getAttribute('name'));
return $response;
});

$response = $app->handle(RequestFactory::create('/matt'));

// The route placeholder does not override the request attribute but the placeholder is still provided to the controller
$this->assertEquals('Hello matt, from Bob', (string) $response->getBody());
}

/**
* @test
*/
Expand Down