When dealing with certain PHP objects, it's possible to do a var_dump() and PHP prints values to the screen that go on and on and on until the PHP memory limit is reached I assume. An example of this is dumping a Simple HTML DOM object. I assume that because you are able to traverse children and parents of objects, that doing var_dump() gives infinite results because it finds the parent of an object and then recursively finds it's children and then finds all those children's parents and finds those children, etc etc etc. It will just go on and on.
My question is, how can you avoid this and keep PHP from dumping recursively dumping out the same things over and over? Using the Simple HTML DOM parser example, if I have a DOM object that has no children and I var_dump() it, I'd like it to just dump the object and no start traversing up the DOM tree and dumping parents, grandparents, other children, etc.
Install XDebug extension in your development environment. It replaces var_dump with its own that only goes 3 members deep by default.
https://xdebug.org/docs/display
It will display items 4 levels deep as an ellipsis. You can change the depth with an ini setting.
All PHP functions: var_dump, var_export, and print_r do not track recursion / circular references.
Edit:
If you want to do it the hard way, you can write your own function
print_rr($thing, $level=0) {
if ($level == 4) { return; }
if (is_object($thing)) {
$vars = get_object_vars($thing);
}
if (is_array($thing)) {
$vars = $thing;
}
if (!$vars) {
print " $thing \n";
return;
}
foreach ($vars as $k=>$v) {
if (is_object($v)) return print_rr($v, $level++);
if (is_array($v)) return print_rr($v, $level++);
print "something like var_dump, var_export output\n";
}
}
Why don't you simply run a foreach loop on your object?
From the PHP docs:
The foreach construct simply gives an easy way to iterate over arrays.
foreach works only on arrays (and objects), and will issue an error
when you try to use it on a variable with a different data type or an
uninitialized variable.
I had this problem and didn't need to see inside the objects, just the object classnames, so I wrote a simple function to replace objects with their classnames before dumping the data:
function sanitizeDumpContent($content)
{
if (is_object($content)) return "OBJECT::".get_class($content);
if (!is_array($content)) return $content;
return array_map(function($node) {
return $this->sanitizeDumpContent($node);
}, $content);
}
Then, when you want to dump something, just do this:
var_dump(sanitizeDumpContent($recursive_content))
Related
I have an xml structure:
<node1><node2><child_1/><child_2/><child_3/></node2></node1>
And i would want to get an array like this:
['child_1', 'child_2', 'child_3']
But to make my method for creating this handle errors elegantly and return an empty array when nothing found i am having to do this:
public function testXmlParse()
{
$config = new SimpleXMLElement("<node1><node2><child_1/><child_2/><child_3/></node2></node1>");
$result = $config->xpath('/node1/node2');
if (! count($result)) {
return [];
}
$result = $result[0]->children();
}
But i have even more code to write to check for arrays and valid etc.
Is there an elegantly way to get the correct result and return 0 on nothing finding?
The code you have written won't return an array anyway - the result of ->children() is an iterable SimpleXMLElement object. However, you can take advantage of the fact that a zero-element object is still iterable with foreach, and will simply go round zero times.
Since you are always looking for the first match, your example can also use SimpleXML access instead of XPath, to avoid the extra logic there.
$config = new SimpleXMLElement("<node1><node2><child_1/><child_2/><child_3/></node2></node1>");
// Start with an empty array; if no children are found, it will stay empty
$results = [];
// Note: $config represents the <node1> element, not the document
foreach ( $config->node2->children() as $name => $element ) {
$results[] = $name;
}
If <node2> is not always present, you may need to add an extra if ( isset($config->node2) ) around the loop, to avoid PHP throwing you warnings.
I've got a foreach statement that on an item that has both objects and arrays in it.
foreach($result as $data)
that contains both arrays and objects. how do i specify the foreach to only select to loop through one or the other? when it loops through them all it takes forever
I had tried foreach($result->data as $data) but then it errors on the arrays telling me it is trying to get property of an object, which is understandable. once I add an if statement to check if the first result is an object it almost triples the script run time since there are so many results.
Well you could just use is_object() and is_array() (both return a boolean):
if (is_object($var)) {
// do something
} else if (is_array($var)) {
// well then, do something else
}
given the following code:
$tree = array();
$node =& $tree[];
// imagine tons of code that populates $tree here
how can i entirely delete the ZVAL $node points to by reference? Is that even possible?
Using unset(), only the reference is destroyed and not the node in $tree itself:
unset($node);
print_r($tree);
// outputs:
Array
(
[0] =>
)
I know this is the expected behaviour of unset($reference) and I also know how the ZVAL refcounter works.
But i really need to delete that node after processing in a specific corner case.
Can i somehow find the correct array index and unset the array element directly like unset($tree[$node_index])?
Disclaimer: The above example is minified and isolated. Actually i'm modifying a complex parser for a really ugly nested table data structure that is presented as a stream. The code heavily uses pointers as backreferences and i'd like to avoid refactoring the whole code.
If you grab a reference to an array element and unset the reference the array will not be affected at all -- that's just how unset works, and this behavior is not negotiable.
What you need to do is remember the key of the element in question and unset directly on the array afterwards:
$tree = array();
$tree[] = 'whatever';
end($tree);
$key = key($tree);
// ...later on...
unset($tree[$key]);
Of course this is extremely ugly and it requires you to keep both $tree (or a reference to it) and $key around. You can mitigate this somewhat by packaging the unset operation into an anonymous function -- if there's a good chance you are going to pull the trigger later, the convenience could offset the additional resource consumption:
$tree = array();
$tree[] = 'whatever';
end($tree);
$key = key($tree);
$killThisItem = function() use(&$tree, $key) { unset($tree[$key]); } ;
// ...later on...
$killThisItem();
i've just reworked my recursion detection algorithm in my pet project dump_r()
https://github.com/leeoniya/dump_r.php
detecting object recursion is not too difficult - you use spl_object_hash() to get the unique internal id of the object instance, store it in a dict and compare against it while dumping other nodes.
for array recursion detection, i'm a bit puzzled, i have not found anything helpful. php itself is able to identify recursion, though it seems to do it one cycle too late. EDIT: nvm, it occurs where it needs to :)
$arr = array();
$arr[] = array(&$arr);
print_r($arr);
does it have to resort to keeping track of everything in the recursion stack and do shallow comparisons against every other array element?
any help would be appreciated,
thanks!
Because of PHP's call-by-value mechanism, the only solution I see here is to iterate the array by reference, and set an arbitrary value in it, which you later check if it exists to find out if you were there before:
function iterate_array(&$arr){
if(!is_array($arr)){
print $arr;
return;
}
// if this key is present, it means you already walked this array
if(isset($arr['__been_here'])){
print 'RECURSION';
return;
}
$arr['__been_here'] = true;
foreach($arr as $key => &$value){
// print your values here, or do your stuff
if($key !== '__been_here'){
if(is_array($value)){
iterate_array($value);
}
print $value;
}
}
// you need to unset it when done because you're working with a reference...
unset($arr['__been_here']);
}
You could wrap this function into another function that accepts values instead of references, but then you would get the RECURSION notice from the 2nd level on. I think print_r does the same too.
Someone will correct me if I am wrong, but PHP is actually detecting recursion at the right moment. Your assignation simply creates the additional cycle. The example should be:
$arr = array();
$arr = array(&$arr);
Which will result in
array(1) { [0]=> &array(1) { [0]=> *RECURSION* } }
As expected.
Well, I got a bit curious myself how to detect recursion and I started to Google. I found this article http://noteslog.com/post/detecting-recursive-dependencies-in-php-composite-values/ and this solution:
function hasRecursiveDependency($value)
{
//if PHP detects recursion in a $value, then a printed $value
//will contain at least one match for the pattern /\*RECURSION\*/
$printed = print_r($value, true);
$recursionMetaUser = preg_match_all('#\*RECURSION\*#', $printed, $matches);
if ($recursionMetaUser == 0)
{
return false;
}
//if PHP detects recursion in a $value, then a serialized $value
//will contain matches for the pattern /\*RECURSION\*/ never because
//of metadata of the serialized $value, but only because of user data
$serialized = serialize($value);
$recursionUser = preg_match_all('#\*RECURSION\*#', $serialized, $matches);
//all the matches that are user data instead of metadata of the
//printed $value must be ignored
$result = $recursionMetaUser > $recursionUser;
return $result;
}
I've got a rather large multidimensional stdClass Object being outputted from a json feed with PHP.
It goes about 8 or 9 steps deep and the data that I need is around 7 steps in.
I'm wondering if I can easily grab one of the entires instead of doing this:
echo $data->one->two->anotherone->gettinglong->omg->hereweare;
I'm saying this because the data structure may change over time.
Is this possible?
You could try to parse the object into an array and search the array for the wanted values, it just keeps looping through each level of the object.
function parseObjectArrayToArray($objectPassed, &$in_arr = array()) {
foreach($objectPassed as $element) {
if(is_object($element) || is_array($element)) {
parseObjectArrayToArray($element,$in_arr);
} else {
// XML is being passed, need to strip it
$element = strip_tags($element);
// Trim whitespace
$element = trim($element);
// Push to array
if($element != '' && !in_array($element,$in_arr)) {
$in_arr[] = $element;
}
}
}
return $in_arr;
}
How to call
$parsed_obj_arr = parseObjectArrayToArray($objectPassed);
Not without searching through whats probably inefficient.
Json is a structured data object with the purpose of eliminating something like this.
If the datastructure can change, but doesn't very often, your best bet is to write a wrapper object so you will only have to change a path at a single point on change:
class MyDataWrapp {
public $json;
function __construct($jsonstring){
$this->json = json_decode($jsonstring);
}
function getHereWeAre(){
return $this->json->one->two->anotherone->gettinglong->omg->hereweare;
}
}
If the datastructure changes dramatically and constantly, I'd json_decode as an array of arrays, and probably use RecursiveFilterIterator.