Originally published on decentphp.dev — part of the Writing decent PHP course. View this lesson on the original site.
Rules and invariants you can rely on
An invariant is a rule that must always be true.
If those rules are vague or spread around the codebase, bugs slip through and reviews become guesswork.
Before
<?php
function updateInvoice(array $input, $invoiceRepository): void
{
$status = $input['status'] ?? '';
$totalInPence = (int) ($input['total_in_pence'] ?? 0);
// accepts any status string and allows negative totals
$invoiceRepository->save([
'id' => (int) $input['id'],
'status' => $status,
'total_in_pence' => $totalInPence,
]);
}
After
<?php
const ALLOWED_INVOICE_STATUSES = ['draft', 'sent', 'paid', 'void'];
function updateInvoice(array $input, $invoiceRepository): void
{
$status = (string) ($input['status'] ?? '');
if (! in_array($status, ALLOWED_INVOICE_STATUSES, true)) {
return;
}
$totalInPence = (int) ($input['total_in_pence'] ?? 0);
if ($totalInPence < 0) {
return;
}
$invoiceRepository->save([
'id' => (int) $input['id'],
'status' => $status,
'total_in_pence' => $totalInPence,
]);
}
Why this version is better
- the allowed rules are explicit and easy to find
- impossible data is rejected early instead of persisted
- other developers can rely on the saved data shape with less defensive code
From Writing decent PHP — What decent PHP looks like