Статические методы и свойства (static) в PHP: подробный разбор

Раздел: Разработка на PHP -> ООП

Статические методы и свойства в PHP: ключевые концепции

Статические методы и свойства (объявленные с ключевым словом static) принадлежат классу, а не отдельным экземплярам. Они доступны без создания объекта и используются для общих данных или действий, не зависящих от конкретного состояния объекта.

Основной способ объявления и использования

Для объявления статического члена перед public, protected или private ставится ключевое слово static. Обращение к нему выполняется через имя класса и двойное двоеточие ::.

class Counter {
    public static int $count = 0;

    public static function increment(): void {
        self::$count++;
    }
}

Counter::increment();
echo Counter::$count; // 1

Внутри класса для доступа к статическим членам используется self:: (для текущего класса) или static:: (для позднего статического связывания). Внутри статического метода нет псевдопеременной $this, так как метод вызывается не в контексте объекта.

Типичная ошибка:

Попытка использовать $this в статическом методе приведёт к фатальной ошибке, так как $this недоступен в статическом контексте.

class Example {
    public static function test() {
        echo $this->property; // Fatal error
    }
}

Как обратиться к статическому свойству внутри класса?

Внутри нестатического метода можно обратиться к статическому свойству через self::$property или static::$property. При наследовании разница важна:

class ParentClass {
    public static $value = 'parent';
}

class ChildClass extends ParentClass {
    public static $value = 'child';
}

class Test {
    public function showSelf() {
        echo self::$value;  // всегда 'parent'
    }
    public function showStatic() {
        echo static::$value; // зависит от вызванного класса
    }
}

Проблема: если в дочернем классе переопределить статическое свойство, а в родительском методе использовать self::, то будет вызвано свойство родителя, что не всегда ожидается. Решение - использовать static::.

Как подсчитать количество созданных объектов с помощью статического свойства?

Статическое свойство может хранить общий счётчик, который увеличивается в конструкторе:

class User {
    public static int $count = 0;
    public string $name;

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

$u1 = new User('Alice');
$u2 = new User('Bob');
echo User::$count; // 2

Ошибка: забыть объявить свойство как static или попытаться обратиться через $this->. В конструкторе нужно использовать self::$count.

Как реализовать шаблон Singleton с помощью статических членов?

Singleton гарантирует единственный экземпляр класса. Обычно используется приватное статическое свойство для хранения экземпляра и публичный статический метод для его получения:

class Singleton {
    private static ?Singleton $instance = null;

    private function __construct() {} // запрет new
    private function __clone() {}    // запрет клонирования

    public static function getInstance(): Singleton {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

$obj1 = Singleton::getInstance();
$obj2 = Singleton::getInstance();
var_dump($obj1 === $obj2); // true

Проблемы: Singleton сложно тестировать, он вводит глобальное состояние. При сериализации/десериализации может нарушиться. Для защиты от сериализации можно объявить метод __wakeup() приватным или выбросить исключение.

Как использовать статический метод как фабрику?

Статический фабричный метод создаёт и возвращает объект, часто с предварительной конфигурацией:

class Product {
    private string $name;
    public function __construct(string $name) {
        $this->name = $name;
    }

    public static function fromArray(array $data): self {
        return new self($data['name'] ?? 'default');
    }
}

$product = Product::fromArray(['name' => 'Laptop']);
echo $product->getName(); // Laptop

Ошибка: если в фабричном методе используется new self(), то при наследовании будет создан объект родительского класса. Для создания объекта вызываемого класса используют new static() (позднее статическое связывание).

Как позднее статическое связывание влияет на наследование?

Использование static:: позволяет переопределить статические методы и свойства в потомках. Пример:

class Animal {
    public static function who() {
        echo 'Animal';
    }
    public static function callSelf() {
        self::who();
    }
    public static function callStatic() {
        static::who();
    }
}

class Dog extends Animal {
    public static function who() {
        echo 'Dog';
    }
}

Dog::callSelf();   // Animal (self привязан к классу, где определён метод)
Dog::callStatic(); // Dog   (static разрешается в контексте вызова)

Распространённое заблуждение: разработчики ожидают, что self будет вести себя как static. Для корректного переопределения нужно использовать static::.

Расширенные примеры статических методов и свойств

1. Позднее статическое связывание в иерархии классов

Пример
abstract class Base {
    protected static string $table = 'base_table';

    public static function getTable(): string {
        return static::$table; // разрешается в контексте вызывающего класса
    }
}

class User extends Base {
    protected static string $table = 'users';
}

class Admin extends User {
    protected static string $table = 'admins';
}

echo Base::getTable();  // base_table
echo User::getTable();  // users
echo Admin::getTable(); // admins
base_table
users
admins

2. Статический метод как callable для функций высшего порядка

Пример
class StringHelper {
    public static function uppercase(string $s): string {
        return strtoupper($s);
    }
}

$names = ['alice', 'bob', 'charlie'];
$uppercased = array_map(['StringHelper', 'uppercase'], $names);
print_r($uppercased);
Array
(
    [0] => ALICE
    [1] => BOB
    [2] => CHARLIE
)

3. Статическое свойство в трейте и его переопределение

Пример
trait CounterTrait {
    public static int $count = 0;

    public function increment() {
        static::$count++;
    }
}

class A {
    use CounterTrait;
}

class B {
    use CounterTrait;
}

// У каждого класса своё статическое свойство, несмотря на трейт
A::$count = 10;
B::$count = 100;
echo A::$count; // 10
echo B::$count; // 100
10
100

4. Кэширование результата с помощью статического свойства

Пример
class ExpensiveCalculator {
    private static array $cache = [];

    public static function compute(int $n): int {
        if (!isset(self::$cache[$n])) {
            // Симуляция долгого вычисления
            sleep(1);
            self::$cache[$n] = $n * $n;
        }
        return self::$cache[$n];
    }
}

echo ExpensiveCalculator::compute(5) . "\n"; // ждёт 1 секунду
echo ExpensiveCalculator::compute(5) . "\n"; // мгновенно из кэша
25
25

5. Использование new static() в статическом фабричном методе

Пример
class ParentClass {
    public static function create(): self {
        return new static(); // создаёт экземпляр того класса, который вызван
    }
}

class ChildClass extends ParentClass {}

$child = ChildClass::create();
echo get_class($child); // ChildClass
ChildClass

6. Статический метод, возвращающий замыкание

Пример
class Math {
    public static function multiplier(int $factor): callable {
        return static function (int $x) use ($factor) {
            return $x * $factor;
        };
    }
}

$double = Math::multiplier(2);
echo $double(5); // 10
10

7. Статические методы и рефлексия

Пример
class Config {
    private static array $data = [];

    public static function load(string $key, $default = null) {
        return self::$data[$key] ?? $default;
    }
}

$ref = new ReflectionMethod('Config', 'load');
echo $ref->isStatic() ? 'Статический' : 'Не статический'; // Статический
Статический

Статические методы и свойства в PHP - comments

En
Php static (php)