Flat pair arrays: three pitfalls from the Related articles block

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.