PHP 7-Schnittstellen, Rückgabetyp und Self

87

UPDATE : PHP 7.4 unterstützt jetzt Kovarianz und Kontravarianz, wodurch das in dieser Frage aufgeworfene Hauptproblem behoben wird.


Ich bin auf ein Problem mit der Verwendung von Hinweisen zum Rückgabetyp in PHP 7 gestoßen. Mein Verständnis ist, dass Hinweise : selfbedeuten, dass Sie beabsichtigen, dass eine implementierende Klasse sich selbst zurückgibt. Daher habe ich : selfin meinen Schnittstellen darauf hingewiesen, aber als ich versuchte, die Schnittstelle tatsächlich zu implementieren, bekam ich Kompatibilitätsfehler.

Das Folgende ist eine einfache Demonstration des Problems, auf das ich gestoßen bin:

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");

Die erwartete Ausgabe war:

Fred Wilma Barney Betty

Was ich tatsächlich bekomme ist:

Schwerwiegender PHP-Fehler: Deklaration von Foo :: bar (int $ baz): Foo muss mit iFoo :: bar (int $ baz) kompatibel sein: iFoo in test.php in Zeile 7

Die Sache ist, dass Foo eine Implementierung von iFoo ist, soweit ich das beurteilen kann, sollte die Implementierung perfekt mit der angegebenen Schnittstelle kompatibel sein. Vermutlich könnte ich dieses Problem beheben, indem ich entweder die Schnittstelle oder die implementierende Klasse (oder beide) so ändere, dass ein Hinweis auf die Schnittstelle nach Namen zurückgegeben wird, anstatt sie zu verwendenself . Mein Verständnis ist jedoch, dass semantisch self"die Instanz der Klasse zurückgeben, für die Sie gerade die Methode aufgerufen haben" bedeutet ". Eine Änderung an der Schnittstelle würde daher theoretisch bedeuten, dass ich jede Instanz von etwas zurückgeben könnte, die die Schnittstelle implementiert, wenn meine Absicht für die aufgerufene Instanz ist, was zurückgegeben wird.

Ist dies ein Versehen in PHP oder ist dies eine bewusste Designentscheidung? Wenn es das erstere ist, gibt es eine Chance, es in PHP 7.1 behoben zu sehen? Wenn nicht, was ist dann die richtige Art der Rückgabe, die darauf hindeutet, dass Ihre Schnittstelle erwartet, dass Sie die Instanz zurückgeben, für die Sie gerade die Methode zur Verkettung aufgerufen haben?

