Lazy Function Definition in PHP - is it possible? - php

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?

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.

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.

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 :)

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().

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

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.

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.

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.

Related

Global variable in PHP is not working

I need to access $layers from inside the function isOk($layer), but everything i tried the outside function variable $layers is ok, but inside the function, even with global is return null.
Here is the code:
$layers = array();
foreach($pcb['PcbFile'] as $file){
if(!empty($file['layer'])){
$layers[$file['layer']] = 'ok';
}
}
// Prints OK!
var_dump($layers);
function isOk($layer){
global $layers;
// Not OK! prints NULL
var_dump($layers);
if(array_key_exists($layer, $layers))
return ' ok';
return '';
}
// NOT OK
echo isOk('MD');
I always use Object orientation, but this was something so simple that i made with a simple function... Why $layers is not being 'received' correcly inside the function?
Watch this...
HalfAssedFramework.php
<?php
class HalfAssedFramework {
public static function run($path) {
include $path;
}
}
HalfAssedFramework::run('example.php');
example.php
<?php
$layers = array();
foreach($pcb['PcbFile'] as $file){
if(!empty($file['layer'])){
$layers[$file['layer']] = 'ok';
}
}
// Prints OK!
var_dump($layers);
function isOk($layer){
global $layers;
// Not OK! prints NULL
var_dump($layers);
if(array_key_exists($layer, $layers))
return ' ok';
return '';
}
// NOT OK
echo isOk('MD');
Run example.php directly, and it should work. Run HalfAssedFramework.php, and it won't.
The problem is scope. When example.php is included inside the run function, all the code inside it inherits the function's scope. In that scope, $layers isn't global by default.
To fix this, you have a couple of options:
If you know example.php will never run directly, you can say global $layers; at the beginning of the file. I'm not sure whether this will work if the script is run directly, though.
Replace $layers with $GLOBALS['layers'] everywhere.
Add $layers as an argument to isOk.
Put this code within a class, as suggested by Geoffrey.
Not exactly answering the question, but have you considered not using globals? Globals really aren't that cool: they make your code harder to read, harder to understand and as a consequence, harder to maintain.
Consider something like that:
<?php
class LayerCollection
{
private $layers;
public function __construct($layers)
{
$this->layers = $layers;
}
public static function fromPcbFile($data)
{
$layers = array();
foreach ($data['PcbFile'] as $layer) {
if (!empty($layer)) {
$layers[$layer] = true;
}
}
return new self($layers);
}
public function hasLayer($layer)
{
return array_key_exists($layer, $this->layers);
}
}
$layers = LayerCollection::fromPcbFile($pcb);
var_dump($layers->hasLayer('MD'));
Doesn't it look better? You can then proceed to enrich LayerCollection as you need more ways of interacting with your layers. This is not perfect, since there's still a static method lying around (makes testing harder, a factory would be better suited for that job), but that's a good start for a refactoring.
More about why globals are evil: https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil

PHP two methods with the same name

Can I have two methods sharing the same name, but with different arguments?
One would be public static and would take 2 arguments, the other one just public and takes only one argument
example
class product{
protected
$product_id;
public function __construct($product_id){
$this->product_id = $product_id;
}
public static function getPrice($product_id, $currency){
...
}
public function getPrice($currency){
...
}
}
No. PHP does not support classic overloading. (It does implement something else that is called overloading.)
You can get the same result by using func_get_args() and it's related functions though:
function ech()
{
$a = func_get_args();
for( $t=0;$t<count($a); $t++ )
{
echo $a[$t];
}
}
I'm just giving you the super lazy option:
function __call($name, $args) {
$name = $name . "_" . implode("_", array_map("gettype", $args)));
return call_user_func_array(array($this, $name), $args);
}
That would for example invoke the real function name getPrice_string_array for two parameters of that type. That's sort of what languages with real method signature overloading support would do behind the scenes.
Even lazier would be just counting the arguments:
function __callStatic($name, $args) {
$name = $name . "_" . count($args);
return call_user_func_array(array($this, $name), $args);
}
That would invoke getPrice_1 for 1 argument, or getPrice_2 for, you guessed it, two arguments. This might already suffice for most use cases. Of course you can combine both alternatives, or make it more clever by search for all alternative real method names.
If you want to keep your API pretty and user-friendly implementing such elaborate workarounds is acceptable. Very much so.
PHP currently doesn't support overloading in known way, but you can still achieve your goal by using magic methods.
From PHP5 manual: overloading.
You could, kind of...
I consider it very much "hack" solutions, but you could make a single function and assign a standard value, that wouldn't otherwise be okay to use, to the parameters as needed. Then if you do not pass the function a certain parameter, it will be set to fx "-1".
public function getPrice($product_id = "-1", $currency) {
if($product_id = "-1") {
//do something
}else {
//do something
}
}
Or if you really need one method to be static, you can make a method that evaluates which method to call and call that instead of your getPrice:
public function whichGetPrice($product_id = "-1", $currency) {
if($product !== "-1") {
getStaticPrice($product_id, $currency);
}else {
getPrice($currency);
}
}
Like I said, very much "hack" solutions. It's not exactly pretty, nor a way people would expect you to do it. So I wouldn't necessarily recommend it, but it can help you do what you want.

