Suggestion: Call method class, injecting constructor and method params
LuanMaik opened this issue · comments
I'm try to use this container with Slim framework, but I'm trying to get the less verbose possible in call the method controller in route definition, but the lib is limited to constructor injection.
My actual code:
$container = new \League\Container\Container();
$container->delegate((new League\Container\ReflectionContainer())->cacheResolutions(false));
// .. define dependencies
Slim\Factory\AppFactory::setContainer($container);
$app = Slim\Factory\AppFactory::create();
$app->get('/api/v1/users', function() {
// $this = \League\Container\Container
// Create the controller instance with constructor dependencies
$controller = $this->get(\App\Controllers\UserController::class);
// But I need resolve the method params before call it
$bar = $this->get(\Foo\Bar::class);
return $controller->list($bar);
});
My proposal is create a method that execute a class method, resolving all dependencies of constructor and method.
My code with the proposal:
$container = new \League\Container\Container();
$container->delegate((new League\Container\ReflectionContainer())->cacheResolutions(false));
// .. define dependencies
Slim\Factory\AppFactory::setContainer($container);
$app = Slim\Factory\AppFactory::create();
$app->get('/api/v1/users', function() {
// $this = \League\Container\Container
// Resolves constructor and method dependencies, then execute it
return $this->callClassMethod(\App\Controllers\UserController::class, 'list');
});
The new method:
// src/Container.php
class Container implements ContainerInterface
{
// ...
/**
* Execute a class method, injecting all dependencies necessaries in constructor class and method params
*
* @param string $className
* @param string $methodName
* @param bool $new
* @return mixed
* @throws \ReflectionException
*/
public function callClassMethod(string $className, string $methodName, bool $new = false)
{
if (!class_exists($className)) {
throw new \InvalidArgumentException("The class {$className} not exist.");
}
if (!method_exists($className, $methodName)) {
throw new \InvalidArgumentException("The method '{$methodName}' not exist in class {$className}.");
}
$classInstance = $this->get($className, $new);
$reflectionMethod = new ReflectionMethod($className, $methodName);
$methodParams = $reflectionMethod->getParameters();
$resolvedMethodParams = [];
foreach ($methodParams as $param) {
if (empty($param->getClass())) {
throw new \Exception("It's not possible resolve the param '\${$param->getName()}' in '{$className}::{$methodName}'.");
}
$resolvedMethodParams[] = $this->get($param->getClass()->getName(), $new);
}
return call_user_func_array([$classInstance, $methodName], $resolvedMethodParams);
}
}
I can use this approach separately, but i think that this feature would be interesting to help with routers libraries implementations like in my case.
The PHP-DI lib, has the call()
method, thats seems my proposal, but it resolves the dependencies using 'resolved dependencies cache'.
ReflectionContainer already has a call
method on it, but uses reflection exclusively iirc, not really something I'd want to add to the main container though.
It's not really an intended use, but as you've shown above, it's easy enough to put something together if someone wants to do it this way.