Closures

JA JASWANT Updated 30 Oct 2026
7 min read ·Lesson 193 of 209

What Is a Closure?

In simple terms: A closure is a function that remembers values from the place where it was created. Even after that surrounding code has finished running, the closure still has access to the values it captured. Technically: In PHP, every anonymous function and arrow function is an instance of the built-in final class Closure. A closure can capture variables from its enclosing scope (explicitly with use for anonymous functions, automatically for arrow functions) and, when created inside a class, is automatically bound to $this. The Closure class also provides methods — bind(), bindTo(), call(), and fromCallable() — to control what a closure is bound to.

Why Closures Matter

Closures let you package behavior together with data:

  • A function that produces other, pre-configured functions (factories)
  • Callbacks that need extra information, such as a filter that knows the maximum price
  • Event listeners and middleware that remember configuration
  • Small pieces of private state without creating a class The basics of anonymous functions and use are covered in Anonymous Functions. This lesson focuses on what makes closures powerful: capturing state, binding, and practical patterns.

Closures Capture Their Environment

<?php
function makeGreeter(string $greeting): Closure
{
    return function (string $name) use ($greeting): string {
        return "$greeting, $name!";
    };
}
$hello = makeGreeter('Hello');
$vanakkam = makeGreeter('Vanakkam');
echo $hello('Asha') . "\n";
echo $vanakkam('Asha') . "\n";

Output

Hello, Asha!
Vanakkam, Asha!

How It Works

  1. Each call to makeGreeter() creates a new closure.
  2. The closure captures the current value of $greeting with use.
  3. When makeGreeter() returns, its local variables are gone — but each closure keeps its own captured copy.
  4. $hello and $vanakkam are two separate closures with different captured values. This is the essence of a closure: a function plus the variables it captured.

Closures With Private State

Capturing a variable by reference lets a closure keep state that changes between calls:

<?php
function makeCounter(int $start = 0): Closure
{
    $count = $start;
    return function () use (&$count): int {
        return ++$count;
    };
}
$visitors = makeCounter();
$downloads = makeCounter(100);
echo $visitors() . "\n";
echo $visitors() . "\n";
echo $downloads() . "\n";
echo $visitors() . "\n";

Output

1
2
101
3

How It Works

  • $count is a local variable of makeCounter(), captured by reference (&$count).
  • Each call to the returned closure increments that captured variable, so the value persists between calls.
  • $visitors and $downloads each have their own $count, because each makeCounter() call creates a new variable.
  • No code outside the closure can read or change $count directly — the state is private.

Practical Pattern: Memoization Wrapper

A closure can wrap another function and cache its results:

<?php
function memoize(callable $function): Closure
{
    $cache = [];
    return function (int $input) use (&$cache, $function): mixed {
        if (!array_key_exists($input, $cache)) {
            echo "(computing $input)\n";
            $cache[$input] = $function($input);
        }
        return $cache[$input];
    };
}
$square = memoize(fn(int $n): int => $n * $n);
echo $square(4) . "\n";
echo $square(4) . "\n";
echo $square(5) . "\n";

Output

(computing 4)
16
16
(computing 5)
25

How It Works

  1. memoize() receives a function and returns a new closure that captures both the function ($function, by value) and a cache array ($cache, by reference).
  2. The first call with 4 computes and stores the result; the second call returns the cached value without recomputing.
  3. The wrapped function never needs to know about caching. This is useful for expensive operations, such as reading configuration or calling a slow API with the same arguments during one request.

Closures Inside Classes: Automatic $this

When a closure is created inside a class method, it is automatically bound to the object, so it can use $this — including private properties:

<?php
class Cart
{
    private array $items = [];
    private float $discountPercent = 10;
    public function add(string $name, float $price): void
    {
        $this->items[] = ['name' => $name, 'price' => $price];
    }
    public function discountedLabels(): array
    {
        return array_map(function (array $item): string {
            $price = $item['price'] * (1 - $this->discountPercent / 100);
            return $item['name'] . ': ₹' . number_format($price, 2);
        }, $this->items);
    }
}
$cart = new Cart();
$cart->add('Desk Lamp', 1299);
$cart->add('Gel Pen', 20);
echo implode("\n", $cart->discountedLabels()) . "\n";

Output

Desk Lamp: ₹1,169.10
Gel Pen: ₹18.00

The callback passed to array_map() reads $this->discountPercent without any use clause, because closures created in a method are bound to the current object. Classes are covered in the PHP OOP section.

Static Closures

A closure declared static is not bound to $this:

$format = static fn(float $price): string => '₹' . number_format($price, 2);

Use static when a closure does not need the object. It documents that independence and prevents the closure from keeping the object alive in memory longer than necessary.


Binding Closures: call(), bind(), and bindTo()

