I've tested the following and it works on both PHP 5.2 and 5.3, however, it's not documented anywhere as far as I can see so I'm evaluating its use.
I have a function in a class called isValid, this checks a hash to see if the given value is in the set of allowed values. There are some values that are valid, but deprecated; I'd like my isValid function to update the passed in value to the current one and return true.
That's fine for when I call it myself, however, I'd like to use this method when used as a callback for array_filter too.
Here's a test case, which as expected results in an array with the values 2,3,4,5,6.
<?php
$test = array(1, 2, 3, 4, 5);
echo print_r(array_filter($test, 'maptest'), true);
function maptest(&$value)
{
$value ++;
return true;
}
So StackOverflow: is this allowed, or is it undocumented functionality that may disappear/stop working/cause errors in the future?
Yes, it's allowed.
In this respect, there's nothing special about calling functions through callbacks.
However, your specific example does not illustrate one difficulty. Consider:
function inc(&$i) { $i++; }
$n = 0;
// Warning: Parameter 1 to inc() expected to be a reference, value given:
call_user_func('inc', $n);
The problem is that you're passing $n to call_user_func and call_user_func doesn't accept values by reference. So by the time inc is called, it won't receive a reference. This isn't a problem with array_filter because it traverses the array directly and can directly pass the variables in the array to the callback function.
You could use call-time pass-by-reference, but this is deprecated and removed from trunk:
function inc(&$i) { $i++; }
$n = 0;
// Deprecated: Call-time pass-by-reference has been deprecated
call_user_func('inc', &$n);
So the best option is to use call_user_func_array instead:
function inc(&$i) { $i++; }
$n = 0;
call_user_func_array('inc', array(&$n));
This function will pass-by-reference the elements that have the is_ref flag set and will pass-by-value the other ones.
Except when explicitly noted, I'd say this is fine and acceptable. A callback can be any built-in or user-defined function.
Related
Unfortunately I am having problems using uasort (or any function which expects a callback function) within a Namespace.
Within this script I am not using any classes (therefore no OOP).
I didn't come up with any solution (declaring the Namespace with the callback function did not help).
I am always getting the error (or rather notice): PHP Warning: uasort() expects parameter 2 to be a valid callback,[...]
That is my (simplified) code:
<?php namespace Wire;
include("../index.php");
$test[0] = "Test1";
$test[1] = "Test2";
$test[2] = "Test3";
function selfsort($a,$b){
$stats["Test1"] = 5;
$stats["Test2"] = 6;
$stats["Test3"] = 0;
if ($stats[$a]==$stats[$b]) return 0;
return ($stats[$a]<$stats[$b])?-1:1;
}
function getPrio($arr){
uasort($arr, 'selfsort');
//usort($arr, array(__NAMESPACE__, 'selfsort')); //doesn't work either:
return $arr;
}
//returns PHP Warning: uasort() expects parameter 2 to be a valid callback, function 'selfsort' not found or invalid function name in...
print_r(getPrio($test));
Is there any possible way to declare that I want to use uasort with that specific function or any other workaround?
That was a rather stupid question, but for anyone coming up with this problem and searching for hours, I found the answer from the PHP manual comments section for callables.
There it says:
When trying to make a callable from a function name located in a namespace, you MUST give the fully qualified function name (regardless of the current namespace or use statements).
Link to PHP manual comment section
For my particular question, the answer would have been:
uasort($arr, 'Wire\selfsort');
Not sure what you are using the namespace for here, but try:
uasort($arr, '\Wire\selfsort');
The error first appears with the call to JB_do_house_keeping(). This function gets called at every request!
JBPlug_do_callback('jb_init', $A=false); // **ERROR HERE**
define ('JB_INIT_COMPLETED', true);
================================
Then also with JB_save_session():
JBPLUG_do_callback('house_keeping_critical_section', $A = false); // **ERROR HERE**
Please help! I am stuck here: I am a basic coder trying something new...
There are two ways to pass information to a function in PHP:
as a value (e.g. false, 48, 'foobar')
as a reference to a variable (e.g. $a)
Functions define how they want to receive their arguments. If they expect to receive it as a reference, that means that the changes the function makes to the variable will have an effect in the scope where the function was called.
If you provide a value where the function is expecting a reference, the function won't properly do what you expect.
For instance, array_splice expects its first argument to be a reference, so that you can get the modifications made to the array. But imagine if you did this:
array_splice(array('foo', 'bar'), 1);
You've passed in a value, not a reference. The code that called your function can't get the modified array, because array_splice doesn't return it. The correct way is like this:
$array = array('foo', 'bar');
array_splice($array, 1);
echo count($array); // echoes 1
This is because array_splice takes a reference, and so modifies a variable.
In your case, you're doing this:
JBPlug_do_callback('jb_init', $A=false);
Presumably, JBPlug_do_callback is expecting a reference as its second argument. You are providing a value (= returns a value). It may make no difference to what the function does, but technically it's a breach of PHP's rules. (That's why it's a "strict standards" error: it may well not have a bad effect, but it's technically invalid.)
You can solve this, again, simply by providing what PHP wants: a variable:
$A = false;
JBPlug_do_callback('jb_init', $A);
I have a couple of libraries that use code similar to the following one.
$args = array_merge(array(&$target, $context), $args);
$result = call_user_func_array($callback, $args);
The code is different in both the cases, but the code I shown is what essentially is done. The $callback function uses the following signature:
function callback(&$target, $context);
Both the libraries document that, and third-party code (call it plug-in, or extension) adopts that function signature, which means none of the extensions defines the callback as, e.g., function my_extension_loader_callback($target, $context).
What confuses me are the following sentence in the documentation for call_user_func_array().
Before PHP 5.4, referenced variables in param_arr are passed to the function by reference, regardless of whether the function expects the respective parameter to be passed by reference. This form of call-time pass by reference does not emit a deprecation notice, but it is nonetheless deprecated, and has been removed in PHP 5.4. Furthermore, this does not apply to internal functions, for which the function signature is honored. Passing by value when the function expects a parameter by reference results in a warning and having call_user_func() return FALSE.
In particular, the highlighted sentence seems to suggest that is not done for functions define in PHP code.
Does using call_user_func_array() in this way work in PHP 5.4?
When using call_user_func_array, passing by value when a function expects a reference is considered an error, in newer versions of PHP.
This was valid PHP code before PHP 5.3.3:
//first param is pass by reference:
my_function(&$strName){
}
//passing by value, not by reference, is now incorrect if passing by reference is expected:
call_user_func_array("my_function", array($strSomething));
//correct usage
call_user_func_array("my_function", array(&$strSomething));
The above pass by value is no longer possible without a warning (my project is also set to throw exceptions on any kind of error (notice, warning, etc).) so I had to fix this.
Solution
I've hit this problem and this is how I solved it (I have a small RPC server, so there is no such thing as referenced values after deserializing params):
//generic utility function for this kind of situations
function &array_make_references(&$arrSomething)
{
$arrAllValuesReferencesToOriginalValues=array();
foreach($arrSomething as $mxKey=>&$mxValue)
$arrAllValuesReferencesToOriginalValues[$mxKey]=&$mxValue;
return $arrAllValuesReferencesToOriginalValues;
}
Although $strSomething is not passed by reference, array_make_references will make it a reference to itself:
call_user_func_array("my_function", array_make_references(array($strSomething)));
I think the PHP guys were thinking of helping people catch incorrectly called functions (a well concealed pitfall), which happens often when going through call_user_func_array.
If call_user_func_array() returns false you have a problem, otherwise everything should be fine.
Parameters aren't passed by reference by default anymore, but you do it explicitly.
The only trouble could be that your reference gets lost during array_merge(), haven't tested that.
I've found this same problem when upgrading to PHP5.4 when there were several sites using call_user_func_array with arguments passed by reference.
The workaround I've made is very simple and consists on replacing the call_user_func_array itself with the full function call using eval(). It's not the most elegant solution but it fits the purpose for me :)
Here's the old code:
call_user_func_array($target, &$arguments);
Which I replace with:
$my_arguments = '';
for ($i=0; $i<count($arguments); $i++) {
if ($i > 0) { $my_arguments.= ", "; }
$my_arguments.= "\$arguments[$i]";
}
$evalthis = " $target ( $my_arguments );";
eval($evalthis);
Hope this helps!
The following pattern used to be possible in PHP:
function foo($arr)
{
// modify $arr in some way
return $arr;
}
This could then be called using pass-by-value:
$arr = array(1, 2, 3);
$newarr = foo($arr);
or pass-by-reference:
$arr = array(1, 2, 3);
foo(&$arr);
but "Call-time pass-by-reference has been deprecated". Modifying the function signature:
function foo(&$arr)
will handle the pass-by-reference case, but will break the dual-purpose nature of the original function, since pass-by-value is no longer possible.
Is there any way around this?
I think this is as close as you get:
function foo(&$bar) { ... }
foo($array); // call by reference
$bar = foo($_tmp = $array); // call by value
Unfortunately it will require some changes to every call.
The dual-purpose nature was stupid, which is why the behaviour is deprecated. Modify the function signature, as you suggest.
Sadly, I don't believe there is a way around it.
Just make your function use the reference with &.
A simple workaround is to prepare a wrapper function:
function foo($arr) {
return foo_ref($arr);
}
function foo_ref(&$arr) {
...
Then depending on the current use, either invoke the normal foo() or the foo_ref() if you want the array to be modified in place.
There is also a common array(&$wrap) parameter cheat, but that doesn't seem suitable in your case. A more contemporary workaround would be this tricky trick:
// pass by value
foo($array);
// pass by reference (implicitly due to being an object)
foo($array = new ArrayObject($array));
This allows for similar reference passing to unprepared functions. Personally I would prefer keeping the E_DEPRECATED warning and the intended syntax for this purpose.
This works without warning:
function test($a)
{
echo 1;
}
test(2, 1);
This will cause warning:
function test($a)
{
echo 1;
}
test();
If it's standard, any reason for this?
Because in the first example test() may use func_get_args() to access its arguments, so it shouldn't throw an error.
In the second example, the $a is not optional. If you want it to be optional, tack a default in the arguments signature like so
function test($a = 1)
{
echo 1;
}
test();
So yes, it is default behaviour, and it makes sense once you know the above.
Concerning Edit
In the edited first example, you will be able to access 2 as $a, however the 1 will only be accessible via func_get_args() (or one of its similar functions like func_get_arg()).
That is the correct behavior. Your function declaration states that it is expecting one argument. If you want to make a function argument optional, you should apply a default value. Otherwise you will raise an exception. Read the PHP documentation on Function Arguments for full details on how to declare your functions and the ways you can pass in values.
[Edit] This should work fine for you:
function test($a = null)
{
echo 1;
}
test();
I'm just speculating, but a function not receiving a parameter it's expecting is potentially more dangerous than a function receiving an extra parameter it can safely ignore.
Also, I wonder if it might have something to do with the fact that PHP will let you declare defaults for the parameter values, so the warning may be to prevent a situation where you've just forgotten to give $a a default