I have been blissfully unaware of the php function func_get_args(), and now that I have discovered it I want to use it everywhere. Are there any limitations of using func_get_args as compared to explicit argument declaration in the function definition?
You shouldn't use func_get_args unless you actually need it.
If you define a function to take a specific number of arguments, PHP will raise an error if you don't supply enough arguments at call time.
If you take any number of arguments via func_get_args, it's up to you to specifically check that all the arguments you're expecting have been passed to your function.
Similarly, you lose the ability to use type hinting, you can't supply default values, and it becomes much harder to tell what arguments your function expects at a glance.
In short, you prevent PHP from helping you catch (potentially difficult to debug) logic errors.
function do_stuff(MyClass tmpValue, array $values, $optional = null) {
// This is vastly better...
}
function do_stuff() {
// ... than this
}
Even if you want to allow a variable number of arguments, you should explicitly specify as many arguments as you can:
/**
* Add some numbers
* Takes two or more numbers to add together
*/
function add_numbers($num_1, $num_2 /* ..., $num_N */) {
$total = 0;
for ($i = 0; $i < func_num_args(); ++$i)
$total += func_get_arg($i);
return $total;
}
add_numbers(1,2); // OK!
add_numbers(1,2,3); // OK!
add_numbers(1) // Error!
For starters I think it has a performance impact.
It makes your code much harder to read and understand.
No automatic error alert will make debugging a pain.
I think auto-completion is harder if not impossible to do for the IDE (it might use the phpdoc #param declaration though)
EDIT: you may use it when your have only one argument : a one-dimensional array, which keys do not matter. It then becomes very handy.
The only limitation I'm aware of is that stuff is harder to check at parse time. Note that this includes parsers for, say, automated documentation tools, since the functions have args that aren't right there in the function declaration
Related
I like creating my PHP functions using key=>value pairs (arrays) as arguments instead of individual parameters.
For example, I prefer:
function useless_func($params) {
if (!isset($params['text'])) { $params['text'] = "default text"; }
if (!isset($params['text2'])) { $params['text2'] = "default text2"; }
if (!isset($params['text3'])) { $params['text3'] = "default text3"; }
echo $params['text'].$params['text2'].$params['text3'];
return;
}
And I don't like:
function useless_func($text = "default text", $text2 = "default text2", $text3 = "default text3") {
echo $text.$text2.$text3;
return;
}
I had first seen things done this way extensively in the Wordpress codebase.
The reason I prefer arrays:
Function arguments can be provided in any order
Easier to read code / more self documenting (in my opinion)
Less prone to errors, because when calling a function I must investigate the proper array keys
I was discussing this with a co-worker and he says that it's useless and just leads to extra code and it's much harder to set the default values. Basically, he disagrees with me completely on all three points.
I am looking for some general advise and guidance from experts who might be able to provide insight: What's the better or more proper way to do this?
Don't do that!
Passing all in an array is a bad idea most of the time.
It prevents people from using your function without knowing what it needs to operate.
It lets you create functions needing lots of parameters when probably you should create a function with more precise argument needs and a narrower goal
It seems like the contrary of injecting in a function what it needs.
Function arguments can be provided in any order
I have no such preference. I don't understand that need.
Easier to read code / more self documenting (in my opinion)
Most IDEs will present you with the different arguments a function needs. If one sees a function declaration like foo(Someclass $class, array $params, $id) it is very clear what the function needs. I disagree that a single param argument is easier to read or self documenting.
Less prone to errors, because when calling a function I must investigate the proper array keys
Allowing people to pass in an array without knowing that values will be defaulted is not close to "not error-prone". Making it mandatory for people to read your function before using it is a sure way for it never to be used. Stating that it needs three arguments along with their defaults is less error prone because people calling your function will know which values the parameters will be defaulted to, and trust that it will present the result they expect.
If the problem you are trying to solve is a too great number of arguments, the right decision is to refactor your functions into smaller ones, not hide function dependencies behind an array.
Well, it's kinda usefully. But for some arguments which is passing always it's better to use classic passing like function some($a1, $a2). I'm doing like this in my code:
function getSome(SomeClass $object, array $options = array())
{
// $object is required to be an instance of SomeClass, and there's no need to get element by key, then check if it's an object and it's an instance of SomeClass
// Set defaults for all passed options
$options = array_merge(array(
'property1' => 'default1',
'property2' => 'default2',
... => ...
), $options);
}
So, as you can see I like that code style too, but for core-arguments I prefer classic style, because that way PHP controls more things which should I, if I used the you code style.
I'm assuming you're asking whether it's A Good Thing to write all functions so that they accept only one argument, and for that argument to be an array?
If you're the only person who's ever going to work on your code then you can do what you like. However, by passing all argument values through an array, anyone else is going to have to work harder to understand what the function does and why / how they could use it, especially if they're using an IDE with auto-complete for function names etc. They don't call it a "function signature" for nothing.
I'd recommend that array parameters are reserved either for items where you don't know how many there will be (e.g. a series of data items), or for groups of related options / settings (which may be what's going on in the Wordpress example that you mention?).
If you do continue with a blanket approach to array arguments then you should at least be aware of its impact on readability and take some steps to counter that issue.
Your co-worker is right. Not only is it more code for the same functionality, it is harder to read and probably has lowered performance (Since you need to call isset for each param and you need to access an array to set values).
This borders on Cargo Cult programming. You say this is more readable and self-documenting. I would ask how? To know how to use your function/method I have to read into the code itself. There's no way I can know how to use it from the signature itself. If you use any half-decent IDE or editor that supports method signature hinting this will be a real PITA. Plus you won't be able to use PHP's type-hinting syntax.
If you find you are coding a load of parameters, especially optional parameters then it suggests there might be something wrong with your design. Consider how else you might go about it. If some or all of the parameters are related then maybe they belong to their own class.
Using array_merge() works okay, but using the + operator can be used too; it works the other way, it only adds default values where one hasn't been given yet.
function useless_func(array $params = array())
{
$params += array(
'text' => 'default text',
'text2' => 'default text2',
'text3' => 'default text3',
);
}
See also: Function Passing array to defined key
A few things you don't get with using arrays as function arguments is:
type checking (only applicable to objects and arrays, but it can be useful and in some cases expected).
smart(er) text editors have a code insight feature that will show the arguments a function understands; using arrays takes away that feature, though you could add the possible keys in the function docblock.
due to #2 it actually becomes more error prone, because you might mistype the array key.
Your co-worker is crazy. It's perfectly acceptable to pass in an array as a function argument. It's prevalent in many open source applications including Symfony and Doctrine. I've always followed the 2 argument rule, if a function needs more than two arguments, OR you think it will use more than two arguments in the future, use an array. IMO this allows for the most flexibility and reduces any calling code defects which may arise if an argument is passed incorrectly.
Sure it takes a little bit more work to extrapolate the values from the array, and you do have to account for required elements, but it does make adding features much easier, and is far better than passing 13 arguments to the function every time it needs to be called.
Here is a snippet of code displaying the required vs optional params just to give you an idea:
// Class will tokenize a string based on params
public static function tokenize(array $params)
{
// Validate required elements
if (!array_key_exists('value', $params)) {
throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
}
// Localize optional elements
$value = $params['value'];
$separator = (array_key_exists('separator', $params)) ? $params['separator'] : '-';
$urlEncode = (array_key_exists('urlEncode', $params)) ? $params['urlEncode'] : false;
$allowedChars = (array_key_exists('allowedChars', $params)) ? $params['allowedChars'] : array();
$charsToRemove = (array_key_exists('charsToRemove', $params)) ? $params['charsToRemove'] : array();
....
I have used arrays to substitute a long list of parameters in many occasions and it has worked well. I agree with those in this post that have mentioned about code editors not being able to provide hints for the arguments. Problem is that if I have 10 arguments, and the first 9 are blank/null it just becomes unwieldy when calling that function.
I would also be interested in hearing an how to re-design a function that requires a lot of arguments. For example, when we have a function that builds SQL statements based on certain arguments being set:
function ($a1, $a2, ... $a10){
if($a1 == "Y"){$clause_1 = " something = ".$a1." AND ";}
...
if($a10 == "Y"){$clause_10 = " something_else = ".$a10." AND ";}
$sql = "
SELECT * FROM some_table
WHERE
".$clause_1."
....
".$clause_10."
some_column = 'N'
";
return $sql;
}
I would like to see PHP entertain adding a native helper function that could be used within a the function being called that would assist in passing an array of parameters by undertaking the necessary type checking. PHP recognized this to a certain extent by creating the func_get_args() function which allows arguments to be passed in any order. BUT this will only pass a COPY of the values, so if you want to pass objects to the function this will be a problem. If such a function existed, then the code editors would be able to pick this up and provide details on possible arguments.
#Mike, you could also "extract()" your $params argument into local variables, like this:
// Class will tokenize a string based on params
public static function tokenize(array $params)
{
extract($params);
// Validate required elements
if (!isset($value)) {
throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
}
// Localize optional elements
$value = isset($value) ? $value : '';
$separator = isset($separator) ? $separator] : '-';
$urlEncode = isset($urlEncode) ? $urlEncode : false;
$allowedChars = isset($allowedChars) ? $allowedChars : array();
$charsToRemove = isset($charsToRemove) ? $charsToRemove : array();
....
Same implementation, but shorter.
What is the difference between these two code excerpts, respectively, in PHP?:
function eat_apple($apple)
{
return true;
}
And:
function eat_apple()
{
$apple = func_get_arg(0);
return true;
}
Or is there a difference? If there is not, what is the point of func_get_arg()? Surely it serves some purpose I'm not aware of?
func_get_arg (and func_get_args) make it possible to have functions with a variable number of parameters, so called variadic functions.
Take array_merge as an example, which takes a variable number of arrays to be merged.
Hello. What is the difference between these two code excerpts, respectively, in PHP?:
-function eat_apple($apple)
+function eat_apple()
{
+ $apple = func_get_arg(0);
return true;
}
what is the point of func_get_arg()? Surely it serves some purpose I'm not aware of?
The officially stated purpose is to:
Return an item from the argument list
-- http://php.net/func-get-arg
In truth, there isn't much difference, and a standard passed argument is usually preferable over func_get_arg().
There are two occasions when you might want to use func_get_arg() rather than using defined arguments:
If you want to have an unlimited number of arguments. Typically, this would be better done with a single array argument, but there are times when you may want to allow unlimited arguments. A real example of a function that works this way is PHP's built-in printf function.
Where you have a complex function where the number of arguments given may alter what the arguments are used for and/or their data type. For example, passing a single argument may cause the function to expect a string, whereas passing two arguments, it may expect an integer first, and then the string. I can't think of any examples of this off the top of my head, but I'm fairly sure there are some. Ordinarily, I would say that this sort of behaviour is bad practice, but I can see how it may be useful for adding features while maintaining backward compatibility.
There are two things that you will definitely lose if you use fung_get_arg() instead of standard function arguments:
The ability to pass by reference.
The ability for your IDE to do any type hinting or auto-completion.
The other possible reason I could think of is avoiding a fatal error.
You have a function:
<?php
function display ($a, $b, $c) {
echo $a . " is " . $b . " " . $c;
}
?>
and you could call
<?php
display ("Rock", "good", "boy");
- this will not throw any error.
display ("Rock", "good");
- this will throw fatal error.
?>
So, now think of the second call, you could avoid the fatal error, see how many arguments are passed with func_get_args and process your code with func_get_arg(1) etc.
Several reasons:
You can use that magic to emulate overloading
You can write function (like printf) which can take undetermined number of arguments.
Example for first:
class a{
function X(MyOBJ $obj){...}
}
class b extends a{
function X(string $s,int $i){...}
}
class c extends b{
function X(){...}
}
This will throw a warning. If you use func_get_args() inside the function to get relevant parameters, no warning will be thrown.
Example for the second:
//not good, you can only send 3 strings
function merge_strings($s1, $s2, $s3){
return $s1 . $s2 . $s3;
}
//good one
function merge_strings(){
return join('',func_get_args());
}
Why don't the function handling functions like call_user_func() support passing parameters by reference?
The docs say terse things like "Note that the parameters for call_user_func() are not passed by reference." I assume the PHP devs had some kind of reason for disabling that capability in this case.
Were they facing a technical limitation? Was it a language design choice? How did this come about?
EDIT:
In order to clarify this, here is an example.
<?php
function more(&$var){ $var++; }
$count = 0;
print "The count is $count.\n";
more($count);
print "The count is $count.\n";
call_user_func('more', $count);
print "The count is $count.\n";
// Output:
// The count is 0.
// The count is 1.
// The count is 1.
This is functioning normally; call_user_func does not pass $count by reference, even though more() declared it as a referenced variable. The call_user_func documentation clearly says that this is the way it's supposed to work.
I am well aware that I can get the effect I need by using call_user_func_array('more', array(&$count)).
The question is: why was call_user_func designed to work this way? The passing by reference documentation says that "Function definitions alone are enough to correctly pass the argument by reference." The behavior of call_user_func is an exception to that. Why?
The answer is embedded deep down in the way references work in PHP's model - not necessarily the implementation, because that can vary a lot, particularly in the 5.x versions. I'm sure you've heard the lines, they're not like C pointers, or C++ references, etc etc... Basically when a variable is assigned or bound, it can happen in two ways - either by value (in which case the new variable is bound to a new 'box' containing a copy of the old value), or by reference (in which case the new variable is bound to the same value box as the old value). This is true whether we're talking about variables, or function arguments, or cells in arrays.
Things start to get a bit hairy when you start passing references into functions - obviously the intent is to be able to modify the original variables. Quite some time ago, call-time pass-by-reference (the ability to pass a reference into a function that wasn't expecting one) got deprecated, because a function that wasn't aware it was dealing with a reference might 'accidentally' modify the input. Taking it to another level, if that function calls a second function, that itself wasn't expecting a reference... then everything ends up getting disconnected. It might work, but it's not guaranteed, and may break in some PHP version.
This is where call_user_func() comes in. Suppose you pass a reference into it (and get the associated the call-time pass-by-reference warning). Then your reference gets bound to a new variable - the parameters of call_user_func() itself. Then when your target function is called, its parameters are not bound where you expect. They're not bound to the original parameters at all. They're bound to the local variables that are in the call_user_func() declaration. call_user_func_array() requires caution too. Putting a reference in an array cell could be trouble - since PHP passes that array with "copy-on-write" semantics, you can't be sure if the array won't get modified underneath you, and the copy won't get detached from the original reference.
The most insightful explanation I've seen (which helped me get my head around references) was in a comment on the PHP 'passing by reference' manual:
http://ca.php.net/manual/en/language.references.pass.php#99549
Basically the logic goes like this. How would you write your own version of call_user_func() ? - and then explain how that breaks with references, and how it fails when you avoid call-time pass-by-reference. In other words, the right way to call functions (specify the value, and let PHP decide from the function declaration whether to pass value or reference) isn't going to work when you use call_user_func() - you're calling two functions deep, the first by value, and the second by reference to the values in the first.
Get your head around this, and you'll have a much deeper understanding of PHP references (and a much greater motivation to steer clear if you can).
See this:
http://hakre.wordpress.com/2011/03/09/call_user_func_array-php-5-3-and-passing-by-reference/
Is it possible to pass parameters by reference using call_user_func_array()?
http://bugs.php.net/bug.php?id=17309&edit=1
Passing references in an array works correctly.
Updated Answer:
You can use:
call_user_func('more', &$count)
to achieve the same effect as:
call_user_func_array('more', array(&$count))
For this reason I believe (unfoundedly) that call_user_func is just a compiler time short cut. (i.e. it gets replaced with the later at compile time)
To give my view on you actual question "Why was call_user_func designed to work this way?":
It probably falls under the same lines as "Why is some methods strstr and other str_replace?, why is array functions haystack, needle and string functions needle, haystack?
Its because PHP was designed, by many different people, over a long period of time, and with no strict standards in place at the time.
Original Answer:
You must make sure you set the variable inside the array to a reference as well.
Try this and take note of the array(&$t) part:
function test(&$t) {
$t++;
echo '$t is '.$t.' inside function'.PHP_EOL;
}
$t = 0;
echo '$t is '.$t.' in global scope'.PHP_EOL;
test($t);
$t++;
echo '$t is '.$t.' in global scope'.PHP_EOL;
call_user_func_array('test', array(&$t));
$t++;
echo '$t is '.$t.' in global scope'.PHP_EOL;
Should output:
$t is 0 in global scope
$t is 1 inside function
$t is 2 in global scope
$t is 3 inside function
$t is 4 in global scope
Another possible way - the by-reference syntax stays the 'right' way:
$data = 'some data';
$func = 'more';
$func($more);
function more(&$data) {
// Do something with $data here...
}
I'm using someone elses class and that person has defined a function with five arguments.
in Sentry.php:
function checkLogin($user = '',$pass = '',$group = 10,$goodRedirect = '',$badRedirect = '')
If all five fields are filled in this leads to a login procedure.
Now on the page where he explains how to use this there is a snippet which, according to php.net, doesn't make sense.
in a page that loads the sentry:
require_once('../system/Sentry.php');
$theSentry = new Sentry();
if(!$theSentry->checkLogin(2)){ header("Location: login.php"); die(); }
which by default should behave as such that it checks if the $group argument is <= 10 (default). In this situation it should be two. If the user checked has a group-variable of <=2 this should enable the person to view the page.
However, this doesn't work and for a very obvious reason: the php manual states:
Note that when using default
arguments, any defaults should be on
the right side of any non-default
arguments; otherwise, things will not
work as expected.
So the code, according to phpbuilder.com should have no optional ($variable = default_something) field to fill in with a call to the function and it definitely shouldn't be defined as the third of five arguments.
How can I use the function like this?:
checkLogin(2)
Default arguments are PHP's way of dealing with the lack of overloaded functions. In Java you can write this:
public void login(String username)
public void login(String username, String password)
In PHP you have to solve it like this:
function login($username, $password = '')
This way $username is mandatory and $password is optional. While this may be convenient, it isn't always. In your example there are a log of arguments and they are all optional. A clean solution for this would be to make 1 function that does the work and add 'convenience' methods to make the interface cleaner.
So add:
function checkLoginGroup($group = 10) {
$this->checkLogin('', '', $group);
}
It simply calls the function that already exists, but it allows for a cleaner interface, just call:
$theSentry->checkLoginGroup(2);
It's even neater to make the provided method private (or protected, depending on your needs) and create public 'convience' methods so you can hide the implementation details.
However, if you can not or don't want to change the original class, you might be able to create a subclass.
Changing the order of the arguments is a possibility... But what for the time you'll need another argument to be "more optionnal" or "less optionnal" ? Will you change that order again ?
And, with it, every call that's made to the function ?
To pass only the third argument, considering your function's declaration, the only method I see is to give the three first arguments... Passing the default value (which you'll have to know, of course -- just look at it's declaration, for that) for the first two :
checkLogin('', '', 20);
In my opinion, using this instead of that one :
checkLogin(null, null, 20);
Has the advantage of being explicit : someone looking at the function's declaration will immediatly notice you're using the default values for the first two parameters.
Using NULL, the personn reading your code would have to check inside the function's code to see if NULL is handled in a special way (it could, afterall !) ; it wouldn't be as easy for someone to understand that this is your way of passing default values... As they are not the default values ^^
Other solutions would imply refactoring the function,
either to pass only one array as parameter (with its keys not being required by the code)
ot using Variable-length argument lists
Either way, you'd lose the ability your IDE has to show you the parameters the function is waiting for ; and that's bad :-(
And you'd also lose the phpdoc...
In addition to Mythicas excellent answer, I just want to point out another way of dealing with a lot of optional arguments: Arrays.
function checkLogin($args) {
$defaults = array('user' => null, 'pass' => null, 'group' => null, ...);
$args = array_merge($defaults, $args);
...
}
checkLogin(array('user' => 'x', 'group' => 9));
This essentially bypasses PHPs optional/required argument syntax entirely and instead custom-handles these things internally. It has the advantage of avoiding the problem you describe and making the function call extremely readable. The problem is that it makes the function more complex and you can't take advantage of PHP checking the existence of arguments for you.
The easiest way is just to change the order of arguments so that $group is first.
checkLogin(NULL,NULL,2);
but honestly bad coding style.
Named function parameters can be emulated in PHP if I write functions like this
function pythonic(array $kwargs)
{
extract($kwargs);
// .. rest of the function body
}
// if params are optional or default values are required
function pythonic(array $kwargs = array('name'=>'Jon skeet'))
{
extract($kwargs);
// .. rest of the function body
}
Apart from losing intellisense in IDEs what are the other possible downsides of this approach?
Edit:
Security: Shouldn't security be a non-issue in this case, as the extracted variables are limited to function scope?
I would suggest using the associative array to pass named parameters, but keep them in the array without extracting them.
function myFunc(array $args) {
echo "Hi, " . $args['name'];
// etc
}
There's a couple of reasons for this. Looking at that function, you can quite clearly see that I'm referring to one of the arguments passed into the function. If you extract them, and don't notice the extract() you (or the next guy) will be there scratching your head wondering where this "$name" variable came from. Even if you do know you're extracting the arguments to local variables, it's still a guessing game to a certain degree.
Secondly, it ensures that other code doesn't overwrite the args. You may have written your function expecting only to have arguments named $foo and $bar, so in your other code, you define $baz = 8;, for example. Later on, you might want to expand your function to take a new parameter called "baz" but forget to change your other variables, so no matter what gets passed in the arguments, $baz will always be set to 8.
There are some benefits to using the array too (these apply equally to the methods of extracting or leaving in the array): you can set up a variable at the top of each function called $defaults:
function myFunc (array $args) {
$default = array(
"name" => "John Doe",
"age" => "30"
);
// overwrite all the defaults with the arguments
$args = array_merge($defaults, $args);
// you *could* extract($args) here if you want
echo "Name: " . $args['name'] . ", Age: " . $args['age'];
}
myFunc(array("age" => 25)); // "Name: John Doe, Age: 25"
You could even remove all items from $args which don't have a corresponding $default value. This way you know exactly which variables you have.
Here's another way you could do this.
/**
* Constructor.
*
* #named string 'algorithm'
* #named string 'mode'
* #named string 'key'
*/
public function __construct(array $parameter = array())
{
$algorithm = 'tripledes';
$mode = 'ecb';
$key = null;
extract($parameter, EXTR_IF_EXISTS);
//...
}
With this set up, you get default params, you don't lose intellisense in IDEs and EXTR_IF_EXISTS makes it secure by just extracting array keys that are already existing as variables.
(By the way, creating default values from the example you provided is not good, because if an array of param is provided without a 'name' index, your default value is lost.)
In my experience, this approach is really only beneficial if one of two things is true
For whatever extenuating reasons, your argument signature is big. I kinda go by 6 as a maximum - not for any specific reason though just seems right - but I freely admit that this number is arbitrary.
All or many of your arguments are optional, and sometimes you only need to set a value for the 5th one or some such thing. It's annoying to write someFunc( null, null, null, null, 1 );
If either of these is true for you, faking named params with an associative array might be the right implementation. Aside from knowing when to avoid extract (or avoiding it altogether) I can't immediately think of other downsides.
That being said, oftentimes both of these problems can be solved through refactoring as well.
In my experience, the downside of this method is more code to write.
consider something like this:
function someFunc($requiredArg, $arg1 = "default11", $arg2 = "default2") {
To simulate this behavior when passing everything in an array you'll need to write more code, and the "function signature" will be less "clear and obvious".
function someFunc($requiredArg, $optionalArgs) {
// see other answers for good ways to simulate "named parameters" here
I'm wondering if it would be a good idea for PHP to address that in a future release, maybe have something like Pascal or VB syntax available for function arguments.
Anyway, I only pass parameters in a single array when really needed - like functions that have a parameter set that is likely to change a lot during development. These functions usually also have numerous parameters.
Others have alreay answered your other points, I would just like to comment on security aspect.
Security: Shouldn't security be a non-issue in this case, as the
extracted variables are limited to function scope?
Yes and no. The code you have written could (depends on whether you always initialize your variables after this call) overwrite your vars. Example:
function pythonic(array $kwargs = array('name'=>'Jon skeet'))
{
$is_admin = check_if_is_admin(); // initialize some variable...
extract($kwargs);
// Q: what is the value of $is_admin now?
// A: Depends on how this function was called...
// hint: pythonic([ 'is_admin' => true ])
}
What makes this code "kind-of-secure" is that YOU are the one who is calling it - so user can't supply arbitrary parameters (unless you redirect POST vars there, of course ;).
As a rule of thumb you should avoid such magic. The line with extract() could have unintended side effects, so you should not use it. Actually, I can't think of a legitimate use of extract() function in any application (I don't think I have ever used it myself).