Reputation: 63606
In JavaScript, you can use Lazy Function Definitions to optimize the 2nd - Nth call to a function by performing the expensive one-time operations only on the first call to the function.
I'd like to do the same sort of thing in PHP 5, but redefining a function is not allowed, nor is overloading a function.
Effectively what I'd like to do is like the following, only optimized so the 2nd - Nth calls (say 25-100) don't need to re-check if they are the first call.
$called = false;
function foo($param_1){
global $called;
if($called == false){
doExpensiveStuff($param_1);
$called = true;
}
echo '<b>'.$param_1.'</b>';
}
PS I've thought about using an include_once() or require_once() as the first line in the function to execute the external code just once, but I've heard that these too are expensive.
Any Ideas? or is there a better way to tackle this?
Upvotes: 5
Views: 2625
Reputation: 858
in PHP 8.3 I'm using:
class Helper
{
/**
* @template T
* @param callable(): T $callback
* @return callable(): T
*/
public static function lazy(callable $callback): callable
{
return function () use ($callback) {
static $run = true;
static $cache = null;
if ($run) {
$cache = $callback();
$run = false;
}
return $cache;
};
}
}
used like:
$c = 0;
// pass any task that should only be performend once and/or only if you need it, like:
$x = Helper::lazy(fn() => $c);
$y = $x();
echo "0 $y\n"; // 0 0
$c = 1;
$y = $x();
echo "1 $y\n"; // 1 0
Upvotes: 0
Reputation: 93725
Please don't use include()
or include_once()
, unless you don't care if the include()
fails. If you're including code, then you care. Always use require_once()
.
Upvotes: 1
Reputation: 166106
Any reason you're commited to a functional style pattern? Despite having anonymous functions and plans for closure, PHP really isn't a functional language. It seems like a class and object would be the better solution here.
Class SomeClass{
protected $whatever_called;
function __construct(){
$this->called = false;
}
public function whatever(){
if(!$this->whatever_called){
//expensive stuff
$this->whatever_called = true;
}
//rest of the function
}
}
If you wanted to get fancy you could use the magic methods to avoid having to predefine the called booleans. If you don't want to instantiate an object, go static.
Upvotes: 0
Reputation: 12939
How about using local static variables?
function doStuff($param1) {
static $called = false;
if (!$called) {
doExpensiveStuff($param1);
$called = true;
}
// do the rest
}
If you need to do expensive stuff only once for given parameter value, you could use an array buffer:
function doStuff($param1) {
static $buffer = array();
if (!array_key_exists($param1, $buffer)) {
doExpensiveStuff($param1);
$buffer[$param1] = true;
}
// do the rest
}
Local static variables are persistent across function calls. They remember the value after return.
Upvotes: 0
Reputation: 117567
PHP doesn't have lexical scope, so you can't do what you want with a function. However, PHP has classes, which conceptually works in exactly the same way for this purpose.
In javascript, you would do:
var cache = null;
function doStuff() {
if (cache == null) {
cache = doExpensiveStuff();
}
return cache;
}
With classes (In PHP), you would do:
class StuffDoer {
function doStuff() {
if ($this->cache == null) {
$this->cache = $this->doExpensiveStuff();
}
return $this->cache;
}
}
Yes, class-based oop is more verbose than functional programming, but performance-wise they should be about similar.
All that aside, PHP 5.3 will probably get lexical scope/closure support, so when that comes out you can write in a more fluent functional-programming style. See the PHP rfc-wiki for a detailed description of this feature.
Upvotes: 0
Reputation: 57374
you can do conditional function definiton.
if( !function_exists('baz') )
{
function baz( $args ){
echo $args;
}
}
But at present, a function becomes a brick when defined.
You can use create_function
, but I would suggest you DONT because it is slow, uses lots of memory, doesn't get free()'d untill php exits, and is a security hole as big as eval().
Wait till PHP5.3, where we have "closures" http://wiki.php.net/rfc/closures
Then you'll be permitted to do
if( !isset( $baz ) )
{
$baz = function( $args )
{
echo $args;
}
}
$baz('hello');
$baz = function( $args )
{
echo $args + "world";
}
$baz('hello');
Upon further reading, this is the effect you want.
$fname = 'f_first';
function f_first( $even )
{
global $fname;
doExpensiveStuff();
$fname = 'f_others';
$fname( $even );
/* code */
}
function f_others( $odd )
{
print "<b>".$odd."</b>";
}
foreach( $blah as $i=>$v )
{
$fname($v);
}
It'll do what you want, but the call might be a bit more expensive than a normal function call.
In PHP5.3 This should be valid too:
$func = function( $x ) use ( $func )
{
doexpensive();
$func = function( $y )
{
print "<b>".$y."</b>";
}
$func($x);
}
foreach( range(1..200) as $i=>$v )
{
$func( $v );
}
( Personally, I think of course that all these neat tricks are going to be epically slower than your earlier comparison of 2 positive bits. ;) )
If you're really concerned about getting the best speed everywhere
$data = // some array structure
doslowthing();
foreach( $data as $i => $v )
{
// code here
}
You may not be able to do that however, but you've not given enough scope to clarify. If you can do that however, then well, simple answers are often the best :)
Upvotes: 4
Reputation: 8280
Use a local static var:
function foo() {
static $called = false;
if ($called == false) {
$called = true;
expensive_stuff();
}
}
Avoid using a global for this. It clutters the global namespace and makes the function less encapsulated. If other places besides the innards of the function need to know if it's been called, then it'd be worth it to put this function inside a class like Alan Storm indicated.
Upvotes: 13
Reputation: 2411
If you do wind up finding that an extra boolean test is going to be too expensive, you can set a variable to the name of a function and call it:
$func = "foo";
function foo()
{
global $func;
$func = "bar";
echo "expensive stuff";
};
function bar()
{
echo "do nothing, i guess";
};
for($i=0; $i<5; $i++)
{
$func();
}
Give that a shot
Upvotes: 0
Reputation: 200846
Have you actually profiled this code? I'm doubtful that an extra boolean test is going to have any measurable impact on page rendering time.
Upvotes: 6