Reputation: 2346
What is the best way to check if an array is recursive in PHP ?
Given the following code:
<?php
$myarray = array('test',123);
$myarray[] = &$myarray;
print_r($myarray);
?>
From the PHP Manual:
The print_r() will display RECURSION when it gets to the third element of the array.
There doesn't appear to be any other way to scan an array for recursive references, so if you need to check for them, you'll have to use print_r() with its second parameter to capture the output and look for the word RECURSION.
Is there more elegant way of checking ?
PS. This is how I check and get the recursive array keys using regex and print_r()
$pattern = '/\n \[(\w+)\] => Array\s+\*RECURSION\*/';
preg_match_all($pattern, print_r($value, TRUE), $matches);
$recursiveKeys = array_unique($matches[1]);
Thanks
Upvotes: 12
Views: 2810
Reputation: 45
I think the quickest and easiest way to check RECURSION is simple query
if(array(($variable['variable_name'])==('*RECURSION*')))
{
return true;
}
else
{
return false;
}
Upvotes: 0
Reputation: 294
Many of the solutions you will find on SO are broken (see explanation below). The function I propose works for all arrays and is much more efficient than print_r
:
function is_cyclic(&$array) {
$isRecursive = false;
set_error_handler(function ($errno, $errstr) use (&$isRecursive) {
$isRecursive = $errno === E_WARNING && mb_stripos($errstr, 'recursion');
});
try {
count($array, COUNT_RECURSIVE);
} finally {
restore_error_handler();
}
return $isRecursive;
}
The count
function takes a second parameter $mode
that can be set to the constant COUNT_RECURSIVE
to count recursively (see docs). If a recursive array gets passed to count
, it will emit a warning that we can trap and check. I have written more about this solution on my blog. Tests and benchmarks are on github.
Any implementation that adds markers to arrays and then later checks for the presence of these markers does not work for all inputs. Specifically, they fail to detect recursion in some cases where the arrays have previously been assigned by value (e.g. returned by a function). This is due to the way PHP handles value assignments of arrays, as described in chapter 4 of the PHP Language Specification. I have written more extensively about this on my blog.
Upvotes: 0
Reputation: 31
The following function is simpler[opinion] than the code in the accepted answer, and seems to work for any use case that I have been able to contrive. It also seems to be surprisingly fast, typically taking microseconds, though I have not done extensive benchmarking. If there is a problem with it, I would be grateful if somebody could point it out?
// returns TRUE iff the passed object or array contains
// a self-referencing object or array
function is_r($obj, &$visited=array())
{
$visited[] = $obj;
foreach ($obj as $el)
{
if (is_object($el) || is_array($el))
{
if (in_array($el, $visited, TRUE))
return TRUE;
if (is_r($el, $visited))
return TRUE;
}
}
return FALSE;
}
Upvotes: 3
Reputation: 437414
It's always fun to try solving "impossible" problems!
Here's a function that will detect recursive arrays if the recursion happens at the top level:
function is_recursive(array &$array) {
static $uniqueObject;
if (!$uniqueObject) {
$uniqueObject = new stdClass;
}
foreach ($array as &$item) {
if (!is_array($item)) {
continue;
}
$item[] = $uniqueObject;
$isRecursive = end($array) === $uniqueObject;
array_pop($item);
if ($isRecursive) {
return true;
}
}
return false;
}
Detecting recursion at any level would obviously be more tricky, but I think we can agree that it seems doable.
And here is the recursive (pun not intended but enjoyable nonetheless) solution that detects recursion at any level:
function is_recursive(array &$array, array &$alreadySeen = array()) {
static $uniqueObject;
if (!$uniqueObject) {
$uniqueObject = new stdClass;
}
$alreadySeen[] = &$array;
foreach ($array as &$item) {
if (!is_array($item)) {
continue;
}
$item[] = $uniqueObject;
$recursionDetected = false;
foreach ($alreadySeen as $candidate) {
if (end($candidate) === $uniqueObject) {
$recursionDetected = true;
break;
}
}
array_pop($item);
if ($recursionDetected || is_recursive($item, $alreadySeen)) {
return true;
}
}
return false;
}
Of course this can also be written to work with iteration instead of recursion by keeping a stack manually, which would help in cases where a very large recursion level is a problem.
Upvotes: 8
Reputation: 14222
I've dug into this in depth some time ago, and I was unable to find any useful mechanism for detecting recursion in PHP arrays.
The question boils down to whether it's possible to tell whether two PHP variables are references to the same thing.
If you're working with objects rather than arrays (or even objects within your arrays), then it is possible, as one can find out whether two objects are the same reference using spl_object_hash()
. So if you have objects in your structure, then you can detect recursion by traversing up the tree and comparing the objects.
However for regular variables -- ie non-objects -- it isn't possible to detect this easily using standard PHP.
The work arounds are to use print_r()
(as you already know) or var_dump()
, but neither of these are particularly elegant solutions.
There is also a function provided by xDebug which can help, xdebug_debug_zval()
, but that's obviously only available if you've got xDebug installed, which isn't recommended on a production system.
Further advice and suggestions available here.
Upvotes: 2
Reputation: 48121
I believe you can't check that. Read the ReferenceDoc for more information on reference.
Here is the function to check for RECURSION (from PHP doc comments), altough it seems to be very slow (I would not suggest it):
function is_array_reference ($arr, $key) {
$isRef = false;
ob_start();
var_dump($arr);
if (strpos(preg_replace("/[ \n\r]*/i", "", preg_replace("/( ){4,}.*(\n\r)*/i", "", ob_get_contents())), "[" . $key . "]=>&") !== false)
$isRef = true;
ob_end_clean();
return $isRef;
}
Upvotes: 1