Почему свойства 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 /* ... */ }
}

Ответ 1

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

У меня не было большого количества кофеина, но я дам ему трещину.

Строго говоря, abstract означает, что должен выполняться подкласс, а static означает код только для этого конкретного класса. В совокупности abstract static означает, что "подкласс должен реализовывать только код для этого конкретного класса". Полностью ортогональные понятия.

Но... PHP 5.3+ поддерживает статическое наследование благодаря LSB. Таким образом, мы фактически открываем это определение немного: self принимает прежнее определение static, а static становится "кодом для этого конкретного класса или любого из его подклассов". Новое определение abstract static - это "подкласс" должен реализовать код для этого конкретного класса или любого из его подклассов ". Это может привести некоторых людей, которые думают о 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 добавил статическое наследование, он должен иметь дело с этими угловыми случаями. Такова природа особенностей. Некоторые другие языки (С#, Java и т.д.) Не имеют этой проблемы, поскольку они принимают строгое определение и просто не допускать 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;
    }
}

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