Perché PHP 5.2+ non consente metodi di classe statici astratti?


121

Dopo aver abilitato gli avvisi rigorosi in PHP 5.2, ho visto un carico di avvisi sugli standard rigidi da un progetto che era stato originariamente scritto senza avvisi rigorosi:

Standard rigorosi : la funzione statica Program :: getSelectSQL () non dovrebbe essere astratta in Program.class.inc

La funzione in questione appartiene a una classe genitore astratta Program ed è dichiarata astratta statica perché dovrebbe essere implementata nelle sue classi figlie, come TVProgram.

Ho trovato riferimenti a questa modifica qui :

Eliminate le funzioni di classe statiche astratte. A causa di una svista, PHP 5.0.xe 5.1.x consentivano funzioni statiche astratte nelle classi. A partire da PHP 5.2.x, solo le interfacce possono averli.

La mia domanda è: qualcuno può spiegare in modo chiaro perché non dovrebbe esserci una funzione statica astratta in PHP?


12
I nuovi lettori dovrebbero notare che questa restrizione irrazionale è stata rimossa in PHP 7.
Mark Amery

Risposte:


76

i metodi statici appartengono alla classe che li ha dichiarati. Quando estendi la classe, puoi creare un metodo statico con lo stesso nome, ma in realtà non stai implementando un metodo astratto statico.

Lo stesso vale per l'estensione di qualsiasi classe con metodi statici. Se estendi quella classe e crei un metodo statico con la stessa firma, non stai effettivamente sovrascrivendo il metodo statico della superclasse

