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

  1. Identify hidden dependencies (global, $_ENV, date(), external helpers).
  2. Move those dependencies to explicit inputs.
  3. 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