Pure functions and side effects — functional PHP philosophy
Concept
A pure function has two properties: (1) given the same arguments, it always returns the same result, and (2) it has no side effects (doesn't modify external state, doesn't perform I/O, doesn't depend on mutable global state).
Side effects are any observable changes outside the function: writing to a file, modifying a database, updating $_SESSION or globals, printing output, modifying objects passed by reference, calling date() or rand() (which depend on external state).
Why pure functions matter in PHP:
- Testable without mocking: A pure function is trivially testable — call it, check the return value. No database setup, no fake filesystem, no clock mocking.
- Cacheable: Same inputs → same output means the result can be memoized without correctness concerns.
- Parallelizable: Pure functions have no shared state to coordinate.
- Composable: Pure functions chain without unexpected coupling.
PHP's functional tools (array_map, array_filter, array_reduce) naturally encourage pure functions because they operate on data and return results without mutation.
Realistic balance: Real applications must have side effects (database writes, emails, logs). The goal is not to eliminate side effects but to push them to the edges — keep business logic pure and side effects in dedicated, isolated places (repositories, service classes, command handlers).
Code Example
<?php
declare(strict_types=1);
// Pure functions — deterministic, no side effects
function add(int $a, int $b): int { return $a + $b; }
function double(int $n): int { return $n * 2; }
function slugify(string $title): string
{
return strtolower(preg_replace('/[^a-z0-9]+/i', '-', $title));
}
// Impure functions — have side effects
function impureDouble(int $n): int
{
echo "Doubling $n\n"; // side effect: output
return $n * 2;
}
function getAge(): int
{
return (int) date('Y') - 1990; // depends on current time — not deterministic
}
function saveUser(array $user): void
{
// database write — side effect
}
// Pipeline of pure functions
$prices = [10.5, 20.0, 5.25, 99.99];
$result = array_reduce(
array_filter(
array_map(fn($p) => round($p * 1.1, 2), $prices), // apply 10% tax
fn($p) => $p > 10, // filter < 10
),
fn($carry, $p) => $carry + $p, // sum
0
);
// Impure function with pure core
class OrderService
{
// Pure core — testable without DB
public function calculateTotal(array $items, float $taxRate): float
{
return array_reduce(
$items,
fn($total, $item) => $total + ($item['price'] * $item['qty']),
0.0
) * (1 + $taxRate);
}
// Impure shell — wraps pure logic with I/O
public function processOrder(int $orderId): void
{
$order = $this->repository->find($orderId); // I/O
$total = $this->calculateTotal($order->items, 0.20); // pure
$order->total = $total;
$this->repository->save($order); // I/O
}
}