PHP Development

A Comprehensive Exploration of Functional Programming (FP) in PHP

By the Domain India teamPublished 8 min read
Knowledge base article
Contents (9 sections)

PHP is best known for classes and plain procedural scripts, but modern PHP also supports a functional style: small pure functions, closures passed around as values, immutable objects and pipelines of transformations. You don't need to rewrite your application to benefit. Used in the right places, these habits make PHP code easier to test and reason about. This guide covers the core ideas and the PHP 8 features that support them.

Key takeaways

Functional programming in PHP means writing pure functions, avoiding shared mutable state, and treating functions as values you can pass, return and combine. Modern PHP supports this well: arrow functions, first-class callable syntax (strlen(...)), readonly properties and classes, array_map/array_filter/array_reduce, generators, and the pipe operator |> from PHP 8.5. Mix it with object-oriented code where it helps, rather than forcing everything into one style.

1. The core ideas

Pure functions
The same input always gives the same output, and the function changes nothing outside itself: no globals, no database writes, no echo.
Immutability
Instead of changing a value, create a new one with the change applied. Old values stay valid.
Functions as values
Functions can be stored in variables, passed as arguments and returned from other functions. A function that takes or returns a function is a higher-order function.
Composition
Build bigger behaviour by chaining small functions, each doing one transformation.

Real applications must still read requests, write to databases and send email. The functional approach is to keep those side effects at the edges and keep the core logic pure.

2. Pure functions, typed

Declare strict types and type every parameter and return value. It documents intent and catches mistakes early:

php
<?php
declare(strict_types=1);

function priceWithGst(float $net, float $rate = 0.18): float
{
    return round($net * (1 + $rate), 2);
}

echo priceWithGst(100.0); // 118

This function is pure: it depends only on its arguments. Compare an impure version that reads a global $gstRate or looks up a rate in the database. It is harder to test because the result depends on something outside the call.

3. Closures, arrow functions and first-class callables

PHP has three convenient ways to treat functions as values.

php
<?php
// Anonymous function (closure); `use` captures variables by value
$discount = 0.1;
$applyDiscount = function (float $price) use ($discount): float {
    return $price * (1 - $discount);
};

// Arrow function (PHP 7.4+): one expression, captures outer variables automatically
$applyDiscount = fn(float $price): float => $price * (1 - $discount);

// First-class callable syntax (PHP 8.1+): a Closure from any function or method
$len   = strlen(...);
$upper = strtoupper(...);
echo $len('Domain'); // 6

A higher-order function returns or accepts a function:

php
<?php
function multiplier(int $factor): Closure
{
    return fn(int $n): int => $n * $factor;
}

$triple = multiplier(3);
echo $triple(7); // 21

Prefer first-class callables such as strtoupper(...) or $this->format(...) over string callables like 'strtoupper'. They are checked when created, and static analysis tools and IDEs understand them.

4. Immutability with readonly

PHP 8.1 added readonly properties and PHP 8.2 added readonly classes. A readonly property can be set once, inside the class, and never changed afterwards:

php
<?php
final readonly class Money
{
    public function __construct(
        public int $paise,
        public string $currency = 'INR',
    ) {}

    public function add(Money $other): self
    {
        return new self($this->paise + $other->paise, $this->currency);
    }
}

$a = new Money(10000);
$b = $a->add(new Money(2500));
// $a is still 10000 paise; $b is 12500

Methods like add() or withCurrency() return a new object instead of changing the current one. This avoids a whole class of bugs where one part of the code changes an object another part is still using.

Arrays are already copied on write

PHP arrays behave like values: passing one to a function and changing it inside doesn't change the caller's copy, unless you pass it by reference with &. Avoid & parameters in functional code, and you get immutable-style arrays for free.

5. map, filter and reduce

The built-in array functions are the everyday tools of functional PHP:

php
<?php
$orders = [
    ['id' => 1, 'total' => 1200, 'paid' => true],
    ['id' => 2, 'total' => 450,  'paid' => false],
    ['id' => 3, 'total' => 3000, 'paid' => true],
];

$paid   = array_filter($orders, fn(array $o): bool => $o['paid']);
$totals = array_map(fn(array $o): int => $o['total'], $paid);
$sum    = array_reduce($totals, fn(int $carry, int $t): int => $carry + $t, 0);

echo $sum; // 4200

Watch three details:

  • array_filter() keeps the original keys. Wrap the result in array_values() if you need a list numbered from 0, for example before json_encode().
  • Always give array_reduce() an initial value (the 0 above). Without one it starts with null.
  • array_map() passes values only. Use array_filter($arr, $fn, ARRAY_FILTER_USE_KEY) or ARRAY_FILTER_USE_BOTH when you need keys.

PHP 8.4 added array_find(), array_find_key(), array_any() and array_all(), which replace many hand-written loops:

php
<?php
$hasUnpaid = array_any($orders, fn(array $o): bool => !$o['paid']);
$firstBig  = array_find($orders, fn(array $o): bool => $o['total'] > 1000);

6. Composition and the pipe operator

Composition chains functions so that the output of one becomes the input of the next. Before PHP 8.5 you would write a small helper:

