Flat pair arrays: three pitfalls from the Related articles block
| The flat pair array | |
| Count entries, not articles | |
| Cap the array you loop over | |
| array_search and index 0 | |
| Prove it with a test | |
| When to retire the flat array |
The flat pair array
The Related articles block of this site is fed by a flat array: url, title, url, title and so on. The renderer walks it two steps at a time:
<?php
$i = 0;
while ($i < $limit) {
$url = $items[$i];
$name = $items[$i + 1];
$i += 2;
}
Three small mistakes hide in this loop. All three shipped on this very site, so here they are with fixes.
Count entries, not articles
Twelve articles are twenty-four array entries. Capping the loop at 12 shows six articles; capping at 24 shows twelve. And the cap must stay even, otherwise the last iteration reads one past the end of the array:
<?php
// BAD: six articles, not twelve
$limit = 12;
// GOOD: twelve articles, even cap
$limit = min(12 * 2, count($items));
$limit -= $limit % 2;
An odd-length array (a trailing url with no title after a bad edit) would otherwise end in an undefined-index warning or a broken link.
Cap the array you loop over
Do not take the limit from a stale length property when the loop runs over a filtered copy. Removing the current page shrinks the working array by two, so a limit computed before the filter can run past its end:
<?php
// BAD: limit from the unfiltered array
$limit = $this->related_urls_length;
$shown = array_values($filtered);
// GOOD: limit from the array in the loop
$shown = array_values($filtered);
$limit = min(12 * 2, count($shown));
$limit -= $limit % 2;
array_search and index 0
Filtering out the current page usually starts with array_search(). It returns the index, and the first element lives at index 0, which is falsy. A loose comparison silently skips the removal exactly when the current page is first in the list:
<?php
$at = array_search($current, $items);
// BAD: misses index 0, because 0 != false is false
if ($at != false) {
unset($items[$at], $items[$at + 1]);
}
// GOOD: strict comparison, then reindex the pair list
if ($at !== false) {
unset($items[$at], $items[$at + 1]);
$items = array_values($items);
}
The reindex matters too: after unset() the pair loop expects consecutive numeric keys, so array_values() restores them.
Prove it with a test
All three fixes fit in one script you can run before every deploy. Feed the renderer a list with the current page first (catches the array_search bug), an odd-length list (catches the cap bug), and a filtered list shorter than the original (catches the stale-limit bug):
<?php
$items = ['/here', 'Here', '/a', 'A'];
remove_current($items, '/here');
assert($items === ['/a', 'A']);
render(array_slice($items, 0, 24)); // even cap, no warnings
If any assertion or a single warning appears, the block is broken — no manual clicking through pages required.
When to retire the flat array
The flat array survives while the list is small, unordered and edited by hand. Retire it when you need any of: per-item metadata (dates, weights), sorting that is not file order, or more than one consumer with different limits. The replacement is an array of small arrays (url, title, extra), where count() finally means articles and every limit bug in this article disappears by construction.