Interfaces PHP 7, indication de type de retour et self

89

MISE À JOUR : PHP 7.4 prend désormais en charge la covariance et la contravariance, ce qui résout le problème majeur soulevé dans cette question.


J'ai rencontré un problème avec l'utilisation de l'indication de type de retour dans PHP 7. Je crois comprendre que l'indication : selfsignifie que vous avez l'intention qu'une classe d'implémentation se renvoie elle-même. Par conséquent, j'ai utilisé : selfdans mes interfaces pour indiquer cela, mais lorsque j'ai essayé d'implémenter réellement l'interface, j'ai eu des erreurs de compatibilité.

Ce qui suit est une simple démonstration du problème que j'ai 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 j'obtiens en fait, c'est:

Erreur fatale PHP: 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 est que Foo est une implémentation d'iFoo, donc pour autant que je sache, l'implémentation doit être parfaitement compatible avec l'interface donnée. Je pourrais probablement 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 son nom au lieu de l'utiliser self, mais je crois comprendre que cela selfsignifie sémantiquement "renvoyer l'instance de la classe sur laquelle vous venez d'appeler la méthode ". Par conséquent, le changer pour l'interface signifierait en théorie que je pourrais retourner n'importe quelle instance de quelque chose qui implémente l'interface lorsque mon intention est que l'instance invoquée est ce qui sera retourné.

Est-ce un oubli de PHP ou est-ce une décision délibérée de conception? Si c'est le premier, y a-t-il une chance de le voir corrigé en PHP 7.1? Si ce n'est pas le cas, quelle est la manière correcte de renvoyer en 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?

GordonM
la source
Je pense que c'est une erreur dans l'indication de type de retour PHP, vous devriez peut-être le signaler comme un bogue ; mais il est peu probable qu'aucun correctif n'entre dans PHP 7.1 à ce stade tardif
Mark Baker
Depuis la mise en ligne de la dernière version bêta de 7.1 il y a quelques jours, il est très peu probable qu'un correctif entre dans 7.1.
Charlotte Dunois
Par intérêt, où lisez-vous votre interprétation de la façon dont le selftype de retour est censé fonctionner?
Adam Cameron
@Adam: Il semble juste que ce soit logique pour selfsignifier "Renvoyez l'instance sur laquelle vous avez appelé ceci, et non une autre instance qui implémente la même interface". Je semble me souvenir que Java avait un type de retour similaire (même si cela fait un moment que je n'ai pas fait de programmation Java)
GordonM
1
Salut Gordon. Eh bien, à moins que ce soit documenté pour fonctionner quelque part, je ne compterais pas sur ce qui pourrait être logique étant la réalité. TBH avec la situation que je décrirais, je serais juste aussi déclaratif que possible et utiliserais iFoo comme type de retour. Y a-t-il une situation dans laquelle cela ne fonctionnera pas réellement? (Je me rends compte que c'est un "conseil" / une opinion plutôt qu'une "réponse", cela dit.
Adam Cameron

Réponses:

94

selfne fait pas référence à l'instance, il fait référence à la classe courante. Il n'y a aucun moyen pour une interface de spécifier que la même instance doit être retournée - l'utilisation selfde la manière que vous essayez ne ferait que forcer l'instance renvoyée à être de la même classe.

Cela dit, les déclarations de type de retour en PHP doivent être invariantes alors que ce que vous essayez 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 RFC des déclarations de type de retour a ceci à dire :

L'application du type de retour déclaré lors de 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 au parent et ne peut pas être omis. Si le parent ne déclare pas de type de retour, l'enfant est autorisé à en déclarer un.

...

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


Pour le moment, au moins, le mieux que vous puissiez faire est:

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

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}
user3942918
la source
18
Cela dit, je me serais attendu à un indice de type de retour staticau travail, mais ce n'est même pas reconnu
Mark Baker
J'ai pensé que ce serait le cas, et c'est ainsi que je vais résoudre le problème. J'aurais cependant préféré utiliser simplement: self si possible car si une classe implémente une interface, une valeur de retour de self est également implicitement une valeur de retour d'une instance de l'interface.
GordonM
19
Paul, supprimer les commentaires que vous avez supprimés ici est en fait nuisible car (A) cela perd des informations importantes, et (B) cela perturbe le flux de la discussion par rapport aux autres commentaires. Je ne vois aucune raison pour laquelle vos commentaires relatifs à Mark et Gordon devaient être supprimés. En fait, vous faites cela partout, et cela doit s'arrêter. Il n'y a absolument aucune bonne raison de revenir à une question vieille d'un an et de supprimer tous vos commentaires, détruisant complètement le flux de la discussion. En fait, c'est nuisible et perturbateur.
Cody Gray
Il y a une préface importante à une partie de votre citation vue ici: "Les types de retour covariants sont considérés comme du type son et sont utilisés dans de nombreux autres langages (C ++ et Java mais pas C # je crois). Cette RFC proposait à l'origine des types de retour covariants mais a été changé en invariant en raison de quelques problèmes. ". Je suis curieux des problèmes rencontrés par PHP. Leur choix de conception entraîne plusieurs limitations qui causent également des problèmes étranges avec les traits, les rendant quelque peu inutiles dans de nombreux cas, à moins que vous ne desserriez les restrictions de type de retour (comme indiqué dans certaines réponses ci-dessous). Très frustrant.
John Pancoast le
1
@MarkBaker le type de retour staticest ajouté à PHP 8.
Ricardo Boss
16

Cela peut également être une solution, que vous ne définissiez pas explicitement le type de retour dans l'interface, uniquement dans le PHPDoc et que vous puissiez ensuite 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  {...}
}
Gabor
la source
2
Ou au lieu de Foosimplement utiliser self.
au lieu du
2

Dans le cas où vous souhaitez forcer depuis l'interface, cette méthode retournera un objet, mais le type d'objet ne sera pas le type d'interface, mais la classe elle-même, alors 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.4.

au lieu
la source
0

Cela ressemble au comportement attendu pour moi.

Changez simplement votre Foo::barméthode pour revenir iFooau lieu de selfet en finir avec elle.

Explication:

selftel qu'utilisé dans l'interface signifie «un objet de type iFoo».
selftel qu'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.

L'un des commentaires mentionne Java et indique si vous rencontrez ce problème. La réponse est oui, vous auriez le même problème si Java vous permettait d'écrire du code comme celui-là - ce qui n'est pas le cas. Puisque Java vous oblige à utiliser le nom du type au lieu du selfraccourci PHP , vous ne verrez jamais cela. (Voir ici pour une discussion d'un problème similaire en Java.)

Moshe Katz
la source
Alors, est-ce que déclarer est selfsimilaire à déclarer MyClass::class?
peterchaula
1
@Laser Oui, ça l'est.
Moshe Katz
4
Mais si Foo implémente iFoo, Foo est par définition de type iFoo
GordonM