Strict standard di PHP: la dichiarazione di dovrebbe essere compatibile

Ho la seguente gerarchia di classi:

class O_Base {...} class O extends O_Base {...} abstract class A_Abstract { public function save(O_Base $obj) {...} } class A extends A_Abstract { public function save(O $obj) { echo 'save!'; } } $o = new O; $a = new A; $a->save($o); 

Quando eseguo questo codice ottengo il messaggio:

Strict Standards: Dichiarazione di A :: save () dovrebbe essere compatibile con A_Abstract :: save (O_Base $ obj) in .php sulla row 21

Conosco il livello di errore E_STRICT ma non riesco a trovare (e capire) il motivo di tale comportmento. Qualcuno può aiutarmi?

    Il tuo codice è una chiara violazione del principio di sostituzione di Liskov . La class astratta richiede che un'istanza di O_Base venga passata al metodo di save , pertanto tutti i figli di A_Abstract dovrebbero essere definiti in modo tale che possano accettare tutte le istanze di O_Base . La class figlio implementa una versione di save che limita ulteriormente l'API. vedere un altro esempio qui

    Nel tuo codice, A viola il contratto che Abstract_A applica / descrive. Proprio come nella vita reale, se si firma un contratto, è necessario concordare determinati termini e concordare sulla terminologia che si intende utilizzare. Questo è il motivo per cui la maggior parte dei contratti inizia nominando le parti, e poi dicendo qualcosa sulla falsarow di "Henceforth Mr X sarà indicato come il dipendente" .
    Questi termini, come i tuoi suggerimenti di tipo astratto sono non-negoziabili , così che, più avanti, non puoi dire: "Oh, beh … quelle che chiami spese, chiamo salari standard"
    Ok, smetterò di usare queste analogie half-arsed, e basta usare un semplice esempio per illustrare perché quello che stai facendo è, giustamente, non permesso.

    Considera questo:

     abstract class Foo { abstract public function save(Guaranteed $obj); //or worse still: final public function doStuff(Guaranteed $obj) { $obj->setSomething('to string'); return $this->save($obj);//<====!!!! } } class FBar extends Foo { public function save(Guaranteed $obj) { return $obj->setFine(true); } } class FBar2 extends Foo { public function save(ChildOfGuaranteed $obj) {//FAIL: This method is required by Foo::doStuff to accept ALL instances of Guaranteed } } 

    Vedi qui, in questo caso la class astratta perfettamente valida chiama il metodo save con un'istanza di Guaranteed . Se ti è stato permesso di applicare un suggerimento tipo più severo nei bambini di questa class, potresti facilmente rompere questo metodo doStuff . Per aiutarti a proteggersi da questo tipo di ferite autoinflitte, le classi figlie non dovrebbero essere autorizzate a imporre tipi più severi sui methods che ereditano dalle loro classi genitore.
    Considerare anche lo scenario in cui eseguiamo il ciclo su alcune istanze e controlliamo se hanno questo metodo di save , in base al fatto che queste istanze sono instanceof Foo :

     $arg = new OtherChildOfGuaranteed; $arrays = arrays( 'fb' => new FBar, 'fb2' => new FBar2 ); foreach($arrays as $k => $class) { if ($class instanceof Foo) $class->save($arg); } 

    Ora functionrà bene se ti accenni a Guaranteed nella firma del metodo. Ma nel secondo caso, abbiamo reso il suggerimento del tipo un po 'troppo severo e questo codice si tradurrà in un errore fatale. Divertiti nel debugging in un progetto più complesso …

    PHP è molto clemente, se non troppo indulgente nella maggior parte dei casi, ma non qui. Invece di farti grattarti la testa fino a quando non sentirai cadere, PHP ti avvisa molto sensibilmente, dicendo che l'implementazione del tuo metodo viola il contratto, quindi devi risolvere questo problema.

    Ora la soluzione rapida (ed è anche uno di uso comune) sarebbe questa:

     class FBar2 extends Foo { /** * FBar2 save implementation requires instance of ChildOfGuaranteed * Signature enforced by Foo * @return Fbar2 * @throw InvalidArgumentException **/ public function save(Guaranteed $obj) { if (!$obj instanceof ChildOfGuaranteed) throw new InvalidArgumentException(__METHOD__.' Expects instance of ChildOfGuaranteed, you passed '.get_class($obj)); //do save here... } } 

    Quindi, lasci il tipo-suggerimento astratto così com'è , ma usa i docblock per documentare il tuo codice

    Vedi la differenza delle firme delle funzioni?

     // A_Abstract public function save(O_Base $obj) {...} // A public function save(O $obj) { 

    Non sono compatibili.

    Cambia A_Abstract in

     abstract class A_Abstract { public function save(O $obj) {...} } 

    Non import che O sia esteso da O_Base , sono due oggetti diversi, e poiché A estende A_Abstract le funzioni di salvataggio devono essere compatibili tra loro, il che significa che devi passare lo stesso object in, O

    Ho avuto un problema simile.

    Per me la spiegazione è molto più semplice, senza teorie fantastiche: in PHP il codice sopra riportto sta rendendo il metodo prioritario . Ma l'idea di tale codice nel mio caso era l' overloading dei methods che non è ansible in PHP in questo modo.

    Pertanto, per creare la logica richiesta (richiesta dalla propria applicazione ) è necessario utilizzare soluzioni alternative per rendere le firme dei methods compatibili (ereditando i tipi di parametri o rendendo facoltativi alcuni parametri).