Почему в PHP 5.2+ запрещены методы абстрактного статического класса?

121

После включения строгих предупреждений в PHP 5.2 я увидел множество предупреждений о строгих стандартах из проекта, который изначально был написан без строгих предупреждений:

Строгие стандарты : статическая функция Program :: getSelectSQL () не должна быть абстрактной в Program.class.inc

Рассматриваемая функция принадлежит абстрактному родительскому классу Program и объявляется абстрактным статическим, поскольку она должна быть реализована в своих дочерних классах, таких как TVProgram.

Я нашел ссылки на эти изменения здесь :

Отброшены абстрактные статические функции класса. По недосмотру PHP 5.0.x и 5.1.x разрешили абстрактные статические функции в классах. Начиная с PHP 5.2.x, они могут быть только в интерфейсах.

У меня вопрос: может ли кто-нибудь четко объяснить, почему в PHP не должно быть абстрактной статической функции?

Артем Русаковский
источник
12
Новые читатели должны отметить, что это иррациональное ограничение было снято в PHP 7.
Марк Эмери

Ответы:

76

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

То же самое касается расширения любого класса статическими методами. Если вы расширите этот класс и создадите статический метод с той же сигнатурой, вы фактически не переопределите статический метод суперкласса

РЕДАКТИРОВАТЬ (16 сентября 2009 г.)
Обновление по этому поводу. Запустив PHP 5.3, я вижу, что абстрактная статика вернулась, хорошо это или плохо. (см. http://php.net/lsb для получения дополнительной информации)

КОРРЕКЦИЯ (по philfreo)
abstract staticдо сих пор не разрешены в PHP 5.3, LSB связано , но отличается.

Джонатан Фингланд
источник
3
Хорошо, а что, если бы я хотел обеспечить необходимость функции getSelectSQL () во всех дочерних элементах, расширяющих мой абстрактный класс? getSelectSQL () в родительском классе не имеет веских причин для существования. Какой план действий лучше всего? Причина, по которой я выбрал абстрактную статику, заключается в том, что код не будет компилироваться, пока я не реализовал getSelectSQL () во всех дочерних элементах.
Артем Русаковский
1
Скорее всего, вам следует изменить дизайн, чтобы getSelectSQL () был абстрактным / экземпляром / методом. Таким образом, у каждого дочернего элемента будет такой метод.
Мэтью Флашен
7
Абстрактная статика по-прежнему запрещена в PHP 5.3. Поздние статические привязки не имеют к этому никакого отношения. См. Также stackoverflow.com/questions/2859633
Artefacto
40
На мой взгляд, это строгое предупреждение просто глупо, поскольку PHP имеет «позднее статическое связывание», что, естественно, дает идею использования статических методов, как если бы сам класс был объектом (как, например, в ruby). Что приводит к перегрузке статического метода и abstract staticможет быть полезно в этом случае.
дмитрий
4
Этот ответ в некоторой степени неверен. «все еще не разрешено» просто означает, что вы получите предупреждение уровня E_STRICT, по крайней мере, в версии 5.3+ вы можете создавать абстрактные статические функции, реализовывать их в расширенных классах, а затем ссылаться на них через ключевое слово static ::. Очевидно, что статическая версия родительского класса все еще существует и не может быть вызвана напрямую (через self :: или static :: внутри этого класса), поскольку она абстрактна и приведет к фатальной ошибке, как если бы вы вызвали обычную нестатическую абстрактную функцию. Функционально это полезно, я согласен с мнением @dmitry по этому поводу.
ahoffner
79

Это долгая печальная история.

Когда PHP 5.2 впервые представил это предупреждение, поздние статические привязки еще не были в языке. Если вы не знакомы с поздними статическими привязками, обратите внимание, что такой код работает не так, как вы могли ожидать:

<?php

abstract class ParentClass {
    static function foo() {
        echo "I'm gonna do bar()";
        self::bar();
    }

    abstract static function bar();
}

class ChildClass extends ParentClass {
    static function bar() {
        echo "Hello, World!";
    }
}

ChildClass::foo();

Не говоря уже о предупреждении о строгом режиме, приведенный выше код не работает. self::bar()Вызов в foo()явном виде относится к bar()способу ParentClass, даже когда foo()вызываются в качестве метода ChildClass. Если вы попытаетесь запустить этот код с выключенным строгим режимом, вы увидите сообщение « Неустранимая ошибка PHP: невозможно вызвать абстрактный метод ParentClass :: bar ()». ».

Учитывая это, абстрактные статические методы в PHP 5.2 были бесполезны. Вся точка использования абстрактного метода является то , что вы можете написать код , который вызывает метод , не зная , что реализация это будет вызов - а затем предоставить различные реализации на разных классах детей. Но поскольку PHP 5.2 не предлагает чистого способа написать метод родительского класса, который вызывает статический метод дочернего класса, для которого он вызывается, такое использование абстрактных статических методов невозможно. Следовательно, любое использование abstract staticв PHP 5.2 является плохим кодом, вероятно, вызванным непониманием того, как selfработает ключевое слово. Было вполне разумно сделать предупреждение по этому поводу.

Но затем появился PHP 5.3, добавивший возможность ссылаться на класс, для которого был вызван метод, через staticключевое слово (в отличие от selfключевого слова, которое всегда относится к классу, в котором был определен метод ). Если вы измените значение self::bar()на static::bar()в моем примере выше, он отлично работает в PHP 5.3 и выше. Вы можете узнать больше о selfvs staticна New self vs. new static .

С добавлением ключевого слова static явным аргументом в пользу наличия abstract static исчез выдачи предупреждения. Основная цель поздних статических привязок состояла в том, чтобы позволить методам, определенным в родительском классе, вызывать статические методы, которые будут определены в дочерних классах; разрешение абстрактных статических методов кажется разумным и последовательным, учитывая наличие поздних статических привязок.

Думаю, вы все еще можете убедить вас в сохранении предупреждения. Например, можно утверждать , что с PHP позволяет вызывать статические методы абстрактных классов, в моем примере выше (даже после фиксации его замены selfс static) вы обнажая публичный метод , ParentClass::foo()который сломан и что вы действительно не хотите разоблачить. Использование нестатического класса - то есть создание всех методов экземпляра методов и создание дочерних элементовParentClass синглтонами или что-то в этом роде, решило бы эту проблему, поскольку ParentClass, будучи абстрактным, нельзя создать экземпляр, и поэтому его методы экземпляра не могут называться. Я считаю этот аргумент слабым (потому что я думаю, что разоблачениеParentClass::foo() не имеет большого значения, и использование синглтонов вместо статических классов часто излишне многословно и некрасиво), но вы можете разумно не согласиться - это несколько субъективный вызов.

Итак, основываясь на этом аргументе, разработчики PHP сохранили предупреждение на языке, верно?

Ну, не совсем .

В отчете об ошибке PHP 53081, ссылка на который приведена выше, содержится призыв к удалению предупреждения, поскольку добавление static::foo()конструкции сделало абстрактные статические методы разумными и полезными. Расмус Лердорф (создатель PHP) начинает с того, что помечает запрос как поддельный и проходит длинную цепочку неверных аргументов, чтобы попытаться оправдать предупреждение. Затем, наконец, происходит этот обмен:

Джорджио

я знаю, но:

abstract class cA
{
      //static function A(){self::B();} error, undefined method
      static function A(){static::B();} // good
      abstract static function B();
}

class cB extends cA
{
    static function B(){echo "ok";}
}

cB::A();

Расмус

Да, именно так это и должно работать.

Джорджио

но нельзя :(

Расмус

Что нельзя?

abstract class cA {
      static function A(){static::B();}
      abstract static function B();
}

class cB extends cA {
    static function B(){echo "ok";}
}

cB::A();

Это прекрасно работает. Очевидно, вы не можете вызвать self :: B (), но static :: B () в порядке.

Утверждение Расмуса о том, что код в его примере «отлично работает», является ложным; как вы знаете, он выдает предупреждение о строгом режиме. Я думаю, он тестировал без включения строгого режима. Тем не менее, сбитый с толку Расмус оставил запрос ошибочно закрытым как «поддельный».

И поэтому предупреждение все еще на языке. Возможно, это не совсем удовлетворительное объяснение - вы, вероятно, пришли сюда в надежде, что предупреждение было рациональным. К сожалению, в реальном мире выбор порой является результатом обыденных ошибок и неверных рассуждений, а не рационального принятия решений. Это просто один из таких случаев.

К счастью, уважаемый Никита Попов удалил предупреждение из языка PHP 7 как часть PHP RFC: Reclassify E_STRICT notices . В конце концов, здравомыслие восторжествовало, и как только выйдет PHP 7, мы все сможем с радостью использовать, abstract staticне получая этого глупого предупреждения.

Марк Эмери
источник
70

Есть очень простой способ решения этой проблемы, который действительно имеет смысл с точки зрения дизайна. Как писал Джонатан:

То же самое касается расширения любого класса статическими методами. Если вы расширите этот класс и создадите статический метод с той же сигнатурой, вы фактически не переопределите статический метод суперкласса

Итак, в качестве обходного пути вы можете сделать это:

<?php
abstract class MyFoo implements iMyFoo {

    public static final function factory($type, $someData) {
        // don't forget checking and do whatever else you would
        // like to do inside a factory method
        $class = get_called_class()."_".$type;
        $inst = $class::getInstance($someData);
        return $inst;
    }
}


interface iMyFoo {
    static function factory($type, $someData);
    static function getInstance();
    function getSomeData();
}
?>

И теперь вы обеспечиваете, чтобы любой класс, являющийся подклассом MyFoo, реализовывал статический метод getInstance и общедоступный метод getSomeData. И если вы не создаете подкласс MyFoo, вы все равно можете реализовать iMyFoo для создания класса с аналогичной функциональностью.

Воутер Ван Влит
источник
2
Можно ли с помощью этого шаблона сделать функцию защищенной . Когда я это делаю, это означает, что классы, расширяющие MyFoo, выдают предупреждения о том, что getInstance должен быть общедоступным. И вы не можете поместить protected в определение интерфейса.
artfulrobot 05
3
иногда static::может быть полезно.
опубликовано
3
Не работает с чертами. Если бы только у Traits могли быть abstract staticметоды, без скуки PHP ....
Руди
1
Использование интерфейса, вероятно, здесь лучшее решение. +1.
Хуан Карлос Кото,
На самом деле это очень элегантно из-за своей простоты. +1
Дж. Стюарт
12

Я знаю, что это старое, но ....

Почему бы просто не выбросить исключение в статическом методе родительского класса, таким образом, если вы не переопределите его, возникнет исключение.

Петах
источник
1
Это не помогает, исключение произойдет при вызове статического метода - в то же время возникнет ошибка «метод не существует», если вы ее не переопределите.
BT
3
@BT Я имел в виду, не объявляйте абстрактный метод, реализуйте его, а просто генерируйте исключение при его вызове, что означает, что он не будет генерировать, если он был переопределен.
Петах
Это кажется наиболее элегантным решением.
Alex S
Лучше увидеть что-то подобное во время компиляции, чем во время выполнения. Легче найти проблему до того, как они будут
запущены
4

Я бы сказал, что абстрактный класс / интерфейс можно рассматривать как контракт между программистами. Он больше касается того, как вещи должны выглядеть / вести себя, а не реализовывать фактическую функциональность. Как видно из php5.0 и 5.1.x, это не естественный закон, который мешает разработчикам php делать это, а побуждение идти вместе с другими шаблонами проектирования OO на других языках. В основном эти идеи пытаются предотвратить неожиданное поведение, если человек уже знаком с другими языками.

merkuro
источник
Еще одно хорошее объяснение, хотя и не связанное с php: stackoverflow.com/questions/3284/…
merkuro
3
О Господи! Два человека, которые проголосовали против вас, совершенно не в своем уме! Это наиболее содержательный ответ в этой теме.
Теодор Р. Смит
5
@ TheodoreR.Smith Insightful? Он содержит ошибки и плохо понятен. Утверждение, что абстрактные классы «не реализуют фактическую функциональность» , не обязательно верно, и в этом вся суть их существования в дополнение к интерфейсам. В заявлении о том, что «это не естественный закон, который мешает разработчикам PHP это делать» , я понятия не имею, что «это» такое. И в заявлении о том, что «в основном эти идеи пытаются предотвратить неожиданное поведение» , я понятия не имею, что собой представляют «эти идеи» или потенциальное «неожиданное поведение». Какое бы понимание вы ни извлекли из этого, для меня оно потеряно.
Марк Эмери
2

Я не вижу причин запрещать статические абстрактные функции. Лучший аргумент в пользу того, что нет причин запрещать их, - это то, что они разрешены в Java. Вопросы следующие: - Возможно ли это технически? - Да, поскольку они существовали в PHP 5.2 и существуют в Java. Итак, где МОЖЕТ это сделать. ДОЛЖНЫ ли мы это делать? - Есть ли в них смысл? Да. Имеет смысл реализовать часть класса и оставить другую часть класса пользователю. Это имеет смысл в нестатических функциях, почему это не имеет смысла для статических функций? Одно из применений статических функций - это классы, в которых не должно быть более одного экземпляра (синглтоны). Например, механизм шифрования. Необязательно, чтобы она существовала в нескольких экземплярах, и есть причины для предотвращения этого - например, вы должны защитить только одну часть памяти от злоумышленников. Таким образом, имеет смысл реализовать одну часть механизма и оставить алгоритм шифрования пользователю. Это только один пример. Если вы привыкли использовать статические функции, вы найдете гораздо больше.

user2964021
источник
3
Ваше мнение об абстрактных статических методах, существующих в 5.2, сильно вводит в заблуждение. Во-первых, в 5.2 было введено предупреждение о строгом режиме, запрещающее их; вы говорите так, как будто в 5.2 они были разрешены. Во-вторых, в 5.2 их нельзя было легко использовать «для реализации части класса и оставления другой части класса пользователю», потому что поздних статических привязок еще не существовало.
Марк Эмери
0

В php 5.4+ используйте трейт:

trait StaticExample {
    public static function instance () {
    return new self;
    }
}

и в вашем классе поставьте с самого начала:

use StaticExample;
камил
источник
1
Я смог добавить abstract public static function get_table_name();черту и использовать ее в своем абстрактном классе без предупреждений E_STRICT! Это все еще заставляло определять статический метод у детей, как я и надеялся. Фантастика!
Программист
-1

Изучите проблемы PHP «Поздняя статическая привязка». Если вы помещаете статические методы в абстрактные классы, вы, вероятно, столкнетесь с этим раньше, чем позже. Имеет смысл, что строгие предупреждения говорят вам избегать использования неработающих языковых функций.

Шон McSomething
источник
4
Я думаю, он имеет в виду предупреждения о "строгих стандартах".
Джейкоб Хьюм,
2
Какие "проблемы", по вашему мнению, возникают у поздних статических привязок? Вы утверждаете, что они «сломаны», что является смелым заявлением, сделанным здесь без доказательств или объяснений. У меня эта функция всегда работала нормально, и я считаю этот пост чушью.
Марк Эмери