Dlaczego PHP 5.2+ zabrania abstrakcyjnych statycznych metod klasowych?

121

Po włączeniu ścisłych ostrzeżeń w PHP 5.2, zobaczyłem ładunek ostrzeżeń o ścisłych standardach z projektu, który został pierwotnie napisany bez ścisłych ostrzeżeń:

Ścisłe standardy : funkcja statyczna Program :: getSelectSQL () nie powinna być abstrakcyjna w Program.class.inc

Omawiana funkcja należy do abstrakcyjnej klasy nadrzędnej Program i jest deklarowana jako abstrakcyjna statyczna, ponieważ powinna być zaimplementowana w jej klasach potomnych, takich jak TVProgram.

Znalazłem odniesienia do tej zmiany tutaj :

Usunięto abstrakcyjne funkcje klas statycznych. Z powodu przeoczenia PHP 5.0.x i 5.1.x dopuszcza abstrakcyjne funkcje statyczne w klasach. Od PHP 5.2.x mogą je mieć tylko interfejsy.

Moje pytanie brzmi: czy ktoś może wyjaśnić w jasny sposób, dlaczego w PHP nie powinno być abstrakcyjnej funkcji statycznej?

Artem Russakovskii
źródło
12
Nowi czytelnicy powinni zauważyć, że to irracjonalne ograniczenie zostało usunięte w PHP 7.
Mark Amery,

Odpowiedzi:

76

metody statyczne należą do klasy, która je zadeklarowała. Rozszerzając klasę, możesz stworzyć statyczną metodę o tej samej nazwie, ale tak naprawdę nie implementujesz statycznej metody abstrakcyjnej.

To samo dotyczy rozszerzania dowolnej klasy metodami statycznymi. Jeśli rozszerzysz tę klasę i utworzysz metodę statyczną o tej samej sygnaturze, w rzeczywistości nie zastąpisz statycznej metody nadklasy

