PHP 5.6 unset $_SESSION sub array entry - php

I got the following function:
public function stopWatcherSession($sessionID) {
if(array_key_exists($sessionID, $_SESSION[self::WATCHER_SESSION_KEY])) {
foreach ($_SESSION[self::WATCHER_SESSION_KEY][$sessionID]['names'] as $v) {
$ptype = $this->paramTypeFromName($v);
unset($_SESSION[self::SESSION_KEY][$ptype][$v]); //does not work, sets entry to null
}
unset($_SESSION[self::WATCHER_SESSION_KEY][$sessionID]); //does not work, sets entry to null
}
}
As the comments say, the array entry does not get unset, the array_key_exists()-function still returns true, and if you var_dump($_SESSION) you can see, that $_SESSION[self::WATCHER_SESSION_KEY][$sessionID] is null.
How can I unset the variable, so that also the key in the array gets removed?
Things i tried, that did not work:
// v1 (tried also for `$_SESSION[self::WATCHER_SESSION_KEY][$sessionID]` )
$tmp = $_SESSION[self::SESSION_KEY][$ptype];
unset($tmp[$v]);
$_SESSION[self::SESSION_KEY][$ptype] = $tmp;
//v2
unset($_SESSION[self::WATCHER_SESSION_KEY][$sessionID]);
session_write_close();
session_start();
//v3 => v1 & v2 combined
$tmp = $_SESSION[self::SESSION_KEY][$ptype];
unset($tmp[$v]);
$_SESSION[self::SESSION_KEY][$ptype] = $tmp;
session_write_close();
session_start();
I could add a crappy hack all over the code to check whether it's null, but then »empty« values must be changed to something else (like a predefined const, but thats a nasty workaround and leads to confusion for other devs!)
Anyone got some ideas?

Got it working:
unset($GLOBALS['_SESSION'][self::WATCHER_SESSION_KEY][$sessionID]); does the job.
http://php.net/manual/en/function.unset.php says:
The behavior of unset() inside of a function can vary depending on what type of variable you are attempting to destroy. […] To unset() a global variable inside of a function, then use the $GLOBALS array to do so
Seems like a classical »PHP behaviour is undefined in some cases«-example.

Related

How to json_encode $GLOBALS?

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!

Yii - Manipulating a sesssion variable

