Figuring Out Foreach
For a while I have been aware of some odd behaviour when you use branch coverage with PHP's foreach language construct. Usually with PHPUnit.
Take for example this bit of code:
<?php
function showLanguages(array $languages): void
{
foreach ($languages as $language) {
echo $language, "\n";
}
}
It seems that in order to cover all paths by test cases to pass in an array without any elements, and one with at least one element.
But no, when you run the analysis, you will see that there only two out of the three paths are covered:
Let's first actually analyse which opcodes, PHP's internal execution units, are generated by the engine:
line #* E I O op return operands
------------------------------------------------
2 0 E > RECV !0
4 1 EXT_STMT
2 > FE_RESET_R $2 !0, ->9
3 > > FE_FETCH_R $2, !1, ->9
5 4 > EXT_STMT
5 ECHO !1
6 EXT_STMT
7 ECHO '%0A'
4 8 > JMP ->3
9 > FE_FREE $2
7 10 EXT_STMT
11 > RETURN null
This output from VLD shows on line 4, two relevant opcodes: FE_RESET_R and FE_FETCH_R. Both of them can either followed by the next opcode on the list, or they can jump (->9) to opcode 9, which marks the end of the loop.
However from the diagram above, we don't see that path (the green one) being taken. Only the two paths that both continue with the next opcode (the red and blue lines). It seems that the emptiness of a normal array is checked by the FE_FETCH_R opcode.
Now the question is how to trigger the other path, so that 100% path (and branch) coverage can be reached.
This turns out harder than it is. I originally thought that this would be possible by trying to create a broken iterator — for example one where you inherit from an internal class with a custom one, and not call the original constructor. But this creates an exception which pre-empts the engine from even running the rest of the function.
The only situation where I managed to do this was by creating an iterator that after a correct initialisation, had no items to return. An example of such as case is a DatePeriod iterator where the start date of the iterator is behind the end date:
$i1 = new DatePeriod(
new DateTimeImmutable("2025-01-14"),
DateInterval::createFromDateString("+1 day"),
new DateTimeImmutable("2025-01-01")
);
I had to change the definition of showLanguages too so that it accepts DatePeriod besides just array. But with that done, this specific iterator now lights up the green path:
There is currently an open bug in Xdebug's issue tracker to merge the branch analysis information for the two subsequent opcodes (FE_RESET_R and FE_FETCH_R).
I think I will now rather hide the jump away from the FE_RESET_R (and FE_RESET_RW) opcode.
But at last I now have an explanation as to where this phantom path came from.
Life Line
Updated an alcohol shop
Updated 2 benches
Created a bench; Updated a bench
I hiked 19.0km in 4h35m50s
I hiked 19.0km in 4h35m50s
I walked 6.8km in 1h15m36s
Updated an estate_agent office
I walked 4.1km in 55m33s
I walked 1.1km in 10m05s
My First Lapwing!
I went to the London Wetland Centre yesterday, for a day out in nature.
While hiding in a hide, this chap and a friend showed up starting to forage for grubs.
#BirdPhotography #BirdsOfMastodon #Photography #Birds #London #Nature
Created a waste_basket; Updated a cafe and a restaurant; Confirmed an estate_agent office
I walked 6.6km in 1h8m53s
@bennuttall Are you at the Crucible this year again?
I walked 9.7km in 5h29m12s
Updated a gate
Staring Contest with a Squirrel
On my walk on the weekend, I sat down on a tree branch of a tree that had fallen over some time ago. Just listening to the birds.
Then after hearing rustling in the foliage above me, I looked up, and saw this chap staring at me.
I walked 3.0km in 41m38s
I walked 1.1km in 12m20s
Bluebell Carpet
I had a lovely walk on Hampstead Heath yesterday, finding all the nooks and crannies away from the busy paths.
This field of bluebells under the colourful tree was a stand-out quiet spot.
I walked 2.3km in 21m51s
Fix paths
Created a memorial
Created a bench
@Edent Seems like my Android stopped sending coordinates to @openbenches as well, which is surprising as I haven't updated anything as far as I'm aware. Could it be a problem with the reader in your side after the latest changes and the addition of the warning?
I walked 5.8km in 2h15m45s




Shortlink
This article has a short URL available: https://drck.me/fidgety-forach-jbc