php
<?php
function pipe(callable ...$fns): Closure
{
    return fn($x) => array_reduce($fns, fn($acc, $f) => $f($acc), $x);
}

$slugify = pipe(
    trim(...),
    strtolower(...),
    fn(string $s): string => preg_replace('/[^a-z0-9]+/', '-', $s),
    fn(string $s): string => trim($s, '-'),
);

echo $slugify('  Hello World, PHP! '); // hello-world-php

PHP 8.5 adds the pipe operator |>, which passes the value on the left into the callable on the right:

php
<?php
$slug = '  Hello World, PHP! '
    |> trim(...)
    |> strtolower(...)
    |> (fn(string $s): string => preg_replace('/[^a-z0-9]+/', '-', $s))
    |> (fn(string $s): string => trim($s, '-'));

Each step must be a callable that takes one argument. Arrow functions need parentheses in a pipe chain, as shown above. If your server runs an older PHP version, use the pipe() helper instead.

7. Lazy sequences with generators

Generators produce values one at a time, so you can process a large file or result set without loading it all into memory:

php
<?php
function readLines(string $path): Generator
{
    $h = fopen($path, 'r');
    try {
        while (($line = fgets($h)) !== false) {
            yield rtrim($line, "\n");
        }
    } finally {
        fclose($h);
    }
}

function filterLines(iterable $lines, callable $keep): Generator
{
    foreach ($lines as $line) {
        if ($keep($line)) {
            yield $line;
        }
    }
}

foreach (filterLines(readLines('access.log'), fn($l) => str_contains($l, ' 500 ')) as $line) {
    echo $line, PHP_EOL;
}

8. Libraries, trade-offs and when to use FP

Libraries add fluent, chainable helpers. The best known is Laravel's Collections, which is also available on its own as illuminate/collections. Install libraries with Composer on your own computer or in CI.

Good for
  • Business rules and calculations that must be easy to unit test
  • Data transformation: API responses, CSV imports, reports
  • Value objects such as Money, Email and DateRange
  • Validation and formatting pipelines
Watch out for
  • Code that is mostly I/O: forcing it into pure functions adds ceremony without benefit
  • Deeply nested closures, which are harder to read than a plain loop
  • Very hot loops, where a simple foreach can be faster than several array_* passes
  • Teams new to the style: introduce it gradually, with clear names

The best PHP code usually mixes styles: classes for structure, pure functions and immutable value objects for logic, and side effects at the edges. For the other paradigms, see asynchronous programming in PHP and event-driven programming in PHP.

9. Running this on Domain India

Everything above is plain PHP and runs on shared hosting, provided your account uses a PHP version that has the features you use:

  • cPanel: PHP versions from 5.1 up to 8.5 are selectable per account, and new accounts default to PHP 8.3. The pipe operator needs 8.5; array_find() and friends need 8.4.
  • DirectAdmin: the newest version offered is PHP 8.3, so use the pipe() helper and your own loops instead of 8.4 and 8.5 features.
  • Composer isn't pre-installed on shared hosting, but over jailed SSH you can run php composer.phar install, adding --no-scripts if it stops with a proc_open message. Or run composer install on your computer or in CI and upload the project with its vendor/ folder. See PHP disabled functions on shared hosting for what is switched off.

For your current versions, see software versions on Domain India hosting. If you need Composer scripts on the server, a VPS gives you full control.

cPanel Starter
₹125/mo + GST
  • 25 GB NVMe SSD Storage
  • 50 GB Monthly Bandwidth
  • 1 Website
  • 10 Email Accounts
See plan details

The price on the card is a live Domain India list price and excludes 18% GST.

Is PHP a functional programming language?

PHP is multi-paradigm. It is mainly procedural and object-oriented, but it supports functional techniques: closures, arrow functions, first-class callables, higher-order functions, readonly objects, generators and, from PHP 8.5, the pipe operator.

What is a pure function in PHP?

A pure function always returns the same result for the same arguments and has no side effects: it doesn't change globals, write to files or databases, or print output.

What is the difference between a closure and an arrow function in PHP?

A closure (function () use ($x) { … }) can hold several statements and captures only the variables listed in use. An arrow function (fn($y) => …) is a single expression and captures outer variables automatically, by value.

Which PHP version do I need for the pipe operator?

The pipe operator |> was added in PHP 8.5. On older versions, write a small pipe() helper that applies a list of callables in order with array_reduce.

Why does array_filter leave gaps in my array keys?

array_filter keeps the original keys of the elements it keeps. Wrap the result in array_values() to renumber it from 0.

Can I use Composer libraries such as Laravel Collections on shared hosting?

Yes. Composer isn't pre-installed on Domain India shared hosting, but you can run composer.phar over jailed SSH (on request), or install dependencies on your computer or in CI and upload the project together with its vendor folder.

Ready to try it? Check your PHP version in your control panel, then read the asynchronous PHP guide next, or compare plans on cPanel hosting.

Host modern PHP

Choose your PHP version per account, up to PHP 8.5 on our cPanel servers, with free SSL and weekly backups.

See cPanel plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app
Functional Programming in PHP 8 Guide | Domain India