I'm working through debugging some legacy code, and want to use a pre-built function that is essentially a wrapper for get_defined_vars().
Running this code directly in the calling file prints an array of variables as expected:
print_r(get_defined_vars());
However, wrapping this in a simplified version of my function prints an empty array:
function debugAllVars() {
print_r(get_defined_vars());
}
debugAllVars();
Regardless of the scope, I would have expected "superglobal" variables such as $_POST to be present in the output.
Why is the output completely empty?
get_defined_vars() prints all variables in the "symbol table" of the scope where it is called. When you try to wrap it as debugAllVars, you introduce a new scope, which has a new symbol table.
For a standalone function like this, the symbol table consists of:
the function's parameters
any global variables imported into the current scope using the global keyword
any static variables declared in the current scope with the static keyword (even if not assigned a value)
any variables implicitly declared by assigning a value to them
any variables implicitly declared by taking a reference to them (e.g. $foo = &$bar would implicitly declare $bar if not already defined; $foo = $bar would not)
Notably, this list does not include the superglobals, such as $_GET, $_POST, and $GLOBALS. If you run get_defined_vars() in global scope (i.e. outside any function), you will see that these are present in the symbol table there, which is also what the magic variable $GLOBALS points to. So, why are they not present in every scope, and how can we use them if they're not?
For this, we need to dig into the internals of the implementation, where these variables are referred to as "auto-globals" rather than "superglobals".
The answer to why is performance: the naive implementation of an "auto-global" would be one that acted as though every function automatically had a line at the top reading global $_GET, $_POST, ...;. However, this would mean copying all those variables into the symbol table before every function was run, even if they weren't used.
So instead, these variables are special-cased in the compiler, while converting your PHP code into the internal "opcodes" used by the VM which executes the code.
Using a source code browser, we can see how this works.
The key function is zend_is_auto_global in zend_compile.c (taken from current master, effectively PHP 7.2):
zend_bool zend_is_auto_global(zend_string *name) /* {{{ */
{
zend_auto_global *auto_global;
if ((auto_global = zend_hash_find_ptr(CG(auto_globals), name)) != NULL) {
if (auto_global->armed) {
auto_global->armed = auto_global->auto_global_callback(auto_global->name);
}
return 1;
}
return 0;
}
Here, name is the name of a variable, and CG means "compiler globals", so the main job of this function is to say "if the variable name given is in a compiler-global hash called auto_globals, return 1". The additional call to auto_global_callback allows the variable to be "lazy loaded", and only populated when it is first referenced.
The main usage of that function appears to be this conditional, in zend_compile_simple_var_no_cv:
if (name_node.op_type == IS_CONST &&
zend_is_auto_global(Z_STR(name_node.u.constant))) {
opline->extended_value = ZEND_FETCH_GLOBAL;
} else {
opline->extended_value = ZEND_FETCH_LOCAL;
}
In other words, if the variable name you referenced is in the list of superglobals, the compiler switches the opcode into a different mode, so that when it is executed, it looks up the variable globally rather than locally.
get_defined_vars gets all variables defined in the scope that it's called in. Your debugAllVars function introduces a new scope, so get_defined_vars will at most give you all variables within debugAllVars. It cannot give you variables from the caller's scope.
Also see Reference: What is variable scope, which variables are accessible from where and what are "undefined variable" errors?.
Related
Is there any way I can define a variable such a way that I don't need to global $var1; in every function? Just define it in beginning and keep using where ever needed. I know $GLOBALS exist, but don't want to use it.
First let me try to explain why you shouldn't use globals because they are extremely hard to manage. Say a team member overrides an $ADMIN variable in a file to be 1, and then all code that references the $ADMIN variable will now have the value of 1.
globals are so PHP4, so you need to pick a better design pattern, especially if this is new code.
A way to do this without using the ugly global paradigm is to use a class container. This might be known as a "registry" to some people.
class Container {
public static $setting = "foo";
}
echo Container::$setting;
This makes it more clear where this variable is located, however it has the weakness of not being able to dynamically set properties, because in PHP you cannot do that statically.
If you don't mind creating an object, and setting dynamic variables that way, it would work.
You need to pass the variable as a parameter to that function to avoid using GLOBALS.
The Problematic Scenario (Works ! but avoid it at all costs)
<?php
$test = 1;
function test()
{
global $test;
echo $test; // <--- Prints 1
}
test();
The right way...
<?php
$test = 1;
function test($test)
{
echo $test; // <--- Prints 1
}
test($test); //<--- Pass the $test param here
This behaviour called as superglobal variable. But php has limited predefined list of them: Superglobals.
So you cannot add your own superglobal variable.
Variable scope
My suggestions from the most to less radical:
Edit source code of PHP and add your own superglobals.
Do not write code at all.
Do not use PHP (use different language with different variable scope policy).
Do not use functions (write plain script).
Use constants instead of variables.
Use function params instead of using globals.
Use static variables instead of globals. (\G::$variable)
Consider the following situation..
$var = 'Lots of information';
function go($var) {
// Do stuff
}
Now, when PHP exits the function, does it clear the memory automatically of all local variables within the function or should I be doing:
unset($var);
...within the function on any local variables that store large amounts of data?
It will clear itself inside the function scope. This means that the $var parameter of the function will no longer exists after the function call.
Notice that $var = 'Lots of information'; is outside the function block therefore will not be cleared automatically. In this case $var in the global scope and $var in the function scope are two different things and inside the function block only $var in the function scope will exists.
This question goes to the concept of Variable Scope. Inside the function, the variables are "contained" and unless declared global, are not related to variables of the same name outside of the function. So if you created a large variable inside a function, and you want to unset() it, you would need to unset() inside the function. This page is important, especially the part about "global" and "static" variables. PHP also has a way to pass a variable by reference using an ampersand in front of the variable name. In that case, the function is operating on the variable itself, not the function's copy of the variable.
http://php.net/manual/en/language.variables.scope.php
Lets say I have this:
function myFunc()
{
global $distinct_variable;
die ($distinct_variable);
}
function anotherFunc()
{
$distinct_variable = 'Hello World';
myFunc();
}
anotherFunc();
For anotherFunc() to correctly show 'Hello World', it must be written like this
{
global $distinct_variable;
$distinct_variable = 'Hello World';
myFunc();
}
Now it will show the message, but why must I global $distinct_variable; in anotherFunc() since it is a global in myFunc() which is within anotherFunc()
Yes, I know that variables inside functions won't go outside of them, but I was thinking it should have worked...
Could someone explain why isn't it working?
Thanks.
Thank you for your answers, I get it now :)
A global variable is exactly that - it exists in the GLOBAL scope ONLY.
Everything in PHP (except superglobals) exist in only one scope - be that the global scope, or the scope of a function/method. Scope does not cascade - so just because you have a variable in an "outer" function does not make it available to an "inner" function.
Similarly, global fetches the variables defined in the GLOBAL scope only (the top-most scope), not simply "the scope above this one, from which I was called". This is what you tried to do, but it absolutely will not work. This level of finer-grained control is what function arguments/return values are for.
Each function has its own symbol table. There is also a global symbol table. Just because one function is being called from within another doesn't mean that the variables declared global in one are global in the other, or inherited from the other. They still refer to the variable in the "local" symbol table by default.
Doing global $somevar; echo $somevar boils down to echo $GLOBALS['somevar'];. That $GLOBALS superglobal does not include variables that were defined inside a function: only truly 'global' vars, which exist at the top level of the script.
i have to function in two different files.
one of them should add a new item to an array each time is called and the array should be accessible .what i did for it is :
function1(){
global $array;
$array[] = 'hi';
}
but it just create one item in array even if i call this function 4 times .
What you did should work.
<?php
function function1(){
global $array;
$array[] = 'hi';
}
function1();
function1();
function1();
print_r($array);
Test it.
You probably have another problem. Please note that the lifetime of all variables is the current run of your script. They won't exist in a successive run. For that you need to use some sort of persistence like session, cookie, file system, database.
For more help post your complete code.
I'm a bit confused by the wording of your question. When you say "i have to function in two different files." does you mean you have "two" functions?
If you have two functions both trying to use your $array variable, you'll need to call global $array; in both functions.
The reason for this is that global is a bit misleading. All it's really doing is assigning a reference to a member of $_GLOBALS to a variable in the local scope which has the same name as the $_GLOBALS index. In other words, if you do something like this:
global $variable;
it's essentially the same thing as saying this:
$variable =& $_GLOBALS['variable']; (assign by reference)
The actual variable $variable is still scoped at the function level, it just happens to have a reference to a global variable.
The implication of this is that if you don't define global $variable in every function, you're just creating a brand new variable within the scope of that function. When the function ends, the variable is unset and any changes made to it within the function are lost.
With all of that said, global variables still tend to be a bad idea. It's a lot clearer if you just maintain a local variable, and pass it as a parameter to other functions when needed.
I had the problem that a global $array was not known in a function. But when I placed the first def: $array = array(); before the first function call, it worked.
Why does THIS not work when called via e.g. wd(1) (it always returns '-2')?
$zoom=$user['zoom'];
function wd($hrs){
return $hrs*$zoom-2;
}
But THIS works fine:
function wd($hrs){
return $hrs*30-2;
}
Assuming this was a casting problem, I tried all sorts of variations like
(int)$hrs * ((int)$zoom)
or
(int)$hrs * (float)$zoom
but no success :(
Any help would be appreciated.
(And BTW, does it matter whether the function is located within
include('header.php')
-- although I tried it both within and outside the header?)
EDIT: You should pass that variable as an argument to the function, but if you absolutely need to keep the variable global, do the following.
You need to bring the global into scope:
$zoom=$user['zoom'];
function wd($hrs){
global $zoom;
return $hrs*$zoom-2;
}
This isn't a casting issue - it's because you're trying to use a variable that's out of scope.
Whilst you'll need to read the PHP docs for the full low-down, at a basic level, you can only access variables that are defined within the same function or method. (Although you can use the global keyword to access global variables. That said, global variables are less than ideal.)
As such, you could simply update your function to also pass in 'zoom' as a parameter as follows:
function wd($hrs, $zoom){
return $hrs*$zoom-2;
}
$zoom=$user['zoom'];
function wd($hrs){
// there is no variable $zoom within the function's visibility scope
// so you will get a "Notice: undefined variable 'zoom'" here.
return $hrs*$zoom-2;
}
see http://docs.php.net/language.variables.scope