Interfacce PHP 7, suggerimenti sul tipo di ritorno e self


89

AGGIORNAMENTO : PHP 7.4 ora supporta covarianza e controvarianza che risolvono il problema principale sollevato in questa domanda.


Ho riscontrato un problema con l'utilizzo dell'hinting del tipo di ritorno in PHP 7. La mia comprensione è che hinting : selfsignifica che si intende che una classe di implementazione ritorni da sola. Pertanto ho usato : selfnelle mie interfacce per indicarlo, ma quando ho provato a implementare effettivamente l'interfaccia ho ricevuto errori di compatibilità.

Quanto segue è una semplice dimostrazione del problema che ho riscontrato:

interface iFoo
{
    public function bar (string $baz) : self;
}

class Foo implements iFoo
{

    public function bar (string $baz) : self
    {
        echo $baz . PHP_EOL;
        return $this;
    }
}

(new Foo ()) -> bar ("Fred") 
    -> bar ("Wilma") 
    -> bar ("Barney") 
    -> bar ("Betty");

L'output previsto era:

Fred Wilma Barney Betty

Quello che ottengo effettivamente è:

Errore irreversibile PHP: Dichiarazione di Foo :: bar (int $ baz): Foo deve essere compatibile con iFoo :: bar (int $ baz): iFoo in test.php alla riga 7

Il fatto è che Foo è un'implementazione di iFoo, quindi per quanto ne so l'implementazione dovrebbe essere perfettamente compatibile con l'interfaccia data. Presumibilmente potrei risolvere questo problema modificando l'interfaccia o la classe di implementazione (o entrambi) per restituire un suggerimento sull'interfaccia per nome invece di utilizzarlo self, ma la mia comprensione è che semanticamente selfsignifica "restituire l'istanza della classe su cui hai appena chiamato ". Pertanto, cambiarlo nell'interfaccia significherebbe in teoria che potrei restituire qualsiasi istanza di qualcosa che implementa l'interfaccia quando il mio intento è che l'istanza invocata sia ciò che verrà restituito.

È una svista in PHP o è una decisione di progettazione deliberata? Se è il primo, c'è qualche possibilità di vederlo corretto in PHP 7.1? In caso contrario, qual è il modo corretto di restituire suggerendo che la tua interfaccia si aspetta che tu restituisca l'istanza su cui hai appena chiamato il metodo per il concatenamento?


Penso che sia un errore nel suggerimento del tipo restituito da PHP, forse dovresti sollevarlo come un bug ; ma è improbabile che qualsiasi correzione entri in PHP 7.1 in questa fase
avanzata

Dato che l'ultima versione beta di 7.1 è andata online pochi giorni fa, è molto improbabile che qualsiasi correzione possa arrivare alla 7.1.
Charlotte Dunois

Per interesse, dove leggi la tua interpretazione di come selfdovrebbe funzionare il tipo di reso?
Adam Cameron

@Adam: Sembra solo logico selfche significhi "Restituisci l'istanza su cui hai chiamato questo, e non qualche altra istanza che implementa la stessa interfaccia". Mi sembra di ricordare che Java aveva un tipo di ritorno simile (anche se è passato un po 'di tempo da quando ho programmato Java)
GordonM

1
Ciao Gordon. Beh, a meno che non sia documentato per funzionare da qualche parte, non conterei su ciò che potrebbe essere logico essere la realtà. TBH con la situazione che descriverei, sarei il più dichiarativo possibile e userei iFoo come tipo di ritorno. C'è una situazione in cui ciò non funzionerà effettivamente? (Mi rendo conto che questo è "consiglio" / opinione piuttosto che "una risposta", detto questo.
Adam Cameron

Risposte:


94

selfnon si riferisce all'istanza, si riferisce alla classe corrente. Non è possibile che un'interfaccia specifichi che deve essere restituita la stessa istanza : utilizzare selfnel modo in cui si sta tentando si imporrebbe solo che l'istanza restituita sia della stessa classe.

Detto questo, le dichiarazioni del tipo di ritorno in PHP devono essere invarianti mentre ciò che stai tentando è covariante.

Il tuo utilizzo selfè equivalente a:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : Foo  {...}
}

che non è consentito.