A closure's binding — which object $this refers to and which class scope it runs in — can be changed. This is mainly used by frameworks and testing tools.

Closure::call() (PHP 7.0+)

call() temporarily binds a closure to an object and calls it immediately:

<?php
class Wallet
{
    private int $balance = 500;
}
$readBalance = function (): int {
    return $this->balance;
};
echo $readBalance->call(new Wallet()) . "\n";

Output

500

Bound to the Wallet object and running in its class scope, the closure can read the private $balance. This shows how binding works, but it also bypasses encapsulation — in application code, access private data through public methods instead.

bindTo() and Closure::bind()

bindTo() returns a new closure bound to a given object (and optionally a class scope), without calling it:

<?php
class Counter
{
    private int $value = 0;
}
$increment = function (int $by): int {
    return $this->value += $by;
};
$counter = new Counter();
$boundIncrement = $increment->bindTo($counter, Counter::class);
echo $boundIncrement(5) . "\n";
echo $boundIncrement(3) . "\n";

Output

5
8

Closure::bind($closure, $object, $scope) is the static equivalent of $closure->bindTo($object, $scope). These methods power features such as framework "macros" that add methods to existing classes.


Creating Closures From Existing Functions

Any callable can be turned into a Closure object:

TechniqueExampleSince
First-class callable syntaxstrtoupper(...)PHP 8.1
Closure::fromCallable()Closure::fromCallable('strtoupper')PHP 7.1
<?php
$upper = strtoupper(...);
$lower = Closure::fromCallable('strtolower');
echo $upper('php') . " / " . $lower('PHP') . "\n";
var_dump($upper instanceof Closure);

Output

PHP / php
bool(true)

First-class callable syntax is the modern form; see First-class Callable Syntax.


Practical Example: A Simple Event System

Closures are ideal for listeners: small pieces of behavior registered to run when something happens, each remembering its own configuration.

<?php
function dispatch(array $listeners, string $event, array $payload): void
{
    foreach ($listeners[$event] ?? [] as $listener) {
        echo $listener($payload) . "\n";
    }
}
$adminEmail = 'orders@gocourse.in';
$pointsPerHundred = 3;
$listeners = [
    'order.placed' => [
        function (array $order) use ($adminEmail): string {
            return "Email to $adminEmail: new order #{$order['id']}";
        },
        fn(array $order): string => sprintf(
            'Award %d points to %s',
            intdiv($order['total'], 100) * $pointsPerHundred,
            $order['customer'],
        ),
    ],
    'order.cancelled' => [
        fn(array $order): string => "Restock items from order #{$order['id']}",
    ],
];
dispatch($listeners, 'order.placed', ['id' => 501, 'customer' => 'Asha', 'total' => 1580]);
dispatch($listeners, 'order.cancelled', ['id' => 498, 'customer' => 'Bala', 'total' => 300]);
dispatch($listeners, 'order.refunded', ['id' => 470, 'customer' => 'Charu', 'total' => 900]);

Output

Email to orders@gocourse.in: new order #501
Award 45 points to Asha
Restock items from order #498

Step-by-Step Explanation

  1. $listeners maps event names to lists of closures.
  2. The first listener captures $adminEmail with use; the second (an arrow function) captures $pointsPerHundred automatically.
  3. dispatch() runs every listener registered for an event, passing the event data. Events without listeners (order.refunded) simply do nothing, thanks to ?? [].
  4. intdiv(1580, 100) * 3 awards 15 × 3 = 45 points. New behavior is added by registering another closure — dispatch() never changes. Frameworks such as Laravel and Symfony use the same idea in their event systems.

Common Mistakes

Capturing by Value When State Must Persist

Effect: A counter closure always returns the same number, because it works on a copy. Fix: Capture by reference (use (&$count)) for intentional shared state.

Capturing by Reference Unnecessarily

Effect: Outside code changes the variable later, unexpectedly changing the closure's behavior. Fix: Capture by value unless sharing is required.

Using $this in a Static Closure or Outside a Class

Effect: Error: Using $this when not in object context. Fix: Remove static, bind the closure, or pass the needed object as a parameter.

Using Binding to Bypass Encapsulation

Effect: Code depends on private internals that may change. Fix: Reserve call()/bind() for framework-level code and tests; use public methods in application code.


Best Practices

  • Use closures for small, configured behaviors: callbacks, factories, listeners, and wrappers.
  • Capture only what the closure needs, by value by default.
  • Declare closures static when they do not need $this.
  • Prefer arrow functions for single-expression closures and first-class callable syntax for wrapping existing functions.
  • Move complex logic from closures into named functions or classes so it can be tested and reused. Official reference: https://www.php.net/manual/en/class.closure.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PHP XML Parsers PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions
Ask about this post
AI Ask about this post

Ask questions about Closures and get answers drawn from it.

Signed-in readers only.