8 ms·
Calling PHP functions with named parameters
If you need a simpler solution like call_user_func_array you can use this gist.
- jessedhillon 14y agoThe result is some very sad looking code.
- maxpert 14y agoSad in what sense?
- beezee 14y agoSad is definitely overkill here, especially for such a short snippet, but personally I prefer avoiding nested conditionals and else statements in general. Here's a fork that keeps conditional clauses to one line, and I personally find much more readable - https://gist.github.com/3029246 https://gist.github.com/3029246 (disclaimer- not tested at all)
- maxpert 14y agoThanks for forking and keep improving, I wish PHP guys do it at a native level too.
- nodrama 14y agoI think he means sad in the sense that this should not be in application code. It should be in PHP interpreter. Fixing the interpreter in application code. Sad.
- gburt 14y agoIn the same sense as code that uses call_user_func_array is dirty looking. It's cruft that has nothing to do with application logic. It'd be nice to be able to do away with these things.
- ranza 14y agoBesides the ugly syntax look... Awesome feature!
- superasn 14y agoCool, but if you're using it in a production code do make sure to link this blog post as a comment. You don't want some poor developer who takes over your project scratching his head. P.S. Sometimes that poor developer can you be too ;) Don't know how many times what seemed to be a great idea at a moment to me, leads to the biggest wtf after a month.
- shepik 14y agoI'd suggest adding "take this regular function and return function callable with array" function: https://gist.github.com/3029279 https://gist.github.com/3029279 (also, this variant is better because you can mitigate performance losses by moving all reflection operations out of lambda function to its parent, make_named_array_function)
- maxpert 14y agoAdded
- ary 14y agoOr use Python?
- notJim 14y agoSeriously. Implementing your own language features like this is a MUCH MUCH bigger kludge than just using your language as-designed. Code like this is so much harder to reason about and read, and so much more complicated. If you really feel you can't program effectively in a language that lacks keyword arguments, then for christ's sake, use a language with keyword arguments.
- mistercow 14y agoI've always thought that named parameters, while useful as a pattern, were not entirely worthy of first class status. You can easily get the same functionality with a single associative array config parameter. That also has the additional benefit that the user of a function can reuse the config array, tweaking individual settings between multiple function calls. All in all, the amount of boilerplate you save by making named parameters first class is tiny (unless, of course, your language has some kind of preposterously verbose associative array constructor syntax, like PHP does).
- masklinn 14y ago> You can easily get the same functionality with a single associative array config parameter. Not really, you lose most of the validation and documentation, you get 2 levels of parameters (first-class positionals and second-class keywords), you may not correctly detect or handle redundant provisions, defaults may or may not work the same way (and correctly), and it's not possible to provide through keywords a parameter the function's implementer has defined as positional, even if that's clearer for the callsite. It works-ish, but it is and remains a hack and a pale shadow of first-class keyword parameters. On the other hand, I can get behind the inverse: keyword-only parameters and positionals by abusing arrays (à la Smalltalk, Self or Objective-C), in my experience that significantly increases the code's readability and flexibility. Though it hardly plays nicely with partial application. > That also has the additional benefit that the user of a function can reuse the config array parameter packing & unpacking takes care of that, it's not an advantage of "a single associative array". > the amount of boilerplate you save by making named parameters first class is tiny To get the same functionality as e.g. Python's kwargs, we're probably talking about half a dozen lines. In each function. That's not tiny.
- wdewind 14y agoWhat? The benefit is not saved lines, it's ease of expressing explicitness (ie: inline with the function call instead of throw assigning to variables above it). Please show an example of how this is saving lines in a way that's not comprable to simply doing multiple assigns on one line (ie: destroying readability).
- wdewind 14y agoThis doesn't actually save anytime or space, and just adds a confusing layer. If you have to explicitly define a map of the name of the argument to the value of it, it's not significantly shorter than just assigning the value to the name of a variable and passing that in, ie: $some_user_flag = true; $some_other_flag = false; $user->someFunction($some_user_flag, $some_other_flag); vs. (assuming $user->someFunction is now your already defined/returned function because this has been abstracted away in your stack somehow): $user->someFunction(array( 'some_user_flag' => true, 'some_other_flag' => false) );
- iamumassthrower 14y agoIt's not about brevity. Do you like extending functions that are called in 1500 places by adding another un-named parameter? Unless the function you're writing is something that intrinsically should always take the same parameters that it starts with, named parameters are better. If I'm implementing function add_one($number){ return $number++; } then there's no need for named parameters. If I'm implementing function send_email($args){...} then I'm going to go with named parameters.
- TamDenholm 14y agoAm i missing something or does the following code not achieve the same thing? // define function blah($arr){ extract($arr); // use vars here } // call blah(['foo' => 'bar', 'one' => 'two']);
- masklinn 14y agoTFA's code performs validation of the arguments through reflection: it ensures all required parameters are provided, and no extra parameter is. It also is a callsite interface, it's not part of the function's implementation details.
- maxpert 14y agoYou cant ask your users (if you are designing framework) to write such a mess. Take an mvc framework for example where named url segments take place in variables automatically.
- ericingram 14y agoThe OP solution looked messier to me honestly. We already use the pattern above in JavaScript quite successfully. I'd rather see the above implementation with a simple validator in place of extract().
- dexen 14y agoAgreed on the `messier' part. How's extract($arr, EXTR_IF_EXISTS) not validating enough?
- iamumassthrower 14y agoYou don't want to use extract without validation. But, if you unset the keys that aren't allowed you can call extract safely.
- FuzzyDunlop 14y agoThe nice thing about named parameters in Python is that they're optional, and aren't all or nothing. From what I can tell, with this (and other similar hacks I've seen), you can either provide no named parameters, as usual, or you have to name every argument passed, in an array. In such a case, I fail to see how implementing this can do anything but increase code complexity.
- melvinmt 14y agoThe example explicitly shows that optional parameters are not required: if (!$p->isOptional() and !isset($arr[$p->name])) throw new MissingArgumentException
- MattBearman 14y agoYou can do the following in PHP: some_function($param='hello', $somthingelse=3); It's not quite as good as the method in this post, as you still have to specify params in the correct order, but it means at a glance it's easier to remember what each parameter is. In fach I do this a lot for that very reason.
- function_seven 14y agoThat doesn't seem "right" to me, though. What if you have a variable in your calling scope that conflicts with a parameter in your function?
- ericingram 14y agoThat's an interesting way to express this!
- jstsch 14y agoThis will kill your performance... function calls are slow, but calling them via call_user_func is crazy slow.
- chrisohara 14y agoIt's pretty negligible in the scheme of things. I was abusing PHP reflection a while back to add decorator support (https://github.com/chriso/Request https://github.com/chriso/Request) and used this pattern to reduce the cost: switch (count($args)) { case 0: $method(); break; case 1: $method($args[0]); break; case 2: $method($args[0], $args[1]); break; //etc default: call_user_func_array($method, $args); break; }
- dexen 14y agoI'm often using compact()/extract() in similar, but not necessarily identical pattern. For example: $foo = get_some_value(); $bar = get_other_value(); my_function(compact('foo', 'bar')); function my_function($args) { # optionally provide default values :-) $foo = default_value; $bar = default_value; extract($args, EXTR_IF_EXISTS); # the EXTR_IF_EXISTS is optional, for when we want to enforce sanity check /* function code goes here */ } (edits: clarifications)
- notJim 14y agoWhy? This is a horrible idea. It makes your code so much harder to read or reason about, because it's impossible to tell what variables will be passed in without looking at every single callsite.
- dexen 14y agoThat's not how the code works; I can assure you after using it for some time. The client code (the callee) is in control of variables and lists explicitly what will be provided, either as default or as an actual (keyword) argument. When using EXTR_IF_EXISTS and list of default values, you know exactly what variables you will get -- either passed as argument, or by default, by reading the callee. When not using EXTR_IF_EXISTS and you want to extract only known-good variables from untrusted input, array_intersect_key() helps. The idiom may seem strange at first, but once you get used to it, you read it naturally. That's the thing with idioms, after all. Point in case, our new hire got up to speed very quickly. ---- EDIT: in some cases I just skip the extract()ing and simply access $args['field_name']. It's safe as PHP will raise error, stop execution and drop to error handler if some field was not provided by mistake -- or malicious user action. And any superfluous fields remain unaccessed, safe again.
- program 14y agoWordPress has a similar approach. The wp_parse_args function which accept an array or a URL-like string. You can also pass default values (and extract() them if you want.) http://codex.wordpress.org/Function_Reference/wp_parse_args http://codex.wordpress.org/Function_Reference/wp_parse_args You can pass the arguments as an array or as a URL-like ampersand separed string.
- mildweed 14y agoWordpress should upgrade wp_parse_args to use this Gist.
- Xcelerate 14y agoThis is an interesting post. I'm designing a little programming language as a hobby and I am stuck at how I want to implement function calls. Without named parameters, you end up with something like the Win32API: ReadSomeFile("file.txt", SOME_FLAG, 0, SOME_OTHER_FLAG, NULL, NULL, CREATE_FILE); This can largely be avoided by structuring your API into little single-purpose chunks, but it remains a bit of a problem. Also I think it's somewhat annoying that you have to remember the order of arguments. In my opinion, this order should not be significant. It leads to a lot of bugs (for people who don't check the documentation for every method) with things like: str_replace(needle, haystack) vs str_replace(haystack, needle) It would almost be easier to have everything take a map ajax url: "example.com/newwidget" method: "post" error: -> ... success: -> ... But if you enforce this for everything you get something like: square number: 4 which is just ridiculous. I kind of like Python's way of doing it -- making them optional. One thing's for certain -- Javascript frameworks need to develop some kind of consistency. Sometimes you pass in parameters, and sometimes you pass in a configuration object. This inconsistency makes it difficult, and it's too hard to remember which calls take parameters and which take a map.
- robomartin 14y agoThis is not criticism, but I'd just like to point out that the farther you get from the raw realities of the efficiency of C-style code you increase the probability of code becoming bloated with constructs and "rigging" under the hood that's there just to make things convenient to a programmer. At one point or another there is no escaping the reality that pointers will end-up as addresses in a register used to make an indirect call or "MOV" of some sort and that other data types will similarly land on registers or various memory areas as they are passed to and from functions. I got my start writing machine code (not assembler, but entering raw hex codes into hexadecimal keypads) and that caused me to always be very aware of where I am just being lazy and loading things-up with code just to make my life easier. Granted, sometimes this is justified. Today's coding tools (editors and IDE's with code completion and "live" API reference) make it harder to screw some of this up. I tend to always go for simple, fast and efficient. But that's my preference.
- zippie 14y agoPerformance note - having call_* functions in your code guarantees that your work will not opcode cache friendly - WP is guilty of this (in addition to their include/require spaghetti).
- antihero 14y agoWhy not something like? function myfunc($kwargs) { extract($kwargs); } And then just call it with an array. Sure, it's ugly, but that's the price of using PHP.
- jaequery 14y agoi'd advise the OP to pass in an array. and look into an API-inspired pattern which organizes code and above all, proper type-chcking and error handling. for instance: $result = App:api('/do/something', array(name=>'john', age=>10, etc)); class Do extends Api { function something($params = array('name'=>'', age=>'')){ ... } }