New self vs. new static: секрет гибкого PHP-кода в 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Одна маленькая буква, которая меняет всё

Представьте: вы пишете крутой 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');                

Блоги

Рекомендаций по книгам