foreach $_POST.. is this bad?

Is it bad practice to use use a foreach to make an array of all post data vs defining exactly what posts you are accepting?
class Foo {
function __construct()
{
foreach($_POST as $key => $var)
{
$this->post[] = $key = $var;
}
}
function bar()
{
echo $this->post['postdata'];
}
}
vs;
class Foo {
function __construct()
{
$this->post = array(
"postdata" => $_POST['postdata']
);
}
function bar()
{
echo $this->post['postdata'];
}
}
There is no great harm in automatically processing all of POSTed fields, as long as that processing is limited to storing in a hash. Matter of fact, it can make fore a significantly easier to maintain code.
The moment you start doing something (especially system related) with that data, you need to validate.
The only really bad practice I can find in this code is use of a global state/data. Your objects should never relay on a outside data. It's better to pass this data to the object as a method argument:
__construct(array $post) {
foreach (...) {
}
}
new Abc($_POST);
And returning to your question... More strict validators are usually better, however it always depends on what you're trying to achieve. You could define a list of expected elements and throw an exception when some extra element is being found.

Static variable for optimization

I'm wondering if I can use a static variable for optimization:
public function Bar() {
static $i = moderatelyExpensiveFunctionCall();
if ($i) {
return something();
} else {
return somethingElse();
}
}
I know that once $i is initialized, it won't be changed by by that line of code on successive calls to Bar(). I assume this means that moderatelyExpensiveFunctionCall() won't be evaluated every time I call, but I'd like to know for certain.
Once PHP sees a static variable that has been initialized, does it skip over that line of code? In other words, is this going to optimize my execution time if I make a lot of calls to Bar(), or am I wasting my time?
I find it easier to do something like the code below. That way the caching is done globally instead of per implementation of the function.
function moderatelyExpensiveFunctionCall()
{
static $output = NULL;
if( is_null( $output ) ) {
//set $output
}
return $output;
}
static $i = blah() won't compile, because php doesn't allow expressions and function calls in static initializers. You need something like
function foo() {
static $cache = null;
if(is_null($cache)) $cache = expensive_func();
do something with $cache
}
This should work in your (quite simple) case:
function your_function() {
static $output;
if (!isset($output)) {
$output = 'A very expensive operation';
}
return $output;
}
As for a global caching mechanism, you may use a method similar to this one.
Here is quite shorter approach:
function stuff()
{
static $smthg = []; // or null, or false, or something else
if ($smthg) {
return $smthg;
}
// filling $smthg goes here
// with a lot of
// code strings
return $smthg;
}
How about:
if (!isset($i))
{
static $i = moderatelyExpensiveFunctionCall();
}

Same named function with multiple arguments in PHP

