Static Members and Class Constants

Most properties and methods belong to individual objects. Static members belong to the class itself and are used through the class name with :: — no object needed. Class constants attach fixed values to a class. Together they're used for factory methods, utility functions, counters and configuration.

Class constants

Declare constants inside a class with const, and read them with ClassName::NAME (or self::NAME inside the class). Since PHP 8.3 they can have types:

PHP
<?php

class Shipping
{
    public const float FREE_FROM = 50.0;
    public const float FEE = 4.99;
    private const array ZONES = ['EU', 'UK', 'US'];

    public static function cost(float $orderTotal, string $zone): float
    {
        if (!in_array($zone, self::ZONES, true)) {
            throw new DomainException("We don't ship to $zone");
        }
        return $orderTotal >= self::FREE_FROM ? 0.0 : self::FEE;
    }
}

echo Shipping::FREE_FROM, "\n";
echo Shipping::cost(30, 'EU'), " ", Shipping::cost(80, 'UK'), "\n";
Output
50
4.99 0

Constants can be public, protected or private, and can be marked final so subclasses can't override them.

Static methods

A static method doesn't need an object, so it can't use $this. It's the right choice for helpers that depend only on their arguments, and for factory methods:

PHP
<?php

class Str
{
    public static function slug(string $text): string
    {
        return trim(preg_replace('/[^a-z0-9]+/', '-', strtolower($text)), '-');
    }

    public static function limit(string $text, int $max): string
    {
        return mb_strlen($text) <= $max ? $text : mb_substr($text, 0, $max - 1) . '…';
    }
}

echo Str::slug("Hello World, Again!"), "\n";
echo Str::limit("A rather long sentence", 12), "\n";
Output
hello-world-again
A rather lo…

Many frameworks provide helper classes like this one (Laravel's Str class, for example).

Static properties

A static property is shared by all instances — there's only one copy, belonging to the class:

PHP
<?php

class Ticket
{
    private static int $nextNumber = 1000;
    public static int $issued = 0;

    public readonly int $number;

    public function __construct(public readonly string $holder)
    {
        $this->number = self::$nextNumber++;
        self::$issued++;
    }
}

$a = new Ticket("Ana");
$b = new Ticket("Ben");
$c = new Ticket("Cy");

echo "#{$a->number} {$a->holder}, #{$c->number} {$c->holder}\n";
echo "Issued: ", Ticket::$issued, "\n";
Output
#1000 Ana, #1002 Cy
Issued: 3

Note the syntax: static properties keep their dollar sign — self::$nextNumber, Ticket::$issued.

self, static and parent

Inside a class, self:: refers to the class where the code is written, static:: to the class that was actually called (relevant with inheritance), and parent:: to the parent class:

PHP
<?php

class Model
{
    public static function create(): static
    {
        return new static();
    }

    public static function describe(): string
    {
        return "self is " . self::class . ", static is " . static::class;
    }
}

class User extends Model {}

echo get_class(User::create()), "\n";
echo User::describe(), "\n";
Output
User
self is Model, static is User

This "late static binding" is what lets a base class's factory method create instances of whichever subclass it was called on. ClassName::class gives the fully qualified class name as a string.

Static state: use with care

Static properties are effectively global variables attached to a class: any code can change them, and they persist for the whole request. That makes code harder to test and reason about. Good uses are counters, caches and constants; for shared services, prefer passing objects through constructors (dependency injection, lesson 34).

Static methods are fine for pure helpers, but if a method needs configuration or collaborators, an instance method on an injected object is easier to swap and test.

Constants in expressions

Class constants can be built from other constants, and since PHP 8.3 you can fetch a constant whose name is in a variable:

PHP
<?php

class Limits
{
    public const int KB = 1024;
    public const int MB = self::KB * 1024;
    public const int MAX_UPLOAD = 5 * self::MB;
}

echo Limits::MAX_UPLOAD, "\n";

$unit = 'MB';
echo Limits::{$unit}, "\n";
echo constant(Limits::class . '::KB'), "\n";
Output
5242880
1048576
1024

Try it yourself

Give each Invoice an automatic number like INV-0001, and add a static method returning how many have been created.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Output
INV-0001 INV-0002 INV-0003
Created: 3, total value 495.5