I'm trying to debug a PHP application, and as a section of the debug process, I passed print_r($GLOBALS) through the AJAX request to my browser. However, I'd prefer to see it in native JSON form because it comes out better in the browser. I'm trying to use the following snippet of code:
json_encode($GLOBALS);
but I've found it returns bool(false). The JSON documentation says "Returns a JSON encoded string on success or FALSE on failure." But what about $GLOBALS makes it fail? Is it the recursive $GLOBALS['GLOBALS']?
I was thinking as an alternative to loop over $GLOBALS and put that in an array, but that seems quite pointless when the point of json_encode is to encode an array.
Upon testing this myself, it appears json_encode() can't handle recursion such as what's provided in $GLOBALS['GLOBALS']... etc.
So a trick(?) you can do is:
json_encode(array_slice($GLOBALS, 1));
Which will skip past $GLOBALS['GLOBALS'] and continue encoding the rest of the array.
*EDIT: $GLOBALS['GLOBALS'] appears first for me when printing this array, but a better way is to find where $GLOBALS['GLOBALS'] appears and skip that element entirely.
I propose a way where the position of GLOBALS is not important:
json_encode(array_intersect_key($GLOBALS,array_flip(array("_GET", "_POST", "_FILES", "_COOKIE"))));
or a better way:
$new_array = $GLOBALS;
$index = array_search('GLOBALS',array_keys($new_array));
json_encode(array_splice($new_array, $index, $index-1));
The fact that $GLOBALS contains reference to itself results in an infinite recursion that json_encode can't handle because it exceeds the maximum depth of 512 and thus will return false by default.
The solution would be creating a copy of $GLOBALS without self-referencing, the function below references superglobals _GET _POST _SERVER .. only, assuming you don't use $_ in your variables naming conventions:
function getEncodableGlobals()
{
$g = [];
foreach ($GLOBALS as $key => &$val)
{
if ($key[0] == '_')
{
$g[$key] = &$val;
}
}
return $g;
}
Notice that $g doesn't hold copies but only references to variables, just like $GLOBALS does. If you wish to include all the variables in the global scope simply change the condition to exclude the troublesome reference:
...
if ($key !== 'GLOBALS')
...
Now you can safely encode it:
json_encode(
getEncodableGlobals()
);
You could also do it the array_filter way and copy the variables.
$encodableGlobals = array_filter($GLOBALS, function($key) {
return $key !== 'GLOBALS';
}, ARRAY_FILTER_USE_KEY);
json_encode can't handle recursion such as what's provided in $GLOBALS['GLOBALS']... etc.
JSON.stringify is same!
you will retrieve error :
So in php you can create a new variable, example $globals which holds an array of $GLOBALS.
And, unset the recursive $globals['GLOBALS'].
$globals = $GLOBALS;
unset($globals['GLOBALS']);
echo json_encode($globals);
And... Congrats!! Now you can json_encode $GLOBALS variable!
Related
I have an array that I want to test for empty elements, I have tried using array_walk to walk an array (a single dimension array) and return true if the array of elements (values) has got any empty elements.
I naturally went to the PHP website, looked at the examples on offer and they don't make any sense because it does not give a clear example of how you would use array_walk for this. I tried array_filter and that didn't seem to do much either.
Example of what I was trying to do.
$test = array("Tree"=>"Ash","TreeID"=>"Q23-123","count"=>14,"User"=>"P.Williams");
$result = array_walk( $test, "empty", true );
All I get as a result is "Array".
and PHP parser is having a hissy fit about it, they say clearly, bool array_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] ) in their site, this I deduced to be something $result array_walk ( $theTargetArray, "StringNameOfFunction", theResultIfTure); but nothing is that simple.
So far I have found lots of examples on many sites and it seems that people have just copied and pasted the PHP examples, some have changed their names to hide that they have copied and pasted the example...
Can someone let me know what it is that I am doing wrong here please?
(Also FAO stackoverflow site maintainers, What is the point in suggesting a tag, I click to use it and then I get told I can not create a new tag unless I have 1500 points??? Seriously Why? Good idea if you go and think about that one.)
$my_arr = array(....); // your array here
$has_empty_value = sizeof($my_arr) != sizeof(array_filter($my_arr));
array_walk is not going to help you on that. It's a mutator function, and is intended to change array elements, not to retrieve information about them. What you're looking for is a boolean aggregator function known as any or some in other languages. PHP doesn't provide it out of the box, so you have to write it by hand:
function any($iter, $pred) {
foreach($iter as $item)
if($pred($item))
return true;
return false;
}
However, an attempt to use it with empty, as in
print any($test, 'empty')
will fail, because empty is not a real function and can't be used indirectly. A workaround is to wrap it in yet another function and pass that one to any:
any($test, function($x) { return empty($x); })
Another option is to filter an array through boolval, thus removing "falsy" values, and compare lengths:
$hasEmptyElements = count(array_filter($test, 'boolval')) < count($test);
Note that, unlike any, which is "lazy", filter always processes the whole array.
The docs for array_walk say "Applies the user-defined callback function to each element of the array array." Therefore, you can use it with your own callbacks or with with a closure, like so:
$test = array("Tree"=>"Ash","TreeID"=>"Q23-123", "count"=> 14, "User"=>"P.Williams");
$result = array_walk( $test, function($value) {
return empty($value);
});
Of course, this depends on what you are trying to achieve as this will loop through all the values and $result will be true if all the values are empty but the last one is not.
If you are looking to find out if any of the values are empty, a function that stops after it finds an empty item would be better:
function hasEmptyValues(array $array)
{
foreach ($array as $key => $value) {
if (empty($value)) {
//Empty value found
return true;
}
}
//None of the values are empty
return false;
}
Is there anything problematic in simply setting $GLOBALS to be an empty array? I want to reverse the effect of register_globals where it is turned on and one does not have access to the .ini file, but rather than iterate over each of the relevant super globals and unset where necessary, skipping such elements as $GLOBALS['_POST'], $GLOBALS['_GET'], etc. as is usually done, I wonder if it's OK to just remove them all.
Are there any problems that may arise from this? I don't ever plan to reference the $GLOBALS array as any variables that are to be scope-independent will either be set in the relevant super global ($_GET, $_POST, etc.) or will be stored as properties of a relevant registry class.
For information, the FAQ at http://www.php.net/manual/en/faq.misc.php#faq.misc.registerglobals has the following to emulate register_globals = 0:
<?php
// Emulate register_globals off
function unregister_GLOBALS()
{
if (!ini_get('register_globals')) {
return;
}
// Might want to change this perhaps to a nicer error
if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
die('GLOBALS overwrite attempt detected');
}
// Variables that shouldn't be unset
$noUnset = array('GLOBALS', '_GET',
'_POST', '_COOKIE',
'_REQUEST', '_SERVER',
'_ENV', '_FILES');
$input = array_merge($_GET, $_POST,
$_COOKIE, $_SERVER,
$_ENV, $_FILES,
isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array());
foreach ($input as $k => $v) {
if (!in_array($k, $noUnset) && isset($GLOBALS[$k])) {
unset($GLOBALS[$k]);
}
}
}
unregister_GLOBALS();
?>
Presumably performing unset($GLOBALS[$k]); does the same as performing $GLOBALS = array(); except that the latter removes everything and involves only one line of code.
The question then is: is it bad to unset $GLOBALS['_GET'], $GLOBALS['_PUT'], etc. ("the variables that shouldn't be unset" as the example states -- is this really the case?)?
Update:
I've answered this myself below. Silly me for not trying earlier.
PHP Manual says
$GLOBALS — References all variables available in global scope
If the word 'Refernences' is used in the typical PHP way, setting $GLOBALS=array() won't help you, you would need:
foreach ($GLOBALS as $k=>$v) unset($$k);
Edit: This could be expanded to
foreach ($GLOBALS as $k=>$v)
if (substr($k,0,1)!='_')
unset($$k);
just for safety! (Didn't try out)
Edit 2:
As from Karoly's comment, if you do this in a class or function, you need
foreach ($GLOBALS as $k=>$v) {
global $$k;
unset($$k);
}
Edit 3 was editied awy
.. because it contained fat fingers - unexpected result came from a typo!
I've answered part of this myself. I was stupid for not trying earlier:
$var = 'Hello, world!';
unset($GLOBALS['var']);
echo $var; // Notice: Undefined variable: var
$var = 'Hello, world!';
$GLOBALS = array();
echo $var; // Hello, world!
So simply setting $GLOBALS as an empty array won't unset the global variables. They need to be explicitly unset.
Update:
Using unset($GLOBALS['_GET']); is the same as using unset($_GET) which is definitely not what I want. Question answered.
I am just doing some experimenting. How do I print the value of this $chvalues[] or test (as below) it's an array. Is it possible to print this out as-is? $chvalues[] (yes my input field is properly defined with name=seminar etc...)
$chvalues = array();
if (isset($_POST['seminar'])) {
foreach ($_POST['seminar'] as $ch => $value) {
$chvalues[] = $value;
if(is_array('$chvalues[]')) {
echo "Yes. It's an Array.";
}
}
}
I am trying to test this:
if(is_array('$chvalues[]')) { ...
I get this:
Fatal error: Cannot use [] for reading in...
You are reading the array as a string by using single quotes around it (which should just return false).
You should use if(is_array($chvalues)) without the brackets.
$chvalues will always be an array, because you just assigned it to one the line before, as well as at the start of your code, before the loop.
Maybe you meant to check $value, or run the is_array() after the loop and not initialize $chvalues as an array before running the loop.
Following a variable by [] is only for appending new elements to the end of the array within the variable. If you want to look at the array then just use the variable itself.
if(is_array($chvalues)) {
...
You only use $value[] for accessing the "top" element of the $value array, the unset next position, if you will.
Change it to $chvalues in the is_array() function.
To print an array, it depends on output. Check print_r for debugging.
Also don't use quotes as they make the array act like a string, in this case (because they are single quotes) the string '$chvalues[]' is being tested for being an array.
Two problems here. First, everytime you do $chvalues[] with empty brackets, you are appending a new empty element onto your array. So when you do:
if(is_array($chvalues[]))
You always get an empty element, never an array.
Instead you probably want:
if(is_array($chvalues))
Second problem is you cannot single-quote '$chvalues[]' as you have done. It should have no quotes as in my first code block above.
don't use $chvalues[], just $chvalues (without single quotes inside the is_array function).
$chvalues = array();
if (isset($_POST['seminar'])) {
foreach ($_POST['seminar'] as $ch => $value) {
$chvalues = $value;
if(is_array($chvalues)) {
echo "Yes. It's an Array.";
}
}
}
You really want, is_array($chvalues) (which will always be true in your code) or, if you're trying to find out if the last item added to $chvalues is an array, you should call is_array($value). If you don't have that option (not sure why you wouldn't), then you can use is_array(end($chvalues)) to test the last thing added to the array. I will warn, though, that you will need to call reset to iterate through $chvalues with foreach.
Of course, you could also call $arr = array_keys( $chvalues ); is_array($chvalues[end($arr)]);, but that gets very silly (especially since I left out the last paren originally).
I have a foreach loop, that will loop through an array, but the array may not exist depending on the logic of this particular application.
My question relates to I guess best practices, for example, is it ok to do this:
if (isset($array))
{
foreach($array as $something)
{
//do something
}
}
It seems messy to me, but in this instance if I dont do it, it errors on the foreach. should I pass an empty array?? I haven't posted specific code because its a general question about handling variables that may or may not be set.
Just to note: here is the 'safest' way.
if (isset($array) && is_array($array)) {
foreach ($array as $item) {
// ...
}
}
Try:
if(!empty($array))
{
foreach($array as $row)
{
// do something
}
}
That's not messy at all. In fact, it's best practice. If I had to point out anything messy it would be the use of Allman brace style, but that's personal preference. (I'm a 1TBS kind of guy) ;)
I'll usually do this in all of my class methods:
public function do_stuff ($param = NULL) {
if (!empty($param)) {
// do stuff
}
}
A word on empty(). There are cases where isset is preferable, but empty works if the variable is not set, OR if it contains an "empty" value like an empty string or the number 0.
If you pass an empty array to foreach then it is fine but if you pass a array variable that is not initialized then it will produce error.
It will work when array is empty or even not initialized.
if( !empty($array) && is_array($array) ) {
foreach(...)
}
I would say it is good practice to have a 'boolean' other value that is set as 0 (PHP's false) to start, and any time some function adds to this array, add +1 to the boolean, so you'll have a definite way to know if you should mess with the array or not?
That's the approach I would take in an object oriented language, in PHP it could be messier, but still I find it best to have a deliberate variable keeping track, rather than try to analyze the array itself. Ideally if this variable is always an array, set the first value as 0, and use it as the flag:
<?PHP
//somewhere in initialization
$myArray[0] = 0;
...
//somewhere inside an if statement that fills up the array
$myArray[0]++;
$myArray[1] = someValue;
//somewhere later in the game:
if($myArray[0] > 0){ //check if this array should be processed or not
foreach($myArray as $row){ //start up the for loop
if(! $skippedFirstRow){ //$skippedFirstRow will be false the first try
$skippedFirstRow++; //now $skippedFirstRow will be true
continue; //this will skip to the next iteration of the loop
}
//process remaining rows - nothing will happen here for that first placeholder row
}
}
?>
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.