Lassen Sie uns für eine Sekunde ignorieren, dass es sich um die betreffende Methode handelt, __constructund rufen Sie sie auf frobnicate. Angenommen, Sie haben ein Objekt apiimplementiert IHttpApiund ein Objekt configimplementiert IHttpConfig. Dieser Code passt eindeutig zur Benutzeroberfläche:
$api->frobnicate($config)
Aber nehmen wir apian IApi, wir sind verärgert , um es zum Beispiel weiterzugeben function frobnicateTwice(IApi $api). Nun wird in dieser Funktion frobnicateaufgerufen, und da es sich nur IApium eine Funktion handelt , kann es einen Aufruf ausführen, wie beispielsweise $api->frobnicate(new SpecificConfig(...))where SpecificConfigimplementiert, IConfigaber nicht IHttpConfig. Zu keinem Zeitpunkt tat jemand etwas Unangenehmes mit Typen, IHttpApi::frobnicatebekam aber einen, SpecificConfigwo er einen erwartete IHttpConfig.
Das ist nicht gut. Wir wollen Upcasting nicht verbieten, wir wollen Subtyping und wir wollen eindeutig mehrere Klassen, die eine Schnittstelle implementieren. Daher ist die einzig sinnvolle Option, eine Subtyp-Methode zu verbieten, die spezifischere Typen für Parameter erfordert . (Ein ähnliches Problem tritt auf, wenn Sie wollen zurückkehren einen allgemeinen Typ.)
Formal sind Sie in eine klassische Falle geraten, die sich mit Polymorphismus und Varianz befasst . Nicht alle Vorkommen eines Typs Tkönnen durch einen Untertyp ersetzt werden U. Umgekehrt können nicht alle Vorkommen eines Typs Tdurch einen Supertyp ersetzt werden S. Sorgfältige Überlegungen (oder noch besser strikte Anwendung der Typentheorie) sind erforderlich.
Zurück zu __construct: Da Sie mit AFAIK eine Schnittstelle nicht genau instanziieren können, sondern nur einen konkreten Implementierer, scheint dies eine sinnlose Einschränkung zu sein (sie wird niemals über eine Schnittstelle aufgerufen). Aber warum sollte man in diesem Fall zunächst __constructin die Benutzeroberfläche aufnehmen? Unabhängig davon wäre es hier von geringem Nutzen, Sonderfälle zu machen __construct.