PHPStatic Properties & Methods

Static Properties & Methods

Every property and method you've written so far has belonged to a specific instance$product1->name and $product2->name are two separate pieces of storage, even though both objects come from the same Product class. static properties and methods flip that around: instead of belonging to an object, they belong to the class itself. There is exactly one copy, shared by every instance, and you can often use it without ever creating an object at all.

Declaring and accessing static members

You mark a property or method static, and you reach it with the scope resolution operator :: against the class name — ClassName::$property or ClassName::method() — never $this->. $this refers to a particular object, and a static member does not belong to any particular object.

A static helper method

PHP
<?php
class Product {
    public static function formatPrice(float $amount): string
    {
        return '$' . number_format($amount, 2);
    }
}

echo Product::formatPrice(19.9), "\n";
echo Product::formatPrice(1250), "\n";
$19.90
$1,250.00

formatPrice() doesn't read or write any particular product's data — it's a pure utility that happens to live inside the Product class because that's where it's conceptually at home. Calling it never required a new Product(...) at all.

Static properties are shared across every instance

This is the part that surprises people coming from instance properties: a static property is not "reset" per object. There is one storage location for the entire class, and every instance reads and writes the same value.

Counting every Product ever created

PHP
<?php
class Product {
    public static int $totalCreated = 0;

    public string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
        self::$totalCreated++;
    }
}

$p1 = new Product('Keyboard');
$p2 = new Product('Mouse');
$p3 = new Product('Monitor');

echo Product::$totalCreated, "\n";
3

Notice self::$totalCreated++ inside the constructor — from inside the class, you use self:: rather than the class name, which matters more once inheritance is involved (covered on the Inheritance page). From outside, Product::$totalCreated reads the one shared counter, no matter which instance last touched it.

Shared means shared — there is no per-object copy
If you expected `$p1` and `$p2` to each have their own `$totalCreated`, this is the pitfall to internalize now: a static property genuinely has a single value for the whole class. Setting it through one instance's context (`self::$totalCreated`) changes what *every* instance, and every future instance, will see. There is no way to make one object's static value diverge from another's — if you need per-object state, it has to be a regular (non-static) property.
A registry-style static property

Because a static property persists for the lifetime of the request and is reachable from anywhere the class is visible, it's a natural fit for a lightweight in-memory registry — for example, tracking every SKU that has been registered in a catalog without wiring a database call through every layer of the code.

A simple static catalog registry

PHP
<?php
class Catalog {
    private static array $skus = [];

    public static function register(string $sku): void
    {
        self::$skus[] = $sku;
    }

    public static function count(): int
    {
        return count(self::$skus);
    }
}

Catalog::register('SKU-KEYBOARD-001');
Catalog::register('SKU-MOUSE-002');

echo Catalog::count(), "\n";
2
When static helps, and when it hurts

Static members earn their keep for small, self-contained jobs: pure utility functions like Product::formatPrice() that don't depend on any object's state, simple counters, or a single shared registry/singleton where "exactly one" is actually the correct model. In those cases, static removes the ceremony of instantiating an object just to call one method.

The cost shows up in testing and flexibility. A static property is effectively global mutable state — any part of the codebase can change Catalog::$skus, and every other part will see that change immediately, which makes it hard to reason about and hard to reset between test cases. Static methods also can't be swapped out the way instance methods can (there's no straightforward way to "mock" Catalog::register() the way you can substitute an object that implements an interface). If a class's behavior needs to vary — say, a test double that fakes registration instead of really doing it — prefer an ordinary object passed around explicitly (dependency injection) over reaching for a static class.

  • ClassName::$property and ClassName::method() access static members from outside the class; self::$property and self::method() access them from inside.

  • A static property has exactly one value for the entire class — every instance reads and writes the same storage.

  • Good fits: pure utility functions, simple counters, small registries/singletons.

  • Poor fits: anything that needs to vary between tests or between callers — static state is hard to isolate and hard to mock.

Static methods can't use $this
Because a static method isn't called on a particular instance, it has no `$this` inside it. If a method needs `$this->someProperty`, it cannot be `static`.
Tip
Reach for `static` only when the data or behavior is genuinely class-wide, not object-specific. If you find yourself adding a static property just to avoid passing an object around, that's usually a sign the design wants dependency injection instead — it keeps the same functionality testable without hidden shared state.