I am a still a newbie when it comes to using YII, but I been working with session variables for the past few days, and I can't seem to grasp to the concept behind my error. Any advice will be appreciated.
My add function works perfectly so far, for my current purpose of keeping track of the last 3 variables added to my session variable nutrition.
public function addSessionFavourite($pageId)
{
$page = Page::model()->findByPk($pageId);
$categoryName = $page->getCategoryNames();
if($categoryName[0] == 'Nutrition')
{
if(!isset(Yii::app()->session['nutrition']))
{
Yii::app()->session['nutrition'] = array();
}
$nutrition = Yii::app()->session['nutrition'];
array_unshift($nutrition, $pageId);
array_splice($nutrition, 3);
Yii::app()->session['nutrition'] = $nutrition;
}
My remove function doesn't seem to work at all, no matter what I try to do with it. The reason why I am transfering the session array to a temp array was to try to get around the "If a globalized variable is unset() inside of a function, only the local variable is destroyed. The variable in the calling environment will retain the same value as before unset() was called." But it was a total failure.
public function removeSessionFavourite($pageId)
{
$page = Page::model()->findByPk($pageId);
$categoryName = $page->getCategoryNames();
if($categoryName[0] == 'Nutrition')
{
if(!isset(Yii::app()->session['nutrition']))
{
return true;
}
$nutritionArray = Yii::app()->session['nutrition'];
unset($nutritionArray[$pageId]);
Yii::app()->session['nutrition'] = $nutritionArray;
}
Any advice or push toward to the correct direction will be appreciated.
I personally I have never used Yii::app()->session I normally use the Yii user and I have never had any issues with it:
Yii::app()->user->setState('test', array('a'=>1,'b'=>2));
print_r(Yii::app()->user->getState('test')); //see whole array
$test = Yii::app()->user->getState('test');
unset($test['b']);
Yii::app()->user->setState('test',$test);
print_r(Yii::app()->user->getState('test')); //only 'a'=>1 remains
Yii::app()->user->setState('test', null);
print_r(Yii::app()->user->getState('test')); //now a null value
As I put in a comment above there seems to be issues with multidimensional arrays with the session variable: https://code.google.com/p/yii/issues/detail?id=1681

php unset local reference affecting global scope

i have come across some very strange php behaviour (5.3.2 on ubuntu 10.04). an unset which should occur within local scope is affecting the scope of the caller function. the following snippet is a simplification of my code which displays what i can only assume is a bug:
<?php
function should_not_alter($in)
{
$in_ref =& $in['level1'];
should_only_unset_locally($in);
return $in;
}
function should_only_unset_locally($in)
{
unset($in['level1']['level2_0']);
}
$data = array('level1' => array('level2_0' => 'first value', 'level2_1' => 'second value'));
$data = should_not_alter($data); //test 1
//should_only_unset_locally($data); //test 2
print_r($data);
?>
if you run the above you will see that the value 'first value' has been unset from the $data array in the global scope. however if you comment out test 1 and run test 2 this does not happen.
i can only assume that php does not like referencing an element of an array. in my code i need to alter $in_ref - hence the reason for the $in_ref =& $in['level1']; line in the above code. i realize that removing this line would fix the problem of 'first value' being unset in the global scope, but this is not an option.
can anyone confirm if this is intended behaviour of php?
i suspect it is a bug, rather than a feature, because this behaviour is inconsistent with the way that php handles scopes and references with normal (non-array) variables. for example, using a string rather than an array function should_only_unset_locally() has no effect on the global scope:
<?php
function should_not_alter($in)
{
$in_ref =& $in;
should_only_unset_locally($in);
return $in;
}
function should_only_unset_locally($in)
{
unset($in);
}
$data = 'original';
$data = should_not_alter($data); //test 1
//should_only_unset_locally($data); //test 2
print_r($data);
?>
both test1 or test2 output original as expected. actually, even if $data is an array but $in_ref is referenced to the entire array (ie $in_ref =& $in;) then the buggy behaviour goes away.
update
i have submitted a bug report
Yup, looks like a bug.
As the name of the function implies, should_not_alter should not alter the array since it's passed by value. (I'm of course not basing that just off of the name -- it also should not alter anything based on its definition.)
The fact that commenting $in_ref =& $in['level1']; makes it leave $in alone seems to be further proof that it's a bug. That is quite an odd little quirk. No idea what could be happening internally to cause that.
I'd file a bug report on the PHP bug tracker. For what it's worth, it still exists in 5.4.6.
$data = should_not_alter($data)
This line is overwriting the $data array with the return value of should_not_alter, which is $in. This is normal behavior.
Also, while you're creating a reference $in_ref =& $in['level1']; but you're not doing anything with it. It will have no effect on the program output.
Short answer:
Delete the reference variable via unset($in_ref) before calling the should_only_unset_locally() function.
Long answer:
When a reference to an array element is created, the array element is replaced with a reference. This behavior is weird but it isn't a bug - it's a feature of the language and is by design.
Consider the following PHP program:
<?php
$a = array(
'key1' => 'value1',
'key2' => 'value2',
);
$r = &$a['key1'];
$a['key1'] = 'value3';
var_dump($a['key1']);
var_dump($r);
var_dump($a['key1'] === $r);
Output:
string(6) "value3"
string(6) "value3"
bool(true)
Assigning a value to $a['key1'] changes the value of $r as they both reference the same value. Conversely updating $r will update the array element:
$r = 'value4';
var_dump($a['key1']);
var_dump($r);
Output:
string(6) "value4"
string(6) "value4"
The value doesn't live in $r or $a['key'] - those are just references. It's like they're both referencing some spooky, hidden value. Weird, huh?
For most use cases this is desired and useful behavior.
Now apply this to your program. The following line modifies the local $in array and replaces the 'level1' element with a reference:
$in_ref = &$in['level1'];
$in_ref is not a reference to $in['level1'] - instead they both reference the same spooky value. So when this line comes around:
unset($in['level1']['level2_0']);
PHP sees $in['level1'] as a reference to a spooky value and removes the 'level2_0' element. And since it's a reference the removal is also felt in the scope of the should_not_alter() function.
The solution to your particular problem is to destroy the reference variable which will automagically restore $in['level1'] back to normal behavior:
function should_not_alter($in) {
$in_ref =& $in['level1'];
// Do some stuff with $in_ref
// After you're done with it delete the reference to restore $in['level1']
unset($in_ref);
should_only_unset_locally($in);
return $in;
}

Using reference to nonexistent value sets variable to NULL?

When passing a non-existent value by reference, PHP creates the value and sets it to NULL. I noticed it when memory increases were occurring while checking empty values in some functions. Take the following function:
function v(&$v,$d=NULL){return isset($v)?$v:$d;}
$bar = v($foo, $default);
This would be shorthand for:
if(isset($foo))
{
$bar = $foo;
}
else
{
$bar = $default;
}
However, when passing non-existent variables PHP creates them. In the case of variables - they are removed as soon as the method/function ends - but for checking super global arrays like $_GET or $_POST the array element is never removed causing extra memory usage.
$request_with = v($_SERVER['HTTP_X_REQUESTED_WITH']);
Can anyone explain why this happens and if it is a PHP todo fix or a feature for some other crazy use of values?
XeonCross' function v is a shorthand for the often used:
$val= isset($arr['elm']) ? $arr['elm'] : 'default'
to avoid the dreaded 'Undefined index: elm' notice. A nice helper function would be:
function ifset(&$v1, $v2 = null) {
return isset($v1) ? $v1 : $v2;
}
as Xeoncross suggested, so you could write the much nicer
$val = ifset($arr['elm'],'default')
however, this has a lot of interesting (?) quirks in our beloved "language" that we call PHP:
inside the function ifset, $v1 seems UNSET, so it correctly returns the value $v2 and you might conclude that ifset works ok. But afterwards $arr['elm'] is silently set to NULL. So consider the following:
function wtf(&$v) {
if (isset($v))
echo "It is set";
else
echo "It is NOT set";
}
$p=[];
wtf($p['notexist']); => It is NOT set
$p; => [ 'notexist' => NULL ]
But this is another delusion, as the isset() function returns false for NULL values as well:
$x=NULL;
isset($x) => false... huh??
Did we expect this? well.. it is in the documentation, so this is by design as well. Welcome to the wonderful world of php.
The reason you have the memory leak, is because you're telling it to.
When you ask for a reference parameter, PHP will provide you with one. When you are calling a function with an unset variable, PHP will set the variable and then pass the reference to that new variable. When you call it with a superglobal, it creates the missing index. That's because you told it to.
However, I must ask why specifically do you need variable references? 99.9% of the time you don't really need them. I suspect that it'll work just fine to do:
function v($v, $d = null) { return isset($v) ? $v : $d; }
Or, if you really must use references (which you can't get around your original problem with), you should also return a reference:
function &v(&$v, $d = null) {
if (isset($v)) {
return $v;
}
return $d;
}
Otherwise it's pointless to take a reference and not return one...

Delete from array help

I have a cookie which stores info in an array.
This is for a classifieds website, and whenever users delete their 'ads' the cookie must also be removed of the ad which was deleted.
So I have this:
if (isset($_COOKIE['watched_ads'])){
$expir = time()+1728000;
$ad_arr = unserialize($_COOKIE['watched_ads']);
foreach($ad_arr as $val){
if($val==$id){ // $id is something like "bmw_m3_10141912"
unset($val);
setcookie('watched_ads', serialize($ad_arr), $expir, '/');
}
}
}
This doesn't work... any idea why? I think its a problem with the unset part...
Also, keep in mind if there is only one value inside the array, what will happen then?
Thanks
You got two bugs here: 1) you unset the $val instead of the array element itself. 2) You set the cookie within the loop to the unknown $ad_arr2 array.
foreach($ad_arr as $key => $val){
if($val==$id){ // $id is something like "bmw_m3_10141912"
unset($ad_arr[$key]);
}
}
setcookie('watched_ads', serialize($ad_arr), $expir, '/');
array_filter seems appropriate:
$array = array_filter($array, create_function('$v', 'return $v != '.$id.';'));
You are correct that you are using unset incorrectly. The manual on unset states:
If a static variable is unset() inside
of a function, unset() destroys the
variable only in the context of the
rest of a function. Following calls
will restore the previous value of a
variable.
When you use 'as' you're assigning the value of that array element to a temporary variable. You want to reference the original array:
foreach ($ad_arr as $key => $val)
...
unset($ad_arr[$key]);
...

Categories