I started off OOP with Java, and now I'm getting pretty heavy into PHP. Is it possible to create multiples of a function with different arguments like in Java? Or will the interpreted / untyped nature of the language prevent this and cause conflicts?
Everyone else has answers with good code explanations. Here is an explanation in more high level terms: Java supports Method overloading which is what you are referring to when you talk about function with the same name but different arguments. Since PHP is a dynamically typed language, this is not possible. Instead PHP supports Default arguments which you can use to get much the same effect.
If you are dealing with classes you can overload methods with __call() (see Overloading) e.g.:
class Foo {
public function doSomethingWith2Parameters($a, $b) {
}
public function doSomethingWith3Parameters($a, $b, $c) {
}
public function __call($method, $arguments) {
if($method == 'doSomething') {
if(count($arguments) == 2) {
return call_user_func_array(array($this,'doSomethingWith2Parameters'), $arguments);
}
else if(count($arguments) == 3) {
return call_user_func_array(array($this,'doSomethingWith3Parameters'), $arguments);
}
}
}
}
Then you can do:
$foo = new Foo();
$foo->doSomething(1,2); // calls $foo->doSomethingWith2Parameters(1,2)
$foo->doSomething(1,2,3); // calls $foo->doSomethingWith3Parameters(1,2,3)
This might not be the best example but __call can be very handy sometimes. Basically you can use it to catch method calls on objects where this method does not exist.
But it is not the same or as easy as in Java.
Short answer: No. There can only be one function with a given name.
Longer answer: You can do this by creating a convoluted include system that includes the function with the right number of arguments. Or, better yet, you can take advantage of PHP allowing default values for parameters and also a variable amount of parameters.
To take advantage of default values just assign a value to a parameter when defining the function:
function do_something($param1, $param2, $param3 = 'defaultvaule') {}
It's common practice to put parameters with default values at the end of the function declaration since they may be omitted when the function is called and makes the syntax for using them clearer:
do_something('value1', 'value2'); // $param3 is 'defaultvaule' by default
You can also send a variable amount of parameters by using func_num_args() and func_get_arg() to get the arguments:
<?php
function dynamic_args() {
echo "Number of arguments: " . func_num_args() . "<br />";
for($i = 0 ; $i < func_num_args(); $i++) {
echo "Argument $i = " . func_get_arg($i) . "<br />";
}
}
dynamic_args("a", "b", "c", "d", "e");
?>
Following isn't possible with php
function funcX($a){
echo $a;
}
function funcX($a,$b){
echo $a.$b;
}
Instead do this way
function funcX($a,$b=null){
if ($b === null) {
echo $a; // even though echoing 'null' will display nothing, I HATE to rely on that
} else {
echo $a.$b;
}
}
funcX(1) will display 1, func(1,3) will display 13
Like everyone else said, it's not supported by default. Felix's example using __call() is probably the best way.
Otherwise, if you are using classes that inherit from each other you can always overload the method names in your child classes. This also allows you to call the parent method.
Take these classes for example...
class Account {
public function load($key,$type) {
print("Loading $type Account: $key\n");
}
}
class TwitterAccount extends Account {
public $type = 'Twitter';
public function load($key) {
parent::load($key,$this->type);
}
}
Then you can call them like so...
$account = new Account();
$account->load(123,'Facebook');
$twitterAccount = new TwitterAccount();
$twitterAccount->load(123);
And your result would be...
Loading Facebook Account: 123
Loading Twitter Account: 123
No this isn't possible, because PHP cannot infer from the arguments which function you want (you don't specify which types you expect). You can, however, give default values to arguments in php.
That way the caller can give different amounts of arguments. This will call the same function though.
Example is:
function test($a = true)
This gives a default of true if 0 arguments are given, and takes the calling value if 1 argument is given.
I know it's a bit old issue, but since php56 you can:
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4);
ref: http://php.net/manual/en/functions.arguments.php
Overloading is not possible in PHP but you can get around it to some extend with default parameter values as explained in other responses.
The limit to this workaround is when one wants to overload a function/method according to the parameter types. This is not possible in PHP, one need to test the parameter types yourself, or write several functions. The functions min and max are a good example of this : if there is one parameter of array type it returns the min/max of the array, otherwise it returns the min/max of the parameters.
I had the idea of something like:
function process( $param1 , $type='array' ) {
switch($type) {
case 'array':
// do something with it
break;
case 'associative_array':
// do something with it
break;
case 'int_array':
// do something with it
break;
case 'string':
// do something with it
break;
// etc etc...
}
}
I have got 2 methods, getArrayWithoutKey which will output all the entries of an array without supplying any key value. The second method getArrayWithKey will output a particular entry from the same array using a key value. Which is why I have used method overloading there.
class abcClass
{
private $Arr=array('abc'=>'ABC Variable', 'def'=>'Def Variable');
public function setArr($key, $value)
{
$this->Arr[$key]=$value;
}
private function getArrWithKey($key)
{
return $this->Arr[$key];
}
private function getArrWithoutKey()
{
return $this->Arr;
}
//Method Overloading in PHP
public function __call($method, $arguments)
{
if($method=='getArr')
{
if(count($arguments)==0)
{
return $this->getArrWithoutKey();
}
elseif(count($arguments)==1)
{
return $this->getArrWithKey(implode(',' , $arguments));
}
}
}
}
/* Setting and getting values of array-> Arr[] */
$obj->setArr('name', 'Sau');
$obj->setArr('address', 'San Francisco');
$obj->setArr('phone', 7777777777);
echo $obj->getArr('name')."<br>";
print_r( $obj->getArr());
echo "<br>";

Categories