GordonM
quelle
Ich denke, es ist ein Fehler in der PHP-Rückgabe Typ-Hinweis, vielleicht sollten Sie es als Fehler auslösen ; Es ist jedoch unwahrscheinlich, dass ein Fix zu diesem späten Zeitpunkt in PHP 7.1 aufgenommen wird
Mark Baker,
Da die letzte Beta-Version von 7.1 vor einigen Tagen online gegangen ist, ist es sehr unwahrscheinlich, dass eine Korrektur in 7.1 erfolgt.
Charlotte Dunois
Wo lesen Sie aus Interesse Ihre Interpretation, wie der selfRückgabetyp funktionieren soll?
Adam Cameron
@Adam: Es scheint nur logisch selfzu sein, "die Instanz zurückzugeben, auf der Sie dies aufgerufen haben, und nicht irgendeine andere Instanz, die dieselbe Schnittstelle implementiert". Ich scheine mich zu erinnern, dass Java einen ähnlichen Rückgabetyp hatte (obwohl es eine Weile her ist, seit ich Java programmiert habe)
GordonM
1
Hallo Gordon. Nun, wenn nicht dokumentiert ist, dass es irgendwo funktioniert, würde ich nicht damit rechnen, was logisch sein könnte, wenn es Realität ist. TBH mit der Situation, die ich beschreiben würde, wäre ich nur so deklarativ wie möglich und würde iFoo als Rückgabetyp verwenden. Gibt es eine Situation, in der das nicht funktioniert? (Mir ist klar, dass dies eher "Rat" / Meinung als "eine Antwort" ist.
Adam Cameron

Antworten:

94

selfbezieht sich nicht auf die Instanz, sondern auf die aktuelle Klasse. Es gibt keine Möglichkeit für eine Schnittstelle anzugeben, dass dieselbe Instanz zurückgegeben werden muss. selfWenn Sie auf die von Ihnen versuchte Weise verwenden, wird nur erzwungen, dass die zurückgegebene Instanz derselben Klasse angehört.

Das heißt, Rückgabetypdeklarationen in PHP müssen unveränderlich sein, während das, was Sie versuchen, kovariant ist.

Ihre Verwendung von selfentspricht:

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

class Foo implements iFoo
{

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

was nicht erlaubt ist.


Der Return Type Declarations RFC hat Folgendes zu sagen :

Die Durchsetzung des deklarierten Rückgabetyps während der Vererbung ist unveränderlich. Dies bedeutet, dass, wenn ein Untertyp eine übergeordnete Methode überschreibt, der Rückgabetyp des untergeordneten Elements genau mit dem übergeordneten übereinstimmen muss und nicht weggelassen werden darf. Wenn der Elternteil keinen Rückgabetyp deklariert, darf das Kind einen deklarieren.

...

Dieser RFC schlug ursprünglich kovariante Rückgabetypen vor, wurde jedoch aufgrund einiger Probleme in invariant geändert. Es ist möglich, zu einem späteren Zeitpunkt kovariante Rückgabetypen hinzuzufügen.


Zumindest das Beste, was Sie vorerst tun können, ist:

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

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}
user3942918
quelle
18
Das heißt, ich hätte erwartet, dass ein Rückgabetyp staticfunktioniert, aber er wird nicht einmal erkannt
Mark Baker
Ich dachte mir, dass dies der Fall sein würde, und so werde ich das Problem lösen. Ich hätte es jedoch vorgezogen, wenn möglich nur: self zu verwenden, denn wenn eine Klasse eine Schnittstelle implementiert, ist ein Rückgabewert von self implizit auch ein Rückgabewert einer Instanz der Schnittstelle.
GordonM
19
Paul, das Löschen der Kommentare, die Sie hier gelöscht haben, ist tatsächlich schädlich, weil (A) wichtige Informationen verloren gehen und (B) der Diskussionsfluss in Bezug auf die anderen Kommentare gestört wird. Ich kann keinen Grund erkennen, warum Ihre Kommentare zu Mark und Gordon gelöscht werden mussten. Tatsächlich machst du das überall und es muss aufhören. Es gibt absolut keinen guten Grund, zu einer einjährigen Frage zurückzukehren und alle Ihre Kommentare zu entfernen, wodurch der Diskussionsfluss vollständig zerstört wird. In der Tat ist es schädlich und störend.
Cody Gray
Es gibt ein wichtiges Vorwort zu einem Teil Ihres Zitats, das hier zu sehen ist: " Kovariante Rückgabetypen werden als Typensound betrachtet und in vielen anderen Sprachen verwendet (C ++ und Java, aber nicht C #, glaube ich). Dieser RFC schlug ursprünglich kovariante Rückgabetypen vor, aber wurde aufgrund einiger Probleme in invariant geändert. " Ich bin gespannt, welche Probleme PHP hatte. Ihre Designauswahl führt zu mehreren Einschränkungen, die auch zu merkwürdigen Problemen mit Merkmalen führen, wodurch sie in vielen Fällen etwas unbrauchbar werden, es sei denn, Sie lösen die Einschränkungen für den Rückgabetyp (wie in einigen Antworten unten dargestellt). Sehr frustrierend.
John Pancoast
@ MarkBaker Der Rückgabetyp staticwird zu PHP 8 hinzugefügt.
Ricardo Boss
15

Es kann auch eine Lösung sein, dass Sie den Rückgabetyp nicht explizit in der Schnittstelle definieren, sondern nur im PHPDoc und dann den bestimmten Rückgabetyp in den Implementierungen definieren:

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

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}
Gabor
quelle
2
Oder anstatt Foonur zu benutzen self.
Stattdessen
1

Wenn Sie von der Schnittstelle erzwingen möchten, gibt diese Methode ein Objekt zurück, aber der Objekttyp ist nicht der Schnittstellentyp, sondern die Klasse selbst. Dann können Sie es folgendermaßen schreiben:

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

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

Es funktioniert seit PHP 7.4.

stattdessen
quelle
0

Das sieht für mich nach dem erwarteten Verhalten aus.

Ändern Sie einfach Ihre Foo::barMethode, um iFoostatt zurückzukehren, selfund fertig.

Erläuterung:

selfwie in der Schnittstelle verwendet bedeutet "ein Objekt vom Typ iFoo".
selfwie in der Implementierung verwendet bedeutet "ein Objekt vom Typ Foo".

Daher sind die Rückgabetypen in der Schnittstelle und der Implementierung eindeutig nicht identisch.

In einem der Kommentare wird Java erwähnt und ob Sie dieses Problem haben würden. Die Antwort lautet: Ja, Sie hätten das gleiche Problem, wenn Java Ihnen erlauben würde , solchen Code zu schreiben - was nicht der Fall ist. Da Java erfordert, dass Sie den Namen des Typs anstelle der PHP- selfVerknüpfung verwenden, werden Sie dies nie wirklich sehen. (Siehe hier für eine Diskussion eines ähnlichen Problems in Java.)

Moshe Katz
quelle
Ist das Deklarieren selfähnlich wie das Deklarieren MyClass::class?
Peterchaula
1
@ Laser Ja, das ist es.
Moshe Katz
3
Aber wenn Foo iFoo implementiert, dann ist Foo per Definition vom Typ iFoo
GordonM