Почему PHP 5.2+ запрещает методы абстрактного статического класса? - PullRequest
120 голосов
/ 16 июня 2009

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

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

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

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

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

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

Ответы [ 8 ]

76 голосов
/ 16 июня 2009

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

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

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

ИСПРАВЛЕНИЕ (от philfreo)
abstract static все еще не разрешен в PHP 5.3, LSB связан, но отличается.

72 голосов
/ 06 июля 2015

Это длинная, грустная история.

Когда 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 и выше. Вы можете прочитать больше о self против static на Новая личность против новой статики .

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

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

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

Э-э, не совсем .

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

Giorgio

я знаю, но:

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();

Rasmus

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

Giorgio

но это не разрешено: (

Rasmus

Что не разрешено?

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: Реклассифицировать уведомления E_STRICT . В конечном счете здравомыслие восторжествовало, и после выхода PHP 7 мы все можем с радостью использовать abstract static, не получив этого глупого предупреждения.

70 голосов
/ 17 июня 2011

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

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

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

<?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 для создания класса с похожей функциональностью.

12 голосов
/ 16 июня 2010

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

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

4 голосов
/ 16 июня 2009

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

2 голосов
/ 07 ноября 2013

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

0 голосов
/ 01 сентября 2014

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

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

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

use StaticExample;
0 голосов
/ 16 июня 2009

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

...