PHP 7 interfaces, sugerencias de tipo de retorno y self
return-type php-7 (5)
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.
Esto me parece el comportamiento esperado.
Simplemente cambie su método
Foo::bar
para devolver
iFoo
lugar de
self
y
iFoo
con él.
Explicación:
self
como se usa en la interfaz significa "un objeto de tipo
iFoo
".
self
como se usa en la implementación significa "un objeto de tipo
Foo
".
Por lo tanto, los tipos de retorno 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 como ese, lo cual no es así.
Como Java requiere que uses el nombre del tipo en lugar del atajo
self
de PHP, nunca verás esto realmente.
(Ver
here
para una discusión de un problema
similar
en Java).
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 {...}
}