Closure::bind and Closure::bindTo — rebinding $this
Concept
Closure::bind() and Closure::bindTo() allow you to change a closure's bound object and scope, effectively transplanting the closure into a different class context. This is one of the most powerful — and most misunderstood — mechanisms in PHP, and it is the engine behind several of Laravel's most "magical" features.
Closure::bind(Closure $closure, ?object $newThis, string|object|null $newScope) is a static factory that returns a new closure with $this pointing to $newThis and the class scope set to $newScope. The scope controls which private and protected members are accessible. Closure::bindTo() is the instance equivalent: $closure->bindTo($newThis, $newScope). Both return a new Closure; the original is not modified.
The scope parameter is crucial. If you bind a closure to an object of class Foo but set scope to Bar, the closure sees Bar's private members, not Foo's. Setting scope to static preserves the scope of the outer context. If scope is null, private member access is disabled entirely even if the object's class has those members.
In Laravel's service container (Illuminate\Container\Container), Closure::bind() is used when resolving a binding that is a closure: the container calls $concrete->call($this) using the Container::call() method, which internally uses a ReflectionFunction to match parameters and then spreads resolved values. For macros (via the Macroable trait), Laravel stores closures and rebinds them to the calling object: Closure::bind($macro, $this, static::class). This is how Collection::macro('toUpper', fn() => $this->map(strtoupper(...))) lets $this inside the macro refer to the Collection instance.
Code Example
<?php
declare(strict_types=1);
class BankAccount
{
private float $balance = 1000.0;
private string $owner = 'Alice';
}
// Access private state from outside — for testing or meta-programming
$inspector = Closure::bind(
static function (BankAccount $account): string {
return "{$account->owner}: \${$account->balance}";
},
null, // no $this — static closure
BankAccount::class // scope grants private access
);
$account = new BankAccount();
echo $inspector($account) . PHP_EOL; // Alice: $1000
// Rebind $this — simulating Laravel's Macroable trait
class Collection
{
private array $items;
public function __construct(array $items)
{
$this->items = $items;
}
private static array $macros = [];
public static function macro(string $name, Closure $fn): void
{
static::$macros[$name] = $fn;
}
public function __call(string $name, array $args): mixed
{
if (!isset(static::$macros[$name])) {
throw new \BadMethodCallException("Method {$name} not found.");
}
// Bind the macro closure to this Collection instance
$macro = Closure::bind(static::$macros[$name], $this, static::class);
return $macro(...$args);
}
}
Collection::macro('sum', function (): float|int {
return array_sum($this->items); // $this = the Collection instance
});
$col = new Collection([1, 2, 3, 4, 5]);
echo $col->sum() . PHP_EOL; // 15
// bindTo — instance method equivalent
class Config
{
private array $data = ['debug' => true, 'version' => '1.0'];
}
$reader = function (string $key): mixed {
return $this->data[$key] ?? null;
};
$bound = $reader->bindTo(new Config(), Config::class);
var_dump($bound('debug')); // bool(true)
var_dump($bound('version')); // string(3) "1.0"Interview Q&A
Q: Explain how Laravel's Macroable trait uses Closure::bind() and why it passes static::class as the scope.
The Macroable trait stores registered macros as closures in a static $macros array. When __call or __callStatic is triggered, it retrieves the stored closure and calls Closure::bind($macro, $this, static::class). The third argument — static::class — sets the class scope to the class that __call ran on, not the class where Macroable is defined. This matters because with scope set to the concrete class, the bound closure can access that class's protected properties. If the scope were set to Macroable, the closure would be restricted to Macroable's protected/private namespace, which contains nothing useful. Using static::class makes the macro behave as if it were a real method of the called class, with full access to its protected internals.
Q: What is the difference between binding a closure with null as $newThis versus binding it to an object?
Passing null as $newThis creates a static closure (no bound object). Inside such a closure, $this is undefined and using it throws a fatal error. The closure can still access class members if the scope is set, but only static members and, in the case of static private members, those of the specified scope class. This is the correct form when you need private member access for introspection (testing, serialization, meta-programming) without mutating any object state. Binding to an actual object makes the closure a pseudo-method: $this points to the object, and both instance and static members of the scope are accessible.
Q: What security or architectural risks exist when using Closure::bind() to access private members?
Bypassing visibility modifiers via Closure::bind() breaks encapsulation. Private members exist to allow the class to enforce internal invariants without external interference. Accessing them directly in tests or utilities means you are testing implementation details rather than observable behaviour, making tests brittle. Modifying them externally can corrupt the object's internal state in ways the class's own methods cannot anticipate. In production application code, this pattern is almost never justified — if you need access to private state, redesign the API. The legitimate uses are: framework internals (Laravel's container, macro system), test utilities that need to inject state without adding test-only constructors, and serialization/deserialization frameworks where reconstructing private state is necessary for correct object hydration.