Введение: Одна маленькая буква, которая меняет всё
Представьте: вы пишете крутой PHP-фреймворк или просто библиотеку для своего проекта. Всё работает идеально, но спустя месяц вы замечаете, что дочерние классы ведут себя не так, как вы ожидали. Вы создаёте объект, а он — бац! — возвращает экземпляр родительского класса. Знакомо? Добро пожаловать в клуб тех, кто столкнулся с new self vs. new static.
Этот вопрос на Stack Overflow собрал тысячи просмотров, и не зря. Разница между self и static в PHP — это не просто синтаксический сахар, а фундаментальный механизм, который может как спасти ваш проект, так и превратить его в кошмар поддержки. В 2025 году, когда PHP продолжает эволюционировать, понимание позднего статического связывания (Late Static Binding) стало обязательным навыком для любого уважающего себя разработчика.
В этой статье мы не просто перескажем документацию. Мы разберём реальные кейсы, покажем антипаттерны и дадим чёткие best practices, которые вы сможете применить прямо сегодня. Поехали!
Основная часть: self vs. static — битва за контекст
1. self — это компиляторный консерватор
Ключевое слово self в PHP всегда ссылается на класс, в котором оно написано. Думайте о нём как о старом добром друге, который никогда не меняется. Когда вы пишете new self() в родительском классе, этот код всегда будет создавать объект именно этого родительского класса — даже если его вызывает дочерний класс.
<?php
class Animal {
public static function create() {
return new self(); // Всегда Animal
}
}
class Dog extends Animal {
// Наследует create()
}
$dog = Dog::create();
echo get_class($dog); // Выведет: Animal
?>
Видите? Мы вызвали метод у класса Dog, а получили Animal. Это часто становится сюрпризом для новичков. В чём же подвох? А в том, что self разрешается на этапе компиляции. PHP «запечатывает» ссылку на класс, где метод объявлен, и никакая магия наследования это не изменит.
2. static — это поздний связыватель-революционер
А вот static — полная противоположность. Он работает в момент вызова метода и определяет, какой класс его вызвал. Это и есть позднее статическое связывание (Late Static Binding), появившееся в PHP 5.3.0.
<?php
class Animal {
public static function create() {
return new static(); // Создаёт экземпляр того класса, который вызвал метод
}
}
class Dog extends Animal {
// Наследует create()
}
$dog = Dog::create();
echo get_class($dog); // Выведет: Dog
?>
Вот это поворот! Теперь Dog::create() возвращает собаку, а не абстрактное животное. static смотрит на то, кто вызвал метод, и создаёт объект именно этого класса. Это даёт невероятную гибкость при построении иерархий классов.
3. Когда использовать self, а когда static?
Золотое правило: используйте self, когда вам нужна жёсткая привязка к текущему классу, и static, когда вы хотите, чтобы дочерние классы могли переопределять поведение.
Вот несколько конкретных сценариев:
Сценарий А: Фабричные методы (Используем static)
Классический пример — паттерн Factory Method. Вы хотите, чтобы каждый дочерний класс создавал сам себя.
<?php
abstract class Vehicle {
abstract public function getType(): string;
public static function create(): static {
return new static();
}
}
class Car extends Vehicle {
public function getType(): string {
return 'Автомобиль';
}
}
class Bike extends Vehicle {
public function getType(): string {
return 'Велосипед';
}
}
$car = Car::create();
echo $car->getType(); // Автомобиль
$bike = Bike::create();
echo $bike->getType(); // Велосипед
?>
Без static этот код бы не работал. Vehicle::create() всегда возвращал бы Vehicle, а не Car или Bike.
Сценарий Б: Иммутабельные объекты и Value Objects (Используем self)
Если вы создаёте объект, который не должен быть унаследован, или вам нужна строгая типизация, self — ваш выбор.
<?php
final class Money {
private int $amount;
private string $currency;
public function __construct(int $amount, string $currency) {
$this->amount = $amount;
$this->currency = $currency;
}
public static function fromCents(int $cents): self {
return new self($cents, 'USD');
}
// Важно: fromCents всегда будет создавать Money, даже если от него унаследоваться
}
?>
Здесь self гарантирует, что Money::fromCents(100) вернёт именно объект Money, а не какого-то потомка. Это критично для Value Objects, где идентичность и тип должны быть строгими.
Сценарий В: Singleton и антипаттерны (Осторожно с static!)
Паттерн Singleton — частый гость в спорах о self vs static. Если вы используете static в Singleton, вы можете случайно создать несколько экземпляров разных классов.
<?php
class Singleton {
private static ?self $instance = null;
public static function getInstance(): static {
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
}
class ChildSingleton extends Singleton {}
$s1 = Singleton::getInstance();
$s2 = ChildSingleton::getInstance();
var_dump($s1 === $s2); // false! Разные объекты
?>
Это нарушает идею Singleton. Правильный вариант — использовать self для хранения экземпляра, если вы хотите единый экземпляр для всей иерархии.
4. Продвинутые техники: static и тип возврата
Начиная с PHP 8.0, вы можете использовать static как тип возврата (Return Type). Это делает код ещё более выразительным и безопасным.
<?php
class Builder {
public function setName(string $name): static {
// ... логика
return $this;
}
public function setAge(int $age): static {
// ... логика
return $this;
}
}
class UserBuilder extends Builder {
public function setEmail(string $email): static {
// ... логика
return $this;
}
}
$user = (new UserBuilder())
->setName('Иван')
->setAge(30)
->setEmail('ivan@example.com');