Ignoriamo per un secondo che il metodo in questione è __construct e chiamalo frobnicate . Supponiamo ora di avere un oggetto api che implementa IHttpApi e un oggetto config che implementa IHttpConfig . Chiaramente, questo codice si adatta all'interfaccia:
$api->frobnicate($config)
Ma supponiamo di raddoppiare api a IApi , ad esempio passandolo a function frobnicateTwice(IApi $api) . Ora in quella funzione, viene chiamato frobnicate , e poiché si occupa solo di IApi , può eseguire una chiamata come $api->frobnicate(new SpecificConfig(...)) dove SpecificConfig implementa IConfig ma non IHttpConfig . In nessun momento nessuno ha fatto nulla di sgradevole con i tipi, eppure IHttpApi::frobnicate ha ottenuto un SpecificConfig dove si aspettava un IHttpConfig .
Questo non va bene. Non vogliamo proibire l'upcasting, vogliamo sottotitolare e vogliamo chiaramente più classi che implementino un'interfaccia. Quindi l'unica opzione sensata è proibire un metodo di sottotipo che richiede tipi più specifici per i parametri. (Un problema simile si verifica quando si desidera restituire un altro tipo generale .)
Formalmente, sei entrato in una classica trappola che circonda il polimorfismo, varianza . Non tutte le occorrenze di un tipo T possono essere sostituite da un sottotipo U . Al contrario, non tutte le occorrenze di un tipo T possono essere sostituite da un supertype S . È necessaria un'attenta considerazione (o, meglio ancora, una rigorosa applicazione della teoria dei tipi).
Tornando a __construct : Poiché AFAIK non è possibile creare un'istanza esattamente come un'interfaccia, solo un implementatore concreto, può sembrare una limitazione inutile (non verrà mai chiamata attraverso un'interfaccia). Ma in tal caso, perché includere __construct nell'interfaccia per cominciare? Indipendentemente da ciò, sarebbe di scarsa utilità per caso speciale __construct qui.