My new best friend - PHP's create_function()
Posted on 18/5/07 by Felix Geisendörfer
Deprecated post
The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.
Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.
Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).
Hey folks,
I meant to introduce you to my new best friend for quite a while but didn't get around to do it so far. His name is 'create_function' and he's a really useful co-worker. For those of you who just vaguely know him - don't worry, he's not so much like his ev(a|i)l cousin. Well, that doesn't mean he can't be harmful, but he's more likely to help you instead.
Alright enough of that non-sense ; ). But I really recommend everybody to give this function a closer look. One does not need it very often, but there are things that can be incredibly simplified and made very powerful using create_function. I mostly use it for quick conversion / manipulation functions that I just need temporally, for filtering items or for advanced mappings based on rules. So for those of you who haven't done any of this so far, or used verbose and complex solutions for it, here is a neat little example:
The task is the following, you are the project manager of a big PHP application and want to hire some new programmers. However all the ones on your list kind of sound the same and you don't really feel like reading their long résumés. So in order to speed things up you make yourself a little PHP array with the most important facts about all of them:
'Jim' => array(
'languages' => array('PHP', 'C++', 'JavaScript')
, 'rate' => 50
),
'John' => array(
'languages' => array('Java', 'Visual Basic', 'PHP', 'C++')
, 'rate' => 55
),
'Jack' => array(
'languages' => array('ActionScript', 'PHP')
, 'rate' => 45
),
'James' => array(
'languages' => array('C++', 'C', 'JavaScript', 'Python', 'Ruby', 'PHP', 'ActionScript', 'VB.NET')
, 'rate' => 75
),
'Jeff' => array(
'languages' => array('PHP')
, 'rate' => 25
),
'Joe' => array(
'languages' => array('Lisp', 'Haskell', 'Python', 'Ruby', 'Scheme', 'Ada')
, 'rate' => 60
)
);
Now that you have that list you start to think about what kind of people you want to hire, and come up with the following criterias:
- The programmer must know PHP
- The programmer should have no Java background (no offense if you are one, this is just an example ^^)
- The programmer should not charge too much, less or equal to $50 / hour is ok.
- The programmer needs to know at least 2 different languages.
So most people (including me until a while ago) would solve this by looping through all $programmers and then nest a long if-else chain in it that will unset() the current programmer if one of the if statements evaluates to true. However, a much more readable and extendable approach is to define the $conditions in an array of short PHP code snippets that return true or false depending whether the condition is met. This way we can implement a neat little 'filter' function that can be used to filter items fairly comfortable from now on like this:
foreach ($conditions as $name => $condition) {
$condition = create_function('$item', $condition);
foreach ($items as $i => $item) {
if ($condition($item) != true) {
unset($items[$i]);
}
}
}
return $items;
}
$conditions = array(
'knows-php' => 'return in_array("PHP", $item["languages"]);',
'no-java-background' => 'return !in_array("Java", $item["languages"]);',
'cheap' => 'return $item["rate"] <= 50;',
'multi-lingual' => 'return count($item["languages"]) >= 2;',
);
$programmers = filter($programmers, $conditions);
echo join(', ', array_keys($programmers)); // Outputs "Jim, Jack"
Of course this is just a very simple example but you should get my point: create_function allows you to create very flexible functions that help you to save many lines of code. Also note that the filter function I showed here just takes code snippets as conditions and takes care of calling create_function for you. While this is neat in certain situations, you get more flexibility if you allow your conditions to be callbacks so you can use new functions created using 'create_function' or existing functions to do your filtering.
Another neat way to use create_function is to create temporary shortcuts to already existing functions with some parameters being pre-populated:
echo "Hello ".$user['User']['name'].' today is '.$formatDate()."\n\n";
echo "Your most recent emails are:\n";
foreach ($user['Email'] as $email) {
echo $formatDate($email['date']).' - '.$email['Subject']."\n";
}
Now of course this style of coding is not for the faint of heart and I would not recommend it to everybody / every environment. But if you decide to use it make sure to validate eventual user-enterable data for not containing PHP code to avoid security problems.
Alright, I hope some of you find this useful and I'd be interested in what ways you have discovered to use those lambda style functions in PHP.
-- Felix Geisendörfer aka the_undefined
PS: The filter function from above can be simplified further like this:
foreach ($conditions as $name => $condition) {
$items = array_filter($items, create_function('$item', $condition));
}
return $items;
}
I just wanted to keep things simple for the example.
You can skip to the end and add a comment.
Hey there,
nice code but...
have you tested if there are performance hits? because I'm think that if we make a cost-benefit analysis( op-code cache, memory allocation, debugging ), the traditional approach will prove to be far more efficient.
however, that's a cool idea which may quite be useful in some cases.
Henrique: Yeah create_function is not for the faint of heart and I don't recommend you to go out and throw it in wherever you can. But I think there are many legitimate use-cases for it and it should be a neat little asset in any PHP coders bag of tricks ; ).
Georgi: No I have not : ). The reason I smile is because this is very unlikely to ever become a performance leak. Not because it might not be slower then the traditional approaches (not even sure if it is), but because bottlenecks are usually on the database side of big applications. So unless you are going to filter a million items with this on a heavy traffic site please check out what chris hartjes thinks about things like this:
http://www.littlehart.net/atthekeyboard/2007/01/02/just-build-it-damnit/
And also: Anything I will blog about here will show you how to solve programming problems faster and save development time rather then CPU cycles. The reason for that is that the latter is getting increasingly cheaper allowing us to be much more wasteful with it. If you are unwilling to accept the idea of writing unoptimized code and knowing it, checkout http://paulgraham.com/hundred.html and read how wasteful programming languages have gotten in the past and will more and more do so in future : ).
Felix Geisendorfer's Blog: My new best friend - PHP's create_function()...
...
[...] In a new post to his blog today, Felix Geisendorfer wants to introduce you to his new best friend in the wonderful world of PHP - the create_function function. His name is ‘create_function‘ and he’s a really useful co-worker. For those of you who just vaguely know him - don’t worry, he’s not so much like his ev(a|i)l cousin. Well, that doesn’t mean he can’t be harmful, but he’s more likely to help you instead. [...]
Hey Felix,
yes, I read Chris Hartjes' blog, too :) the main problem I have is that I try to optimise my code even when it works relatively fine ( stupid maximalism.. )
you're absolutely right that hardware time is much more cheaper than your own. And I agree that the snippet of code you suggest is at least very interesting. I have personally witnessed large sites being lagged because of web servers' performance ( which is rare, as you mentioned ) but is worth being noted.
Wonderful blog entry ( all of your blogs are quite interesting, indeed ) - it's just me being too traditional :)
Georgi said
>have you tested if there are performance hits?
This is a problem with create_function. Especially if you're doing it in a loop or calling it repeatedly in any way the memory usage goes up dramatically as compared to more traditional methods. It's basically a memory leak in that you've created a function but when the variable that holds its name goes out of scope the function is orphaned but not garbage collected.
I really like the technique though for making succinct code that uses array_map and friends. Especially for pre-filling the arguments to an existing function for repeated reuse - this technique is called partial function application and I wrote a blog post about writing a class to implement the technique but make sure the result gets garbage collected. See the post (http://metapundit.net/sections/blog/partial_function_application_in_php) with the results table (http://metapundit.net/samples/test.php.html) and sample code (http://metapundit.net/samples/test.php.html).
I should warn that I'm incapable of brevity when tech blogging...
Good write, up, btw...
[...] ThinkingPHP and beyond » My new best friend - PHP’s create_function() I need to re-read this without the benefit of 1/2 a bottle of peach snapps… (tags: php create_function toread) [...]
The huge problem with create_function is that functions created with it are kept in memory for the duration of the program -- even if $formatDate is later unset! -- making them unsuitable to be used as one would use closures. Luckily PHP processes die quickly enough that this might not be a problem if used sparingly.
The best way to use them is to assign them to static vars:
static $formatDate = null; if (!$formatDate) $formatDate = create_function...
Georgi: Good : ). I was one of the pre-optimizers for years. Not as much as some folks, but bad enough to turn good functions into unreadable pieces of optimized maintenance nightmares every once in a while ^^.
l0ne: If you let me know where I'll donate you some kb of RAM so you can get fancy with this : p. No I agree, this is not going to get equally good as JS closures for several reasons. For one, you have to write your code within a string which is unsuitable for anything more difficult then a quick 2-3 liner. There also is the lack of lexical scoping that sucks (if it wasn't for that, my filter function above could be turned into a 1-liner).
So again, I was not suggesting the $formatDate function would be an ideal use case. Personally I only use this shortcut approach in one function in a very big application. The scenario is that I need to call another function with lots of parameters from within this function. But since most of the parameters besides 1/2 are always the same, I made a shortcut using create_function to save myself typing work and keep my code more DRY. For filtering / temporary transformation functions however, I use this quite a lot as it gives me the kind of power I like to have as a programmer ^^.
metapundit: Interesting. Thanks for sharing.
function filter($items, $conditions = array()) {
...
$items = ... create_function('$item', ((stpos('return ')===false)?'return '.$condition.';':$condition) ));
...
and your conditions will be simplified (no "return " and ";" needed):
$conditions = array(
'knows-php' => 'in_array("PHP", $item["languages"])',
'no-java-background' => '!in_array("Java", $item["languages"])',
'cheap' => '$item["rate"] 'count($item["languages"])>= 2',
);
P.S. there might be problem using "return " inside the function: =>'in_array('return ',$a)'
How: I'm aware about this possibility for code reduction but thx for sharing it with the others. When I post things here I will usually only focus on one particular code simplification and not apply every single one I might be able to come up with for keeping things easier to understand and follow.
http://blog.libssh2.org/index.php?/archives/60-create_function-is-not-your-friend.html#extended
I agree :)
lotro leveling guide
If leveling takes you alot of time and you are always running out of gold, or you are just not sure which vocation is the correct choice for your character.
Then you have to visit http://www.scribd.com/doc/460225/Lotro-Guide>lotro leveling guide
This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.
Wow, I wasn't aware of this neat function! It looks very flexible and gave me some ideas. Also, I guess it can be very harmful too, as the "code snippets" on the array aren't parsed by PHP before they get used/created, you can have your app throwing some very unexpected errors which can be hard to debug right? Maybe test driven development can be used to solve this issue.
PS: Nice blog, I'm subscribing to the feed!