EDIT (16 settembre 2009)
Aggiornamento su questo. Eseguendo PHP 5.3, vedo che la statica astratta è tornata, nel bene o nel male. (vedi http://php.net/lsb per maggiori informazioni)

CORREZIONE (di philfreo) non
abstract staticè ancora consentita in PHP 5.3, LSB è correlato ma diverso.


3
OK, e se volessi imporre la necessità della funzione getSelectSQL () in tutti i figli che estendono la mia classe astratta? getSelectSQL () nella classe genitore non ha alcun motivo valido per esistere. Qual è il miglior piano d'azione? Il motivo per cui ho scelto la statica astratta è che il codice non si compilava finché non avessi implementato getSelectSQL () in tutti i bambini.
Artem Russakovskii

1
Molto probabilmente, dovresti riprogettare le cose in modo che getSelectSQL () sia un metodo / istanza / astratto. In questo modo, / instance / di ogni bambino avrà un tale metodo.
Matthew Flaschen

7
La statica astratta non è ancora consentita in PHP 5.3. Gli attacchi statici tardivi non hanno nulla a che fare con questo. Vedi anche stackoverflow.com/questions/2859633
Artefacto

40
A mio parere questo severo avvertimento è semplicemente stupido poiché PHP ha un "binding statico tardivo", che naturalmente fornisce l'idea di usare metodi statici come se la classe stessa fosse stata un oggetto (come, ad esempio, in ruby). Il che porta al sovraccarico del metodo statico e abstract staticpuò essere utile in questo caso.
dmitry

4
Questa risposta è vagamente sbagliata. "ancora non consentito" significa semplicemente che riceverai un avviso di livello E_STRICT, almeno nella versione 5.3+ sei perfettamente il benvenuto per creare funzioni statiche astratte, implementarle in classi estese e quindi fare riferimento ad esse tramite la parola chiave static ::. Ovviamente la versione statica della classe genitore è ancora lì e non può essere chiamata direttamente (tramite self :: o static :: all'interno di quella classe) poiché è astratta e genererà un errore fatale come se si chiamasse una normale funzione astratta non statica. Funzionalmente questo è utile, sono d'accordo con i sentimenti di @dmitry in tal senso.
ahoffner

79

È una storia lunga e triste.

Quando PHP 5.2 ha introdotto per la prima volta questo avviso, i collegamenti statici recenti non erano ancora presenti nel linguaggio. Nel caso in cui non si abbia familiarità con i binding statici tardivi, si noti che il codice come questo non funziona nel modo in cui ci si potrebbe aspettare:

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

Lasciando da parte l'avviso della modalità rigorosa, il codice sopra non funziona. La self::bar()chiamata in si foo()riferisce esplicitamente al bar()metodo di ParentClass, anche quando foo()viene chiamata come metodo di ChildClass. Se provi a eseguire questo codice con la modalità rigorosa disattivata, vedrai " Errore irreversibile PHP: impossibile chiamare il metodo astratto ParentClass :: bar () ".

Detto questo, i metodi statici astratti in PHP 5.2 erano inutili. Il punto centrale dell'utilizzo di un metodo astratto è che puoi scrivere codice che chiama il metodo senza sapere quale implementazione verrà chiamata e quindi fornire implementazioni diverse su classi figlie diverse. Ma poiché PHP 5.2 non offre un modo pulito per scrivere un metodo di una classe genitore che chiama un metodo statico della classe figlia su cui è chiamato, questo utilizzo di metodi statici astratti non è possibile. Quindi qualsiasi utilizzo di abstract staticin PHP 5.2 è un codice errato, probabilmente ispirato da un malinteso sul funzionamento della selfparola chiave. Era del tutto ragionevole lanciare un avvertimento su questo.

Ma poi è arrivato PHP 5.3 aggiunto nella possibilità di fare riferimento alla classe su cui un metodo è stato chiamato tramite la staticparola chiave (a differenza della selfparola chiave, che si riferisce sempre alla classe in cui il metodo è stato definito ). Se cambi self::bar()in static::bar()nel mio esempio sopra, funziona bene in PHP 5.3 e versioni successive. Puoi leggere di più su selfvs staticin New self vs. new static .

Con la parola chiave statica aggiunta, l'argomento chiaro per aver abstract staticlanciato un avviso era sparito. Lo scopo principale dei binding statici tardivi era di consentire ai metodi definiti in una classe genitore di chiamare metodi statici che sarebbero stati definiti nelle classi figlie; consentire metodi statici astratti sembra ragionevole e coerente data l'esistenza di binding statici tardivi.

Potresti ancora, immagino, giustificare il mantenimento dell'avvertimento. Ad esempio, potresti sostenere che poiché PHP ti consente di chiamare metodi statici di classi astratte, nel mio esempio sopra (anche dopo averlo risolto sostituendolo selfcon static) stai esponendo un metodo pubblico ParentClass::foo()che è rotto e che non vuoi davvero esporre. L'utilizzo di una classe non statica, ovvero, rendendo tutti i metodi metodi di istanza e facendo in modo che i figli di ParentClasstutti siano singleton o qualcosa del genere, risolverebbe questo problema, poiché ParentClass, essendo astratto, non può essere istanziato e quindi i suoi metodi di istanza non possono essere chiamato. Penso che questo argomento sia debole (perché penso che esporreParentClass::foo() non è un grosso problema e l'uso di singleton invece di classi statiche è spesso inutilmente prolisso e brutto), ma potresti ragionevolmente non essere d'accordo - è una chiamata in qualche modo soggettiva.

Quindi, sulla base di questo argomento, gli sviluppatori PHP hanno mantenuto l'avviso nella lingua, giusto?

Uh, non esattamente .

Il bug report di PHP 53081, collegato sopra, richiedeva di eliminare l'avvertimento poiché l'aggiunta del static::foo()costrutto aveva reso ragionevoli e utili metodi statici astratti. Rasmus Lerdorf (creatore di PHP) inizia etichettando la richiesta come fasulla e passa attraverso una lunga catena di ragionamenti sbagliati per cercare di giustificare l'avvertimento. Quindi, finalmente, avviene questo scambio:

Giorgio

lo so ma:

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

Bene, è esattamente come dovrebbe funzionare.

Giorgio

ma non è consentito :(

Rasmus

Cosa non è permesso?

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

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

cB::A();

Funziona bene. Ovviamente non puoi chiamare self :: B (), ma static :: B () va bene.

L'affermazione di Rasmus che il codice nel suo esempio "funziona bene" è falsa; come sai, genera un avviso di modalità rigorosa. Immagino che stesse testando senza la modalità rigorosa attivata. Indipendentemente da ciò, un Rasmus confuso ha lasciato la richiesta erroneamente chiusa come "fasulla".

Ed è per questo che l'avvertimento è ancora nella lingua. Questa potrebbe non essere una spiegazione del tutto soddisfacente: probabilmente sei venuto qui sperando che ci fosse una giustificazione razionale dell'avvertimento. Sfortunatamente, nel mondo reale, a volte le scelte nascono da errori banali e da un cattivo ragionamento piuttosto che da un processo decisionale razionale. Questa è semplicemente una di quelle volte.

Fortunatamente, la stimabile Nikita Popov ha rimosso l'avviso dal linguaggio in PHP 7 come parte degli avvisi PHP RFC: Reclassify E_STRICT . Alla fine, la sanità mentale ha prevalso e una volta rilasciato PHP 7 possiamo usarlo felicemente abstract staticsenza ricevere questo stupido avvertimento.


70

C'è una soluzione molto semplice per questo problema, che in realtà ha senso dal punto di vista del design. Come ha scritto Jonathan:

Lo stesso vale per l'estensione di qualsiasi classe con metodi statici. Se estendi quella classe e crei un metodo statico con la stessa firma, non stai effettivamente sovrascrivendo il metodo statico della superclasse

Quindi, per aggirare il problema potresti fare questo:

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

E ora imponi che qualsiasi sottoclasse di classe MyFoo implementi un metodo statico getInstance e un metodo getSomeData pubblico. E se non sottoclassi MyFoo, puoi comunque implementare iMyFoo per creare una classe con funzionalità simili.


2
È possibile con questo modello rendere la funzione protetta . Quando lo faccio, significa che le classi che estendono MyFoo lanciano avvisi che getInstance deve essere pubblica. E non puoi mettere protetto in una definizione di interfaccia.
artfulrobot

3
alcune volte static::possono essere utili.
visto il

3
Non funziona con i tratti. Se solo i tratti potessero avere abstract staticmetodi, senza che PHP si lamentasse ....
Rudie

1
L'uso di un'interfaccia è probabilmente la migliore soluzione qui. +1.
Juan Carlos Coto

Questo è in realtà molto elegante per la sua assoluta semplicità. +1
G. Stewart,

12

So che questo è vecchio ma ...

Perché non lanciare semplicemente un'eccezione al metodo statico della classe genitore, in questo modo se non lo si sovrascrive viene causata l'eccezione.


1
Ciò non aiuta, l'eccezione si verifica alla chiamata del metodo statico - nello stesso momento in cui viene visualizzato un errore di "metodo non esistente" se non lo si sovrascrive.
BT

3
@BT Volevo dire, non dichiarare il metodo astratto, implementarlo, ma semplicemente lanciare un'eccezione quando viene chiamato, il che significa che non lancerà se è stato sovrascritto.
Petah

Questa sembra essere la soluzione più elegante.
Alex S

Meglio vedere qualcosa di simile in fase di compilazione, che in fase di esecuzione. È più facile trovare il problema prima che siano in produzione perché devi solo caricare il file, non eseguire il codice per capire se è cattivo o non conforme
Rahly

4

Direi che una classe / interfaccia astratta potrebbe essere vista come un contratto tra programmatori. Si occupa maggiormente di come le cose dovrebbero apparire / comportarsi e non implementare la funzionalità effettiva. Come visto in php5.0 e 5.1.x non è una legge naturale che impedisce agli sviluppatori php di farlo, ma la voglia di andare d'accordo con altri modelli di progettazione OO in altri linguaggi. Fondamentalmente queste idee cercano di prevenire comportamenti imprevisti, se si ha già familiarità con altre lingue.


Anche se non PHP legati qui è un'altra buona spiegazione: stackoverflow.com/questions/3284/...
merkuro

3
Mio Dio! Le due persone che ti hanno svalutato sono totalmente fuori di testa! Questa è la risposta più perspicace su questo thread.
Theodore R. Smith

5
@ TheodoreR.Smith Insightful? Contiene errori ed è a malapena coerente. L'affermazione che le classi astratte "non implementano la funzionalità effettiva" non è necessariamente vera, e questo è l'intero punto in cui esse esistono in aggiunta alle interfacce. Nell'affermazione che "non è una legge naturale che impedisce agli sviluppatori php di farlo" , non ho idea di cosa sia "esso" . E nell'affermazione che "Fondamentalmente queste idee cercano di prevenire comportamenti inaspettati" , non ho idea di cosa siano "queste idee" o il potenziale "comportamento inaspettato". Qualunque intuizione tu abbia tratto da questo, è persa per me.
Mark Amery

2

Non vedo alcun motivo per vietare le funzioni astratte statiche. Il miglior argomento per cui non c'è motivo di proibirli è che sono consentiti in Java. Le domande sono: - Sono tecnicamente fattibili? - Sì, poiché esistevano in PHP 5.2 ed esistono in Java. Quindi possiamo farlo. DOBBIAMO farlo? - Hanno senso? Sì. Ha senso implementare una parte di una classe e lasciare un'altra parte di una classe all'utente. Ha senso nelle funzioni non statiche, perché non dovrebbe avere senso per le funzioni statiche? Un utilizzo delle funzioni statiche sono le classi in cui non deve esserci più di un'istanza (singleton). Ad esempio un motore di crittografia. Non è necessario che esista in diversi casi e ci sono ragioni per impedirlo - ad esempio, devi proteggere solo una parte della memoria dagli intrusi. Quindi ha perfettamente senso implementare una parte del motore e lasciare l'algoritmo di crittografia all'utente. Questo è solo un esempio. Se sei abituato a usare le funzioni statiche troverai molto di più.


3
La tua opinione sui metodi statici astratti esistenti nella 5.2 è fortemente fuorviante. In primo luogo, l'avvertimento della modalità rigorosa che li vieta è stato introdotto nella 5.2; fai sembrare che nella 5.2 fossero consentiti. In secondo luogo, nella 5.2 non potevano essere facilmente usati "per implementare una parte di una classe e lasciare un'altra parte di una classe all'utente" perché i Late Static Bindings non esistevano ancora.
Mark Amery

0

In php 5.4+ usa il tratto:

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

e nella tua classe metti all'inizio:

use StaticExample;

1
Sono stato in grado di inserire abstract public static function get_table_name();un tratto e utilizzare quel tratto all'interno della mia classe astratta senza più avvertimenti E_STRICT! Ciò imponeva ancora la definizione del metodo statico nei bambini, come avevo sperato. Fantastico!
Programster

-1

Esamina i problemi di "Late Static Binding" di PHP. Se stai inserendo metodi statici su classi astratte, probabilmente ti imbatterai in esso prima piuttosto che dopo. È logico che i severi avvertimenti ti dicano di evitare di utilizzare funzioni linguistiche non funzionanti.


4
Penso che intenda gli avvertimenti "Standard rigorosi".
Jacob Hume

2
Quali "problemi" affermi che hanno gli attacchi statici tardivi? Affermi che sono "rotte", che è un'affermazione audace, fatta qui senza prove o spiegazioni. La funzione ha sempre funzionato bene per me e penso che questo post non abbia senso.
Mark Amery
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.