Lesson 65 of 100
Functions15 min5 checksPHP 8.5

Build Predictable Pure Functions

Learn how to write PHP functions that depend only on their arguments and return new values, so results stay predictable and easy to test.

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:

  1. The same arguments always produce the same return value.
  2. 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.

Example
<?php
function addTaxImpure(int $price): int
{
    static $calls = 0;
    $calls++;
    return $price + $calls;
}

echo addTaxImpure(100), "\n";
echo addTaxImpure(100), "\n";
Output
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.

Example
<?php
function addTax(int $priceCents, int $percent): int
{
    return $priceCents + intdiv($priceCents * $percent, 100);
}

echo addTax(2000, 8), "\n";
echo addTax(2000, 8), "\n";
Output
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.

Example
<?php
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";
Output
{"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, global and superglobals are hidden inputs or outputs.
  • Using floats for money and getting rounding differences. Integer cents with intdiv() truncate predictably.
  • Using echo inside 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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Transform inputs without shared state or I/O
  • Implement and test a deterministic PHP 8.4 function
Your task

Make applyDiscount() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.