0

$this — what it is and how it's resolved

Beginner5 min read·php-07-005
interview

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
<?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!)