С поздним статическим связыванием в 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 /* ... */ }
}
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;
}
}
Чувствую себя немного Рубе Голдберг, хотя. Вероятно, взгляните на мотивацию статики и рассмотрите возможность их рефакторинга.
Других решений пока нет …