Modify array recursively in PHP - php

I'm trying to modify an array in a PHP 5 function.
Example input:
array('name' => 'somename', 'products' => stdClass::__set_state(array()))
Expected output:
array('name' => 'somename', 'products' => null)
I have written the following code to replace empty objects (which are stdClass::__set_state(array()) objects) with null. The method works fine (I've used some debug logs to check), but the array I'm giving it does not change.
private function replaceEmptyObjectsWithNull(&$argument){
if (is_array($argument)){
foreach ($argument as $innerArgument) {
$this->replaceEmptyObjectsWithNull($innerArgument);
}
} else if (is_object($argument)){
if (empty((array) $argument)) {
// If object is an empty object, make it null.
$argument = null;
\Log::debug("Changed an empty object to null"); // Is printed many times, as expected.
\Log::debug($argument); // Prints an empty line, as expected.
} else {
foreach ($argument as $innerArgument) {
$this->replaceEmptyObjectsWithNull($innerArgument);
}
}
}
}
I call this method like this:
$this->replaceEmptyObjectsWithNull($myArray);
\Log::debug($myArray); // myArray should be modified, but it's not.
What am I doing wrong here? I'm parsing the argument by reference, right?

There is a very simple way to do this.
You just have to change your foreach loop to reference your variables and not use a copy of your variables. You can do this with the ampersand symbol in front of your $innerArgument.
foreach ($argument as &$innerArgument) {
$this->replaceEmptyObjectsWithNull($innerArgument);
}
Notice the & symbol in front of $innerArgument in the loop.
You can learn more about this in the PHP docs. You can also learn more about references in general in the PHP docs.

Related

Why is my PHP class method returning nothing when I try to use values taken from using preg_replace inside foreach without giving errors?

I'm trying to build an array with a few variations of some data, when using preg_replace and echoing out values, it works like normal, but if I try to store those values in an array, the function simply returns nothing, and gives no error.
Here's a piece of the code I'm using, it's inside a class.
$actions = array();
foreach($controllers as $ck => $cv) {
$cvar = 'br'.$ck.'actions';
foreach(self::$$cvar as $key => $value) {
if ($key != $value) {
$actions[$value] = $key;
if( preg_match('/[A-Z]/',$key)!==0 || preg_match('/[A-Z]/',$value)!==0 )
{
$key2=strtolower(preg_replace('/(?<=\\w)([A-Z])/','-\\1',$key));
$value2=strtolower(preg_replace('/(?<=\\w)([A-Z])/','-\\1',$value));
$actions[$value2] = $key2; // Everything works except for this line, if I comment it, it works, if I don't, it simply doesn't return even an error.
}
}
}
}
return $actions;
What is causing this weird behaviour? It should be able to add to the array like normal, but it doesn't...
So, I found out what was the problem:
In the arrays of values being used to create the $actions array (The $br(controller)actions array) there were some values that accidentally had some diacritics on them (Like the word "Demonstração"), which when going through the preg_replace function, became some of those black question mark boxes (�� Replacement Characters), which then were fed as array keys by the function and somehow made the function return empty instead of the array. Printing the values showed that the function still ran through all the loop iterations, but the array simply didn't return properly with those values on it.
I fixed it by removing the diacritics, then the method ran smooth as it should.

seems like foreach loop doesnt work

I want to check an object props, but it looks like loop never runs.
$object = $this->helix->Footer();
// var_dump($object) ; // the var dump starts with "object(Helix)#118 (9) { ....."
foreach($object as $prop_name => $prop_val){
echo $object->$prop_name ;
}
Does anyobody have idea what Im doing wrong?
You can use this function also: php.net/get_object_vars
But the issue can be the same. This function can show only the properties it can see.
It means, if you call it outside the class, then only the public vars. But is you call it inside the class, then "everything". (see the comments on the manual page.)

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;
}

PHP array not working, multiple

