PHP 8.0: Novedades en Tipos de Datos (2/4)
La versión 8.0 de PHP acaba de ser liberada. Está llena de nuevas características, como ninguna versión antes. Su introducción merece cuatro artículos separados. En la segunda parte, echaremos un vistazo a los tipos de datos.
Retrocedamos en la historia. La introducción de sugerencias de tipos
escalares fue un avance significativo en PHP 7. Casi no sucedió. Andreu Faulds, el autor de la ingeniosa
solución, que era totalmente compatible hacia atrás y opcional gracias a
declare(strict_types=1)
, fue duramente rechazado por la comunidad.
Afortunadamente, Anthony Ferrara la
defendió a ella y a la propuesta en su momento, lanzó una campaña y la RFC
pasó muy cerca. Ufff. La mayoría de las novedades de PHP 8 son cortesía del
legendario Nikita Popov y todas pasaron la
votación como un cuchillo por la mantequilla. El mundo está cambiando
para mejor.
PHP 8 trae tipos a la perfección. La gran mayoría de las anotaciones
phpDoc como @param
, @return
y @var
serán
reemplazadas por notación nativa. Pero lo más importante, los tipos serán
comprobados por el motor de PHP. Sólo las descripciones de estructuras como
string[]
o anotaciones más complejas para PHPStan permanecerán
en los comentarios.
Tipos de Unión
Los tipos de unión son una enumeración de dos o más tipos que puede aceptar una variable:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
Ciertos tipos de unión han sido introducidos en PHP anteriormente. Tipos
anulables, por ejemplo. Como ?string
, que es equivalente al tipo de
unión string|null
. La notación de signo de interrogación puede
considerarse una abreviatura. Por supuesto, también funciona en PHP 8, pero no
se puede combinar con barras verticales. Así que en lugar de
?string|object
hay que escribir string|object|null
.
Además, iterable
siempre fue equivalente a
array|Traversable
. Puede que le sorprenda que float
sea también un tipo de unión, ya que acepta tanto int|float
, pero
convierte el valor a float
.
No se pueden utilizar los pseudotipos void
y mixed
en uniones porque no tendría sentido.
Nette está preparado para tipos de unión. En Schema, Expect::from()
los acepta, y los presentadores también los aceptan. Puedes usarlos en métodos
render y action, por ejemplo:
public function renderDetail(int|array $id)
{
...
}
Por otro lado, el autocableado en Nette DI rechaza los tipos unión. Hasta ahora, no hay ningún caso de uso en el que tenga sentido que el constructor acepte uno u otro objeto. Por supuesto, si tal caso de uso aparece, será posible ajustar el comportamiento del contenedor en consecuencia.
Los métodos getParameterType()
, getReturnType()
y
getPropertyType()
en Nette\Utils\Reflection
lanzan una
excepción en el caso del tipo unión (esto es en la versión 3.1; en la
versión anterior 3.0, estos métodos sí devuelven para mantener la
compatibilidad nula).
mixed
El pseudotipo mixed
acepta cualquier valor.
En el caso de parámetros y propiedades, tiene el mismo comportamiento que si
no especificáramos ningún tipo. Entonces, ¿cuál es su propósito? Distinguir
cuándo un tipo está simplemente ausente y cuándo lo está intencionadamente
mixed
.
En el caso de los valores de retorno de funciones y métodos, no especificar
el tipo difiere de utilizar mixed
. Significa lo contrario de
void
, ya que requiere que la función devuelva algo. Un retorno
omitido produce entonces un error fatal.
En la práctica, rara vez se debe utilizar, ya que gracias a los tipos de unión se puede especificar el valor con precisión. Por lo tanto, sólo es adecuado en situaciones excepcionales:
function dump(mixed $var): mixed
{
// print variable
return $var;
}
false
A diferencia de mixed
, puede utilizar el nuevo pseudotipo
false
exclusivamente en tipos de unión. Surgió de la necesidad de
describir el tipo de retorno de las funciones nativas, que históricamente
devuelven false en caso de fallo:
function strpos(string $haystack, string $needle): int|false
{
}
Por lo tanto, no existe el tipo true
. Tampoco se puede utilizar
false
solo, ni false|null
, ni
bool|false
.
static
El pseudotipo static
sólo puede utilizarse como tipo de retorno
de un método. Dice que el método devuelve un objeto del mismo tipo que el
propio objeto (mientras que self
dice que devuelve la clase en la
que está definido el método). Es excelente para describir interfaces
fluidas:
class Item
{
public function setValue($val): static
{
$this->value = $val;
return $this;
}
}
class ItemChild extends Item
{
public function childMethod()
{
}
}
$child = new ItemChild;
$child->setValue(10)
->childMethod();
resource
Este tipo no existe en PHP 8 y no será introducido en el futuro. Los
recursos son una reliquia de la época en que PHP no tenía objetos.
Gradualmente, los recursos van a ser reemplazados por objetos. Eventualmente,
desaparecerán por completo. Por ejemplo, PHP 8.0 reemplaza el recurso image
por el objeto GdImage
, y el recurso curl por el objeto
CurlHandle
.
Stringable
Es una interfaz que se implementa automáticamente por cada objeto con un
método mágico __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
Es posible indicar explícitamente
class Email implements Stringable
en la definición de la clase,
pero no es necesario.
Nette\Utils\Html
también refleja este esquema de nomenclatura
implementando la interfaz Nette\HtmlStringable
en lugar de la
anterior IHtmlString
. Los objetos de este tipo, por ejemplo, no son
escapados por Latte.
Tipo varianza, contravarianza, covarianza
El Principio de Sustitución de Liskov (LSP) establece que las clases de extensión y las implementaciones de interfaz nunca deben requerir más y proporcionar menos que el padre. Es decir, el método hijo no debe requerir más argumentos o aceptar un rango más estrecho de tipos para los parámetros que el padre, y viceversa, no debe devolver un rango más amplio de tipos. Pero puede devolver menos. ¿Por qué? Porque, de lo contrario, se rompería la herencia. Una función aceptaría un objeto de un tipo concreto, pero no tendría ni idea de qué parámetros puede pasar a sus métodos y qué tipos devolverían. Cualquier hijo podría romperla.
Así que en POO, la clase hija puede:
- aceptar una gama más amplia de tipos en los parámetros (esto se llama contravarianza)
- devolver un rango más estrecho de tipos (covarianza)
- y las propiedades no pueden cambiar de tipo (son invariantes)
PHP ha sido capaz de hacer esto desde la versión 7.4, y todos los nuevos tipos introducidos en PHP 8.0 también soportan contravarianza y covarianza.
En el caso de mixed
, el hijo puede acotar el valor de retorno a
cualquier tipo, pero no void
, ya que no representa un valor, sino
su ausencia. El hijo tampoco puede omitir una declaración de tipo, ya que esto
también permite una ausencia de valor.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Los tipos de unión también pueden ampliarse en los parámetros y reducirse en los valores de retorno:
class A
{
public function foo(string|int $foo): string|int
{}
}
class B extends A
{
public function foo(string|int|float $foo): string
{}
}
Además, false
puede ampliarse a bool
en el
parámetro o viceversa bool
a false
en el valor de
retorno.
Todas las infracciones contra la covarianza/contravarianza conducen a un error fatal en PHP 8.0.
En las próximas partes de esta serie, mostraremos qué son los atributos, qué nuevas funciones y clases han aparecido en PHP e introduciremos el compilador Just in Time.
Para enviar un comentario, inicie sesión