开发者

Calling PHP methods with varying arguments

I'm writing an API class, and my general goal is for it to be easy to make any class's methods accessible via the API, without having to make any serious changes to the class itself. Essentially, I should be able to instantiate an API class instance on any class that I want to use (within my little framework), and have it just work.

For example, In my API class, I have a method call, that I want to use $_GET to call the correct function from the class that I want to make accessible (let's call it Beep). So I specify an action parameter in my API, so that the action is the method of Beep to call, with the remaining arguments in $_GET being, presumably, the arguments开发者_StackOverflow for the method. In API->call, I can do $BeepInstance->$_GET['action'](), but I have no way of determining which arguments from $_GET to send, and in what order to send them.

func_get_args will only return the list of given arguments for the function in which it is called, and I don't necessarily know the correct order in which to pass them with call_user_func_array.

Has anyone tried to do something similar to this?


Here's a solution + example that uses reflection to map your input arguments to method parameters. I also added a way to control which methods are exposed to make it more secure.

class Dispatch {
    private $apis;

    public function registerAPI($api, $name, $exposedActions) {
        $this->apis[$name] = array(
            'api' => $api,
            'exposedActions' => $exposedActions
        );
    }

    public function handleRequest($apiName, $action, $arguments) {
        if (isset($this->apis[$apiName])) {
            $api = $this->apis[$apiName]['api'];
            // check that the action is exposed
            if (in_array($action, $this->apis[$apiName]['exposedActions'])) {
                // execute action

                // get method reflection & parameters
                $reflection = new ReflectionClass($api);
                $method = $reflection->getMethod($action);

                // map $arguments to $orderedArguments for the function
                $orderedArguments = array();

                foreach ($method->getParameters() as $parameter) {
                    if (array_key_exists($parameter->name, $arguments)) {
                        $orderedArguments[] = $arguments[$parameter->name];
                    } else if ($parameter->isOptional()) {
                        $orderedArguments[] = $parameter->getDefaultValue();
                    } else {
                        throw new InvalidArgumentException("Parameter {$parameter->name} is required");
                    }
                }

                // call method with ordered arguments
                return call_user_func_array(array($api, $action), $orderedArguments);
            } else {
                throw new InvalidArgumentException("Action {$action} is not exposed");
            }
        } else {
            throw new InvalidArgumentException("API {$apiName} is not registered");
        }
    }
}

class Beep {
    public function doBeep($tone = 15000)
    {
        echo 'beep at ' . $tone;
    }

    public function notExposedInAPI()
    {
        // do secret stuff
    }
}

Example:

// dispatch.php?api=beep&action=doBeep&tone=20000

$beep = new Beep();
$dispatch = new Dispatch();
$dispatch->registerAPI($beep, 'beep', array('doBeep'));

$dispatch->handleRequest($_GET['api'], $_GET['action'], $_GET);


We did something similar in our API. We used a proxy method _methodName($p) and passed in the $_GET or $_REQUEST array. The proxy method knows the order of the parameters required for the real method, so it invokes the real method correctly. Using call_user_func_array() worked pretty well with that.

Not sure if that's the best way to go about it, but it works well for us.

The controller looks something like this:

if (method_exists($server, "_$method"))
        $resp = call_user_func_array("{$server}::_$method", array($_REQUEST));

And then the model is setup like:

public function test($arg1, $arg2) { ... }
public function _test($p) {
    return $this->test($p['arg1'], $p['arg2']);
}


I'd propose to pass an associative array the the respective method. Since the assoc. array provides a name to value mapping.

Moreover, never do something like this:

  $BeepInstance->$_GET['action']()

This is highly insecure.

Probably define another associate array, which maps actions passed as GET 'action' parameters to actual method names.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