I want to have a dictionary of functions. With this dictionary, I could have a handler that accepts a function name and an array of arguments, and executes that function, returning the value it returns if it returns anything. The handler would throw an error if the name does not correspond to an existing function.
This would be very simple to implement Javascript:
var actions = {
doSomething: function(){ /* ... */ },
doAnotherThing: function() { /* ... */ }
};
function runAction (name, args) {
if(typeof actions[name] !== "function") throw "Unrecognized function.";
return actions[name].apply(null, args);
}
But since functions aren't really first class objects in PHP, I can't figure out how to do this easily. Is there a reasonably simple way to do this in PHP?
$actions = array(
'doSomething' => 'foobar',
'doAnotherThing' => array($obj, 'method'),
'doSomethingElse' => function ($arg) { ... },
...
);
if (!is_callable($actions[$name])) {
throw new Tantrum;
}
echo call_user_func_array($actions[$name], array($param1, $param2));
Your dictionary can consist of any of the allowable callable types.
I don't clearly get what you mean.
If you need an array of functions just do:
$actions = array(
'doSomething'=>function(){},
'doSomething2'=>function(){}
);
You can than run a function with $actions['doSomething']();
Of course you can have args:
$actions = array(
'doSomething'=>function($arg1){}
);
$actions['doSomething']('value1');
You could use PHP's __call() for that:
class Dictionary {
static protected $actions = NULL;
function __call($action, $args)
{
if (!isset(self::$actions))
self::$actions = array(
'foo'=>function(){ /* ... */ },
'bar'=>function(){ /* ... */ }
);
if (array_key_exists($action, self::$actions))
return call_user_func_array(self::$actions[$action], $args);
// throw Exception
}
}
// Allows for:
$dict = new Dictionary();
$dict->foo(1,2,3);
For static invocations, __callStatic() can be used (as of PHP5.3).
If You plan to use this in object context You do not have to create any function/method dictionary.
You can simply raise some error on unexisting method with magic method __call():
class MyObject {
function __call($name, $params) {
throw new Exception('Calling object method '.__CLASS__.'::'.$name.' that is not implemented');
}
function __callStatic($name, $params) { // as of PHP 5.3. <
throw new Exception('Calling object static method '.__CLASS__.'::'.$name.' that is not implemented');
}
}
Then every other class should extend Your MyObject class...
http://php.net/__call
// >= PHP 5.3.0
$arrActions=array(
"doSomething"=>function(){ /* ... */ },
"doAnotherThing"=>function(){ /* ... */ }
);
$arrActions["doSomething"]();
// http://www.php.net/manual/en/functions.anonymous.php
// < PHP 5.3.0
class Actions{
private function __construct(){
}
public static function doSomething(){
}
public static function doAnotherThing(){
}
}
Actions::doSomething();
http://php.net/manual/en/function.call-user-func.php
call_user_func will let you execute your functions from their names as a string and pass them parameters, I don't know the performance implications of doing it this way though.
Related
Say, I have a class with consts like so,
class Car
{
// Types
const TYPE_SALOON = "Saloon";
const TYPE_HATCHBACK = "Hatchback";
...
public static function validTypes() {
return array(
static::TYPE_SALOON,
static::TYPE_HATCHBACK,
...
);
}
}
I need the constructor to accept an input argument called type, which exits in the array returned by validTypes(). The way I'd normally do this is:
function __construct($type, ...) {
if(!in_array($type, static::validTypes())) {
// Throw Exception
}
}
What's a more elegant way to do this?
Is there a way to do this with typeHinting?
I put my question in the following code since it seems the easier way how to explain my question:
class MyClass
{
public function setHandler($function) {
// at this point I would like to validate
// that the function $function can be called
// with certain parameters.
//
// For example: $function has to be a function
// with the synopsis: function (User $user, Contact $contact) {}
// The point of the check is to know about an error soon.
}
}
Reflection seems so impractical for daily use. How would you solve the problem?
It seems like you want to confirm that a given callback function does have a certain interface.
Functions cannot implement interfaces, but classes can. Why don't you pass an object implementing a certain interface instead of a function?
Do you really think this snippet has heavy weight...? I don't think so.
class MyClass {
private $handler;
public function setHandler($function) {
try {
$r = new ReflectionFunction($function);
} catch (ReflectionException $e) {
throw new InvalidArgumentException('Invalid callback passed.');
}
if ($r->getNumberOfParameters() !== 2) {
throw new InvalidArgumentException('The callback must have exactly 2 arguments.');
}
static $d = array('User', 'Contact');
foreach ($r->getParameters() as $i => $p) {
if (!$c = $p->getClass() or strcasecmp($c->getName(), $d[$i])) {
throw new InvalidArgumentException(sprintf(
'The argument #%d must have type hinting: %s',
$i + 1,
$d[$i]
));
}
}
$this->handler = $function;
}
}
Example: http://ideone.com/kt7jk7
Benchmark: http://ideone.com/OmF010
I am new to PHP and encountered some odd behavior that may or may not be a bug in my version of PHP (5.4.13). I found a case where the sequence of function arguments matters in the function declaration. The in the following example:
class Book {
function __construct() { }
public static function withDescriptors($isNotOrmObject, $isArray, $isScalar) {
$instance = new self();
// initialization here
return $instance;
}
}
Invoking withDescriptors causes an 'array to string conversion' exception. The error is thrown when withDescriptors is invoked, ie. withDescriptors is never actually executed. However, switching the object parameter with the array parameter solves the problem. I.e.
public static function withDescriptors($isArray, $isNotOrmObject, $isScalar){ ....
Is this a known characteristic of PHP or is this a bug?
More explicitly:
class Book {
function __construct() { }
public static function withDescriptors($isNotOrmObject, $isArray, $isScalar) {
$instance = new self();
// initialization here
return $instance;
}
}
$book = Book::withDescriptors($isNotORMobject, $isArray, $isScalar);
FAILS and
class Book {
function __construct() { }
public static function withDescriptors($isArray, $isNotORMobject, $isScalar) {
$instance = new self();
// initialization here
return $instance;
}
}
$book = Book::withDescriptors($isArray, $isNotORMobject, $isScalar);
WORKS great. The ONLY difference is the parameter sequence, the "initialization here" code is identical.
The reason you don't get the warning in your second example, is that the Object you're passing as the second parameter is implementing a magic __toString() method. PHP is not a strong typed language, but more recent versions have limited capabilities of type hinting.
To illustrate the warning;
function saySomething($shouldBeAString)
{
echo $shouldBeAString
}
saySomething(array('hello'));
Outputs
'Array'
and a warning array to string conversion
class Foo {
public function __toString() {
return 'Hi I am Foo';
}
}
$bar = new Foo();
saySomething($bar);
Will output
'Hi I am Foo'
Without warning
As mentioned, PHP offers limited type hinting. You can specify a required 'class/object type' and 'array' as accepted arguments, but not scalar types, like 'string', 'int', 'bool' etc.
function (array $myarray) {
// will only accept arrays
// or something that implements array-access
}
function (Foo $var) {
// Will only accept 'Foo' objects
}
Polymorphism / Function overloading
Some other languages allow defining the same method/function multiple times, but with a different signature (other argument types). PHP does not explicitly support this.
For example, in other languages, this is allowed:
function doSomething(string $arg1) { ......}
function doSomething(array $arg1) { .... }
function doSomething(string $arg1, string $arg2) { ... }
In those languages, depending on the type and number of arguments, variant 1, 2 or 3 will be executed. PHP does not support this as it requires functions/methods to have a unique name. PHP will therefore complain that function doSomething() is already defined.
However, you can create something similar in PHP in several ways;
// rename the functions/methods so that they have a unique name
function _doSomething1(string $arg1) { ......}
function _doSomething2(array $arg1) { .... }
function _doSomething3(string $arg1, string $arg2) { ... }
// create the 'wrapper' function *without arguments specified*
function doSomething() {
// determin which variant should be executed
$numargs = func_num_args();
$args = func_get_args();
if ($numargs == 2) {
// 2 arguments -> variant 3
return call_user_func_array('_doSomething3', $args);
} else if {$numargs == 1) {
if (is_array($args[0]) {
// first argument is an array
return call_user_func_array('_doSomething2', $args);
} else {
return call_user_func_array('_doSomething1', $args);
}
}
}
Note: code above is 'fake' code, just to illustrate the idea!
In general, order matters in function arguments.
The only time it doesn't is when arguments are the same type or your function implements something like get opt long.
There are some good answers here but this particular situation arose from a bug in the PHP interpreter.
I'm trying to dump elements of an object's private property through an anonymous function - of course I could achieve this in any number of other ways, but this highlights a PHP conundrum I can't solve off the top of my head, short of $foo = $this and using $foo - but THAT won't give me the private stuff, so... suggestions ?
Sample code:
class MyClass
{
private $payload = Array( 'a' => 'A element', 'b' => 'B element');
static $csvOrder = Array('b','a');
public function toCSV(){
$values = array_map(
function($name) use ($this) { return $this->payload[$name]; },
self::$csvOrder
);
return implode(',',$values);
}
}
$mc = new MyClass();
print $mc->toCSV();
I believe there is absolutely no way to do directly what you propose.
However, you can work around it either by making the anonymous method a class method (this is not what you asked for, but it could be a practical solution) or pulling everything you need out of $this explicitly and passing the extracted values into the function:
class MyClass
{
private $payload = Array( 'a' => 'A element', 'b' => 'B element');
static $csvOrder = Array('b','a');
public function toCSV(){
$payload = $this->payload;
$values = array_map(
function($name) use ($payload) { return $payload[$name]; },
self::$csvOrder
);
return implode(',',$values);
}
}
You can hack around the limitation by creating a wrapper that utilizes Reflection to allow you to access all properties and methods. You can use it like this then:
$self = new FullAccessWrapper($this);
function () use ($self) { /* ... */ }
Here a sample implementation of the wrapper, taken from here:
class FullAccessWrapper
{
protected $_self;
protected $_refl;
public function __construct($self)
{
$this->_self = $self;
$this->_refl = new ReflectionObject($self);
}
public function __call($method, $args)
{
$mrefl = $this->_refl->getMethod($method);
$mrefl->setAccessible(true);
return $mrefl->invokeArgs($this->_self, $args);
}
public function __set($name, $value)
{
$prefl = $this->_refl->getProperty($name);
$prefl->setAccessible(true);
$prefl->setValue($this->_self, $value);
}
public function __get($name)
{
$prefl = $this->_refl->getProperty($name);
$prefl->setAccessible(true);
return $prefl->getValue($this->_self);
}
public function __isset($name)
{
$value = $this->__get($name);
return isset($value);
}
}
Obviously the above implementation doesn't cover all aspects (e.g. it can't use magic properties and methods).
As you said yourself, it is private and therefore in accessible.
You can:
Pass $this->payload as a parameter to the anonymous function.
Create a method in the class and use it instead.
What's the best way to do something like this in PHP?:
$a = new CustomClass();
$a->customFunction = function() {
return 'Hello World';
}
echo $a->customFunction();
(The above code is not valid.)
Here is a simple and limited monkey-patch-like class for PHP. Methods added to the class instance must take the object reference ($this) as their first parameter, python-style.
Also, constructs like parent and self won't work.
OTOH, it allows you to patch any callback type into the class.
class Monkey {
private $_overload = "";
private static $_static = "";
public function addMethod($name, $callback) {
$this->_overload[$name] = $callback;
}
public function __call($name, $arguments) {
if(isset($this->_overload[$name])) {
array_unshift($arguments, $this);
return call_user_func_array($this->_overload[$name], $arguments);
/* alternatively, if you prefer an argument array instead of an argument list (in the function)
return call_user_func($this->_overload[$name], $this, $arguments);
*/
} else {
throw new Exception("No registered method called ".__CLASS__."::".$name);
}
}
/* static method calling only works in PHP 5.3.0 and later */
public static function addStaticMethod($name, $callback) {
$this->_static[$name] = $callback;
}
public static function __callStatic($name, $arguments) {
if(isset($this->_static[$name])) {
return call_user_func($this->_static[$name], $arguments);
/* alternatively, if you prefer an argument list instead of an argument array (in the function)
return call_user_func_array($this->_static[$name], $arguments);
*/
} else {
throw new Exception("No registered method called ".__CLASS__."::".$name);
}
}
}
/* note, defined outside the class */
function patch($this, $arg1, $arg2) {
echo "Arguments $arg1 and $arg2\n";
}
$m = new Monkey();
$m->addMethod("patch", "patch");
$m->patch("one", "two");
/* any callback type works. This will apply `get_class_methods` to the $m object. Quite useless, but fun. */
$m->addMethod("inspect", "get_class_methods");
echo implode("\n", $m->inspect())."\n";
Unlike Javascript you can't assign functions to PHP classes after the fact (I assume you are coming from Javascript becuase you are using their anonymous functions).
Javascript has a Classless Prototypal system, where as PHP has a Classical Classing System. In PHP you have to define every class you are going to use, while in Javascript, you can create and change each object however you want.
In the words of Douglas Crockford: You can program in Javascript like it is a Classical System, but you can't program in a Classical System like it is Javascript. This means that a lot of the stuff you are able to do in Javascript, you can't do in PHP, without modifications.
I smell Adapter Pattern, or maybe even Decorator Pattern!