I have a code problem which stems from the fact that I am using certain libraries of code I cannot change.
I use the following code to pass execution of any undefined methods to another class, and it works fine but it seems like a waste doubling up.
Any suggestions?
Basically I want to know if it's possible to pass an unknown number of parameters to a method (without using call_user_func_array(), just in case they need to be passed by reference). I am not asking how to use func_get_args(), rather the reverse.
Or should I just allow for a few more arguments in the first logic path (the list() code)?
class Foo {
__construct() {
$this->external = new ClassThatIHaveNoControlOver();
}
function bar($name) {
return 'Hi '.$name;
}
function __call($method, $arguments) {
if (count($arguments) < 3) {
// call_user_func_array won't pass by reference, as required by
// ClassThatIHaveNoControlOver->foobar(), so calling the function
// directly for up to 2 arguments, as I know that foobar() will only
// take 2 arguments
list($first, $second) = $arguments + Array(null, null);
return $this->external->$method($first, $second);
} else {
return call_user_func_array(array($this->external, $method), $arguments);
}
}
}
$foo = new Foo();
$firstName = 'Bob';
$lastName = 'Brown';
echo $foo->bar($firstName); // returns Hi Bob as expected
echo $foo->foobar($firstName, $lastName); // returns whatever
// ClassThatIHaveNoControlOver()->foobar() is meant to return
EDIT
Just to clarify, I know I can use this method to rejig the parameters as references, but that would mean passing everything as a reference, even if the method didn't require it - something I was trying to avoid, but seems unlikely at the moment.
As commented in the thread question post's comments this is an example and not necessarily (likely) best practice.
//Some vars
$foo = "shoe";
$bar = "bucket";
//Array of references
$arr = Array(&$foo, &$bar);
//Show that changing variable value affects array content
$foo = "water";
echo $arr[0];
//Sample function
function fooBar($a)
{
$a[0] = "fire";
}
//Call sample function
call_user_func("fooBar",$arr);
//Show that function changes both array contents and variable value by reference
echo $arr[0];
echo $foo;
Expanding a bit on the discussion, again not the most industry standard approach but it'll do the job.
function pushRefOnArray(&$arr, &$var, $key = false)
{
if(isset($key))
$arr[$key] = &$var;
else
$arr[] = &$var;
}
Essentially you can dynamically build your array and call pushRefToArray() any time you need to pass an item to be passed as reference rather than by value.
You could use something like this:
public function __call($method, $params = array()) {
switch (count($params)) {
case 0:
return $this->external->{$method}();
case 1:
return $this->external->{$method}($params[0]);
case 2:
return $this->external->{$method}($params[0], $params[1]);
case 3:
return $this->external->{$method}($params[0], $params[1], $params[2]);
default:
return call_user_func_array(array(&this->external, $method), $params);
}
}
Related
Say I have a callable stored as a variable:
$callable = function($foo = 'bar', $baz = ...) { return...; }
How would I get 'bar'?
if (is_callable($callable)) {
return func_get_args();
}
Unfortunately func_get_args() is for the current function, is it possible to get a key value pair of arguments?
You can use reflection:
$f = new ReflectionFunction($callable);
$params = $f->getParameters();
echo $params[0]->getDefaultValue();
You may want to use get_defined_vars to accomplish this, this function will return an array of all defined variables, specifically by accessing the callable index from the output array.
I came across this question because I was looking for getting the arguments for a callable which is not just the function itself. My case is
class MyClass{
public function f(){
// do some stuff
}
}
$myclass = new MyClass();
$callable = array($myclass, "f);
This is a valid callback in php. In this case the solution given by #Marek does not work.
I worked around with phps is_callable function. You can get the name of the function by using the third parameter. Then you have to check whether your callback is a function or a (class/object) method. Otherwise the Reflection-classes will mess up.
if($callable instanceof Closure){
$name = "";
is_callable($callable, false, $name);
if(strpos($name, "::") !== false){
$r = new ReflectionMethod($name);
}
else{
$r = new ReflectionFunction($name);
}
}
else{
$r = new ReflectionFunction($callable);
}
$parameters = $r->getParameters();
// ...
This also returns the correct value for ReflectionFunctionAbstract::isStatic() even though the $name always uses :: which normally indicates a static function (with some exceptions).
Note: In PHP>=7.0 this may be easier using Closures. There you can do someting like
$closure = Closure::fromCallable($callable);
$r = new ReflectionFunction($closure);
You may also cause have to distinguish between ReflectionFunction and ReflectionMethod but I can't test this because I am not using PHP>=7.0.
I have the following code:
<?php
class Testme {
public static function foo(&$ref) {
$ref = 1;
}
}
call_user_func_array(array('Testme', 'foo'), array(&$test));
var_dump($test);
And correctly displays "1". But I want to do the same, using an "Invoker" method, like the following:
<?php
class Testme {
public static function foo(&$ref) {
$ref = 1;
}
}
class Invoker {
public static function invoke($func_name) {
$args = func_get_args();
call_user_func_array(array('Testme', $func_name), array_slice($args,1));
}
}
$test = 2;
Invoker::invoke('foo', $test);
var_dump($test);
This throws a strict standards error (PHP 5.5) and displays "2"
The question is, is there a way to pass arguments by reference to Testme::foo, when using func_get_args()? (workarounds are welcome)
There is no way to get a reference out of func_get_args() because it returns an array with a copy of the values passed in. See PHP Reference.
Additionally, since runtime pass by reference is no longer supported, you must denote the reference in each method/function signature. Here is an example that should work around the overall issue of having an Invoker that does pass by reference, but there is no work around for func_get_args().
<?php
class Testme {
public static function foo(&$ref) {
$ref = 1;
}
}
class Invoker {
public static function invoke($func_name, &$args){
call_user_func_array(array('Testme', $func_name), $args);
}
}
$test = 10;
$args[] = &$test;
Invoker::invoke('foo', $args);
var_dump($test);
If you know you want to invoke by reference, this can work for you and perhaps have two invokers, one Invoker::invokeByRef an another normal Invoker::invoke that does the standard invoking by copy.
It is not possible because func_get_args() returns copy value not reference. But there is an ugly workaround for it by using many optional parameters.
class Testme {
public static function foo(&$ref, &$ref2) {
$ref = 1;
$ref2 = 2;
}
}
class Invoker {
public static function invoke($func_name,
&$arg1 = null, &$arg2 = null, &$arg3 = null,
&$arg4 = null, &$arg5 = null, &$arg6 = null)
{
$argc = func_num_args();
$args = array();
for($i = 1; $i < $argc; $i++) {
$name = 'arg' . $i;
if ($$name !== null) {
$args[] = &$$name;
}
}
call_user_func_array(array('Testme', $func_name), $args);
}
}
$test = 5;
$test2 = 6;
Invoker::invoke('foo', $test, $test2);
var_dump($test);
var_dump($test2);
The problem
This is not possible to do easily because func_get_args does not deal in references, and there is no alternative that does.
The idea
If you are willing to limit yourself to a maximum known number of arguments and don't mind working with the dark arts, there is a horrible workaround that I believe works correctly in all cases.
First, declare the invoker as accepting an able number of parameters, all of them by reference and having default values (the exact default does not really matter):
public static function invoke(callable $callable, &$p1 = null, &$p2 = null, ...);
Then, inside invoke determine what type of callable you are dealing with. You need to do this in order to create an appropriate instance of ReflectionFunctionAbstract that describes the invocation target. This is important because we absolutely need to determine how many parameters the target requires, and it also enables amenities like detecting a call with an incorrect number of arguments.
After assembling an array of arguments, use call_user_func_array like you were intending to in the first place.
This approach is based on the same idea that invisal uses, but there is an important difference: using reflection allows you to always correctly determine how many arguments to pass (invisal's solution uses a guard value), which in turn does not limit the values that can be passed to the invocation target (with invisal's solution you cannot ever pass the guard value to the invocation target as a legitimate parameter).
The code
public static function invoke(callable $callable, &$p1 = null, &$p2 = null)
{
if (is_string($callable) && strpos($callable, '::')) {
// Strings are usually free function names, but they can also
// specify a static method with ClassName::methodName --
// if that's the case, convert to array form
$callable = explode('::', $callable);
}
// Get a ReflectionFunctionAbstract instance that will give us
// information about the invocation target's parameters
if (is_string($callable)) {
// Now we know it refers to a free function
$reflector = new ReflectionFunction($callable);
}
else if (is_array($callable)) {
list ($class, $method) = $callable;
$reflector = new ReflectionMethod($class, $method);
}
else {
// must be an object -- either a closure or a functor
$reflector = new ReflectionObject($callable);
$reflector = $reflector->getMethod('__invoke');
}
$forwardedArguments = [];
$incomingArgumentCount = func_num_args() - 1;
$paramIndex = 0;
foreach($reflector->getParameters() as $param) {
if ($paramIndex >= $incomingArgumentCount) {
if (!$param->isOptional()) {
// invocation target requires parameter that was not passed,
// perhaps we want to handle the error right now?
}
break; // call target will less parameters than it can accept
}
$forwardedArguments[] = &${'p'.(++$paramIndex)};
}
return call_user_func_array($callable, $forwardedArguments);
}
See it in action.
I have a body function and a function called within the first one.
As can be seen below I don't change the parameters name while using in the second function.
Is it necessary to change the params names for use inside _display_bar();? What are the side effects if I don't?
function main_func($form, &$form_state, $key, $code) {
$output = '';
...
$output .= _display_navbar($trans, $status_names);
return $output
}
function _display_navbar($trans, $status_names) {
$trans = 'bla';
$status_names = 'another bla';
$bar = $trans . ':' .$status_names;
return $bar;
};
Variables have function scope. Unless you specifically declare otherwise, the names are only valid inside the function. They do not bleed into other scopes. There are no side effects. You don't need to use unique names.
It actually does not matter. But you better should not have the same names - it is confusing. Let me give you an example. $s will have 3 after the first function call to sum; 7 after the second function call to sum. The parameters did not have the same name as the function parameter names.
To answer your question fully - there are absolutely no side effects.
function main()
{
$a = 1;
$b = 2;
$s = sum($a, $b);
$d = 3;
$e = 4;
$s = sum($d, $e);
}
function sum($first, $second)
{
$ret = $first + $second;
return $ret;
}
Once a variable is passed to a function, the name of the variable is not important. Only the data is passed through. So your function could be this:
function _display_navbar($foo, $bar) {
$foo = 'bla';
return $bar;
}
And it will return what ever was passed as the second parameter regardless of what the variable name was.
The names you pass as function arguments must be in scope at the point they are called.
It doesn't matter if they have the same name as the formal function parameters, but you must recognise that just because they have the same name doesn't mean that brings them into scope.
So, in your code:
function main_func($form, &$form_state, $key, $code) {
$output = '';
...
$output .= _display_navbar($trans, $status_names);
the last line will be incorrect, unless $trans and $status_names are in scope at the time.
I'm passing all my calls to a main mapping function
and then it should dynamically call other function based on a string (until this part, things are easy)
the problem is that I want to pass arguments to the second function and these parameters may vary.
The following is given (should not be changed):
function test1($x){
echo $x;
}
function test2($x, $y){
echo $x * $y;
}
and now comes the mapping function
function mapping ($str){
switch ($str){
case 'name1':
$fn_name = 'test1';
$var = 5;
break;
case 'name2':
$fn_name = 'test2';
$var = 5;
break;
}
$this->{$fn_name}($var);
}
And then this will run the mapping:
$this->mapping('name1');
$this->mapping('name2'); // This one will crash as it need two variables
Of course the above is simplified to focus on the problem not the purpose of the code.
The problem is when the function has more than one argument (which can easily happen).
I'm expecting to have the switch case and based on how the case parameters are filled, the line
$this->{$fn_name}($var); should work.
Can you please advise or give me ideas, knowing that the functions (test1, test2) structure can NOT be changed. I can NOT suddenly start using func_get_args() or func_get_arg()
You can use ReflectionFunction and its invokeArgs() method to pass the variables in an array:
function mapping ($str) {
switch ($str) {
case 'name1':
$fn_name = 'test1';
$fn_args = array(5);
break;
case 'name2':
$fn_name = 'test2';
$fn_args = array(5, 10);
break;
}
$function = new ReflectionFunction($fn_name);
$function->invokeArgs($fn_args);
}
Since mapping() in your code seems to be a class method, replace it with:
public function __call($method, $args)
{
// possibly validate $method first, e.g. with a static map
return call_user_func_array($method, $args);
}
Examples:
function foo($foo) { echo $foo; }
function bar($foo, $bar) { echo "$foo - $bar"; }
$this->foo('foo'); // outputs 'foo'
$this->bar('foo', 'bar'); // outputs 'foo - bar'
This means that your class shouldn't have the methods foo() or bar() for the __call() to be invoked.
Seems to be a pretty complicated problem though. There's probably a more elegant solution to what you're trying to achieve? :)
I started off OOP with Java, and now I'm getting pretty heavy into PHP. Is it possible to create multiples of a function with different arguments like in Java? Or will the interpreted / untyped nature of the language prevent this and cause conflicts?
Everyone else has answers with good code explanations. Here is an explanation in more high level terms: Java supports Method overloading which is what you are referring to when you talk about function with the same name but different arguments. Since PHP is a dynamically typed language, this is not possible. Instead PHP supports Default arguments which you can use to get much the same effect.
If you are dealing with classes you can overload methods with __call() (see Overloading) e.g.:
class Foo {
public function doSomethingWith2Parameters($a, $b) {
}
public function doSomethingWith3Parameters($a, $b, $c) {
}
public function __call($method, $arguments) {
if($method == 'doSomething') {
if(count($arguments) == 2) {
return call_user_func_array(array($this,'doSomethingWith2Parameters'), $arguments);
}
else if(count($arguments) == 3) {
return call_user_func_array(array($this,'doSomethingWith3Parameters'), $arguments);
}
}
}
}
Then you can do:
$foo = new Foo();
$foo->doSomething(1,2); // calls $foo->doSomethingWith2Parameters(1,2)
$foo->doSomething(1,2,3); // calls $foo->doSomethingWith3Parameters(1,2,3)
This might not be the best example but __call can be very handy sometimes. Basically you can use it to catch method calls on objects where this method does not exist.
But it is not the same or as easy as in Java.
Short answer: No. There can only be one function with a given name.
Longer answer: You can do this by creating a convoluted include system that includes the function with the right number of arguments. Or, better yet, you can take advantage of PHP allowing default values for parameters and also a variable amount of parameters.
To take advantage of default values just assign a value to a parameter when defining the function:
function do_something($param1, $param2, $param3 = 'defaultvaule') {}
It's common practice to put parameters with default values at the end of the function declaration since they may be omitted when the function is called and makes the syntax for using them clearer:
do_something('value1', 'value2'); // $param3 is 'defaultvaule' by default
You can also send a variable amount of parameters by using func_num_args() and func_get_arg() to get the arguments:
<?php
function dynamic_args() {
echo "Number of arguments: " . func_num_args() . "<br />";
for($i = 0 ; $i < func_num_args(); $i++) {
echo "Argument $i = " . func_get_arg($i) . "<br />";
}
}
dynamic_args("a", "b", "c", "d", "e");
?>
Following isn't possible with php
function funcX($a){
echo $a;
}
function funcX($a,$b){
echo $a.$b;
}
Instead do this way
function funcX($a,$b=null){
if ($b === null) {
echo $a; // even though echoing 'null' will display nothing, I HATE to rely on that
} else {
echo $a.$b;
}
}
funcX(1) will display 1, func(1,3) will display 13
Like everyone else said, it's not supported by default. Felix's example using __call() is probably the best way.
Otherwise, if you are using classes that inherit from each other you can always overload the method names in your child classes. This also allows you to call the parent method.
Take these classes for example...
class Account {
public function load($key,$type) {
print("Loading $type Account: $key\n");
}
}
class TwitterAccount extends Account {
public $type = 'Twitter';
public function load($key) {
parent::load($key,$this->type);
}
}
Then you can call them like so...
$account = new Account();
$account->load(123,'Facebook');
$twitterAccount = new TwitterAccount();
$twitterAccount->load(123);
And your result would be...
Loading Facebook Account: 123
Loading Twitter Account: 123
No this isn't possible, because PHP cannot infer from the arguments which function you want (you don't specify which types you expect). You can, however, give default values to arguments in php.
That way the caller can give different amounts of arguments. This will call the same function though.
Example is:
function test($a = true)
This gives a default of true if 0 arguments are given, and takes the calling value if 1 argument is given.
I know it's a bit old issue, but since php56 you can:
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4);
ref: http://php.net/manual/en/functions.arguments.php
Overloading is not possible in PHP but you can get around it to some extend with default parameter values as explained in other responses.
The limit to this workaround is when one wants to overload a function/method according to the parameter types. This is not possible in PHP, one need to test the parameter types yourself, or write several functions. The functions min and max are a good example of this : if there is one parameter of array type it returns the min/max of the array, otherwise it returns the min/max of the parameters.
I had the idea of something like:
function process( $param1 , $type='array' ) {
switch($type) {
case 'array':
// do something with it
break;
case 'associative_array':
// do something with it
break;
case 'int_array':
// do something with it
break;
case 'string':
// do something with it
break;
// etc etc...
}
}
I have got 2 methods, getArrayWithoutKey which will output all the entries of an array without supplying any key value. The second method getArrayWithKey will output a particular entry from the same array using a key value. Which is why I have used method overloading there.
class abcClass
{
private $Arr=array('abc'=>'ABC Variable', 'def'=>'Def Variable');
public function setArr($key, $value)
{
$this->Arr[$key]=$value;
}
private function getArrWithKey($key)
{
return $this->Arr[$key];
}
private function getArrWithoutKey()
{
return $this->Arr;
}
//Method Overloading in PHP
public function __call($method, $arguments)
{
if($method=='getArr')
{
if(count($arguments)==0)
{
return $this->getArrWithoutKey();
}
elseif(count($arguments)==1)
{
return $this->getArrWithKey(implode(',' , $arguments));
}
}
}
}
/* Setting and getting values of array-> Arr[] */
$obj->setArr('name', 'Sau');
$obj->setArr('address', 'San Francisco');
$obj->setArr('phone', 7777777777);
echo $obj->getArr('name')."<br>";
print_r( $obj->getArr());
echo "<br>";