PHP - call_user_function_array or Reflection class

2019-05-28 23:22发布

问题:

I'm trying to dispatch request in MVC framework. I have a routing object that matches current URI against defined routes. If there is a match, it returns instance of Route object. Through that Route object I can access matched controller, method and method arguments.

I can use it like this:

$route->getClass(); name of controller class to instantiate
$route->getMethod(); name of method to call
$route->getArgs(); array holding arguments that should be passed to method

I can also add new arguments if I need to. For example, I can add Dependency Injection Container and current HTTP Request object instances to args like this:

$route->addArg('container', $container);
$route->addArg('request', $request);

Now $route->getArgs holds all arguments fetched from URI, but also a $container instance and $request instance. $container is dependency injection container i wrote. And $request is object that represents current HTTP Request.

So the idea is to instantiate Router, get current Route, then add objects I would like to use inside every method / action controller. And then pass that Route object to Dispatcher, and when request is dispatched, I would be able to use $container, $request and other arguments in every method / action controller.

The problem I have is that when I use lets say blog controller and post method. By default, and always, it shoud have instance of $container, and $request (since I pushed them into Route::$args, plus any other argument / variable defined in routes definitions or fetched from URI.

So when Im in my Blog Controller, post method I want it to act like this:

public function post($container, $request)
{
if($request->isAjax) {
// -- Its ajax request
}

$twig = $container->getService("Twig");
}

I dispatch request something like this:

Im using call_user_function_array to make it work:

$app = new $controller();
call_user_function_array(array($app, $method), $args);

Now this all works as it should but I have one problem. When Im using that post controller I must be careful in what order are post method arguments ordered.

So i can do this:

public function post($id, $url, $container, $request) {
// -- SNIP --
}

but I cant use it this way:

public function post($container, $url, $request, $id) {
/-- SNIP --
}

So you get the problem, I cant mix arguments inside my method the way I want, and I need to keep an eye on order in which those arguments get defined in Route:$args array.

Every method / action controller should have $request, and $container instances plus arguments fetched through URI or defined in route definition.

Is there any way to make it work without having to think In what order is post method getting arguments? Does this have something to do with passing by reference? Since php passes variables by value?

Im using call_user_function_array and I want to find solution for call_user_function_array. Is this possible?

If not, can this be done using Reflection class?

回答1:

With Reflection class you can get the arguments names of each parameter of the function you want to call and in that way check if they are in the correct order very easily. You can't do that simply with call_user_function_array.

In particular you can use the getParameters() method in the ReflectionMethod class.

From there on I think you know what to do (check each key of the parameters array with the key of the argument passed and there you go).



回答2:

This is the solution to my problem:

So, I have everything needed to dispatch request:

$class = "\\Application\\Controller\\Blog";
$method = "post";
$args = array('id' => 1, 'url' => 'some-url', 'container' => DICOBJ, 'request' => RQSTOBJ);

Now I need to dispatch that request, so user can insert arguments as he wants in any order and if he wants.

This is what should be done:

$app = new $class();

$rm = new \ReflectionMethod($app, $method);
$params = $rm->getParameters();

$argsOrdered = array();
foreach($params as $param) {
    $argsOrdered[$param->getName()] = $args[$param->getName()];
}

call_user_func_array(array($app, $method), $argsOrdered);

Ta daaa!!!

So, what this does is it gets defined arguments by user, and returns them as array in order user wrote them. And then we create another $argsOrdered array that holds only arguments user wants, ordered the way he wants :)

Im using ReflectionMethod to inspect method. And at the end call_user_func_array to dispatch request, but you can also use Reflection class to get controller instance.

Thats it. Do you find this solution elegant? Can this be done any faster?