Here's my code:
function prepare_machine($variables)
{
foreach ($variables AS $varname => $vartype)
{
if (isset($_REQUEST[$varname]))
{
$value = $_REQUEST[$varname];
return do_clean($value, $vartype);
}
else
exit;
}
}
It is called like this:
prepare_machine(array('order_by_time' => TYPE_BOOLEAN));
it all works fine, but if you have multiple things in the array, for examples;
prepare_machine(array('order_by_time' => TYPE_BOOLEAN, 'order_by_date' => TYPE_BOOLEAN));
it will only do anything with the first one.
Can anybody see what is wrong with my code?
Thanks
You're doing a return ... when you find a match in your inner loop. That's why it only processes one.
Also, you should be using array_key_exists($varname, $_REQUEST) because isset($_REQUEST[$varname]) will fail if $_REQUEST[$varname] is null.
return returns whatever you give it, and exits the function. You need to change your function somehow so that it only returns after processing all the variables.

Any PHP function that will strip properties of an object that are null?

I am returning a json_encode() of an array of objects pulled from an ORM. It includes lots of properties with a null value. What is the neatest way to remove these properties that are null? I guess I could iterate over the properties, look if they are null and then unset() that property, but surely there must be a more elegant way?
Try this; it will only work on a simple object, but if it's coming from an ORM it should be simple enough.
// Strips any false-y values
$object = (object) array_filter((array) $object);
Thanks to Gordon's answer to another question yesterday for giving me the idea.
This works by
converting the object to an associative array, where object properties are the keys and their values are the array values
using array_filter with default arguments to remove array entries with a false (e.g. empty, or null) values
converting the new array back to a simple object
Note that this will remove all properties with empty values, including empty strings, false boolean values and 0s, not just nulls; you can change the array_filter call if you want to keep those and only remote those that are exactly null.
// Strips only null values
$object = (object) array_filter((array) $object, function ($val) {
return !is_null($val);
});
I'm going to add to the response given by El Yobo because that will only work if you have a 1 dimensional object or array. If there is any array or object nesting then in order to get the accepted solution to work you must create some sort of recursive array filter. Not good.
The best solution my colleague and I came up with was to actually perform a regular expression on the JSON string before it was returned from the server.
$json = json_encode($complexObject);
echo preg_replace('/,\s*"[^"]+":null|"[^"]+":null,?/', '', $json);
The regular expression will remove all places in the string of the form ,"key":null including any whitespace between the leading comma and the start of the key. It will also match "key":null, afterwards to make sure that no null values were found at the beginning of a JSON object.
This obviously isn't the most ideal situation but it will effectively remove null entries without having to develop some kind of recursive array filter.
Despite the name you can also use array_walk with a closure:
// Setup
$obj = (object) array('foo' => NULL, 'bar' => 'baz');
// equivalent to array_filter
array_walk($obj, function($v,$k) use ($obj) {
if(empty($v)) unset($obj->$k);
});
// output
print_r($obj);
Output
stdClass Object
(
[foo] => bar
)
There's no standard function to remove null-valued properties. Writing one of your own isn't inelegant, if you write one elegantly.
I made this function that solves my problem: clean null 'object properties' and 'array properties' inside an object. Also, you can have many 'levels' of objects/arrays inside of an object:
function cleanNullsOfObject(&$object) {
foreach ($object as $property => &$value) {
if (is_object($value)) {
cleanNullsOfObject($value);
if (empty(get_object_vars($value))) {
unset($object->$property);
}
}
if (is_array($value) && is_object($value[0])) {
foreach ($value as $val) {
cleanNullsOfObject($val);
}
}
if (is_null($value) || (is_array($value) && empty($value))) {
unset($object->$property);
}
}
}
//test
$object = new stdClass();
$object->property = "qwe";
$object->nullProperty = null;
$propertyObject = new stdClass();
$propertyObject->property = "asd";
$propertyObject->nullProperty = null;
$object->propertyObject = $propertyObject;
var_dump($object);
echo "<br>";
echo "<br>";
cleanNullsOfObject($object);
var_dump($object);
Building off of #Gordon 's answer, a couple of adjustments would be needed to make that work, but you can also use array_walk_recursive instead. The reference is required or otherwise any changes you make to the object won't apply to the scope outside of the Closure.
IE:
$someObject = (array)$someObject;
array_walk_recursive($someObject, function($v,$k) use (&$someObject) {
if($someObject[$k] == null) {
unset($someObject[$k]);
}
});
$someObject = (object)$someObject;
var_dump($someObject);

Categories