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
4.2 - Middleware Callable Resolver Support #2780
Comments
I know we are going against typical versioning practices by doing this but it's too early in the release cycle to wait until the next major version to fix this. This BC break will be seamless for most users unless you:
|
We might be able to implement this functionality without having to change the interface after some thoughts. Since the method Example of a typed callable (this would be for internal usage only anyway): <?php
declare(strict_types=1);
namespace Slim;
class TypedCallable {
public const GenericCallable = 0;
public const RouteCallable = 1;
public const MiddlewareCallable = 2;
/**
* @var callable|string
*/
private $callable;
public function __construct(int $type, $callable) {
$this->type = $type;
$this->callable = $callable;
}
/**
* @return int
*/
public function getType(): int
{
return $this->type;
}
/**
* @return callable|string
*/
public function getCallable()
{
return $this->callable;
}
/**
* @param $callable
* @return TypedCallable
*/
public static function genericCallable($callable): TypedCallable
{
return new static(self::GenericCallable, $callable);
}
/**
* @param $callable
* @return TypedCallable
*/
public static function routeCallable($callable): TypedCallable
{
return new static(self::RouteCallable, $callable);
}
/**
* @param $callable
* @return TypedCallable
*/
public static function middlewareCallable($callable): TypedCallable
{
return new static(self::MiddlewareCallable, $callable);
}
} Example internal usage resolving a route callable: $callableResolver = new CallableResolver(...);
$typedCallable = new TypedCallable::routeCallable($this->callable);
$resolved = $callableResolver->resolve($typedCallable); |
Good idea. That might work and avoids a breaking change. |
Well, maybe a small BC. If someone implemented a callable resolver and suddenly gets a |
That's right. I'm not sure what to do here. |
Maybe we should leave it as is. The resolver stuff hard coded into the dispatcher. |
If a user would like to have a custom middleware callable resolver, the user has to implement a |
Would it make sense to add a new |
@adriansuter it's not a bad idea, but it still doesn't solve the current problem. Route callables are being resolved where generic callables are being resolved within the current There should be a clear logic separation between:
Adding This is a tough problem to solve without breaking things. |
Maybe an Then, whenever we need to call the I am tired, not sure if that is even possible, does make sense or if that would not break anything. |
Just for the record, a BC break should lead to a new major version. Although here I don't really understand the change: The current signature of the callable resolver is: public function resolve(string|callable $toResolve): callable; Why would the behavior be different depending on whether the callable is a route or a middleware? Shouldn't all callables be resolved in the same way? |
Also that is way too late for that but to add more context, callable resolvers and invokers is something I've worked on standardizing in https://github.com/PHP-DI/Invoker Maybe there's a new interface we could extract here and push to container-interop or the PHP-FIG (later of course). |
As you can pass a If someone designs a class that implements both interfaces, then the resolver has to know, which of the methods should be considered to be the callable. Thus we need to inform the resolver about the scenario we are in.
Ideally, yes. |
We're trying to avoid this at all costs obviously. This is what #2787 circumvents
As @adriansuter stated yes. The problem lies in the fact that when resolving a route, we assume that if it implements The same applies for resolving middleware, currently all the logic is handled within Currently, there is no separation in logic and there should be. Some logic only applies to a specific type of callable and it shouldn't be based on assumptions. The end goal is being able to resolve disctinct types of callables without making assumptions: Generic Callables:
Route Callables:
Middleware Callables:
As you can see, they have a lot of similarities while still having some logic that shouldn't be shared with other types of callables. Hence the need to separate in the 3 proposed methods. |
But… a callable with a class name has to be a If I understand correctly you adopted a convention that It may be too late, but I want to strongly encourage to reconsider this so that, for the callable resolver, a callable is just a callable. There should be (hopefully) no custom logic to apply that is related to Slim's internals. The way you could apply the custom logic would be higher up in Slim. Here is a rough example just to illustrate what I mean (no idea if possible in Slim as it is): class App
{
public function add($middleware, ...) {
// probably more checks to add here
if (is_string($middleware) && class_exists($middleware)) {
// I turn it into a valid callable here because I know what method to call
$middleware = [$middleware, 'handle'];
}
$this->middlewares[] = $middleware;
}
...
private function callMiddleware($middleware) {
// All callables look the same to me here
$middleware = $this->callableResolver->resolve($middleware);
...
}
} The callable resolver should not care what a callable is used for. It's job is just to "resolve callables". I would also recommend to drop the "Slim-style" callables and instead use classic PHP callable signatures, with DI resolution on top of that. Here is a gist that summarizes it that I wrote some time ago: https://gist.github.com/mnapoli/f8afef0f556010c144e7 Advantages:
I really wish we had that discussion a few months ago ^^ |
I did not make those decisions,
I tried to push for that for Slim 4 and it was voted against. The main reason for using this is that we can defer the instantiation of the object while still being able to call the method we want. Example: // Invalid as a regular callable
$invalidAsRegularCallable = [MyUninstantiatedClass::class, 'method'];
// Slim enables this via our custom callable pattern
$validSlimCallable = MyUninstantiatedClass::class . ":method";
Me too! |
OK thank you that's a little more clear to me.
I am talking about doing exactly the same thing but making The main advantage over Anyway, I understand, this ship has sailed. Thanks for taking the time to explain. |
I would have proposed this as a solution initially instead of Slim style callables, unfortunately I wasn't around then. We should actually support that style as well though. It shouldn't be too hard to do. |
I ended up here because I was looking to replace the I really like the syntax of [MyUninstantiatedClass::class, 'staticMethod'] and it works fine for static calls yes but I want it to work with instance methods without having to do a whole bunch of repetitive code for every route and/or add property tags to a custom container wrapper or some other work around in order to get code assistance form my IDE and have a slimple rout = function setup. Having [MyUninstantiatedClass::class, 'instanceMethod'] would be ideal as IDE's can recognize this and provide a lot refactoring assistance without us having to do extra code in order to get that assistance. eg. when type the callable reference even tho the method is a string literal I have the method definition appear with the comments ect, I can auto complete and then ctrl+click through to the definition and means I also see all usages of the method with a single click. Right now we are making a controller for every route path with Seems logical to me that this would be supported since just passing the class name on its own will resolve to container instance if it is defined usually if no method is provided. And I think this would be a not breaking change just supporting this one use case since doing it will only prevent things from breaking when using a instance method statically and nothing can break calling a static method on an instance eg..
|
@b-hayes I'm slightly off topic but in the meantime, if you want this feature a workaround is to use this bridge: http://php-di.org/doc/frameworks/slim.html |
Another option is to create your own CallableResolver in which you can transform one notation to another: in https://github.com/juliangut/slim-php-di/blob/master/src/CallableResolver.php#L136 you can see how Slim's string notation is transformed into PHP-DI's, the opposite is also possible |
@juliangut I ended up doing exactly that after I made the comment. If the class name is defined in the container and the method name exists it will get the instance from there and execute it. Otherwise if the method exists it will try and create an instance and execute it, but obviously only works if the controller construct has no params.
|
See #2779.
We should modify the
CallableResolver
(and its interface) in order to be able to use it in theMiddlewareDispatcher
. There should be 3 methods:Currently, in 4.0.x and 4.1.x, the logic of resolving the middleware callable has been hard coded into the dispatcher. This has to be replaced.
Warning: This is a BC! New users of Slim 4 should, if possible, not mess around with the callable resolver.
At the same time, the arguments of the
App
constructor should be extended by aMiddlewareDispatcherInterface
such that users could provide their own dispatcher implementation.The text was updated successfully, but these errors were encountered: