Closed BusterNeece closed 4 months ago
~I think that would go beyond the scope of Slim.~
By default, Slim controllers have a strict signature: $request, $response, array $args
. The PHP-DI bridge offers an alternative, for strictly typed parameters such as the route placeholders.
But generally, this is also quite easy to write it like this (without RequestResponseNamedArgs):
$app->get('/test/{id}', function($request, $response, array $args) {
$id = (int)$args['id'];
// ...
});
@odan The RequestResponseNamedArgs
controller invocation strategy is provided by, and part of, the Slim repo. While it isn't the default, it's certainly within the scope of Slim.
That's right, obviously this feature was added in version 4.1. https://github.com/slimphp/Slim/pull/3132 I was not aware of that, and the reference is not yet included in the documentation.
I guess this specific feature (typed parameters) is not supported at the moment, because the ... operator in combination with declare(strict_types=1);
requires the correct type. https://3v4l.org/SFgmi
~I guess the easiest solution would be to remove declare(strict_types=1);
in your controller files and let PHP do the type casting.~ The more complex solution would be to add some kind of reflection based type resolver and caster to the RequestResponseNamedArgs
class.
@odan Like I mentioned in the original issue, the type strictness of a call is determined by the calling file, not the called file, so removing strictness on my controller classes wouldn't affect anything.
This will be fixed in Slim 5. In Slim 4 you try to add the following strategy:
Installation:
composer require php-di/invoker
Add this custom strategy class:
File: src/Strategies/RequestResponseTypedArgs.php
<?php
declare(strict_types=1);
namespace App\Strategies;
use Invoker\InvokerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Interfaces\InvocationStrategyInterface;
final class RequestResponseTypedArgs implements InvocationStrategyInterface
{
private InvokerInterface $invoker;
public function __construct(InvokerInterface $invoker)
{
$this->invoker = $invoker;
}
public function __invoke(
callable $callable,
ServerRequestInterface $request,
ResponseInterface $response,
array $routeArguments
): ResponseInterface {
$routeArguments['request'] ??= $request;
$routeArguments['response'] ??= $response;
return $this->invoker->call($callable, $routeArguments);
}
}
Set as default invocation strategy:
use App\Strategies\RequestResponseTypedArgs;
use Invoker\Invoker;
// ...
$container = $app->getContainer();
$strategy = new RequestResponseTypedArgs(new Invoker(null, $container));
$routeCollector = $app->getRouteCollector();
$routeCollector->setDefaultInvocationStrategy($strategy);
// ...
I was in the process of migrating my web application to use the Slim
RequestResponseNamedArgs
controller invoker in favor of the previous PHP-DI/Invoker-based one I was using, and I had expected that the transition would be a fairly straightforward one, but I'm running into one big issue: if you type a parameter in a controller action, for example as anint
, it can never coerce into that value:The reason this fails is because, at least per my own research, there's no way to specify that a route parameter should be cast as an integer, even if it only accepts numeric values per its FastRoute config. Because
declare(strict_types=1)
is set on theRequestResponseNamedArgs
class, it won't coerce the always-string values for parameters to fit the typed argument in the action.I'm not sure what the elegant solution to this is. You could remove the strict typing from that single class, since any type strictness originates from the calling file and this would allow type coercion...or somehow let FastRoute know that it should be parsing some parameters as integers.
I'm not even sure that it's something you'd want to fix on the framework side at all, instead choosing to say that every passed parameter will be a string because, well, URLs are strings. That's certainly what my immediate resolution will be, just to re-type all of those action arguments as strings and parse them down the line.