Сравнение self и static в объектно-ориентированном PHP
Понимание self и static в PHP: различия, примеры и практическое применение
В объектно-ориентированном PHP ключевое слово self ссылается на класс, в котором оно записано (раннее статическое связывание), а static - на класс, из которого был вызван метод (позднее статическое связывание). Это различие становится критичным при наследовании и переопределении статических методов и свойств.
Наиболее эффективное решение для полиморфизма статических методов - использование static::. Оно позволяет дочерним классам переопределять статические методы, сохраняя корректное связывание при вызовах из родительского класса.
class ParentClass {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who(); // всегда вызовет ParentClass::who()
}
public static function testStatic() {
static::who(); // вызовет метод класса, который вызвал testStatic
}
}
class ChildClass extends ParentClass {
public static function who() {
echo __CLASS__;
}
}
ChildClass::test(); // выведет "ParentClass"
ChildClass::testStatic(); // выведет "ChildClass"Php self static (self и static в php)
ParentClass ChildClass
Static classes php (статические классы в php)
Если заменить self:: на static:: в родительском методе test(), то вызов из дочернего класса будет обращаться к переопределённому методу. Это и есть позднее статическое связывание.
Типичная ошибка: использование self:: в статическом методе, который должен вести себя полиморфно. В результате даже при вызове из наследника выполняется код родителя. Решение: заменить self на static. Следует помнить, что static не работает для констант и свойств, объявленных как const или static - для них при переопределении в наследнике static:: также даст корректный результат.
Как использовать self для доступа к константам и свойствам текущего класса?
self:: удобно применять, когда требуется гарантировать обращение к элементу именно того класса, где написан код, например, для констант внутри самого класса без наследования.
class Config {
const VERSION = '1.0';
public static function getVersion() {
return self::VERSION;
}
}
echo Config::getVersion(); // 1.0Php static method (статические методы в php)
Если класс наследуется и в наследнике переопределяется константа, то self:: всё равно возьмёт значение из родительского класса. Для получения переопределённой константы следует использовать static::.
Как реализовать синглтон, поддерживающий наследование?
В классическом синглтоне метод getInstance() возвращает new self(), что не позволяет наследникам иметь собственные экземпляры. Замена на new static() решает проблему.
class SingletonParent {
protected static $instance = null;
public static function getInstance() {
if (self::$instance === null) {
// self::$instance = new self(); // всегда экземпляр SingletonParent
self::$instance = new static(); // экземпляр того класса, который вызвал
}
return self::$instance;
}
}
class SingletonChild extends SingletonParent {}
$a = SingletonParent::getInstance();
$b = SingletonChild::getInstance();
echo get_class($a) . ' ' . get_class($b);Php 8 class (классы php 8 (нововведения))
SingletonParent SingletonChild
Php class method (метод класса php)
Проблема: свойство $instance объявлено в родителе. Если наследник не переопределит его, то все наследники будут использовать одно и то же хранилище. Для полноценного наследования синглтона нужно переопределить $instance в каждом дочернем классе.
Как получить имя вызываемого класса в статическом контексте?
Функция get_called_class() возвращает имя класса, который был вызван во время выполнения (аналог static::class). self::class возвращает имя класса, где написано выражение.
class A {
public static function className() {
echo 'self::class: ' . self::class . PHP_EOL;
echo 'static::class: ' . static::class . PHP_EOL;
echo 'get_called_class(): ' . get_called_class() . PHP_EOL;
}
}
class B extends A {}
B::className();Class string php (класс для работы со строками php)
self::class: A static::class: B get_called_class(): B
Include class php (подключение класса в php (include/require))
Если в коде используется self::class для логирования или идентификации, то при наследовании будет всегда выдаваться имя родительского класса, что может привести к ошибкам идентификации.
Трейты и позднее статическое связывание
Внутри трейта self ссылается на класс, использующий трейт, а static - на класс, который вызвал метод (может быть наследником). Разница невелика, но static полезен для создания методов трейта, которые должны быть полиморфными.
trait Logger {
public static function log() {
echo 'Logged from ' . static::class . PHP_EOL;
}
}
class Base {
use Logger;
}
class Derived extends Base {}
Derived::log(); // выведет 'Logged from Derived'Php class var (свойство (поле) класса php)
Использование static для фабричных методов
Фабричный метод, объявленный в родительском классе, с new static() создаёт объекты того класса, который вызвал этот метод. Это позволяет строить иерархии без дублирования кода.
class Product {
public static function create() {
return new static();
}
}
class Book extends Product {}
class Car extends Product {}
$book = Book::create();
$car = Car::create();
echo get_class($book) . ' ' . get_class($car);Book Car
Ошибки статического связывания при использовании self в наследовании
В случае переопределения статического свойства и обращения к нему через self:: внутри метода родителя, значение будет браться из родителя. Это часто приводит к неожиданному поведению.
class ParentClass {
protected static $value = 'parent';
public static function getValue() {
return self::$value;
}
}
class ChildClass extends ParentClass {
protected static $value = 'child';
}
echo ChildClass::getValue(); // выведет 'parent'Ожидание: 'child'. Решение: заменить self на static.
Расширенные примеры использования self и static
Пример 1: Многоуровневое наследование и цепочка вызовов
class A {
public static function test() {
static::who();
}
public static function who() {
echo 'A';
}
}
class B extends A {
public static function who() {
echo 'B';
}
}
class C extends B {
public static function who() {
echo 'C';
}
}
C::test(); // выведет 'C'C
Здесь static::who() в методе test класса A связывается с классом C, поскольку вызов идёт от C. Если заменить на self::who(), то всегда будет выводиться 'A'.
Пример 2: Абстрактный синглтон с поддержкой наследования
abstract class Singleton {
protected static array $instances = [];
public static function getInstance(): static {
$class = static::class;
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new static();
}
return self::$instances[$class];
}
}
class Database extends Singleton {
// дополнительная логика
}
class Logger extends Singleton {
// дополнительная логика
}
$db1 = Database::getInstance();
$db2 = Database::getInstance();
$log1 = Logger::getInstance();
var_dump($db1 === $db2); // true
var_dump($db1 instanceof Database); // true
var_dump($log1 instanceof Logger); // truebool(true) bool(true) bool(true)
Используется static::class для ключа массива и new static() для создания экземпляра. Каждый наследник получает свой одиночный объект.
Пример 3: Статическая фабрика с настройками дочерних классов
class Vehicle {
protected static $wheels = 0;
public static function create(): static {
$obj = new static();
$obj->configure();
return $obj;
}
protected function configure() {
// пусто, переопределяется в потомках
}
}
class Bicycle extends Vehicle {
protected static $wheels = 2;
protected function configure() {
echo 'Created bicycle with ' . static::$wheels . ' wheels';
}
}
class Car extends Vehicle {
protected static $wheels = 4;
protected function configure() {
echo 'Created car with ' . static::$wheels . ' wheels';
}
}
$bike = Bicycle::create();
$car = Car::create();Created bicycle with 2 wheels Created car with 4 wheels
static::$wheels в методе configure берёт значение из класса, который был создан благодаря позднему связыванию.
Пример 4: Изменение статических свойств в дочерних классах с сохранением общего счётчика
class Counter {
protected static int $count = 0;
public static function increment() {
self::$count++;
echo 'Total: ' . self::$count . ', Class: ' . static::class . PHP_EOL;
}
}
class ChildA extends Counter {}
class ChildB extends Counter {}
Counter::increment(); // Total: 1, Class: Counter
ChildA::increment(); // Total: 2, Class: ChildA
ChildB::increment(); // Total: 3, Class: ChildBTotal: 1, Class: Counter Total: 2, Class: ChildA Total: 3, Class: ChildB
Здесь self::$count обращается к одному и тому же свойству класса Counter (единственное статическое свойство). Если бы требовалось раздельное хранение, нужно было бы объявить свойство в каждом наследнике и использовать static::$count.
Пример 5: Комбинация static с instanceof и get_class
class Base {
public static function whoIs() {
return static::class;
}
}
class Sub extends Base {}
$class = Sub::whoIs();
$obj = new Sub();
echo $class . ' ' . ($obj instanceof Sub ? 'да' : 'нет');Sub да
Метод whoIs возвращает имя вызывающего класса, что удобно для логирования или создания объектов с проверкой.