$this — what it is and how it's resolved
Concept
$this in PHP is a reference to the current object instance. It is automatically available inside any non-static method. Understanding how $this is bound and resolved explains a surprising number of PHP behaviors.
How $this is passed: The engine implicitly passes the calling object to every instance method as a hidden parameter. $obj->method() is conceptually equivalent to method($obj) with $obj becoming $this inside. $this cannot be assigned to ($this = $other throws an error).
$this in closures: Closures defined inside a class method automatically capture $this from the enclosing method. You can also explicitly bind a closure to a different object using Closure::bind() or Closure::bindTo() — this is how Laravel macros work: they bind a user-defined closure to the instance of the class being macrod.
$this in static methods: Static methods have no $this. Calling $this inside a static method is a fatal error. This is why static factory methods must use new self() or new static() rather than $this.
self:: vs static:: vs $this->: self:: refers to the class where the method is defined (compile-time). static:: refers to the class that was actually called (late static binding, runtime). $this-> dispatches instance method calls dynamically, using the actual class of the object.
Code Example
<?php
declare(strict_types=1);
class Counter
{
private int $count = 0;
public function increment(): static // fluent: return $this
{
$this->count++;
return $this; // $this returns the current object → method chaining
}
public function add(int $n): static
{
$this->count += $n;
return $this;
}
public function get(): int
{
return $this->count;
}
}
$result = (new Counter())->increment()->increment()->add(10)->get(); // 12
// $this in closures — automatic capture
class EventEmitter
{
private array $listeners = [];
public function on(string $event, callable $fn): void
{
$this->listeners[$event][] = $fn;
}
public function emit(string $event, mixed $data): void
{
$emit = function(mixed $d) use ($event): void {
foreach ($this->listeners[$event] ?? [] as $fn) { // $this auto-captured
$fn($d);
}
};
$emit($data);
}
}
// Closure::bind — bind closure to a different object (Laravel macros)
class Query
{
private array $wheres = [];
private string $table = '';
}
$macro = function(string $table): static {
$this->table = $table; // $this will be a Query instance
return $this;
};
$bound = Closure::bind($macro, new Query(), Query::class);
// Now $bound() operates as if it were a method of Query
// self vs static
class Base
{
public static function create(): static // returns actual called class
{
return new static(); // late static binding
}
public static function createSelf(): self
{
return new self(); // always creates Base, even when called from Child
}
}
class Child extends Base {}
$obj = Child::create(); // returns Child instance
$obj = Child::createSelf(); // returns Base instance (probably wrong!)