Le dichiarazioni del tipo di ritorno RFC hanno questo da dire :

L'applicazione del tipo restituito dichiarato durante l'ereditarietà è invariante; questo significa che quando un sottotipo sovrascrive un metodo genitore, il tipo restituito del figlio deve corrispondere esattamente al genitore e non può essere omesso. Se il genitore non dichiara un tipo restituito, il figlio può dichiararne uno.

...

Questa RFC originariamente proponeva tipi restituiti covarianti, ma è stata modificata in invariante a causa di alcuni problemi. È possibile aggiungere tipi restituiti covarianti in futuro.


Per il momento almeno il meglio che puoi fare è:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}

19
Detto questo, mi sarei aspettato un suggerimento del tipo di ritorno staticper funzionare, ma non è nemmeno riconosciuto
Mark Baker

Ho pensato che sarebbe stato così, ed è così che risolverò il problema. Tuttavia, avrei preferito usare solo: self se possibile perché se una classe sta implementando un'interfaccia, un valore di ritorno di self è implicitamente anche un valore di ritorno di un'istanza dell'interfaccia.
GordonM

19
Paul, eliminare i commenti che hai eliminato qui è effettivamente dannoso perché (A) perde informazioni importanti e (B) interrompe il flusso di discussione relativo agli altri commenti. Non vedo alcun motivo per cui i tuoi commenti relativi a Mark e Gordon dovessero essere eliminati. In effetti, lo stai facendo dappertutto e deve finire. Non c'è assolutamente alcuna buona ragione per tornare a una domanda di un anno e rimuovere tutti i tuoi commenti, distruggendo completamente il flusso della discussione. In effetti, è dannoso e dirompente.
Cody Grey

C'è un'importante prefazione a una parte della tua citazione qui: "I tipi restituiti covarianti sono considerati suoni di tipo e sono usati in molti altri linguaggi (C ++ e Java ma non C # credo). Questa RFC originariamente proponeva tipi restituiti covarianti ma è stato modificato in invariante a causa di alcuni problemi. ". Sono curioso di sapere quali problemi ha avuto PHP. La loro scelta progettuale causa diverse limitazioni che causano anche strani problemi con i tratti, rendendoli in qualche modo inutili in molti casi a meno che non si allentino le restrizioni sul tipo di ritorno (come si vede in alcune risposte di seguito). Molto frustrante.
John Pancoast

1
@MarkBaker il tipo di ritorno staticviene aggiunto a PHP 8.
Ricardo Boss

16

Può anche essere una soluzione, che non si definisce esplicitamente il tipo di ritorno nell'interfaccia, solo nel PHPDoc e quindi è possibile definire il determinato tipo di ritorno nelle implementazioni:

interface iFoo
{
    public function bar (string $baz);
}

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}

2
O invece di Foousare solo self.
invece il

2

Nel caso in cui, quando vuoi forzare dall'interfaccia, quel metodo restituirà oggetto, ma il tipo di oggetto non sarà il tipo di interfaccia, ma la classe stessa, allora puoi scriverlo in questo modo:

interface iFoo {
    public function bar (string $baz) : object;
}

class Foo implements iFoo {
    public function bar (string $baz) : self  {...}
}

Funziona da PHP 7.4.



0

Questo mi sembra il comportamento previsto.

Cambia semplicemente il tuo Foo::barmetodo per tornare iFooinvece di selfe basta.

Spiegazione:

selfcome usato nell'interfaccia significa "un oggetto di tipo iFoo".
selfcome usato nell'implementazione significa "un oggetto di tipo Foo".

Pertanto, i tipi restituiti nell'interfaccia e nell'implementazione chiaramente non sono gli stessi.

Uno dei commenti menziona Java e se avresti questo problema. La risposta è sì, avresti lo stesso problema se Java ti permettesse di scrivere codice come quello, cosa che non fa. Dato che Java richiede di usare il nome del tipo invece del selfcollegamento di PHP , non lo vedrai mai. (Vedi qui per una discussione su un problema simile in Java.)


Quindi dichiarare è selfsimile a dichiarare MyClass::class?
peterchaula

1
@ Laser Sì, lo è.
Moshe Katz

4
Ma se Foo implementa iFoo, Foo è per definizione di tipo iFoo
GordonM
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.