Чому PHP 5.2+ забороняє абстрактні методи статичного класу?


121

Увімкнувши суворі застереження в PHP 5.2, я побачив навантаження суворих стандартних попереджень від проекту, який спочатку був написаний без суворих попереджень:

Суворі стандарти : Статична функція Програма :: getSelectSQL () не повинна бути абстрактною в Program.class.inc

Розглянута функція належить до абстрактного батьківського класу Програма і оголошується абстрактною статичною, оскільки вона повинна бути реалізована в своїх дочірніх класах, таких як TVProgram.

Я знайшов посилання на ці зміни тут :

Випали абстрактні функції статичного класу. Завдяки контролю, PHP 5.0.x та 5.1.x дозволяють абстрактні статичні функції в класах. Станом на PHP 5.2.x, лише інтерфейси можуть мати їх.

Моє запитання: чи може хтось чітко пояснити, чому в PHP не повинно бути абстрактної статичної функції?


12
Новим читачам слід зауважити, що це ірраціональне обмеження було знято в PHP 7.
Марк Амеррі

Відповіді:


76

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

Те саме стосується розширення будь-якого класу статичними методами. Якщо ви розширите цей клас і створите статичний метод одного і того ж підпису, ви фактично не перекриєте статичний метод надкласу

EDIT (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 має "пізню статичну прив'язку", що, природно, дає уявлення про використання статичних методів так, як ніби сам клас був об'єктом (як, скажімо, в рубіні). Це призводить до перевантаження статичних методів і abstract staticможе бути корисним у цьому випадку.
dmitry

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 Fatal error: Неможливо викликати абстрактний метод ParentClass :: bar () ".

Враховуючи це, абстрактні статичні методи в PHP 5.2 були марними. Весь сенс використання абстрактного методу є те , що ви можете написати код , який викликає метод , не знаючи , що реалізація це буде виклик - а потім надати різні реалізації на різних класах дітей. Але оскільки PHP 5.2 не пропонує чіткого способу написати метод батьківського класу, який викликає статичний метод дочірнього класу, за яким він викликається, таке використання абстрактних статичних методів неможливо. Отже, будь-яке використання abstract staticPHP 5.2 - це поганий код, напевно, натхнений нерозумінням того, як selfпрацює ключове слово. Цілком розумно було кинути попередження через це.

Але PHP 5.3 прийшов разом доданий в здатності ставитися до класу , на якому метод був викликаний з допомогою staticключового слова ( в відміну від selfключового слова, яке завжди відноситься до класу , в якому метод визначений ). Якщо ви перейдете self::bar()до static::bar()мого прикладу вище, він працює чудово у PHP 5.3 та вище. Ви можете прочитати більше про selfvs staticу New Self vs. New 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();

Це чудово працює. Ви, очевидно, не можете назвати себе: B (), але статичний :: B () - це добре.

Твердження Расмуса про те, що код у його прикладі "добре працює" є помилковим; як відомо, воно накидає суворий режим попередження. Я думаю, що він тестував без включеного суворого режиму. Незважаючи на те, розгублений Расмус запит помилково закрив як "фальшивий".

І тому попередження все ще є в мові. Це може бути не цілком задовольняючим поясненням - ви, мабуть, прийшли сюди, сподіваючись, що існує раціональне обґрунтування попередження. На жаль, в реальному світі іноді вибір народжується з приземлених помилок і неправильних міркувань, а не з раціонального прийняття рішень. Це просто один із тих часів.

На щастя, оцінюваний Микита Попов видалив попередження з мови в PHP 7 як частина повідомлення PHP RFC: Перекласифікуйте повідомлення E_STRICT . Зрештою, домінує розум, і коли 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 повинні бути загальнодоступними. І ви не можете поставити захищені в інтерфейсі визначення.
artfulrobot

3
кілька разів static::можуть бути корисними.
засіла

3
Не працює з ознаками. Якби тільки риси могли мати abstract staticметоди, без кучки PHP ....
Rudie

1
Використання інтерфейсу, мабуть, найкраще тут. +1.
Хуан Карлос Кото

Це насправді дуже елегантно через свою простоту. +1
Г. Стюарт

12

Я знаю, це старе, але ....

Чому б просто не викинути виняток статичним методом цього батьківського класу, якщо ви не перекриєте його, виняток викликаний.


1
Це не допомагає, виняток трапиться за викликом статичного методу - у той же час, якщо "метод не існує" з'явиться помилка, якщо ви не перекриєте її.
BT

3
@BT Я мав на увазі не оголошувати метод абстрактним, реалізовувати його, а просто кидати виняток, коли він викликається, а це означає, що він не буде кидати, якщо його буде відмінено.
Петах

Це здається найелегантнішим рішенням.
Alex S

Краще бачити щось подібне під час компіляції, ніж час виконання. Простіше знайти проблему, перш ніж вони з'являться у виробництві, оскільки вам потрібно лише завантажити файл, а не виконувати код, щоб з'ясувати, чи поганий він або невідповідний
Рахлі

4

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


Хоча це і НЕ PHP пов'язаних ось ще хороше пояснення: stackoverflow.com/questions/3284 / ...
merkuro

3
Боже! Двоє людей, котрі тебе прихильнили, абсолютно не з розуму! Це найпроникливіша відповідь на цю тему.
Теодор Р. Сміт

5
@ TheodoreR.Smith Проникливий? Він містить помилки і ледве є когерентним. Твердження, що абстрактні класи "не реалізують фактичну функціональність" , не обов'язково відповідає дійсності, і ось вся суть їх існує крім інтерфейсів. У твердженні, що "це не природний закон, який заважає розробникам php цього робити" , я не маю поняття, що це "це" . І в твердженні, що "в основному ці ідеї намагаються запобігти несподіваній поведінці" , я не маю поняття, що таке "ці ідеї" або потенційна "несподівана поведінка". Яке б прозріння ви не дістали з цього, воно втрачається на мені.
Марк Амері

2

Я не бачу причин забороняти статичні абстрактні функції. Найкращий аргумент того, що немає причин забороняти їм, це те, що вони дозволені на Java. Питання: - Чи технічно це можливо? - Так, оскільки існували в PHP 5.2 і вони існують на Java. Тож коли МОЖЕ це зробити. МОЖЕТЕ ми це зробити? - Вони мають сенс? Так. Має сенс реалізувати частину класу та залишити іншу частину класу користувачеві. Це має сенс у нестатичних функціях, чому не має сенсу статичних функцій? Одним із застосувань статичних функцій є класи, у яких не повинно бути більше одного екземпляра (одиночні кнопки). Наприклад, двигун шифрування. Це не потрібно існувати в декількох випадках, і є причини, щоб запобігти цьому - наприклад, вам потрібно захистити лише одну частину пам'яті від зловмисників. Тому має сенс реалізувати одну частину двигуна і залишити алгоритм шифрування користувачеві. Це лише один приклад. Якщо ви звикли використовувати статичні функції, ви знайдете набагато більше.


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 попереджень! Це все-таки примушувало визначати статичний метод у дітей, як я сподівався. Фантастичний!
Programster

-1

Подивіться на проблеми пізнього статичного зв’язування PHP. Якщо ви ставите статичні методи на абстрактні класи, ви, ймовірно, збираєтесь натрапити на нього раніше, ніж пізніше. Має сенс, що суворі попередження говорять вам про те, щоб уникнути використання порушених мовних функцій.


4
Я думаю, що він має на увазі попередження "Суворих стандартів".
Яків Юм

2
На які «питання» ви стверджуєте, що мають пізні статичні прив’язки? Ви стверджуєте, що вони "зламані", що є сміливим позовом, зробленим тут без доказів чи пояснень. Ця функція для мене завжди добре працювала, і я вважаю, що цей пост - це нісенітниця.
Марк Амері
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.