Interfaces PHP 7, sugerencias de tipo de retorno y self

89

ACTUALIZACIÓN : PHP 7.4 ahora admite covarianza y contravarianza, lo que aborda el problema principal planteado en esta pregunta.


Me he encontrado con un problema con el uso de la sugerencia de tipo de retorno en PHP 7. Tengo entendido que la sugerencia : selfsignifica que tiene la intención de que una clase de implementación regrese a sí misma. Por lo tanto, usé : selfen mis interfaces para indicar eso, pero cuando intenté implementar la interfaz, obtuve errores de compatibilidad.

La siguiente es una demostración simple del problema con el que me he encontrado:

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

El resultado esperado fue:

Fred Wilma Barney Betty

Lo que realmente obtengo es:

Error fatal de PHP: Declaración de Foo :: bar (int $ baz): Foo debe ser compatible con iFoo :: bar (int $ baz): iFoo en test.php en la línea 7

La cosa es que Foo es una implementación de iFoo, por lo que puedo decir, la implementación debería ser perfectamente compatible con la interfaz dada. Presumiblemente podría solucionar este problema cambiando la interfaz o la clase de implementación (o ambas) para devolver la pista a la interfaz por nombre en lugar de usar self, pero tengo entendido que semánticamente selfsignifica "devolver la instancia de la clase en la que acaba de llamar al método ". Por lo tanto, cambiarlo a la interfaz significaría en teoría que podría devolver cualquier instancia de algo que implemente la interfaz cuando mi intención es que la instancia invocada sea lo que se devolverá.

¿Es esto un descuido en PHP o es una decisión de diseño deliberada? Si es lo primero, ¿hay alguna posibilidad de que se solucione en PHP 7.1? Si no es así, ¿cuál es la forma correcta de devolver la sugerencia de que su interfaz espera que devuelva la instancia en la que acaba de llamar al método para encadenar?

GordonM
fuente
Creo que es un error en la sugerencia de tipo de retorno de PHP, quizás debería plantearlo como un error ; pero es poco probable que alguna solución llegue a PHP 7.1 en esta última etapa
Mark Baker
Dado que la última versión beta de 7.1 se puso en línea hace unos días, es muy poco probable que alguna solución llegue a 7.1.
Charlotte Dunois
Por interés, ¿dónde está leyendo su interpretación de cómo selfse supone que funciona el tipo de devolución?
Adam Cameron
@Adam: Parece lógico selfque signifique "Devuelve la instancia a la que llamaste , y no otra instancia que implemente la misma interfaz". Creo recordar que Java tenía un tipo de retorno similar (aunque ha pasado un tiempo desde que hice cualquier programación en Java)
GordonM
1
Hola Gordon. Bueno, a menos que esté documentado para funcionar en algún lugar, no contaría con que lo que podría ser lógico sea la realidad. TBH con la situación que describiría, sería lo más declarativo posible y usaría iFoo como tipo de retorno. ¿Hay alguna situación en la que eso no funcione realmente? (Me doy cuenta de que esto es un "consejo" / opinión en lugar de "una respuesta", dicho eso.
Adam Cameron

Respuestas:

94

selfno se refiere a la instancia, se refiere a la clase actual. No hay forma de que una interfaz especifique que se debe devolver la misma instancia ; si lo usa selfde la manera que está intentando, solo se impondrá que la instancia devuelta sea de la misma clase.

Dicho esto, las declaraciones de tipo de retorno en PHP deben ser invariantes, mientras que lo que está intentando es covariante.

Su uso de selfes equivalente a:

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

class Foo implements iFoo
{

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

que no está permitido.


El RFC de declaraciones de tipo de devolución tiene esto que decir :

La aplicación del tipo de retorno declarado durante la herencia es invariante; esto significa que cuando un subtipo anula un método padre, el tipo de retorno del hijo debe coincidir exactamente con el padre y no puede omitirse. Si el padre no declara un tipo de retorno, entonces el hijo puede declarar uno.

...

Este RFC originalmente propuso tipos de devolución covariantes, pero se cambió a invariante debido a algunos problemas. Es posible agregar tipos de retorno covariantes en algún momento en el futuro.


Por el momento, al menos lo mejor que puede hacer es:

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

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}
user3942918
fuente
18
Dicho esto, hubiera esperado que un tipo de retorno-sugerencia de staticfuncionara, pero ni siquiera se reconoce
Mark Baker
Pensé que ese sería el caso, y así es como voy a resolver el problema. Sin embargo, hubiera preferido usar: self si fuera posible porque si una clase está implementando una interfaz, entonces un valor de retorno de self también es implícitamente un valor de retorno de una instancia de la interfaz.
GordonM
19
Paul, eliminar los comentarios que ha eliminado aquí es realmente perjudicial porque (A) pierde información importante y (B) interrumpe el flujo de la discusión en relación con los otros comentarios. No veo ninguna razón por la cual sus comentarios que se basan en Mark y Gordon deban ser eliminados. De hecho, estás haciendo esto por todas partes y debe detenerse. No hay absolutamente ninguna buena razón para volver a una pregunta de hace un año y eliminar todos sus comentarios, destruyendo por completo el flujo de la discusión. De hecho, es dañino y perturbador.
Cody Gray
Hay un prefacio importante para una parte de su cita que se ve aquí: " Los tipos de retorno covariantes se consideran tipo de sonido y se usan en muchos otros lenguajes (C ++ y Java, pero no C #, creo). Este RFC originalmente propuso tipos de retorno covariantes pero se cambió a invariante debido a algunos problemas. ". Tengo curiosidad por saber qué problemas tenía PHP. Su elección de diseño causa varias limitaciones que también causan problemas extraños con los rasgos, haciéndolos algo inútiles en muchos casos a menos que afloje las restricciones de tipo de retorno (como se ve en algunas respuestas a continuación). Muy frustrante.
John Pancoast
1
@MarkBaker, el tipo de retorno staticse está agregando a PHP 8.
Ricardo Boss
16

También puede ser una solución, que no defina explícitamente el tipo de retorno en la Interfaz, solo en PHPDoc y luego puede definir el tipo de retorno determinado en las implementaciones:

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

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}
Gabor
fuente
2
O en lugar de Foosimplemente usar self.
lugar del
2

En caso de que, cuando desee forzar desde la interfaz, ese método devolverá el objeto, pero el tipo de objeto no será el tipo de interfaz, sino la clase en sí, entonces puede escribirlo de esta manera:

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

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

Funciona desde PHP 7.4.

en lugar
fuente
0

Esto me parece el comportamiento esperado.

Simplemente cambie su Foo::barmétodo para regresar en iFoolugar de selfy terminar con él.

Explicación:

selfcomo se usa en la interfaz significa "un objeto de tipo iFoo".
selfcomo se usa en la implementación significa "un objeto de tipo Foo".

Por lo tanto, los tipos de devolución en la interfaz y la implementación claramente no son los mismos.

Uno de los comentarios menciona Java y si tendría este problema. La respuesta es sí, tendría el mismo problema si Java le permitiera escribir código así, lo cual no es así. Dado que Java requiere que use el nombre del tipo en lugar del selfacceso directo de PHP , nunca verá esto. (Consulte aquí una discusión sobre un problema similar en Java).

Moshe Katz
fuente
Entonces, ¿declarar es selfsimilar a declarar MyClass::class?
peterchaula
1
@Laser Sí, lo es.
Moshe Katz
4
Pero si Foo implementa iFoo, entonces Foo es por definición de tipo iFoo
GordonM