» Details |
---|
|
» Comment |
I have been thinking about this proposal quite a bit and have finally come to my own conclusions about it. Obviously, it will not be accepted as a new package in its current state, so the question is only how to reformulate the idea so that it will be a useful package. From my end, the extensive wrapping around calling the callback must go. There is no good reason to do this: <?php require_once 'PHP/Callback.php'; $a = new PHP_Callback(array('hello', 'there')); $a->addParameter(1); $a->addParameter(2); $a->addParameter(3); $a->execute(); ?> instead of this: <?php call_user_func(array('hello', 'there'), 1, 2, 3); ?> As I said in my offlist email to you, the primary issue I have experienced when coding with callbacks is improper validation. This means two things: 1) forgetting to do an is_callable() check 2) forgetting to ensure that the callback actually accepts the arguments I want it to accept The primary mechanisms in place for ensuring #2 in PHP 5 are reflection, interfaces, and abstract classes. At a certain point, validating this aspect of a callback can become quite complex, i.e.: <?php if (!is_callable($callback)) return; if (is_array($callback)) { if (is_string($callback[0])) { // callback is array('class', 'method') $a = new ReflectionClass($callback[0]); if ($a->implementsInterface('someInterface')) { // check to see if this callback implements a // specific method's signature if ($callback[1] != 'someMethod') return; } else { // more loosely, make sure this callback // accepts 3 parameters $m = $a->getMethod($callback[1]); if ($m->getNumberOfParameters() < 3 || $m->getNumberOfRequiredParameters() > 3) return; } } else { // callback is array($object, 'method') if ($callback[0] instanceof someInterface) { // check to see if this callback implements a // specific method's signature if ($callback[1] != 'someMethod') return; } else { // more loosely, make sure this callback // accepts 3 parameters $a = new ReflectionClass($callback[0]); $m = $a->getMethod($callback[1]); if ($m->getNumberOfParameters() < 3 || $m->getNumberOfRequiredParameters() > 3) return; } } // if we reach here we are certain that this is the interface we expect } else { // callback is 'function' // make sure this callback function // accepts 3 parameters $a = new ReflectionFunction($callback); if ($a->getNumberOfParameters() < 3 || $a->getNumberOfRequiredParameters() > 3) return; } ?> *this* is the kind of thing that may be useful to abstract - a callback validator that can accept as its arguments: - an interface that must be validated (require an array() callback) - the number of parameters that must be accepted With this kind of functionality, one can then use it inside a method <?php require_once 'PHP/CallbackValidator.php'; ... function whatever($a) { if (!PHP_CallbackValidator::validate($a, PHP_CallbackValidator::INTERFACE, 'interfaceName', 'method')) { throw new Whatever_Exception('what evER'); } } ?> or inside a wrapper PHP_Callback-style object for type hinting: <?php require_once 'PHP/CallbackValidator.php'; class MY_CustomCallback { private $_callback; function __construct($callback) { if (!PHP_CallbackValidator::validate($callback, PHP_CallbackValidator::ARGS, 3)) { throw new MY_Exception('callback must accept 3 arguments'); } $this->_callback = $callback; } function execute($a, $b, $c) { call_user_func($this->_callback, $a, $b, $c); } } ?> This PHP_CallbackValidator package need only be a 1-static method class, but it would still be useful and impose a minimal performance penalty for the functionality it would provide. The flexibility of being able to use it as a one-off validation inside a method or function or to create a customized PHP_Callback that can be used for type-hinting is far better in my opinion. I hope that with these ideas, you can find a way to glean the most useful part of your proposal into a package that all PEAR and all PHP developers will want to use. |