skip to content

Static Members & Late Binding

Static properties, static methods and class constants belong to the class, and static:: resolves to the called class where self:: does not. Interviewers use late static binding to test resolution.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In PHP, what does declaring a method or property static change, and why can a static method not use $this?

level: juniorimportance: must knowfreq 68%

answer

  1. belongs to the class, not an object
  2. reached with ClassName:: or self::
  3. no instance, so no $this
  4. non-static called statically: Error since 8.0
  5. -> on a static property: notice

basics

~20 s

A static method or property belongs to the class, not to an object: it is reached with ClassName:: or self:: and holds one value per class. A static method runs without an instance, so $this does not exist inside it.

solid answer

~40 s

Declaring a member `static` attaches it to the **class**, not to instances. A static property is one slot shared by every caller in the request, read and written with `Counter::$total` or `self::$total`, never with `$obj->total`. A static method is called with `Counter::reset()` and runs with no object bound, so `$this` is undefined inside it; touching it throws `Error: Using $this when not in object context`. The reverse direction is also strict: since PHP 8.0, calling a non-static method as `Foo::bar()` from outside the class throws `Error` instead of a deprecation. You can still call a static method on an instance (`$obj->reset()`), which quietly ignores the object. Static state is hidden global state for the rest of the request, so keep it for pure helpers, named constructors and deliberate per-class caches.

code

php · 23 lines
php
<?php
declare(strict_types=1);

final class Counter
{
    private static int $total = 0;

    public static function hit(): int
    {
        return ++self::$total;
    }

    public function describe(): string
    {
        return 'hits so far: ' . self::$total;
    }
}

Counter::hit();
Counter::hit();
$c = new Counter();
echo $c->describe(), PHP_EOL; // hits so far: 2
echo $c->hit(), PHP_EOL;      // 3 (a static method called through an object)

go deeper

for a junior

Recall that static members belong to the class, are reached with ::, and that a static method has no $this.

for a middle

Explain the Error messages for both mismatches in PHP 8 and why $obj->staticProp does not read the static slot.

for a senior

Show judgement about static state as hidden global state: per-request under FPM, process-wide in CLI workers and test runs.

for a principal

Weigh static helpers and named constructors against injected services in a codebase's conventions, and set rules for when static state is acceptable.