EDYCJA (16 września 2009)
Aktualizacja na ten temat. Uruchamiam PHP 5.3 i widzę, że abstrakcyjna statystyka powróciła, na dobre lub na złe. (zobacz http://php.net/lsb, aby uzyskać więcej informacji)

KOREKCJA (autorstwa philfreo)
abstract staticnadal nie jest dozwolona w PHP 5.3, LSB jest powiązane, ale inne.

Jonathan Fingland
źródło
3
OK, a co jeśli chciałbym wymusić potrzebę funkcji getSelectSQL () we wszystkich elementach potomnych, które rozszerzają moją klasę abstrakcyjną? getSelectSQL () w klasie nadrzędnej nie ma ważnego powodu do istnienia. Jaki jest najlepszy plan działania? Powodem, dla którego wybrałem abstrakcyjną statyczną, jest to, że kod nie zostałby skompilowany, dopóki nie zaimplementowałem getSelectSQL () u wszystkich dzieci.
Artem Russakovskii
1
Najprawdopodobniej powinieneś przeprojektować rzeczy, aby getSelectSQL () było abstrakcyjną / instancją / metodą. W ten sposób / instancje / każdego dziecka będą miały taką metodę.
Matthew Flaschen
7
Streszczenie statyczne jest nadal niedozwolone w PHP 5.3. Późne wiązania statyczne nie mają z tym nic wspólnego. Zobacz także stackoverflow.com/questions/2859633
Artefacto,
40
Moim zdaniem to ścisłe ostrzeżenie jest po prostu głupie, ponieważ PHP ma „późne wiązanie statyczne”, które naturalnie dostarcza idei używania metod statycznych tak, jakby sama klasa była obiektem (jak, powiedzmy, w Rubim). Co prowadzi do przeciążenia metody statycznej i abstract staticmoże być przydatne w tym przypadku.
dmitry
4
Ta odpowiedź jest niejasno błędna. „nadal niedozwolone” oznacza po prostu, że otrzymasz ostrzeżenie na poziomie E_STRICT, przynajmniej w wersji 5.3+ możesz tworzyć abstrakcyjne funkcje statyczne, implementować je w klasach rozszerzonych, a następnie odwoływać się do nich za pomocą słowa kluczowego static ::. Oczywiście statyczna wersja klasy nadrzędnej nadal istnieje i nie może być wywoływana bezpośrednio (przez self :: lub static :: wewnątrz tej klasy), ponieważ jest abstrakcyjna i spowoduje fatalny błąd, tak jakbyś wywołał zwykłą niestatyczną funkcję abstrakcyjną. Funkcjonalnie jest to przydatne, zgadzam się z poglądami @dmitry na ten temat.
ahoffner
79

To długa, smutna historia.

Kiedy PHP 5.2 po raz pierwszy wprowadziło to ostrzeżenie, późne statyczne wiązania nie były jeszcze w języku. Jeśli nie jesteś zaznajomiony z późnymi wiązaniami statycznymi, zwróć uwagę, że taki kod nie działa tak, jak można się spodziewać:

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

Pomijając ostrzeżenie o trybie ścisłym, powyższy kod nie działa. self::bar()Rozmowy foo()wyraźnie odnosi się do bar()metody ParentClass, nawet jeśli foo()jest nazywany jako metody ChildClass. Jeśli spróbujesz uruchomić ten kod przy wyłączonym trybie ścisłym, zobaczysz komunikat „ Błąd krytyczny PHP: nie można wywołać metody abstrakcyjnej ParentClass :: bar () ”.

Biorąc to pod uwagę, abstrakcyjne metody statyczne w PHP 5.2 były bezużyteczne. Cały punkt z zastosowaniem metody abstrakcyjne, że można napisać kod, który wywołuje metodę, nie wiedząc, co to realizacja będzie powołanie - a następnie dostarczyć różne implementacje na różnych klasach potomnych. Ale ponieważ PHP 5.2 nie oferuje prostego sposobu na napisanie metody klasy nadrzędnej, która wywołuje statyczną metodę klasy potomnej, na której jest wywoływana, takie użycie abstrakcyjnych metod statycznych nie jest możliwe. Stąd jakiekolwiek użycie abstract staticw PHP 5.2 jest złym kodem, prawdopodobnie zainspirowanym niezrozumieniem działania selfsłowa kluczowego. Było całkowicie rozsądne, aby ostrzec w tej sprawie.

Ale potem pojawił się PHP 5.3 z możliwością odwoływania się do klasy, dla której metoda została wywołana za pomocą staticsłowa kluczowego (w przeciwieństwie do selfsłowa kluczowego, które zawsze odnosi się do klasy, w której metoda została zdefiniowana ). Jeśli zmienisz self::bar()na static::bar()w moim przykładzie powyżej, działa dobrze w PHP 5.3 i nowszych. Możesz przeczytać więcej o selfvs staticat Nowa jaźń kontra nowa statystyka .

Po dodaniu słowa kluczowego static wyraźny argument za abstract staticwysłaniem ostrzeżenia zniknął. Głównym celem późnych wiązań statycznych było umożliwienie metodom zdefiniowanym w klasie nadrzędnej wywoływanie metod statycznych, które zostałyby zdefiniowane w klasach potomnych; dopuszczenie abstrakcyjnych metod statycznych wydaje się rozsądne i spójne, biorąc pod uwagę istnienie późnych wiązań statycznych.

Wydaje mi się, że nadal możesz argumentować za utrzymaniem ostrzeżenia. Na przykład, można argumentować, że skoro PHP umożliwia wywoływanie metod statycznych klas abstrakcyjnych, w moim przykładzie powyżej (nawet po zamontowaniu go zastępując selfz static) jesteś narażając sposób publiczny ParentClass::foo(), który jest uszkodzony i że tak naprawdę nie chcą expose. Użycie klasy niestatycznej - to znaczy uczynienie wszystkich metod instancjami metodami i uczynienie ich elementów potomnych ParentClassjako singletonów lub coś w tym rodzaju - rozwiązałoby ten problem, ponieważ ParentClassbędąc abstrakcyjnymi, nie można utworzyć instancji, więc metody instancji nie mogą Zostać wezwanym. Myślę, że ten argument jest słaby (ponieważ myślę, że ujawniamParentClass::foo() to nic wielkiego i używanie singletonów zamiast klas statycznych jest często niepotrzebnie rozwlekłe i brzydkie), ale możesz się z tym nie zgodzić - jest to nieco subiektywne wezwanie.

Bazując na tym argumencie, twórcy PHP zachowali ostrzeżenie w języku, prawda?

Uh, nie dokładnie .

Raport o błędzie PHP 53081, do którego łącze powyżej, wezwał do usunięcia ostrzeżenia, ponieważ dodanie static::foo()konstrukcji sprawiło, że abstrakcyjne metody statyczne stały się rozsądne i użyteczne. Rasmus Lerdorf (twórca PHP) zaczyna od oznaczenia żądania jako fałszywego i przechodzi przez długi łańcuch złych argumentów, próbując uzasadnić ostrzeżenie. W końcu dochodzi do wymiany:

Giorgio

wiem, ale:

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

Dokładnie tak to powinno działać.

Giorgio

ale nie jest dozwolone :(

Rasmus

Co nie jest dozwolone?

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

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

cB::A();

To działa dobrze. Oczywiście nie możesz wywołać self :: B (), ale static :: B () jest w porządku.

Twierdzenie Rasmusa, że ​​kod w jego przykładzie „działa dobrze” jest fałszywe; jak wiesz, generuje ostrzeżenie o trybie ścisłym. Wydaje mi się, że testował bez włączonego trybu ścisłego. Mimo wszystko zdezorientowany Rasmus pozostawił prośbę błędnie zamkniętą jako „fałszywą”.

I dlatego ostrzeżenie jest nadal w języku. Może to nie być w pełni satysfakcjonujące wyjaśnienie - prawdopodobnie przyszedłeś tutaj z nadzieją, że ostrzeżenie zostało racjonalnie uzasadnione. Niestety, w prawdziwym świecie czasami wybory rodzą się z przyziemnych błędów i złego rozumowania, a nie z racjonalnego podejmowania decyzji. To po prostu jeden z tych czasów.

Na szczęście szacowny Nikita Popov usunął ostrzeżenie z języka w PHP 7 jako część notatek PHP RFC: Reclassify E_STRICT . Ostatecznie zwyciężył rozsądek i po wydaniu PHP 7 wszyscy możemy z radością korzystać abstract staticbez otrzymywania tego głupiego ostrzeżenia.

Mark Amery
źródło
70

Istnieje bardzo proste obejście tego problemu, które w rzeczywistości ma sens z punktu widzenia projektowania. Jak napisał Jonathan:

To samo dotyczy rozszerzania dowolnej klasy metodami statycznymi. Jeśli rozszerzysz tę klasę i utworzysz metodę statyczną o tej samej sygnaturze, w rzeczywistości nie zastąpisz statycznej metody nadklasy

Więc jako obejście możesz to zrobić:

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

A teraz wymuszasz, aby każda klasa podrzędna MyFoo implementowała statyczną metodę getInstance i publiczną metodę getSomeData. A jeśli nie utworzysz podklasy MyFoo, nadal możesz zaimplementować iMyFoo, aby utworzyć klasę o podobnej funkcjonalności.

Wouter Van Vliet
źródło
2
Czy za pomocą tego wzoru można zabezpieczyć funkcję . Kiedy to robię, oznacza to, że klasy, które rozszerzają ostrzeżenia o rzutach MyFoo, które getInstance muszą być publiczne. I nie możesz umieścić ochrony w definicji interfejsu.
artfulrobot
3
czasami static::może się przydać.
widziałem
3
Nie działa z cechami. Gdyby tylko cechy mogły mieć abstract staticmetody, bez narzekania na PHP…
Rudie
1
Zastosowanie interfejsu jest tutaj prawdopodobnie najlepszym rozwiązaniem. +1.
Juan Carlos Coto
Jest to właściwie bardzo eleganckie ze względu na swoją prostotę. +1
G. Stewart
12

Wiem, że to jest stare, ale ....

Dlaczego po prostu nie rzucić wyjątku do statycznej metody tej klasy nadrzędnej, w ten sposób, jeśli jej nie zastąpisz, wyjątek zostanie spowodowany.

Petah
źródło
1
To nie pomaga, wyjątek zdarzyłby się przy wywołaniu metody statycznej - w tym samym czasie pojawiłby się błąd „metoda nie istnieje”, gdybyś go nie zastąpił.
BT,
3
@BT Miałem na myśli, nie deklaruj metody abstrakcyjnej, implementuj ją, ale po prostu wyrzuć wyjątek, gdy zostanie wywołana, co oznacza, że ​​nie zostanie wyrzucona, jeśli została nadpisana.
Petah
Wydaje się, że jest to najbardziej eleganckie rozwiązanie.
Alex S
Lepiej zobaczyć coś takiego w czasie kompilacji niż w czasie wykonywania. Łatwiej jest znaleźć problem, zanim
pojawią
4

Twierdziłbym, że abstrakcyjna klasa / interfejs może być postrzegana jako kontrakt między programistami. Zajmuje się bardziej tym, jak rzeczy powinny wyglądać / zachowywać się, a nie implementować rzeczywistej funkcjonalności. Jak widać w php5.0 i 5.1.x, nie jest to naturalne prawo, które uniemożliwia programistom php zrobienie tego, ale chęć współdziałania z innymi wzorcami projektowymi OO w innych językach. Zasadniczo te pomysły mają na celu zapobieżenie nieoczekiwanym zachowaniom, jeśli ktoś jest już zaznajomiony z innymi językami.

merkuro
źródło
Chociaż nie jest to związane z php, to kolejne dobre wyjaśnienie: stackoverflow.com/questions/3284/ ...
merkuro
3
Mój Boże! Dwie osoby, które zagłosowały przeciwko tobie, całkowicie oszalały! To najbardziej wnikliwa odpowiedź w tym wątku.
Theodore R. Smith
5
@ TheodoreR.Smith Insightful? Zawiera błędy i jest mało spójny. Twierdzenie, że klasy abstrakcyjne „nie implementują rzeczywistej funkcjonalności”, niekoniecznie jest prawdziwe, i to jest cały ich punkt istniejący oprócz interfejsów. W twierdzeniu, że „to nie jest prawo naturalne, które zapobiega programistów PHP ze robi to” , nie mam pojęcia, co „to” jest. A w twierdzeniu, że „w zasadzie te pomysły próbują zapobiec nieoczekiwanym zachowaniom” , nie mam pojęcia, czym są „te pomysły” lub potencjalne „nieoczekiwane zachowanie”. Niezależnie od tego, jaki wgląd z tego wyciągnąłeś, straciłem go.
Mark Amery
2

Nie widzę powodu, aby zabraniać statycznych funkcji abstrakcyjnych. Najlepszym argumentem, że nie ma powodu, aby ich zabraniać, jest to, że są one dozwolone w Javie. Oto pytania: - Czy jest to technicznie wykonalne? - Tak, ponieważ istniały w PHP 5.2 i istnieją w Javie. Więc gdzie MOŻE to zrobić. POWINNIŚMY to zrobić? - Czy mają sens? Tak. Warto zaimplementować część klasy i pozostawić inną część klasy użytkownikowi. Ma to sens w funkcjach niestatycznych, dlaczego nie miałoby mieć sensu w przypadku funkcji statycznych? Jednym z zastosowań funkcji statycznych są klasy, w których nie może być więcej niż jedna instancja (pojedyncze). Na przykład silnik szyfrujący. Nie musi istnieć w kilku przypadkach i są powody, aby temu zapobiec - na przykład przed intruzami trzeba chronić tylko jedną część pamięci. Dlatego sensowne jest zaimplementowanie jednej części silnika i pozostawienie algorytmu szyfrowania użytkownikowi. To tylko jeden przykład. Jeśli jesteś przyzwyczajony do korzystania z funkcji statycznych, znajdziesz o wiele więcej.

user2964021
źródło
3
Twój punkt widzenia na temat abstrakcyjnych metod statycznych istniejących w 5.2 jest bardzo mylący. Po pierwsze, ostrzeżenie trybu ścisłego zabraniające ich zostało wprowadzone w 5.2; sprawiasz, że brzmi to tak, jak w wersji 5.2. Po drugie, w 5.2 nie można ich było łatwo użyć do „zaimplementowania części klasy i pozostawienia innej części użytkownikowi”, ponieważ późne wiązanie statyczne jeszcze nie istniało.
Mark Amery,
0

W php 5.4+ cecha użycia:

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

aw swojej klasie umieść na początku:

use StaticExample;
Kamil
źródło
1
Udało mi się umieścić abstract public static function get_table_name();cechę i użyć tej cechy w mojej abstrakcyjnej klasie bez ostrzeżeń E_STRICT! To wciąż wymuszało zdefiniowanie metody statycznej u dzieci, tak jak się spodziewałem. Fantastyczny!
Programster
-1

Zajrzyj do problemów PHP z późnym wiązaniem statycznym. Jeśli umieszczasz metody statyczne w klasach abstrakcyjnych, prawdopodobnie napotkasz to wcześniej niż później. To ma sens, że surowe ostrzeżenia mówią ci, abyś unikał używania funkcji zepsutego języka.

Sean McSomething
źródło
4
Myślę, że ma na myśli ostrzeżenia „Surowe standardy”.
Jacob Hume
2
Jakie "problemy" mają według ciebie późne wiązania statyczne? Twierdzisz, że są „zepsute”, co jest śmiałym twierdzeniem, przedstawionym tutaj bez dowodów lub wyjaśnienia. Ta funkcja zawsze działała dobrze i myślę, że ten post jest nonsensem.
Mark Amery,