PHP 8.0: Tipos de datos (2/4)
Ha salido la versión 8.0 de PHP. Está tan repleta de novedades como ninguna versión anterior. Su presentación ha requerido cuatro artículos separados. En este segundo, veremos los tipos de datos.

Volvamos a la historia. Un avance fundamental de PHP 7 fue la introducción
de los type hints escalares. Casi no sucedió. La autora de la increíble
solución, Andreu Faulds, que gracias a
declare(strict_types=1)
era totalmente retrocompatible y opcional,
fue rechazada duramente por la comunidad. Afortunadamente, en aquel entonces, Anthony Ferrara la defendió a ella y a
su propuesta, lanzó una campaña y el RFC pasó por muy poco. Ufff. La mayoría
de las novedades en PHP 8 son obra del legendario Nikita Popov y en la votación pasaron como
la seda. El mundo está cambiando a mejor.
PHP 8 lleva los tipos a la perfección. Desaparecerá la gran mayoría de
las anotaciones phpDoc @param
, @return
y
@var
en el código y serán reemplazadas por la notación nativa y,
sobre todo, por el control del motor de PHP. En los comentarios solo quedarán
descripciones de estructuras como string[]
o anotaciones más
complejas para PHPStan.
Tipos de Unión (Union Types)
Los tipos de unión son una enumeración de dos o más tipos que un valor puede tomar:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
PHP ya conocía algunos tipos de unión especiales anteriormente. Por
ejemplo, los tipos anulables como ?string
, que es el equivalente al
tipo de unión string|null
y la notación con signo de
interrogación se puede considerar solo como una abreviatura. Por supuesto,
también funciona en PHP 8, pero no se puede combinar con barras verticales, por
lo que en lugar de ?string|object
es necesario escribir el
string|object|null
completo. Además, iterable
siempre
fue el equivalente a array|Traversable
. Quizás le sorprenda que el
tipo de unión sea en realidad también float
, que en realidad
acepta int|float
, pero lo convierte a float
.
En las uniones no se pueden usar los pseudotipos void
y
mixed
, porque no tendría ningún 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 el contrario, el autowiring en Nette DI rechaza los tipos de unión. Falta todavía un caso de uso donde tendría sentido que, por ejemplo, un constructor aceptara uno u otro objeto. Por supuesto, si 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 caso de un tipo de unión (en la versión 3.1, en la versión anterior
3.0 devuelven null por compatibilidad).
mixed
El pseudotipo mixed
indica que el valor puede ser absolutamente
cualquier cosa.
En el caso de parámetros y propiedades, en realidad es el mismo
comportamiento que si no indicáramos ningún tipo. ¿Para qué sirve entonces?
Para poder distinguir cuándo simplemente falta el tipo y cuándo es realmente
mixed
.
En el caso del valor de retorno de una función o método, no indicar el
tipo difiere de indicar el tipo mixed
. En realidad, es lo opuesto a
void
, ya que indica que la función debe devolver algo. Un return
ausente es entonces un error fatal.
En la práctica, debería usarlo raramente, porque gracias a los tipos de unión puede especificar el valor con mayor precisión. Por lo tanto, es útil en situaciones excepcionales:
function dump(mixed $var): mixed
{
// imprimir variable
return $var;
}
false
El nuevo pseudotipo false
, por el contrario, solo se puede usar
en tipos de unión. Surgió de la necesidad de describir nativamente el tipo del
valor de retorno en funciones nativas que históricamente devuelven false en
caso de fracaso:
function strpos(string $haystack, string $needle): int|false
{
}
Por esta razón, no existe el tipo true
, tampoco se puede usar
false
solo, ni false|null
o
bool|false
.
static
El pseudotipo static
solo se puede usar como tipo de retorno de
un método. Indica que el método devuelve un objeto del mismo tipo que el
objeto mismo (mientras que self
indica que devuelve la clase en la
que se define el método). Lo cual es excelente para describir interfaces
fluidas (fluent interfaces):
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 tampoco se introducirá en el futuro. Los
resources son una reliquia histórica de los tiempos en que PHP aún no tenía
objetos. Gradualmente, los resources serán reemplazados por objetos y con el
tiempo este tipo desaparecerá por completo. Por ejemplo, PHP 8.0 reemplaza el
resource que representa una imagen por el objeto GdImage
y el
resource de conexión curl
por el objeto
CurlHandle
.
Stringable
Es una interfaz que implementa automáticamente cada objeto con el método
mágico __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
En la definición de la clase, es posible indicar explícitamente
class Email implements Stringable
, pero no es necesario.
Este estilo de nomenclatura también se refleja en
Nette\Utils\Html
, que implementa la interfaz
Nette\HtmlStringable
en lugar de la anterior
IHtmlString
. Los objetos de este tipo, por ejemplo, no son
escapados por Latte.
Varianza de tipos, contravarianza, covarianza
El principio de sustitución de Liskov (Liskov Substitution Principle – LSP) dice que los descendientes de una clase y las implementaciones de una interfaz nunca deben requerir más ni proporcionar menos que el padre. Es decir, que un método del descendiente no debe requerir más argumentos o aceptar en los parámetros un rango de tipos más estrecho que el ancestro y, por el contrario, no debe devolver un rango de tipos más amplio. Pero puede devolver uno más estrecho. ¿Por qué? Porque de lo contrario, la herencia no funcionaría en absoluto. Una función aceptaría un objeto de un tipo determinado, pero no sabría qué parámetros se pueden pasar a los métodos ni qué devolverán realmente, porque cualquier descendiente podría romperlo.
Por lo tanto, en POO se aplica que un descendiente puede:
- en los parámetros aceptar un rango de tipos más amplio (esto se llama contravarianza)
- devolver un rango de tipos más estrecho (covarianza)
- y las propiedades no pueden cambiar de tipo (son invariantes)
PHP sabe hacer esto desde la versión 7.4 y todos los tipos recién introducidos en PHP 8.0 también soportan contravarianza y covarianza.
En el caso de mixed
, el descendiente puede estrechar el valor de
retorno a cualquier tipo, pero no a void
, porque no es un tipo de
valor, sino su ausencia. Tampoco el descendiente puede omitir el tipo, porque
eso también permite la ausencia.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
También los tipos de unión se pueden ampliar en los parámetros y estrechar 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 en el parámetro a
bool
o, por el contrario, bool
en el valor de retorno
puede estrecharse a false
.
Todas las infracciones contra la covarianza/contravarianza conducen a un error fatal en PHP 8.0.
En las próximas entregas mostraremos qué son los atributos, qué nuevas funciones y clases han aparecido en PHP y presentaremos el Just in Time Compiler.
Para enviar un comentario, inicie sesión