PHP 8.0: Resumen completo de las novedades (1/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 primero, veremos qué novedades aporta en cuanto al lenguaje.

Antes de sumergirnos en PHP, debe saber que la versión actual de Nette está totalmente preparada para la versión 8. Es más, como regalo, incluso se ha lanzado Nette 2.4, que es totalmente compatible con ella, por lo que desde el punto de vista del framework, nada le impide empezar a usar la nueva versión.
Argumentos nombrados
Y empezamos directamente con una bomba, que se puede calificar audazmente como un game changer. Ahora se pueden pasar argumentos a funciones y métodos no solo posicionalmente, sino también por sus nombres. Lo cual es absolutamente genial en el caso de que un método tenga realmente muchos parámetros:
class Response implements IResponse
{
public function setCookie(
string $name,
string $value,
string|DateInterface|null $time,
string $path = null,
string $domain = null,
bool $secure = null,
bool $httpOnly = null,
string $sameSite = null
) {
...
}
}
Pasamos los dos primeros argumentos posicionalmente, los siguientes por nombre: (los nombrados siempre deben seguir a los posicionales)
$response->setCookie('lang', $lang, sameSite: 'None');
// en lugar del demencial
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');
Antes de la llegada de esta característica, estaba previsto crear una nueva
API en Nette para enviar cookies, ya que el número de parámetros de
setCookie()
realmente había crecido y la notación posicional era
confusa. Ahora ya no es necesario, porque los argumentos nombrados son en este
caso la API más práctica. El IDE los sugerirá y tienen control de tipos.
También son geniales para aclarar parámetros lógicos, donde su uso no es
necesario, pero true
o false
por sí solos no
dicen mucho:
// antes
$db = $container->getService(Database::class, true);
// ahora
$db = $container->getService(Database::class, need: true);
Los nombres de los parámetros ahora forman parte de la API pública. No es posible cambiarlos arbitrariamente como hasta ahora. Por esta razón, Nette también está pasando por una auditoría para verificar si todos los parámetros tienen una nomenclatura adecuada.
Los argumentos nombrados también se pueden usar en combinación con variadics:
function variadics($a, ...$args) {
dump($args);
}
variadics(a: 1, b: 2, c: 3);
// en $args estará ['b' => 2, 'c' => 3]
Ahora, el array $args
puede contener también claves no
numéricas, lo cual es un cierto BC break. Lo mismo se aplica al comportamiento
de la función call_user_func_array($func, $args)
, donde ahora las
claves en el array $args
juegan un papel significativo. Por el
contrario, las funciones de la familia func_*()
están aisladas de
los argumentos nombrados.
Con los argumentos nombrados también está estrechamente relacionado el
hecho de que el operador splat ...
ahora puede desempaquetar
también arrays asociativos:
variadics(...['b' => 2, 'c' => 3]);
Sorprendentemente, esto aún no funciona dentro de arrays:
$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys
La combinación de argumentos nombrados y variadics da la posibilidad de
tener finalmente una sintaxis fija, por ejemplo, para el método del presenter
link()
, al que ahora podemos pasar argumentos nombrados de la misma
manera que los posicionales:
// antes
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // tenía que ser un array
// ahora
$presenter->link('Product:detail', $id, page: 1);
La sintaxis para argumentos con nombre es mucho más sexy que escribir
matrices, así que “Latte la adoptó inmediatamente:https://blog.nette.org/…ot-for-least#…”,
donde puede utilizarse, por ejemplo, en las etiquetas {include}
y
{link}
:
{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}
Volveremos a los parámetros nombrados en la tercera parte en relación con los atributos.
La expresión puede lanzar una excepción
Lanzar una excepción es ahora una expresión. Puede, por ejemplo, envolverla
en paréntesis y añadirla a una condición if
. Hmmm, eso no suena
muy práctico. Pero esto ya es más interesante:
// antes
if (!isset($arr['value'])) {
throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];
// ahora, cuando throw es una expresión
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');
Dado que las funciones de flecha hasta ahora solo pueden contener una expresión, gracias a esta característica pueden lanzar excepciones:
// only single expression
$fn = fn() => throw new \Exception('oops');
Expresiones Match
La construcción switch-case
tiene dos grandes defectos:
- utiliza comparación no estricta
==
en lugar de===
- debe tener cuidado de no olvidar accidentalmente un
break
Por lo tanto, PHP presenta una alternativa en forma de la nueva construcción
match
, que utiliza comparación estricta y, por el contrario, no
utiliza break
.
Ejemplo de código switch
:
switch ($statusCode) {
case 200:
case 300:
$message = $this->formatMessage('ok');
break;
case 400:
$message = $this->formatMessage('not found');
break;
case 500:
$message = $this->formatMessage('server error');
break;
default:
$message = 'unknown status code';
break;
}
Y lo mismo (solo con comparación estricta) escrito usando
match
:
$message = match ($statusCode) {
200, 300 => $this->formatMessage('ok'),
400 => $this->formatMessage('not found'),
500 => $this->formatMessage('server error'),
default => 'unknown status code',
};
Observe que match
no es una estructura de control como
switch
, sino una expresión. En el ejemplo, asignamos su valor
resultante a una variable. Al mismo tiempo, las “opciones” individuales
también son expresiones, por lo que no se pueden escribir múltiples pasos,
como en el caso de switch
.
Si no hay coincidencia con ninguna de las opciones (y no existe la cláusula
default), se lanza una excepción UnhandledMatchError
.
Por cierto, en Latte también existen las etiquetas {switch}
,
{case}
y {default}
. Su funcionamiento corresponde
exactamente al nuevo match
. Utilizan comparación estricta, no
requieren break
y en case
es posible indicar
múltiples valores separados por comas.
Operador Nullsafe
El encadenamiento opcional (optional chaining) permite escribir una
expresión cuya evaluación se detiene si encuentra un null. Y esto gracias al
nuevo operador ?->
. Reemplaza mucho código que de otro modo
verificaría repetidamente si hay null:
$user?->getAddress()?->street
// significa aproximadamente
$user !== null && $user->getAddress() !== null
? $user->getAddress()->street
: null
¿Por qué “significa aproximadamente”? Porque en realidad, la expresión
se evalúa de manera más ingeniosa y ningún paso se repite. Por ejemplo,
$user->getAddress()
se llama solo una vez, por lo que no puede
surgir el problema causado por el hecho de que el método devuelva algo
diferente la primera y la segunda vez.
Esta fresca novedad la trajo Latte hace un año. Ahora llega al propio PHP. Genial.
Promoción de propiedades del constructor
Azúcar sintáctico que ahorra escribir el tipo dos veces y la variable cuatro veces. Lástima que no llegara en la época en que no teníamos IDEs tan inteligentes que hoy lo escriben por nosotros 🙂
class Facade
{
private Nette\Database\Connection $db;
private Nette\Mail\Mailer $mailer;
public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
{
$this->db = $db;
$this->mailer = $mailer;
}
}
class Facade
{
public function __construct(
private Nette\Database\Connection $db,
private Nette\Mail\Mailer $mailer,
) {}
}
Funciona con Nette DI, puede empezar a usarlo de inmediato.
Comportamiento estricto de los operadores aritméticos y bitwise
Lo que una vez catapultó a los lenguajes de scripting dinámicos a la fama, con el tiempo se convirtió en su punto más débil. PHP se deshizo en su día de las “magic quotes”, del registro de variables globales, y ahora el comportamiento laxo está siendo reemplazado por la rigurosidad. La época en la que en PHP podía sumar, multiplicar, etc., casi cualquier tipo de dato, incluso cuando no tenía ningún sentido, ha quedado atrás. A partir de la versión 7.0, PHP es cada vez más estricto, y desde la versión 8.0, intentar usar cualquier operador aritmético/bitwise en arrays, objetos o resources termina en un TypeError. La excepción es la suma de arrays.
// operadores aritméticos y bitwise
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:
Comparación más sensata de cadenas y números
O dicho de otro modo, make loose operator great again.
Parecería que para el operador laxo ==
ya no hay lugar, que es
solo un error tipográfico al escribir ===
, pero este cambio lo
devuelve al mapa. Ya que lo tenemos, que se comporte de manera razonable. La
consecuencia de la anterior comparación “no razonable” era, por ejemplo, el
comportamiento de in_array()
, que podía jugarle una mala
pasada:
$validValues = ['foo', 'bar', 'baz'];
$value = 0;
dump(in_array($value, $validValues));
// sorprendentemente devolvía true
// desde PHP 8.0 devuelve false
El cambio en el comportamiento de ==
se refiere a la
comparación de números y cadenas “numéricas” y lo muestra la
siguiente tabla:
Comparación | Antes | PHP 8.0 |
---|---|---|
0 == "0" |
true | true |
0 == "0.0" |
true | true |
0 == "foo" |
true | false |
0 == "" |
true | false |
42 == " 42" |
true | true |
42 == "42 " |
true | true |
42 == "42foo" |
true | false |
42 == "abc42" |
false | false |
"42" == " 42" |
true | true |
"42" == "42 " |
false | true |
Sorprendentemente, se trata de un BC break en la base misma del lenguaje, que fue aprobado sin ninguna oposición. Y eso es bueno. Aquí JavaScript podría tener mucha envidia.
Reporte de errores
Muchas funciones internas ahora lanzan TypeError y ValueError en lugar de
advertencias, que se podían pasar por alto fácilmente. Se han reclasificado
numerosas advertencias del núcleo. El operador shutup @
ahora no
silencia los errores fatales. Y PDO en modo predeterminado lanza
excepciones.
Estas cosas siempre Nette ha intentado resolverlas de alguna manera. Tracy modificaba el comportamiento del operador shutup, Database cambiaba el comportamiento de PDO, Utils contiene reemplazos de funciones estándar que lanzan excepciones en lugar de advertencias discretas, etc. Es bueno ver que la dirección estricta, que Nette tiene en su ADN, se está convirtiendo en la dirección nativa del lenguaje.
Arrays con índice negativo
$arr[-5] = 'first';
$arr[] = 'second';
¿Cuál será la clave del segundo elemento? Antes era 0
, desde
PHP 8 es -4
.
Coma final
El último lugar donde no podía haber una coma final era la definición de parámetros de función. Eso ya es cosa del pasado:
public function __construct(
Nette\Database\Connection $db,
Nette\Mail\Mailer $mailer, // coma final
) {
....
}
$object::class
La constante mágica ::class
funciona también con objetos
$object::class
, reemplazando así por completo la función
get_class()
.
catch sin variable
Y finalmente: en la cláusula catch no es necesario indicar la variable para la excepción:
try {
$container->getService(Database::class);
} catch (MissingServiceException) { // sin $e
$logger->log('....');
}
En las próximas entregas nos esperan novedades fundamentales en los tipos de datos, 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