PHP 7 interfaces, sugerencias de tipo de retorno y self




return-type php-7 (4)

Me he encontrado con un problema con el uso de sugerencias de tipo de retorno en PHP 7. Tengo entendido que la sugerencia : self significa que tiene la intención de que una clase implementadora se devuelva. Por lo tanto, utilicé : self en 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 cuestión es que Foo es una implementación de iFoo, por lo que puedo decir que la implementación debe ser perfectamente compatible con la interfaz dada. Presumiblemente podría solucionar este problema cambiando la interfaz o la clase implementadora (o ambas) para devolver una pista a la interfaz por nombre en lugar de usar self , pero entiendo que semánticamente self significa "devolver la instancia de la clase que acaba de llamar método en ". 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 verlo arreglado en PHP 7.1? Si no es así, ¿cuál es la forma correcta de regresar insinuando que su interfaz espera que devuelva la instancia a la que acaba de llamar el método para encadenar?


Al ejecutar algunas pruebas con PHP 7.3, no puedo quejarme (incluso con estricto) cuando hago esto ...

interface A {
 function f() {}
}

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

O mi prueba se ha roto o PHP ha cambiado algunas cosas. En términos generales, si reduce los posibles tipos de retorno, eso tiende a no romper la POO. Como en cualquier clase, el uso de ese método puede manejar cualquier devolución, incluido un subconjunto de tipos de devolución. Lo contrario es más o menos el caso de los parámetros.

Implementaron esto en 7.2.


En caso de que 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.


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 cierto tipo de retorno en las implementaciones:

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

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

self no se refiere a la instancia, se refiere a la clase actual. No hay forma de que una interfaz especifique que debe devolverse la misma instancia ; usar self de la manera que intenta solo exigiría que la instancia devuelta sea de la misma clase.

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

Su uso de self es equivalente a:

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

class Foo implements iFoo
{

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

Que no está permitido.

Las declaraciones de tipo de retorno RFC tiene esto que decir :

La aplicación del tipo de retorno declarado durante la herencia es invariable; Esto significa que cuando un subtipo anula un método principal, el tipo de retorno del elemento secundario debe coincidir exactamente con el elemento primario y no puede omitirse. Si el padre no declara un tipo de retorno, el niño puede declarar uno.

...

Originalmente, este RFC propuso tipos de retorno covariante, 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  {...}
}




type-hinting