I need to define a function in PHP that contains multiple arguments (as shown below):
function the_function($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, ...)
but I believe there is a more efficient and clean method of doing this. Can anyone help me? I thought of using an array, something like that. obs: sorry for the noob question! :b
Edit: i'll use something like
function foo(
'number of topics', 'topics per page', 'topics per line'
, 'type of topic', 'number of excerpt words'
);
I think you can go three ways with this:
If all arguments are semantically the same, use func_get_args() within your function to iterate over the given arguments. A single array argument is a respected alternative.
If most arguments are semantically the same, group them together as a single array argument. Treat the other arguments separately.
In all other cases, group related arguments together into a separate object.
They're not entirely mutually exclusive btw.
An array is the standard approach to avoiding a ton of arguments (if a function does truly need that many). Just consider whether or not the type of things in the array are numerical in nature or if naming them makes sense. You may want to pass in an associate array rather than numerical. Of course, be absolutely sure that the function really needs those arguments and can't be broken down further.
Here's just a random example where an associative array might make sense.
$phone = [
'home' => '555-5551',
'cell' => '555-5552',
'work' => '555-5553'
];
function updateUserInfo($userId, $age, array $phone) {
}
rather than:
function updateUserInfo($userId, $age, $homePhone, $cellPhone, $workPhone) {
}
I recommend sticking to declaring your arguments explicitly. It makes for easier testing and less ambiguity. Also, you can enforce some type checking on objects and arrays.
function foo( User $user, array $user_friend_ids);
Related
I think this is quite interesting!!! :).
What I've got?
In the application that I'm using on some level in some objects (doesn't really matter) I get an array, for example:
$array = array(
'argument_label' => 'value_label',
'argument_name' => 'value_name',
'argument_id' => 'value_id'
)
I don't have any impact on how and when this array is created. Next, I've got a method:
public function arrayArgument($array) {
$label = isset($array['argument_label']) ? $array['argument_label'] : 'default_label';
$name = isset($array['argument_name']) ? $array['argument_name'] : 'default_name';
$id = isset($array['argument_id']) ? $array['argument_id'] : 'default_id';
// Do something
return 'something';
}
I really hate it. There is no way of proper documentation for the method arguments (as PHPDocumentator work not so well with arrays), and those issets are driving me crazy. Additionally it is a nightmare for someone who will work with this code in the future, when I will already be a "retired nerd".
What I want?
I want to have a function like that:
public function notArrayArgument(
$id='default_id',
$label='default_label',
$name='default_name'
) {
// Do something
return 'something';
}
What I can do?
When I get array, I can change some code, and make my own method run. So I need some kind of solution to get from here:
$array = array(
'argument_label' => 'value_label',
'argument_name' => 'value_name',
'argument_id' => 'value_id'
)
To here:
notArrayArgument('value_id', 'value_label', 'value_name');
Or here:
notArrayArgument($array['argument_id'], $array['argument_label'], $array['argument_name']);
What are the problems?
This is not template like. The number of variables is always different, the names are always different, and sometimes some of them are passed, sometimes not.
It should work really fast...
Calling the method arguments in the right order. Array can be sorted, not sorted or random sorted, while the arguments inside method are always in the same order. The array should be reordered to match the method arguments order, and after that the method should be called.
What I came with?
I've got an idea using reflectionClass. I can check the names of method arguments, get them in order, reorder the array and try to call this method. But this is quite resource eating solution, as reflectionClass is not so fast I think.
Solution using extract? This would be great. But after extract, I need to use exact variables names in code. And I don't know them as every time they are different, and I need an universal approach.
NEW (thx to comment): call_user_func_array(). It is great, but it only works with indexed arrays, so this will be the last but not least step of the possible solution. As the order of arguments is still unknown...
Does this problem have a nice semantic, pragmatic solution?
I read my question once more, and I hope it is clear to understand. If not, please post a comment and I will do my best to describe the problem better.
Kudos for thinking about the maintainer, but I'd argue simplicity is just as important as nice semantics and pragmatism. Consider this: if you had to ask how to write such a pattern, what are the chances that it will be obvious to the reader? I'd much rather come across code where I can just think "Yep, that's clear" than "Oh cool that's a really intricate and clever way of setting array defaults".
With this in mind, it seems to me that an array is being used in a situation more suited to a class. You have an id, a label and a name, which surely represents an entity - exactly what classes are for! Classes make it easy to set defaults and provide PHPDoc on each of their properties. You could have a constructor that simply takes one of your existing arrays and array_merge()s it with an array of defaults. For the reverse conversion, conveniently, casting an object to an array in PHP results in an associative array of its properties.
Try to use classes as George Brighton mentioned.
If you can't for some legacy or library constraint, you will have to use reflection. Don't worry too much about the performance of reflection classes, a lot of frameworks use them to do the request routing.
You can use a function like:
function arrayArgument($object, $method, $array)
{
$arguments = [];
$reflectionMethod = new ReflectionMethod(get_class($object), $method);
foreach ($reflectionMethod->getParameters() as $parameter)
{
$arguments[] = isset($array[$parameter->name]) ? $array[$parameter->name] : $parameter->getDefaultValue();
}
call_user_func_array(array($object, $method), $arguments);
}
So you can do
$instance = new MyClass();
arrayArgument($instance, 'notArrayArgument', ['name' => 'myname']);
I have a function with multiple parameters.
Sometimes the user may need to specify, say, the first, second, and fourth, but not the third.
In JavaScript we can do that easily using an anonymous object as a single parameter for the whole function :
function foo(args) {
if(args.arg0 != null) alert(args.arg0);
if(args.arg1 != null) alert(args.arg2);
if(args.arg2 != null) alert(args.arg1);
}
foo({
arg0: 'foo',
arg2: 10
});
If i want to do that in PHP, i can use an associative array to play the same role as this anonymous objects "args" in the function above :
foo(array(
'arg0' => 'foo',
'arg2' => 10
));
Which would not be possible using multiple parameters as one may not write :
foo('foo', , 10);
For some reason i find using arrays dirty for that, and wonder if there isn't a "cleaner" way.
Thanks for your help :)
I agree with other commenters that say an array is a good solution. However, if you still want a different way to handle this check func_get_args()
When writing a CRUD MVC application, would you suggest using arrays instead of long (even short) parameter lists when writing an api for your business layer (model)?
For instance, which would you suggest:
1
// Posts::getPosts(20, 0, $category, 'date_added');
static function getPosts($limit = NULL, $offset = NULL, Model_Category $category = NULL, $sort_by = NULL);
2
// Posts::getPosts(array('limit' => 20, 'offset' => 0, 'category' => $category, 'sort_by' => 'date_added'));
static function getPosts(array $options = NULL);`
1 seems a lot cleaner and less prone to bugs, but 2 seems WAY more flexible (can easily add/switch parameters without changing the api). Just looking for reasons to go either way.
Thanks
With just an array, the guy who will try to call your method doesn't know which parameter(s) it expects.
And his IDE will not be able to help either...
=> He'll have to go read the documentation -- which takes time.
On the other hand, with the first solution, just looking at the declaration of the method (and my IDE does display that when I type the name of the method), I know what parameters it expects.
I agree that your second solution (array of named parameters) is way more flexible.
But, especially when there are only a few parameters, I tend to prefer the first one -- just for the reason I wrote.
I go with this rule of thumb:
If
there are more than 5 arguments
there is no logical order of the arguments
there is not a clear logical dependency between arguments
and/or
most of the arguments are optional
Then it is probably a good idea to use an array to simulate keyword arguments. Otherwise, just go with standard arguments.
Also, consider using a parameter object to do complicated method calls.
EDIT: What would I do with this?
public static function search(
$keywords,
$limit = NULL,
$offset = NULL,
Model_Post_Type $type = NULL,
Model_Category $category = NULL
)
Well, with a parameter array (also known as keyword arguments in languages that support them, like Python), my personal preference would be to do this:
public static function search($keywords, $options = array()) {
$default_options = array(
'limit' => NULL,
'offset' => NULL,
'post_type' => NULL,
'category' => NULL
);
extract(array_merge($default_options,$options));
// search logic, using $keywords, $limit, $offset, $post_type, $category
}
This gives you a few benefits:
Anything in $options is completely optional. Any required arguments should be arguments.
Gives you complete control over the defaults of those options, even allowing for complicated expressions in the array initializer.
Allows you to add new (optional) search options later on, while remaining backwards-compatible with existing code.
extract() makes key-value pairs into variable-value pairs, so the rest of the method is completely oblivious to the fact you're using a parameter array and not normal arguments.
That probably depends on what you need. Do the parameters you use have a logical order? Is one inapplicable without another? It's not really a one-or-the-other-for-everything.
Because of the nature of this case, separate arguments are probably the best route. One thing I would suggest however is that it provide more useful defaults.
What is the best way to define a method signature when you have to pass many values to a function and some of these may be optional. And in future, May be I have to pass more variables or subtract some passed values given to function.
For example: (phone and address are optional)
function addInfo( $name, $dob, $phone='', $address='' ) {
// Store data
}
addInfo( 'username', '01-01-2000', '1111111' ); // address is not given
OR
function addInfo( $info ) {
// Store data
}
$info = array( 'name'=>'username',
'dob'=>'01-01-2000',
'phone'=>'1111111',
'address'=>'' );
addInfo( $info );
Martin suggests in "Clean Code", that you should limit the number of parameters atmost to 2-3 to keep the code easily understandable. So it would be better to wrap them into an object and pass the object as parameter. It's definitely preferable.
There's one more OOP-like way: create parameter object (for example, person) with fields 'name', 'dob', 'phone', 'address' (this is called Introduce Parameter Object refactoring in Fowler's "Refactoring" book).
It seems appropriate in your case, since all fields you pass to function actually are related to one object.
Although the second version of the function seems better, it's not. That's because you don't know what kind of data the function expects. An array of elements is simply too generic. An object would fit better IMHO. A class object is well defined, where you expect eg. an Employee object and you call the function with an Employer, the call would fail.
So, to sum up, I would use either the first version or the class object way.
I prefer the first version as it is more explicit and therefore more programmer friendly. A programmer can see at one glance what the function expects.
The second version would be preferred if the function could accept a large number of (optional) arguments. In that case it would make sense to bundle them and pass them as one unit.
I don't know about the performance implications to comment. I suspect that the penalty if any won't be severe.
In short, what I want is a kind of export() function (but not export()), it creates new variables in symbol table and returns the number of created vars.
I'm trying to figure out if it is possible to declare a function
function foo($bar, $baz)
{
var_dump(func_get_args());
}
And after that pass an array so that each value of array would represent param.
Just wondering if it is possible (seems that is not).
I need this for dynamic loading, so number of arguments, size of array may vary - please dont offer to pass it as
foo($arr['bar']);
and so on.
Again, ideal thing solution will look like
foo(array('foo'=>'1', 'bar'=>'2', ..., 'zzz'=>64));
for declaration
function foo($foo, $bar, ..., $zzz) {}
As far as I rememeber in some dynamical languages lists may behave like that (or maybe I'm wrong).
(I want to create dynamically parametrized methods in class and built-in mechanism of controlling functions arguments number, default value and so on is quite good for this, so I could get rid of array params and func_get_args and func_get_num calls in the method body).
Thanks in advance.
You're looking for call_user_func_array
example:
function foo($bar, $baz)
{
return call_user_func_array('beepboop',func_get_args());
}
function beepboop($bar, $baz){
print($bar.' '.$baz);
}
foo('this','works');
//outputs: this works
I don't know about the speed but you could use ReflectionFunction::getParameters() to get what name you gave the parameters, combined with call_user_func_array() to call the function. ReflectionFunction::invokeArgs() could be used as well for invoking.
What about just passing in an associative array and acting on that array?
How about using extract()?
<?php
/* Suppose that $var_array is an array returned from
wddx_deserialize */
$size = "large";
$var_array = array("color" => "blue",
"size" => "medium",
"shape" => "sphere");
extract($var_array, EXTR_PREFIX_SAME, "wddx");
echo "$color, $size, $shape, $wddx_size\n";
?>
(Code taken from PHP example)