Упс — Почему PHP-черты не могут иметь статические абстрактные методы?

С поздним статическим связыванием в PHP v5.3 можно с пользой объявить static методы в интерфейсах; с чертами в PHP v5.4, методы могут быть static или же abstract но не оба. Это кажется нелогичным и противоречивым.

В частности, предположим, что есть интерфейс, для которого черта обеспечивает всю реализацию, кроме статического метода; если этот метод не объявлен в признаке, статические анализаторы блокируют любые ссылки на него из признака. Но предоставление конкретной реализации внутри черты больше не заставляет реализацию / использование классов предоставлять собственную реализацию, что опасно; abstract static было бы идеально, но не допускается.

Чем объясняется это противоречие? Как бы вы посоветовали решить эту проблему?

interface MyInterface
{
public static function getSetting();
public function doSomethingWithSetting();
}

trait MyTrait
{
public abstract static function getSetting(); // I want this...

public function doSomethingWithSetting() {
$setting = static::getSetting(); // ...so that I can do this
/* ... */
}
}

class MyClass implements MyInterface
{
use MyTrait;
public static function getSetting() { return /* ... */ }
}

12

Решение

TL; DR: Начиная с PHP 7, вы можете. До этого вы мог определять abstract static на traitНо внутренности посчитали это плохой практикой.


Строго, abstract означает, что подкласс должен реализовать, и static означает код только для этого конкретного класса. Вместе взятые, abstract static означает «подкласс должен реализовывать код только для этого конкретного класса». Абсолютно ортогональные понятия.

Но … PHP 5.3+ поддерживает статическое наследование благодаря LSB. Итак, мы на самом деле немного раскрываем это определение: self принимает прежнее определение статического, в то время как static становится «кодом для этого конкретного класса или любого из его подклассов». Новое определение abstract static is «подкласс должен реализовывать код для этого конкретного класса или любого из его подклассов». Это может привести некоторых людей, которые думают о static в строгом смысле, до путаницы. Смотри например ошибка № 53081.

Что делает trait настолько особенным, чтобы вызвать это предупреждение? Ну, посмотри на код двигателя который реализует уведомление:

if (ptr->flags & ZEND_ACC_STATIC && (!scope || !(scope->ce_flags & ZEND_ACC_INTERFACE))) {
zend_error(error_type, "Static function %s%s%s() cannot be abstract", scope ? ZSTR_VAL(scope->name) : "", scope ? "::" : "", ptr->fname);
}

Этот код говорит только разместить abstract static разрешено в пределах interface, Это не уникально для черт, это уникально для определения abstract static, Зачем? Заметьте, в нашем определении есть небольшой угловой случай:

Подкласс должен реализовать код для этот конкретный класс или любой из его подклассов

С этим кодом:

abstract class Foo {
abstract public static function get();
}

Это определение означает, что я должен быть в состоянии звонить Foo::get, В конце концов Foo это класс (см. это ключевое слово «класс» там) и в строгом определении, get предназначен для реализации в этом классе Foo, Но ясно, что это не имеет смысла, потому что мы вернулись к ортогональности строгой статики.

Если вы попробуете это на PHP, вы получите единственно возможный ответ:

Невозможно вызвать абстрактный метод Foo :: get ()

Так как PHP добавил статическое наследование, он имеет дело с этими угловыми случаями. Такова природа черт. Некоторые другие языки (C #, Джава, и т.д.) не имеют этой проблемы, потому что они принимают строгое определение и просто не позволяют abstract static, Чтобы избавиться от этого углового случая и упростить движок, мы можем применить это правило «абстрактная статическая только в интерфейсе» в будущем. Следовательно, E_STRICT,


Я бы использовал делегат службы для решения проблемы:

У меня есть общий метод, который я хочу использовать в нескольких классах. Этот общий метод основан на статическом методе, который должен быть определен внешне по отношению к общему коду.

trait MyTrait
{
public function doSomethingWithSetting() {
$service = new MyService($this);
return $service->doSomethingWithSetting();
}
}

class MyService
{
public function __construct(MyInterface $object) {
$this->object = $object;
}
public function doSomethingWithSetting() {
$setting = $this->object->getSetting();
return $setting;
}
}

Чувствую себя немного Рубе Голдберг, хотя. Вероятно, взгляните на мотивацию статики и рассмотрите возможность их рефакторинга.

8

Другие решения

Других решений пока нет …