## What `static` means on a class member In PHP a class can hold two kinds of members: - **Instance members** belong to one object. Every `new Invoice()` gets its own copy of each instance property, and an instance method runs with `$this` bound to the object it was called on. - **Static members** belong to the **class**. There is one copy of a static property for the class, whatever number of objects exist (including zero), and a static method runs with no object at all. The keyword `static` is reused elsewhere in PHP (static local variables, static closures, and `static::` for late static binding). On a method or property declaration it means only this: *attach the member to the class*. ## How you reach static members Static members are addressed with the **scope resolution operator** `::`, not the object operator `->`: | Access | From outside | From inside the class | |---|---|---| | static property | `Counter::$total` | `self::$total` or `static::$total` | | static method | `Counter::reset()` | `self::reset()` or `static::reset()` | | instance property | `$c->name` | `$this->name` | | instance method | `$c->run()` | `$this->run()` | The class can also come from a string variable: `$cls = 'Counter'; $cls::reset();` works for methods, properties and constants, but the variable cannot hold the keywords `self`, `parent` or `static`. Two mismatches behave differently: 1. **A static method called through an object** (`$counter->reset()` or `$counter::reset()`) is allowed. PHP simply resolves the class of the object and calls the static method; no instance is passed in. 2. **A static property read through `->`** (`$counter->total`) is *not* the static property. PHP emits `Notice: Accessing static property Counter::$total as non static` and then treats it as an undefined instance property, so you get a warning and `null`. ## Why `$this` is missing in a static method `$this` is the object a method was invoked on. A static method is invoked on a class, so there is no object to bind. Referring to `$this` inside one throws `Error` with the message `Using $this when not in object context` when that line runs. A static method that needs object state must receive the object as a parameter, or the method should not be static. The opposite mistake, calling an **instance** method statically from outside the class (`Invoice::total()` where `total()` is not declared `static`), throws `Error: Non-static method Invoice::total() cannot be called statically`. Before PHP 8.0 this was only an `E_DEPRECATED` notice and the method ran with `$this` undefined, which is why old code sometimes "worked". Inside an instance method, `parent::total()` or `self::total()` on a non-static method is different: it is an ordinary instance call that keeps the current `$this`. ## How long static state lives A static property keeps its value until the current script ends. Under PHP-FPM or any web SAPI that means **one request**: the next request starts with the declared default again, because PHP does not keep user objects or variables between requests. In a CLI script, including a worker that loops over jobs, the value lives as long as the process does. That is useful for a deliberate per-class cache and dangerous for anything that should be per-job or per-test. ## When to use static members Static members are reasonable for: - **Named constructors** such as `Money::fromCents(int $cents): static` that build and return an instance. - **Pure helpers** that need no state and belong with a class for discoverability. - **Deliberate per-class caches or registries**, with a clear reset path for tests and workers. They are a poor fit for anything you might need to substitute: a static call such as `Mailer::send()` is hard-wired to one class, cannot be injected or replaced with a test double, and hides a dependency the constructor signature does not show. That is why modern PHP code prefers injected services and keeps static members small. ## Summary - `static` members belong to the class and are reached with `::`. - There is no `$this` in a static method, and using it throws `Error`. - Calling a non-static method statically from outside throws `Error` since PHP 8.0. - A static property lasts for the request under FPM and for the whole process in the CLI.

  • In PHP, can a static method be called through an object, and what happens to the object?
    Yes. `$obj->helper()` and `$obj::helper()` both call a static method: PHP looks up the object's class and invokes the method with no `$this` bound. The object is used only to find the class, and the call runs without a notice.
  • In PHP, why are static method calls such as Mailer::send() hard to test?
    The call names a concrete class at the call site, so nothing can swap in a test double, and any static state it keeps survives from one test to the next inside the same process. An injected instance behind an interface can be replaced per test and has no shared hidden state.
  • Under PHP-FPM, does a value written to a static property in one request appear in the next?
    No. PHP tears down user state at the end of each request, so the next request on the same FPM worker starts with the property's declared default. Only a long-running CLI process keeps static values across units of work.

saying these in an interview costs you the question

  • Says $this in a static method refers to the class
  • Claims static properties persist between FPM requests
  • Thinks calling an instance method statically only raises a deprecation in PHP 8
  • Reads a static property with $obj->prop and expects its value
  • Uses static methods for every service, making them impossible to replace in tests
open as a page

In PHP, what is the difference between self:: and static:: inside a parent class method, and what is late static binding?

level: middleimportance: must knowfreq 74%

basics

~20 s

self:: always means the class where the method is written; static:: means the class the call was actually made on, resolved at runtime. That runtime lookup is late static binding, and it lets a parent's method reach a child's override.

open as a page

In PHP 8.3 and later, what do typed class constants add, and when does static::NAME instead of self::NAME matter?

level: middleimportance: should knowfreq 38%

basics

~20 s

Since PHP 8.3 a class constant can declare a type, and a subclass override must keep a compatible type. self::NAME reads the constant of the class the code is in; static::NAME reads the called class's value, so a subclass override takes effect.

open as a page

In PHP, when a subclass does not redeclare a parent's static property, do the parent and subclass share one value, and why does that matter?

level: middleimportance: should knowfreq 30%

basics

~20 s

Yes. An inherited static property that the subclass does not redeclare is the same storage as the parent's, so writes through Child::$x change Parent::$x too. Redeclare it in the subclass, or key an array by static::class, to get per-class values.

open as a page

In a PHP model base class, how do you write a static factory such as User::create() that returns the subclass, and what can break it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Write create() as return new static(...) with a : static return type, so User::create() builds a User. Guard the constructor signature (final, abstract or interface-declared), because a subclass constructor with different parameters makes new static throw.

open as a page