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

RequestResponseNamedArgs and Typed Parameters #3198

Closed
BusterNeece opened this issue May 24, 2022 · 5 comments
Closed

RequestResponseNamedArgs and Typed Parameters #3198

BusterNeece opened this issue May 24, 2022 · 5 comments

Comments

@BusterNeece
Copy link
Contributor

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 an int, it can never coerce into that value:

<?
$app->get('/test', function($request, $response, int $id) {
    // This will fail with RequestResponseNamedArgs
});

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 the RequestResponseNamedArgs 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.

@odan
Copy link
Contributor

odan commented May 24, 2022

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'];
    // ...
});

@BusterNeece
Copy link
Contributor Author

@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.

@odan
Copy link
Contributor

odan commented May 27, 2022

That's right, obviously this feature was added in version 4.1. #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.

@BusterNeece
Copy link
Contributor Author

@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.

@odan
Copy link
Contributor

odan commented Aug 3, 2024

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);

// ...

@odan odan closed this as completed Aug 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants