Originally published on decentphp.dev — part of the Writing decent PHP course. View this lesson on the original site.
Hidden dependencies break local reasoning
Local reasoning means a developer can read one function and understand what it does.
That falls apart when behaviour depends on globals, environment values, and helper functions outside the function boundary.
flowchart TB
subgraph before [Before — hidden]
g[global / env] -.-> f[sendDigest...]
h[date / helpers] -.-> f
end
subgraph after [After — everything visible]
a[User + flags + time + mailer] --> f2[sendDigest...]
end
Before
<?php
function sendDigest(array $user): void
{
global $mailer;
if (($user['email'] ?? '') === '') {
return;
}
if (($_ENV['MAIL_ENABLED'] ?? '0') !== '1') {
return;
}
if (date('H') < 8) {
return;
}
if (currentUserTimezone() !== 'UTC') {
return;
}
$mailer->send($user['email'], 'Daily digest');
}
Walkthrough
- Identify hidden dependencies (
global,$_ENV,date(), external helpers). - Move those dependencies to explicit inputs.
- Keep behaviour the same, but make the function understandable in one place.
After
<?php
function sendDigest(
array $user,
bool $mailEnabled,
int $hourOfDay,
string $timezone,
$mailer
): void {
$email = (string) ($user['email'] ?? '');
if ($email === '') {
return;
}
if ($mailEnabled !== true) {
return;
}
if ($hourOfDay < 8) {
return;
}
if ($timezone !== 'UTC') {
return;
}
$mailer->send($email, 'Daily digest');
}
Why this works better
- the function boundary now shows everything it depends on
- behaviour is still the same, but surprises are reduced
- tests become easier because inputs are explicit and controllable
From Writing decent PHP — What decent PHP looks like