web-dev-qa-db-fra.com

PHP 7 interfaces, indication de type de retour et self

J'ai rencontré un problème avec l'utilisation de l'indicateur de type de retour dans PHP 7. D'après ma compréhension, l'indication : self signifie que vous souhaitez qu'une classe d'implémentation renvoie elle-même. J'ai donc utilisé : self dans mes interfaces pour indiquer cela, mais lorsque j'ai essayé d'implémenter réellement l'interface, des erreurs de compatibilité se sont produites. 

Voici une démonstration simple du problème rencontré: 

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

Le résultat attendu était: 

Fred Wilma Barney Betty

Ce que je reçois réellement est: 

Erreur PHP fatale: Déclaration de Foo :: bar (int $ baz): Foo doit être compatible avec iFoo :: bar (int $ baz): iFoo dans test.php à la ligne 7

Le truc, c'est que Foo est une implémentation d'iFoo, donc autant que je sache, l'implémentation devrait être parfaitement compatible avec l'interface donnée. Je pourrais vraisemblablement résoudre ce problème en modifiant soit l'interface, soit la classe d'implémentation (ou les deux) pour renvoyer une indication de l'interface par nom au lieu d'utiliser self, mais ma compréhension est que sémantiquement self signifie "renvoyer l'instance de la classe que vous venez d'appeler le méthode sur ". Par conséquent, le changer pour l'interface signifierait en théorie que je pourrais renvoyer n'importe quelle instance de quelque chose qui implémente l'interface lorsque mon intention est de l'instance invoquée. 

S'agit-il d'un oubli dans PHP ou s'agit-il d'une décision de conception délibérée? Si c'est l'ancien, y a-t-il une chance de le voir résolu dans PHP 7.1? Si ce n'est pas le cas, quelle est la bonne façon de retourner, indiquant que votre interface s'attend à ce que vous retourniez l'instance sur laquelle vous venez d'appeler la méthode pour le chaînage? 

55
GordonM

self ne fait pas référence à l'instance, il fait référence à la classe actuelle. Il n’existe aucun moyen pour une interface de spécifier que le même instance doit être renvoyé - utiliser self de la manière que vous tentez de faire en sorte que l’instance renvoyée soit de la même classe.

Cela dit, les déclarations de type de retour dans PHP doivent être invariantes, alors que ce que vous tentez de faire est covariant.

Votre utilisation de self équivaut à:

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

class Foo implements iFoo
{

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

ce qui n'est pas autorisé.


La déclaration de type de retour RFC a cela signifie :

L'application du type de retour déclaré pendant l'héritage est invariante; Cela signifie que lorsqu'un sous-type remplace une méthode parent, le type de retour de l'enfant doit correspondre exactement à celui de la mère et ne peut pas être omis. Si le parent ne déclare pas un type de retour, l'enfant est autorisé à en déclarer un.

...

Cette RFC proposait à l'origine des types de retour covariants, mais a été changée en invariant en raison de quelques problèmes. Il est possible d’ajouter des types de retour covariants à un moment donné dans l’avenir.


Pour le moment, au mieux, vous pouvez faire:

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

class Foo implements iFoo
{

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

Il peut également s'agir d'une solution, à savoir que vous ne définissiez pas explicitement le type de retour dans l'interface, mais uniquement dans le PHPDoc, puis que vous puissiez définir le type de retour dans les implémentations:

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

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

Au cas où, lorsque vous souhaitez forcer l'interface, cette méthode renverra un objet, mais le type d'objet ne sera pas le type d'interface, mais la classe elle-même, vous pouvez l'écrire de cette façon:

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

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

Cela fonctionne depuis PHP 7.2.

0
instead

Cela ressemble au comportement attendu pour moi.

Modifiez simplement votre méthode Foo::bar pour renvoyer iFoo au lieu de self et en finir.

Explication:

self tel qu'utilisé dans l'interface signifie "un objet de type iFoo."
self utilisé dans l'implémentation signifie "un objet de type Foo."

Par conséquent, les types de retour dans l'interface et l'implémentation ne sont clairement pas les mêmes.

Un des commentaires mentionne Java et si vous auriez ce problème. La réponse est oui, vous auriez le même problème si Java vous permettait d'écrire un code comme celui-ci - ce qui n'est pas le cas. Puisque Java requiert que vous utilisiez le nom du type à la place du raccourci self de PHP, vous ne le verrez jamais réellement. (Voir ici pour une discussion sur un problème similar en Java.)

0
Moshe Katz

Faire quelques tests avec PHP 7.3, je ne peux pas me plaindre (même avec strict) quand je fais ça ...

interface A {
 function f() {}
}

interface B {
 function f():self {}
}

Mon test est rompu ou PHP a changé certaines choses. De manière générale, si vous réduisez les types de retour possibles, cela tend à ne pas rompre la POO. Comme dans toute classe, cette méthode peut gérer tout retour, y compris un sous-ensemble de types de retour. L'inverse est à peu près le cas pour les paramètres.

Ils l'ont implémenté en 7.2.

0
jgmjgm