PHP8 on fire 🔥

Hace años escribí en este blog mi odio a Java, por que su lenguaje se parece a C, que tiene estructuras que sólo tienen sentido en C. También incluí a PHP en la mezcla, por casi la misma razón. Pero por una oportunidad de ser profesor de PHP, me percaté que había madurado bastante, y a partir de la versión 7 ha tenido cambios muy interesantes debido a que corrigieron la forma cómo se interpreta el código fuente, y ahora usan un árbol de sintaxis abstracto previo a la compilación, usado en casi todos los demás lenguajes de programación. Eso les abrió las puertas para poder añadir nuevas características, que siguen hasta ahora.

Y al final me quedé programando para la web en PHP

A fin de año saldrá la siguiente mayor versión de PHP, la 8, y realmente ando emocionado con varios de sus nuevos features, muchos para normalizar algunas incongruencias, y otros son cosas que siempre he extrañado de otros lenguajes. Aquí mi top-10 de los 11 features que me parecen más interesantes :

::class en objetos

::class es usado para obtener el nombre «totalmente calificado» de una clase («FQCN») como un string, resolviendo todos los namespaces . Pero no funcionaba en los objetos, donde debías usar la función get_class(). Ya no más Ahora $object::class devuelve el FQCN como un string.

Múltiples tipos en parámetros y retorno

Ahora será posible añadir varios tipos aceptados en la declaración de parámetros y retorno de funciones. E.g.:

