Created
August 6, 2011 16:43
-
-
Save AoJ/1129502 to your computer and use it in GitHub Desktop.
Having fun with foreach cycles in PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Foreach always creates a working copy of the array, which results in double memory usage. | |
Here are some fun things along with one WTF thingie at the end. | |
We discovered these in WDF (www.wdf.cz) during today's afternoon. | |
Outline | |
------------- | |
<?php | |
$array = array(1, 2, 3, 4, 5); | |
foreach($array as $key => $value) { | |
unset($value); | |
} | |
var_dump($array); | |
?> | |
result: array(5) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) [4]=> int(5) } | |
This is very well known thing. But, it has nice side-effects.. | |
Unsetting the whole array | |
------------------------------- | |
case: | |
<?php | |
$array = array(1, 2, 3, 4, 5); | |
foreach($array as $key => $value) { | |
echo $value . ','; | |
unset($array); | |
} | |
echo '<br />'; | |
var_dump($array); | |
?> | |
Expected result: | |
1, | |
NULL | |
Actual result: | |
1,2,3,4,5, | |
NULL | |
Remember that foreach works with _local working copy_ ? This is the obvious result, | |
the foreach finishes anyway. | |
Obvious solution: Use references | |
-> the presumtion is, that when we are working with reference variable, | |
once the source array changes, then the foreach wouldn't continue. Hence: | |
<?php | |
$array = array(1, 2, 3, 4, 5); | |
foreach($array as $key => & $value) { | |
echo $value . ','; | |
unset($array); | |
} | |
echo '<br />'; | |
var_dump($array); | |
?> | |
The actual result is: | |
1,2,3,4,5, | |
NULL | |
... dammit! | |
Reason: Well, we did not look at sources. But foreach obviously creates a reference | |
to all elements within array. Hence, unset actually unsets only one of the references. | |
(actually; this is not correct. see the (wtf?) case at the end..) | |
Final solution: The references _and_ manual set | |
<?php | |
$array = array(1, 2, 3, 4, 5); | |
foreach($array as $key => & $value) { | |
echo $value . ','; | |
$array = array(); | |
} | |
echo '<br />'; | |
var_dump($array); | |
?> | |
result: | |
1, | |
array(0) { } | |
voila! | |
WARNING: do *not* use <?php $array = null; ?> - that will result in raising | |
Warning "Invalid argument supplied for foreach()" (it's reference ...) | |
Appending the array through foreach | |
----------------------------------------- | |
Case: we needed to re-order the keys within the array while unsetting the old ones. | |
Hence, we did something like this: | |
<?php | |
$array = array(1, 2, 3, 4, 5); | |
foreach($array as $key => $value) { | |
echo $key . ', '; | |
$array['prefix-' . $key] = $value; | |
unset($array[$key]); | |
} | |
echo '<br />'; | |
var_dump($array); | |
?> | |
the result was: | |
0, 1, 2, 3, 4, | |
array(5) { ["prefix-0"]=> int(1) ["prefix-1"]=> int(2) ["prefix-2"]=> int(3) ["prefix-3"]=> int(4) ["prefix-4"]=> int(5) } | |
... also nothing unexpected. But sometimes you need to walk through newly appended | |
items as well (something that we have been doing by using while($item = each($array)) clause) | |
The memory usage is also well, since the changes to the array happens immidiatelly, keeping memory | |
overhead of one item. | |
Using foreach as stack | |
---------------------------- | |
If you use the above & add a reference, then you can work with the array as with stack.. | |
<?php | |
$array = array(1, 2, 3, 4, 5); | |
foreach($array as $key => & $value) { // <- notice the & | |
echo $key . ', '; | |
if(strpos($key, 'prefix-') === false) { // prevent infinite recursion | |
$array['prefix-' . $key] = $value; | |
unset($array[$key]); | |
} | |
} | |
echo '<br />'; | |
var_dump($array); | |
?> | |
and we got: | |
0, 1, 2, 3, 4, prefix-0, prefix-1, prefix-2, prefix-3, prefix-4, | |
array(5) { ["prefix-0"]=> int(1) ["prefix-1"]=> int(2) ["prefix-2"]=> int(3) ["prefix-3"]=> int(4) ["prefix-4"]=> &int(5) } | |
hell yeah! | |
The last one is generally useful for things like walking through directory structure - it ends | |
when you don't add next item into stack. Also it eliminates the recursion. | |
Hence, adopt this one into directory iterator, using the php 4 approach: | |
<?php | |
$folderStack = array(realpath('.')); | |
foreach($folderStack as $key => & $value) { | |
$dir = opendir($value); | |
while($file = readdir($dir)) { | |
if($file == '.' || $file == '..') { | |
continue; | |
} | |
// The foreach magic! | |
if(is_dir($file)) { | |
$folderStack[] = $value . DIRECTORY_SEPARATOR . $file; | |
continue; | |
} | |
// do something with file in here | |
echo $value . DIRECTORY_SEPARATOR . $file . '<br />'; | |
} | |
closedir($dir); | |
unset($folderStack[$key]); | |
} | |
var_dump($folderStack); | |
?> | |
and here comes the catch. It does not continue to further directories! | |
Se we poked around more. And we discovered, that foreach will always continue, unless it's | |
currently on the last element. | |
Hence, we have altered the script: | |
<?php | |
$folderStack = array(realpath('.'), false); | |
foreach($folderStack as $key => & $value) { | |
if($value === false) { // this is where the magic happens actually | |
continue; | |
} | |
$dir = opendir($value); | |
while($file = readdir($dir)) { | |
if($file == '.' || $file == '..') { | |
continue; | |
} | |
$full = $value . DIRECTORY_SEPARATOR . $file; | |
// boring append to stack | |
if(is_dir($full)) { | |
$folderStack[] = $full; | |
$folderStack[] = false; // make sure it's not last element | |
continue; | |
} | |
// do something with file in here | |
echo $full . '<br />'; | |
} | |
closedir($dir); | |
unset($folderStack[$key]); | |
} | |
var_dump($folderStack); | |
?> | |
which produced the wanted result. | |
That is: browsing directories & everything, without the frakkin recursion... | |
Going to sleep, enjoy! | |
last edit today: comming to think of it, do { ... } while(!empty($folderStack)); is much nicer and | |
cleaner approach. This is mainly to demonstrate the dangers we might run into when editing the array | |
with which we are working using references -> that usually happens when we work with huge arrays | |
or we are creating some function which is to be run on many-time basis. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment