01 · THE IDEAREAD → TRY → LEARN
What makes a function pure?
A user-defined function is declared with the function keyword, a name, a parameter list and a body. PHP does not care whether that body is tidy or messy. You decide that. A pure function follows two rules:
- The same arguments always produce the same return value.
- Calling it changes nothing outside itself and does no I/O. It does not echo, write files, touch a database, or alter globals.
The second rule is often called having no side effects. Together the rules mean you can read a call such as addTax(2000, 8) and know the result without knowing what ran earlier.
Spotting an impure function
The next function remembers how many times it has been called, using a static variable. Its answer depends on hidden history, not only on $price.
function addTaxImpure(int $price): int
{
static $calls = 0;
$calls++;
return $price + $calls;
}
echo addTaxImpure(100), "\n";
echo addTaxImpure(100), "\n";101 102
The same input gives 101 and then 102. Testing this means arranging call order, which is a warning sign. Other hidden inputs work the same way: global variables, the current time, random numbers, and reading a file.
The pure version
Pass everything the function needs as parameters and return the result. Here the tax rate is an argument, and money is kept in integer cents so no floating-point rounding appears.
function addTax(int $priceCents, int $percent): int
{
return $priceCents + intdiv($priceCents * $percent, 100);
}
echo addTax(2000, 8), "\n";
echo addTax(2000, 8), "\n";2160 2160
Both calls print 2160. Notice the type declarations. They narrow what the function accepts and make its contract clearer, which supports predictability.
Arrays: return a new value
PHP passes arrays by value, so changing an array parameter inside a function changes only the function's own copy. That is convenient for pure code: modify the copy and return it, leaving the caller's data untouched.
function withLabel(array $item, string $label): array
{
$item['label'] = $label;
return $item;
}
$original = ['id' => 7];
$updated = withLabel($original, 'sale');
echo json_encode($original, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode($updated, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"id":7}
{"id":7,"label":"sale"}
The original still holds only id, while the returned array has both keys. Be careful with two things that break this: reference parameters (&$item) and objects. An object handle lets a function change the caller's object, so treat objects as shared state unless they are immutable.
Why bother?
- Testing is simple. Supply arguments, compare the return value. No setup, no cleanup.
- Reasoning is local. You can understand a function by reading it.
- Reuse is safe. Calling it twice, in any order, cannot cause surprises.
Purity is not an all-or-nothing demand. Real programs must read input and print output. A good pattern is a thin outer layer that does the I/O and passes plain values into pure functions that do the calculating.
Common pitfalls
- Forgetting that
static,globaland superglobals are hidden inputs or outputs. - Using floats for money and getting rounding differences. Integer cents with
intdiv()truncate predictably. - Using
echoinside a function that should just return a value. - Mutating an object that was passed in.
Exercise contract
Write applyDiscount(array $cents, int $percent): array. The first argument is a list of integer prices in cents. The second is a whole-number discount from 0 to 100. Return a new list in the same order where each price is intdiv($price * (100 - $percent), 100), so fractions of a cent are dropped. An empty list returns an empty list, a discount of 0 leaves prices unchanged, and 100 makes every price 0. Return the array and do not print anything. The function must give the same answer however many times it is called.
Functions · Arrays · Types
- Transform inputs without shared state or I/O
- Implement and test a deterministic PHP 8.4 function