PHP 8. Todas las novedades de esta nueva versión (I)
Un repaso a fondo de las novedades que tendremos disponibles a partir del 26 de noviembre para trabajar con PHP.
Hace unos meses escribí un artículo listando las principales novedades que incorporará la versión 8 de PHP que tendremos disponible a partir del día 26 de noviembre.
Como era un artículo donde el objetivo era poneros al día de forma rápida decidí no sobrecargarlo con ejemplos por lo que apenas profundicé en las nuevas características.
Ahora que el lanzamiento está a la vuelta de la esquina me he animado a escribir un artículo algo más largo donde os pongo ejemplos concretos de cómo usar cada una de las novedades que incorpora PHP8.
Además, en este artículo encontrarás un par de sorpresas ya que han aparecido dos características muy interesantes:
- La expresión
match
. - Y el uso de “named arguments”.
Así que no nos entretengamos más y comencemos.
¡Bienvenido a PHP8!
The match expression
PHP 8 incorpora una alternativa a la sintaxis switch
: la expresión match
. Al igual que su predecesora, match
nos va a permitir ejecutar un bloque de código en función del valor de una determinada variable. Sin embargo, la diferencia es que con match
la comprobación será type-safe y estricta, lo cual nos asegura una mayor robustez en la comprobación.
Un código usando switch
:
$count = 1;
$message = '';switch ($count) {
case 0:
$message = 'No books';
break;
case 1:
$message = 'One book';
break;
case 2:
case 3:
$message = 'Some books';
break;
default:
$message = 'So many books';
break;
}
El mismo código usando match
:
$count = 1;
$message = '';$message = match($count) {
0 => 'No books',
1 => 'One book',
2,3 => 'Some books',
default => 'So many books'
};
Como ves, el código que emplea match
es mucho más corto ya que:
- Nos ahorramos la instrucción
break
. - Podemos agrupar los casos en la misma línea separándolos por comas.
Recuerda que la comprobación se realiza de forma estricta. Por tanto, el siguiente código imprimirá el string “So many books” (recuerda, 1 !== '1'
).
$count = '1';
$message = '';$message = match($count) {
0 => 'No books',
1 => 'One book',
2,3 => 'Some books',
default => 'So many books'
};print_r($message); // 'So many books'
Esto sin embargo no sucedía con switch
ya que puesto que la comprobación que realiza es débil ( ==
) obtendríamos el string “One book”.
Finalmente, en el caso de que la instrucción match
no encuentre ningún valor que cumpla la condición lanzará la excepción UnhandledMatchError
:
$count = 4;
$message = '';try {
$message = match($count) {
0 => 'No books',
1 => 'One book',
2,3 => 'Some books'
};
} catch (UnhandledMatchError $exception) {
// handle case
}
Aunque también podemos lanzar la excepción que nosotros queramos del siguiente modo:
$count = '1';
$message = '';$message = match($count) {
0 => 'No books',
1 => 'One book',
2,3 => 'Some books',
default => throw new Exception('Unknown count')
};print_r($message); // 'So many books'
Promoted Properties
Las “promoted properties” son una de las características más interesantes que incorpora PHP 8.
Imagina que tenemos la siguiente clase:
<?php
class Article
{
private string $title;
public string $excerpt;
public function __construct(
string $title = 'Empty title',
string $excerpt = 'Empty excerpt'
) {
$this->title = $title;
$this->excerpt = $excerpt;
}
}
Gracias a la posibilidad de declarar “promoted properties” podemos reescribir el constructor de la clase anterior del siguiente modo:
<?php
class Article
{
public function __construct(
public string $title = 'Empty title',
private string $excerpt = 'Empty excerpt'
) {}
}
De este modo:
- Nos ahorramos las líneas en donde declaramos las propiedades de la clase.
- Nos ahorramos las líneas donde realizamos la asignación entre las propiedades y los argumentos.
Una característica muy interesante para definir las clases que representan DTO’s y Value Objects y, en mi opinión, un azucarillo sintáctico que sienta fenomenal a PHP.
Union Types
Hasta ahora, en PHP no teníamos la manera de definir varios tipos para un argumento. Es decir, si declarábamos la siguiente función:
<?phpdeclare(strict_types=1);function foo(string $var) {
print_r($var);
}
Estábamos obligados a pasar una cadena de texto como argumento:
foo('Hola mundo');foo(1); // Uncaught TypeError
Sin embargo, ahora es posible declarar argumentos que puedan ser de distintos tipos gracias a lo que se conoce como Union Types:
<?phpdeclare(strict_types=1);function foo(string|int $var) {
print_r($var);
}
Lo cual nos permitirá invocar la función foo
bien con un entero o bien con un string.
Por supuesto esta característica también funciona con las clases:
<?phpdeclare(strict_types=1);function foo(Dog|Cat $animal) {
print_r(get_class($animal));
}
Lo cual nos dará una mayor flexibilidad en nuestro código ya que hasta PHP 7 era necesario omitir el tipo de esos argumentos o recurrir a PHPDoc Blocks para lidiar con este tipo de casos.
Union types como valor de retorno
Además de en los argumentos, podemos usar los “union types” de PHP8 para especificar también el tipo de retorno de las funciones, lo cual es también un gran añadido:
<?phpdeclare(strict_types=1);function foo(string|int $var): string|int {
// do something
return $var;
}
Null y Union Types
En el caso de que queramos aceptar null
como posible valor para un “union type” esta sintaxis no es válida:
<?phpdeclare(strict_types=1);// ❌function foo(?string|int $var): string|int {
// do something
return $var;
}
Sino que deberemos añadir null
como un posible tipo para el argumento:
<?phpdeclare(strict_types=1);// ✅function foo(string|int|null $var): string|int {
// do something
return $var;
}
Null Safe Operator
Que levante la mano quien no se haya encontrado con un error similar al siguiente:
class Author
{
private string $name; public function getName(): string
{
return $this->name;
}
}class Article
{
private $author; public function getAuthor():
{
return $this->author;
}
}$article = new Article();// ❌
$authorName = $article->getAuthor()->getName();
Gracias a PHP 8 podemos emplear el operador “null safe” para prevenir errores de este tipo:
$article = new Article();// ✅
$authorName = $article->getAuthor()?->getName();
Al situar el símbolo de interrogación delante de la flecha que empleamos para acceder al método ->getName()
le estaremos diciendo a PHP que sólo ejecute dicho método si lo que devolvió ->getAuthor()
no es null
.
Por supuesto también funciona para propiedades:
class Author
{
private string $name; public function getName(): string
{
return $this->name;
}
}class Article
{
public ?Author $author;
}$article = new Article();// ✅
$authorName = $article?->author->getName();
Named Arguments
Imagina que tenemos la siguiente función:
function log(
string $message,
string $level = 'INFO',
string $date = 'now',
string $prefix = '',
bool $sendByEmail = false
) {
// do something
}
Si solo queremos especificar el argumento message
y sendByEmail
, estaremos obligados a invocar la función del siguiente modo:
log('Some message', 'INFO', 'now', '', true);
Es decir, estamos obligados a rellenar todos los argumentos anterior a sendByEmail
si queremos usar este últimos.
Con PHP8 esto es mucho más sencillo:
// 👏log('Some message', sendByEmail: true);
Esta funcionalidad supone un gran añadido ya que nos va a permitir acortar la invocación de funciones que reciben multitud de argumentos.
Además, los argumentos “nombrados” podremos especificarlos en el orden en que queramos ya que PHP se encargará de ordenarlos por nosotros:
log('Some message', sendByEmail: true, prefix: 'PHP8');
Pero esto no sólo se queda aquí. Podemos combinar los “named arguments” de PHP 8 con el operador “spread” para ejecutar código como el siguiente:
class Developer
{
public function __construct(
public string $name,
public string $company,
public string $language
) {}
}$json = [
'name' => 'Gerardo',
'company' => 'Latte and Code',
'language' => 'PHP'
];$developer = new Developer(...$json);
Gracias a los “named arguments” PHP se encargará de asociar cada clave del array que hemos “propagado” con ...
con los argumentos.
Al final va a resultar facilísimo aprender PHP si ya conoces Javascript.
En el caso de que el array tenga una clave que no se corresponde con ningún argumento obtendremos un error:
class Developer
{
public function __construct(
public string $name,
public string $company,
public string $language
) {}
}$json = [
'name' => 'Gerardo',
'company' => 'Latte and Code',
'language' => 'PHP',
'age' => 32
];$developer = new Developer(...$json); // ❌ Unknown parameter $age
Object Classnames
De esta característica ya hablé en el artículo de resumen pero me gustaría volver a recordarla ya que nos permite prescindir del método get_class
para obtener el FQDN de un objeto.
namespace App;class Developer
{
public function __construct(
public string $name,
public string $company,
public string $language
) {}
}$dev = new Developer('Gerardo', 'LaC', 'PHP');get_class($dev); // App\Developer
A partir de ahora podemos emplear el operador ::class
sobre un objeto para obtener su clase:
$dev::class; // App\Developer
del mismo modo que ya podíamos hacer con las clases mismas Developer::class
.
Continuará…
Pero esto no termina aquí. En el siguiente capítulo repasaré en profundidad el resto de características que incorpora PHP8:
- La creación de Atributos.
- La mejora en el tratamiento de las excepciones.
- Las nuevas funciones que incorpora la clase String.
- La interfaz Stringable
- Los Weak Maps.
- Y el uso de las “trailing commas” en la definición de las funciones.
Vídeo
Para complementar este artículo he grabado un vídeo donde podéis ver todas estas características funcionando en directo. ¡Espero que os guste!
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇
Apóyame en Patreon
🧡🧡🧡 Gracias a: Joseba, Óscar, Alex y Jorge.