function añadir_precio(int|float $precio): int|float|null
{
//...

$precio puede ser únicamente int o float y el resultado puede ser de ámbos tipos, o null. Era difícil ciertas ocasiones usar un único tipo, yo personalmente acababa dejándolo de lado, y usando un método de verificación de tipo en el cuerpo de la función, como is_string()o similares.

catch sin variable

Este es uno chiquito. Ahora será posible no especificar una variable en el catch de un try. Muchas veces es innecesario. E.g.

try {
CambiarContraseña($nueva_contraseña);
} catch (NoAuthorizedException) {
exit ("¡Sin autorización!");
}

Nuevas funciones de búsqueda de cadenas

Buscar si una cadena está dentro de otra solía ser engorroso en PHP, a pesar que es algo muy común. PHP8 traerá nuevas funciones de búsqueda de cadenas:

str_starts_with ( string $haystack , string $needle ) : bool
str_ends_with ( string $haystack , string $needle ) : bool
str_contains ( string $haystack , string $needle ) : bool

Estas tres funciones se usarán para ver si una cadena empieza con otra (str_starts_with()), si termina con otra (str_ends_with()) o si la contiene en cualquier lugar (str_contains()).

Coma suelta al final de la lista de parámetros

Empezamos con las novedades interesantes En los arrays, en los namespaces compuestos, y en los argumentos en la llamada a funciones es posible colocar una coma suelta al final del último elemento. Si colocas cada elemento en una línea, añadir una nueva línea se siente natural:

$parámetros = [
'nombre' => 'Oliver',
'dirección' => 'Av. Probando 123',
];

En el ejemplo, vas a la línea 4, escribes el nuevo elemento, y presionas enter. Pero eso no pasa en la lista de parámetros en la definición de una función. Cuando son muchos los parámetros, o son muy largos, es más legible (y recomendado en el PSR-12) colocar cada parámetro en una línea. Pero añadir un parámetro más al final no es posible sin ir primero a la última línea, ir al final de ella, y añadir una coma, por que es actualmente ilegal dejar la coma suelta al final. A parte, los diffs del código fuente tienen que registrar dos líneas cuando sólo estás añadiendo una.

Ahora PHP8 permite esa coma suelta al final:

class Notas
{
public function __construct(
Alumno $alumno,
Profesor $profesor,
Clase $clase, // UFFF Al fín :-)
) {
//...

Promoción de propiedades del constructor

Es común pasar argumentos al constructor de una clase, para que este a su vez los guarde en propiedades de dicha clase. E.g.:

class Hasher
{
private Database $database;
private Logger $logger;

public function __construct(
Database $database,
Logger $logger
)
{
$this->database = $database;
$this->logger = $logger;
}

PHP8 permitirá remover la redundancia, promoviendo los parámetros del constructor a propiedades de la clase. El código anterior se podrá escribir así:

class Hasher
{
public function __construct(
private Database $database,
private Logger $logger,
) {

}

JIT

PHP 8 incluirá un «compilador al vuelo» («just-in-time compiler«) que acelerará ciertos procesos que usan intensivamente el CPU.

Actualmente cada instrucción de PHP es convertida a un código intermedio parecido al assembler conocido como «opcode», que luego es «interpretado» al vuelo a código máquina del CPU donde está siendo ejecutado. El objetivo del JIT es convertir PHP directo al código máquina del CPU, para que no sea necesario interpretar un opcode, lo cual acelerará su ejecución.

Expresión match

Whoa… El ejemplo principal de mi odio a Java (y de paso, PHP) era la instrucción switch, porque su sintaxis solo tenía sentido en C, y los lenguajes modernos usan versiones más elegantes (ojo con el «tenía», C ya no usa la tabla de saltos… eso se merece otro post luego).

Bueno pues, ya que es bastante común usar switch solo para cambiar el valor de una variable, ahora PHP8 tiene la expresión match que retorna un valor según una lista de comparaciones.

Este es un caso real de uso de switch, del analizador de consultas de Doctrine:

switch ($this->lexer->lookahead['type']) {
case Lexer::T_SELECT:
$statement = $this->SelectStatement();
break;

case Lexer::T_UPDATE:
$statement = $this->UpdateStatement();
break;

case Lexer::T_DELETE:
$statement = $this->DeleteStatement();
break;

default:
$this->syntaxError('SELECT, UPDATE or DELETE');
break;
}

Varias cosas pueden ir mal usando switch para este tipo de casos:

Puedes olvidar escribir esos horribles break y el código se ejecutará sin problemas.

Puedes escribir mal la variable que recibirá el valor (en el ejemplo se repite 3 veces la variable $statement) y el código se ejecutará sin problemas.

O simplemente puede no funcionar como esperas, ya que PHP tiene unas reglas de igualdad extrañas… ya sabes, "hola" == 0 es true…

El mismo código usando match se verá así:

$statement = match ($this->lexer->lookahead['type']) {
Lexer::T_SELECT => $this->SelectStatement(),
Lexer::T_UPDATE => $this->UpdateStatement(),
Lexer::T_DELETE => $this->DeleteStatement(),
default => $this->syntaxError('SELECT, UPDATE or DELETE'),
};

match compara estrictamente el valor del parámetro con cada opción (igual que el operador de identidad ===), y retorna su expresión relacionada. Si no existe una opción con el valor del parámetro, y no hay una opción default, se lanzará una excepción UnhandledMatchError, lo cual evitará un montón de posibles errores.

También es posible tener varias opciones para una expresión resultante. E.g.

echo match(1) {
1, 2 => '¡Hola',
3, 4 => 'mundo!',
}

// Imprimirá '¡Hola'

Operador ‘nullsafe’

Este operador está aun en proceso de votación, pero al 17 de julio tiene 95% de aprobación con 24 votos, así que es muy probable que acabe en PHP8. En este código:

$usuario = $usuarios->obtener(199);
var_dump($usuario->nombre);

Si $usuarios->obtener(199) devuelve null (quizas el usuario no existe), la 2da línea emitirá un warning: «Trying to get property ‘nombre’ of non-object«. Si $usuarios es null, la 1ra línea fallará con un «Call to a member function obtener() on null».

El operador «nullsafe» ?-> devuelve null si la expresión a su izquierda es null y no procesa todas las expresiones que están a su derecha (corto-circuito completo). De lo contrario, se comporta como el operador ->. El código:

$usuario = $usuarios?->obtener(199);
var_dump($usuario?->nombre);

Si uno de los dos errores detallados arriba sucede, imprimirá NULL sin fallar.

Atributos

Actualización 9 de octubre: Después de haber sido aceptado los atributos, Derick Rethans et al. presentaron un nuevo RFC para modificar la sintaxis de los atributos. El RFC fue aprobado, así que he actualizado la sintaxis en esta sección.

Esto es grande. Los atributos son metadatos que se le podrán añadir a clases, propiedades, métodos, argumentos y constantes. Java los llama ‘anotaciones‘, y Python tiene algo parecido llamados ‘decoradores’ (que son realmente wrappers).

Hasta ahora hemos estado usando un bloque de comentarios para añadir metadata, inspirado en phpDocumentor. Pero al ser texto simple, hay que analizarlo para poder obtener la información, lo cual es tedioso y lento. E.g.:

class Persona
{
/**
* @Field\VarChar(12)
*/
private $nombre;

// ...

Luego usando reflexiones obtenemos el bloque de comentario para analizarlo. Ya que tradicionalmente las anotaciones son clases, viene el problema de obtener el FQCN de la clase en la anotación, lo cual requiere un hack bastante feo.

En PHP8 el código se vuelve lindo:

class Persona
{
#[Field\VarChar(12)]
private $nombre;

// ...

PHP se encarga de la resolución del FQCN, y también de la instanciación de la clase de la anotación.

Los frameworks son quienes harán uso extensivo de esto. Originalmente en Vendimia usaba arrays para definir el objeto que tendría una propiedad (algo como $nombre = [Field\VarChar::class, 12] , y después le añadí soporte para usar el docblock con el hack feo que mencioné anteriormente. Ahora estará más elegante.

Argumentos con nombre

Éste es uno de los features que he extrañado más de otros lenguajes No solo simplifica ciertas llamadas a métodos, ayuda a la autodocumentación, y evita problemas de orden que tienen algunos parámetros similares en ciertas funciones de PHP. Aún está en votación, está algo ajustado (requiere 66% de aprobación, tiene actualmente 74%), y espero que el universo nos permita que lo añadan a PHP 8.

Ésta es la declaración del método Write() de TCPDF, una librería para generar PDFs en PHP:

public Write( $h, $txt, $link = '', $fill = false, $align = '', $ln = false, $stretch = 0, $firstline = false, $firstblock = false, $maxh = 0, $wadj = 0, $margin = '' )

Si quieres añadir un texto con todas las opciones con su valor por defecto, excepto el margen (parámetro $margin), tienes que escribir:

$pdf->Write(10, "¡Hola mundo!", '', false, '', false, 0, false, false, 0, 0, [1, 1, 1, 1]);

… Tienes que conocer la cantidad, el orden, los valores por defecto de cada parámetro, y eventualmente el significado de cada uno cuando quieras modificar algo de este caos tiempo después. Y tener cuidado al escribir, añadir u omitir un argumento es muy fácil. A parte, se ve más feo que el año 2020.

Si aprueban el RFC de argumentos con nombre, esta llamada podría verse así:

$pdf->Write(10, "¡Hola mundo!", margin: [1, 1, 1, 1]);

hermoso. Y ya sabes qué significa el último parámetro.

Otro ejemplo. Estas son las declaraciones de las funciones strpos()y in_array():

strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) : int
in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) : bool

…$needle y $haystack están en distinto orden . Esta confusión se puede evitar usando argumentos con nombre:

$pos = strpos(needle: 'er', haystack: 'Perú');

La sintaxis aun está por definirse, otras propuestas incluyen usar el mismo operador de asignación en arrays =>, o usar el nombre de la variable en vez de una constante de caracteres: $pos = strpos($needle: 'er', $haystack: 'Perú');. Personalmente prefiero el uso de los dos puntos.

Este RFC también propone a futuro implementar una sintaxis corta cuando el nombre del parámetro y el nombre de la variable de valor son el mismo. En vez de escribir:

ejecutar (contacto: $contacto, venta: $venta, opciones: $opciones);

Puedes escribir:

ejecutar (:$contacto, :$venta, :$opciones);

Y si esa sintaxis la trasladan a los arrays asociativos, ya podríamos dejar de usar la función compact(), y se vería más elegante.

PHP 8 tiene aun varios otros cambios aceptados y por aceptar, la página PHP RFC Watch muestra el avance de las votaciones de todos esos cambios. En este momento está disponible la mayoría de nuevos features en la versión Alpha 2 de PHP 8, por si quieres probarlos.

Los argumentos con nombre era una de las dos cosas que más deseaba en PHP, la otra es sobrecarga de operadores. Esperemos que sea pronto.