Variable assignment in function call - php

I inherited a php codebase that contains some variable assignments in function calls:
<?php
function some_func($foo, $state) {
....
}
some_func("random stuff", $state = true);
...
some_func("other stuff", $state = false);
...
?>
I did some research and some tests, but I can't find out what the defined behaviour for this code is in PHP.
How is the value of the second argument to some_func() computed? The content of the 4state variable (true on first call, false on second)? Or is it the outcome of the assignment (i.e. assigning true/false to the variable $state was successful, so some_func received true?
What is the value of the $state variable in the global scope? The result of the assignment, i.e. true after the first call, false after the second?

I too had to work with a codebase that had function calls similar to this. Luckily, I had access to developers that wrote the code. Here is what I learned.
Scenario 1:
Simply a way to document the code. You know the variable name that you are passing into the function.
Scenario 2:
Here is a link: http://www.php.net/manual/en/language.references.pass.php
If you see, they do specifically call out your case:
foo($a = 5); // Expression, not variable
A 'dummy' pass-by-ref. Depending on your version of PHP, it may throw a warning. I was getting this: Strict Standards: Only variables should be passed by reference in ...
Now let me go into detail of what is happening in this situation.
The dangerous thing is that your example that you have provided wont display the "gotcha!" behavior. In a case like this, your $arg2 that you are echoing outside of the function will always be what the expression in the function call set it to be. Furthermore, the function that is being called will also be sent a "copy" of that value, and work with that. I say "copy" because even though the function is requiring a pass-by-ref, it is actually getting a copy, similar to what a normal function parameter would get.
If you modify the $arg2 that is inside of the function it WILL NOT modify the $arg2 that is outside of the function, as you would expect from a function that is pass-by-ref.

To assign a variable at function call time, you have to pass it as a reference (&$var):
function my_function($arg1, &$arg2) {
if ($arg1 == true) {
$arg2 = true;
}
}
my_function(true, $arg2 = false);
echo $arg2;
outputs 1 (true)
my_function(false, $arg2 = false);
echo $arg2;
outputs 0 (false)
How is the value of the second argument to some_func() computed?
It's not "computed" but explicitly setup : $state = true / false and then passed as argument to some_func().
What is the value of the $state variable in the global scope?
$state does not exist in the global scope.

Related

Default argument value is overriding provided argument?

I have a function in PHP, which has some arguments default to null, so that I can easily call it with less than the full number of arguments.
The problem is, that when I use a null-defaulted argument directly, I get the given argument, but when I try to copy that value to another variable, the variable only gets the default value of null.
It looks like this:
// inside my MySQLI wrapper class...
public function bind(&$stmt, $types = "", &$arg1, &$arg2 = null, &$arg3 = null /* this goes up to 20; it's auto-generated by another script */)
{
echo "dumping...";
var_dump($arg1); // var_dump shows value from function call (string(0))
var_dump($arg2); // ditto
echo "...dumped";
if ($arg2 != null) $foo = $arg2; var_dump($foo); echo "foo"; // var_dump shows that $foo is NULL
/* ... */
}
I call the function like this, from another script:
(It's a dummy script dealing with trucks and cars.)
$make = "";
$model = "";
$year = 0;
$license = "";
list($error, $message) = $mysql->bind($stmt, "", $make, $model, $year, $license);
My bind() function is a wrapper to MySQLI's bind_param() and bind_result() functions.
I've only included the top couple lines, because it's failing at that point already, before it even gets to the actual logic.
Right now, it just looks like it's a bug in PHP, because this doesn't follow what I know about how variables, arguments, default arguments, and references work.
Furthermore, this problem only appears to manifest itself in my real code, and doesn't appear in my simple php file that I coded up to test this.
Further info:
$foo gets assigned NULL, when $arg2 is an empty string, "", and properly gets assigned when it is a non-empty string. Empty strings are still valid strings, so why is PHP doing this?
The problem is the != comparison. What happens is that PHP type-juggles at least one of your variables, and as such, "" != null evaluates to false. The table part-way down this page shows what will happen for comparisons between different types. A type-strict form !== is needed:
if ($arg2 !== null)
$foo = $arg2;

PHP braces and conditional

What does this line mean?
if ( ${fun("str1")} (fun("str2"), ${fun("str3")}) )
Evaluate function returning_value_for_str1_of_fun()_name with the parameters return value for str2 and variable with name return_value_for_str3 ?
This tests the return value of the function, whose name is the value in the variable named fun("str1"), and given the arguments fun("str2") and the value of the variable named fun("str3").
Example:
If fun("str1") equals "x", fun("str2") equals 34, and fun("str3") equals "y", then the statement would look like:
if ( $x (34, $y) )
fun("str1") returns string that should be name of variable and the value of this variable is anonymous function (that probably is not void and returns boolean) that gets two arguments first is return value fun("str2") and the second is the value of the variable with the name that matches string returned by fun("str3").
Wow. That's convoluted code. Let's examine it bit by bit:
Let's start with this:
fun("str1")
In fact, this is simply a function call to a function named fun(), passing in a string value as a parameter.
This function call is repeated three times in your code, with different strings as the arguments. The function fun() itself is not supplied in your example code, so I can't tell what it does, but given the context I assume it returns a string.
Which leads us onto the next bit we can examine:
${fun("str1")}
The ${...} syntax in PHP takes the contents of the braces and references a variable of that name.
So, for example, ${"myvar"} is the same as saying $myvar. This is called a dynamic variable name. While it does have its uses, it is a very easy way to write bad code, that is difficult to read, understand or maintain. Your example definitely falls into this category.
However, now that we understand the syntax, it's easy to see that it is taking the string output of the fun() function call, and turning it into a variable name.
Expanding further, we can rewrite the code as follows to make it clearer:
$var1 = fun("str1");
$var2 = fun("str2");
$var3 = fun("str3");
if ( $$var1 ($var2, $$var3) )
Here, $$var1 is being used as a function name, called with $var2 and $$var3 as parameters.
So in $var1, we have a function call returning a string that is being referenced as a variable name, which is being called as a function.
We still don't know what fun() function returns, or whether the variable names that are generated by its return are valid, but we can make some assumptions, as $var1 and $var2 would need to be populated with valid function names in order for your line of code to work at all.
We now have an understanding of the whole line of code, but still not a clear view of what it's trying to acheive (beyond being excessively 'clever' and obtuse).
This is very very poorly written code. It is deliberately obscure, and inefficient (ie it will run slowly).
Some work around:
$func = 'fun';
$str3 = 'str3';
echo ${fun("str1")} (fun("str2"), ${fun("str3")}); // will output 'str2'
function fun($param1, $param2 = ''){
if($param1 == 'str2' || $param1 == 'str3')
return $param1;
elseif($param1 == 'str1')
return 'func';
else
echo ' you are done';
}
Evaluates as follows:
fun("str1") -> 'func'
${fun("str1")} -> $func -> fun
fun("str2") -> 'str2'
fun("str3") -> 'str3'
${fun("str3")} -> $str3
${fun("str1")} (fun("str2"), ${fun("str3")})
=> $func ("str2", $str3)
=> fun("str2", "str3")
=> "str2"

PHP. Pass variable by reference vs string. How to works with these two different arguments?

I'm writing my own debug functions and I need some help to fix the code below.
I'm trying to print a variable and its name, the file where the variable and the function was declared and the line of the function call. The first part I did, the variable, the variable name, the file and the line is printed correctly.
At the code, a($variable) works good.
The problem is I'd like this function accepts a string too, out of a variable. But PHP returns with a fatal error (PHP Fatal error: Only variables can be passed by reference in ...). At the code, a('text out').
So, how can I fix this code to accept a variable or a string correctly?
code (edited):
function a(&$var){
$backtrace = debug_backtrace();
$call = array_shift($backtrace);
$line = $call['line'];
$file = $call['file'];
echo name($var)."<br>".$var."<br>".$line."<br>".$file;
}
$variable='text in';
a($variable);
a('text out');
I need pass the variable by reference to use this function below (the function get the variable name correctly, works with arrays too):
function name(&$var, $scope=false, $prefix='unique', $suffix='value'){
if($scope) $vals = $scope;
else $vals = $GLOBALS;
$old = $var;
$var = $new = $prefix.rand().$suffix;
$vname = FALSE;
foreach($vals as $key => $val) {
if($val === $new) $vname = $key;
}
$var = $old;
return $vname;
}
The way your code is currently implementing pass by reference is perfect by design, but also by design cannot be changed to have two a() methods - one accepting a variable by reference and the other as a string-literal.
If the desire to pass a string literal instead of assigning it to a variable first is really needed, I would suggest creating a second convenience method named a_str() that actually accepts a string-literal instead of a variable by reference. This method's sole-purpose would be to relay the variable(s) to the original a() method - thereby declaring a variable to pass by reference.
function a_str($var) {
a($var);
}
The only thing to remember is, use a($variable); when passing by reference and a_str('some text'); when not.
Here is the same convenience-method for your name() function:
function name_str($var, $scope=false, $prefix='unique', $suffix='value'){
return name($var, $scope, $prefix, $suffix);
}
The only way to do what you are asking without writing an additional function like #newfurniturey suggests is plain and simply opening and parsing the file where your function was called as text (e.g. with fopen), using the data from debug_backtrace. This will be expensive in terms of performance, but it might be ok if used only for debugging purposes; and using this method you will no longer need a reference in your function, which means you can freely accept a literal as the parameter.

Overloading in php

Can any one tell what does below given lines means?
The lines below are copied from PHP manual:
Note:
It is not possible to use overloaded properties in other language
constructs than isset(). This means if empty() is called on an
overloaded property, the overloaded method is not called.
To workaround that limitation, the overloaded property must be copied
into a local variable in the scope and then be handed to empty().
BUT this is not true that we cant call empty() on overloaded properties, when i called empty() , it triggered __isset()
It is a documentation bug:
<?php
class PropNameReturn {
function __isset($propname){
return true;
}
function __get($propname){
echo 'Yes, it IS called!'.PHP_EOL;
return $propname;
}
}
$o = new PropNameReturn();
var_dump(empty($o->this_prop_name));
//Yes, it IS called!
//bool(false)
$b = new stdClass();
var_dump(empty($b->this_prop_name));
//bool(true)
Looks like the manual is wrong. This also works in PHP 5.2
By the way it seems that __get() is called when used by empty(), but the result also depends on __isset().
empty() returns true only if __isset() returns true and __get() returns an empty value.
See this example code:
class Foo {
function __get($name) {
if ($name == "bar")
return '';
}
function __isset($name) {
if ($name == "bar")
return true;
}
}
$foo = new Foo();
echo $foo->bar . PHP_EOL; // outputs "" - correct
echo var_export(isset($foo->bar)) . PHP_EOL; // outputs "true" - correct
$bar = $foo->bar;
// outputs both "true" -- so the manual is wrong here
echo var_export(empty($foo->bar)) . PHP_EOL;
echo var_export(empty($bar)) . PHP_EOL;
Also see this bug report
Apparently I didn't notice the link you included in your own question, so here's a basic breakdown of overloading:
Overloading in PHP is the concept of dynamically creating methods and parameters. Consider a basic scenario:
class MyClass {
public $foo = 'bar';
}
$Instance = new MyClass();
echo $Instance->foo;
We would all expect that to print "bar," and it would. Here is that same example using overloding:
class MyOverloadedClass {
public function __get($variable) {
return $variable == 'foo' ? 'bar' : '';
}
}
$Instance = new MyOverloadedClass();
echo $Instance->foo;
In this second example, because MyOverloadedClass::$foo does not exist, the __ge method will be executed, and will be passed the variable that the user asked for (in this case, "foo"). My code inside of the __get method would determine that the user is looking for foo, and return "bar"
Language constructs are parts of the PHP language. In other words, the parser interprets them and knows what to do, without having to lookup function references. Some examples are exit, die, print, echo, isset and empty.
isset() is the only language construct to work with overloading. The manual page describes an overloaded method __isset which you can add to your class, and will be executed every time someone calls isset on a property of that class. Calling empty() on an overloaded property, however, will always return true and likely throw an E_NOTICE. Here's why:
Disclaimer: calling empty() on an overloaded property DOES seem to execute __get(), IF AND ONLY IF you have an __isset() method that returns true for the passed variable name:
empty() will not call your overloaded __get() method. It will look in the class definition to see if that parameter exists. If it does, it'll evaluate whether or not it is empty. However, accessing undefined class parameters throws an error. In my first example, I would have thrown an error by asking for $Instance->bar, because it doesn't exist. This is the same error you would get if you asked for
empty( $Instance->foo );
In the second example. I hope this helps.
See the page you mentioned for additional details on how to apply this to functions, or setting variables.

PHP how to use string as superglobal

I'm building a small abstract class that's supposed to make certain tasks easier.
For example:
$var = class::get('id');
would run check if there's pointer id in the $_GET, returning a string or array according to parameters. This should also work for post and request and maby more.
I'm doing it in the way there's function for all the superglobals. I'm using get as example:
get function gets a pointer as parameter, it calls fetchdata function and uses the pointer and "$_GET" as the parameters.
fetchdata is supposed to just blindly use the string it got as superglobal and point to it with the other param. Then check if it exists there and return either the value or false to get function, that returns the value/false to caller.
Only problem is to get the string work as superglobal when you don't know what it is. I did this before with a switch that checked the param and in case it was "get", it set $_GET to value of another variable. However I don't want to do it like that, I want it to be easy to add more functions without having to touch the fetchdata.
I tried $method = eval($method), but it didn't work. ($method = "$_GET"), any suggestions?
EDIT: Sorry if I didn't put it clear enough. I have a variable X with string value "$_GET", how can I make it so X gets values from the source described in the string?
So simply it's
$X = $_GET if X has value "$_GET"
$X = $_POST if X has value "$_POST"
I just don't know what value X has, but it needs to get data from superglobal with the same name than its value.
According to this page in the manual:
Note: Variable variables
Superglobals cannot be used as variable variables inside functions or class methods.
This means you can't do this inside a function or method (which you would be able to do with other variables) :
$var = '_GET';
${$var}[$key]
Instead of passing a string to fetchdata(), could you not pass $_GET itself? I think PHP will not copy a variable unless you modify it ('copy on write'), so this shouldn't use memory unnecessarily.
Otherwise there are only nine superglobals, so a switch-case as you have suggested isn't unreasonable.
You could do this with eval() if you really had to, something like:
eval('return $_GET;');
I think that would be unnecessary and a bad idea though; it is slow and you need to be extremely careful about letting untrusted strings anywhere near it.
Don't use eval. Just use reference.
//test value for cli
$_GET['test'] = 'test';
/**
* #link http://php.net/manual/en/filter.constants.php reuse the filter constants
*/
function superglobalValue($key, $input = null) {
if ($input === INPUT_POST)
$X = &$_POST;
else
$X = &$_GET;
return (isset($X[$key]) ? $X[$key] : false);
}
function getArrayValue(&$array, $key) {
return (array_key_exists($key, $array) ? $array[$key] : false);
}
//test dump
var_dump(
superglobalValue('test', INPUT_GET),
superglobalValue('test', INPUT_POST),
getArrayValue($_GET, 'test'),
getArrayValue($_POST, 'test')
);
$_GET, $_POST and $_REQUEST dont have any null values by default, only string or array. So I used isset there instead of array_key_exists.
Param order: I always put required params before optional when I can, and the data objects before the manipulation/subjective params. Thats why key is first param for superglobalValue and second param for getArrayValue.
I'm not quite sure what you're trying to achieve, but you could have a look at the __callStatic magic method
class example{
protected static $supers = array('GET', 'POST', 'SERVER', 'COOKIE');
public static function __callStatic($functionName, $arguments){
$index = arguments[0];
$desiredSuper = strtoupper($functionName);
if(in_array($desiredSuper, self::$supers)){
doStuff ( $_{$desiredSuper}[$index] );
}
else{
throw new Exception("$desiredSupper is not an allowed superGlobal");
}
}
}
you could then do:
example::get('id'); //wo do stuff to $_GET['id']
example::server('REQUEST_METHOD'); //Will do stuff to $_SERVER['REQUEST_METHOD']
example::foo('bar'); //throws Exception: 'FOO is not an allowed superGlobal'
Php manual on magic methods: http://ca.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods
Edit
I just noticed your edit, you could try:
$X = {$X};
You can use $_REQUEST["var"] instead of $_GET["var"] or $_POST["var"].
A more complicated way would be to test if the variable exists in the GET array, if it doesnt then its POST. If it does its GET.
$var = null;
if (isset($_GET["varname"]))
{
$var = $_GET["varname"];
}
else
{
$var = $_POST["varname"];
}
If you want a variable to be accessible globally, you can add it tot he $GLOBALS array.
$GLOBALS['test']='test';
Now you can fetch $GLOBALS['test'] anywhere.

Categories