Originally published on decentphp.dev — part of the Writing decent PHP course. View this lesson on the original site.
A collection name should tell you "what's in it" and "what's already been done to it"
Collections (arrays, iterables, lists, maps) carry two pieces of information you usually want at a glance:
- what it contains —
users,orders,invoices - what filtering, grouping, or transformation has already been applied —
active,pending,byEmail,groupedByCustomer
A name like $data or $list throws both of those away.
Before
The names tell you it is "some collection of something", and that is about it.
<?php
function emailReminders(array $list, array $items, array $map): void
{
foreach ($list as $i) {
if (! $i['active']) {
continue;
}
foreach ($items as $row) {
if ($row['user_id'] !== $i['id']) {
continue;
}
if (! isset($map[$row['status']])) {
continue;
}
sendEmail($i['email'], $map[$row['status']]);
}
}
}
You can work it out, but you have to read the loop body to discover:
$listis users$itemsis invoices$mapis keyed by status, holding email templates- only active users get processed
- the loop variable
$icould mean anything
After
The names carry the meaning. The loop body just confirms what you already expect.
<?php
function emailOverdueRemindersToActiveUsers(
array $activeUsers,
array $overdueInvoices,
array $emailTemplatesByStatus
): void {
foreach ($activeUsers as $activeUser) {
foreach ($overdueInvoices as $overdueInvoice) {
if ($overdueInvoice['user_id'] !== $activeUser['id']) {
continue;
}
$template = $emailTemplatesByStatus[$overdueInvoice['status']] ?? null;
if ($template === null) {
continue;
}
sendEmail($activeUser['email'], $template);
}
}
}
Now the function signature alone tells you most of the story. The loop variables ($activeUser, $overdueInvoice) are singular forms of the collections they came from, so the relationship is obvious.
Why this version is better
- the qualifier (
active,overdue) is in the name, not buried in anif - you can tell a list from a keyed map without looking inside (
emailTemplatesByStatusis clearly keyed by status) - the loop variable echoes the collection name, so you never lose track of what you are iterating over
- the function signature is self-describing — no comments needed
Collection naming patterns
| Pattern | When to use | Example |
|---|---|---|
| Plural noun | a list of things | $users, $invoices, $tags |
| Adjective + plural | a filtered subset | $activeUsers, $overdueInvoices, $expiredTokens |
xxxBy<Key> |
a map keyed by something | $usersById, $ordersByCustomer, $templatesByStatus |
xxxFor<Context> |
a subset scoped to something | $invoicesForCustomer, $rolesForUser |
xxxIn<Range> |
scoped by time or range | $ordersInLastWeek, $loginsInToday |
groupedXxx |
grouped, usually nested | $groupedOrdersByMonth |
sortedXxx |
already sorted | $sortedInvoicesByDueDate |
recentXxx, topXxx |
already trimmed | $recentOrders, $topCustomers |
Singular vs plural
A collection is plural. The thing inside it is singular. Keep them in sync.
foreach ($activeUsers as $activeUser) { ... }
foreach ($overdueInvoices as $overdueInvoice) { ... }
foreach ($pendingApprovals as $pendingApproval) { ... }
Avoid foreach ($users as $u) or foreach ($items as $row). Two extra characters save the next reader a translation step.
Names to avoid
$list,$items,$rows,$results,$data— generic and forgettable$arr,$collection— describes the type, not the contents$userArray,$invoiceList— the type suffix adds noise; the plural already tells you it is a collection$userfor a list of users — singular for a plural is the most common naming bug in PHP code
A small collection smell test
Look at the name and ask:
- Can I tell what is in it without opening the function?
- Can I tell whether it has been filtered, sorted, or keyed?
- Does the loop variable name match the singular form?
If any answer is "no", the name is leaving work for the reader.
From Writing decent PHP — Naming variables, functions, classes, and files well