PHP 8.0: Nuevas funciones, clases y JIT (4/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 último, veremos las nuevas funciones y clases y presentaremos el Just in Time Compiler.

Nuevas funciones
La biblioteca estándar de PHP dispone de cientos de funciones y en la versión 8.0 han aparecido seis nuevas. Parece poco, pero la mayoría de ellas cubren puntos débiles del lenguaje. Lo cual encaja bien con el tono general de la versión 8.0, que refina y consolida PHP como ninguna versión anterior. Puede encontrar un resumen de todas las nuevas funciones y métodos en la guía de migración.
str_contains()
str_starts_with()
str_ends_with()
Funciones para determinar si una cadena empieza, termina o contiene una subcadena.
if (str_contains('Nette', 'te')) {
...
}
Junto con la llegada de este trío, PHP define cómo tratar la cadena vacía al buscar, según lo cual se rigen también todas las demás funciones relacionadas, de manera que la cadena vacía se encuentra en todas partes:
str_contains('Nette', '') // true
str_starts_with('Nette', '') // true
strpos('Nette', '') // 0 (antes false)
Gracias a esto, el comportamiento del trío es completamente idéntico a sus análogos en Nette:
str_contains() # Nette\Utils\String::contains()
str_starts_with() # Nette\Utils\String::startsWith()
str_ends_with() # Nette\Utils\String::endsWith()
¿Por qué son tan importantes estas funciones? Las bibliotecas estándar de
todos los lenguajes siempre están cargadas con el desarrollo histórico y no se
pueden evitar inconsistencias y errores. Pero al mismo tiempo, son la tarjeta de
visita de cada lenguaje. Es sorprendente que en un PHP de 25 años falten
funciones para operaciones tan básicas como devolver el primer o último
elemento de un array, escapar HTML sin trampas (htmlspecialchars
no
escapa el apóstrofo), o precisamente buscar una cadena dentro de otra. Que se
pueda de alguna manera evitar no se sostiene, porque el resultado no es
un código legible y comprensible. Es una lección para todos los autores de
API. Cuando ve que una parte considerable de la documentación de una función
se dedica a explicar sus trampas (como los valores de retorno de
strpos
), es una señal clara para modificar la biblioteca y añadir
precisamente str_contains
.
get_debug_type()
Reemplaza al ya obsoleto get_type()
. En lugar de tipos largos
como integer
devuelve los ahora usados int
, en el caso
de objetos devuelve directamente el tipo:
Valor | gettype() | get_debug_type() |
---|---|---|
'abc' |
string |
string |
[1, 2] |
array |
array |
231 |
integer |
int |
3.14 |
double |
float |
true |
boolean |
bool |
null |
NULL |
null |
new stdClass |
object |
stdClass |
new Foo\Bar |
object |
Foo\Bar |
function() {} |
object |
Closure |
new class {} |
object |
class@anonymous |
new class extends Foo {} |
object |
Foo@anonymous |
curl_init() |
resource |
resource (curl) |
curl_close($ch) |
resource (closed) |
resource (closed) |
Objetivación de recursos
Los valores de tipo resource provienen de tiempos en que PHP aún no tenía objetos, pero en realidad los necesitaba. Así nacieron los resources. Hoy tenemos objetos y, en comparación con los resources, funcionan mucho mejor con el recolector de basura, por lo que el plan es reemplazarlos gradualmente todos por objetos.
Desde PHP 8.0, se convierten en objetos los resources de imágenes, conexiones curl, openssl, xml, etc.. En PHP 8.1, será el turno de las conexiones FTP, etc.
$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage // true
is_resource($res) // false - BC break
Estos objetos aún no tienen métodos, ni puede crear directamente sus
instancias. Por ahora, se trata realmente solo de eliminar los obsoletos
resources de PHP sin cambiar la API. Y eso es bueno, porque crear una buena API
es una tarea separada y exigente. Nadie desea que en PHP surjan más clases como
SplFileObject con métodos llamados fgetc()
o
fgets()
.
PhpToken
El tokenizador también se traslada a objetos y, por lo tanto, las funciones
alrededor de token_get_all
. Esta vez no se trata de deshacerse de
los resources, sino que obtenemos un objeto completo que representa un
token PHP.
<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0]; // instancia de PhpToken
echo $token->id; // T_OPEN_TAG
echo $token->text; // '<?php'
echo $token->line; // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING); // false
echo $token->isIgnorable(); // true
El método isIgnorable()
devuelve true para los tokens
T_WHITESPACE
, T_COMMENT
, T_DOC_COMMENT
y
T_OPEN_TAG
.
Mapas débiles (Weak maps)
Los mapas débiles están relacionados con el recolector de basura, que libera de la memoria todos los objetos y valores que ya no se utilizan (es decir, no hay ninguna variable o propiedad en uso que los contenga). Dado que la vida de un hilo PHP es efímera y hoy en día tenemos suficiente memoria disponible en los servidores, generalmente no nos preocupamos por cuestiones relacionadas con la liberación eficiente de memoria. Pero para scripts que se ejecutan durante más tiempo, son fundamentales.
El objeto WeakMap
es similar a SplObjectStorage
. En
ambos, se utilizan objetos como claves y permiten almacenar valores arbitrarios
bajo ellas. La diferencia es que WeakMap
no impide que el objeto
sea liberado por el recolector de basura. Es decir, si el único lugar donde
todavía existe el objeto es como clave en un mapa débil, será eliminado del
mapa y de la memoria.
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 'data for $obj';
dump(count($map)); // 1
unset($obj);
dump(count($map)); // 0
¿Para qué sirve esto? Por ejemplo, para el almacenamiento en caché.
Supongamos que tenemos un método loadComments()
, al que le pasamos
un artículo de blog y devuelve todos sus comentarios. Como el método se llama
repetidamente con el mismo artículo, crearemos también
getComments()
, que almacenará en caché el resultado del primer
método:
class Comments
{
private WeakMap $cache;
public function __construct()
{
$this->cache = new WeakMap;
}
public function getComments(Article $article): ?array
{
$this->cache[$article] ??= $this->loadComments($article);
return $this->cache[$article]
}
...
}
El truco está en que, en el momento en que se libera el objeto
$article
(por ejemplo, la aplicación empieza a trabajar con otro
artículo), también se libera su entrada de la caché.
PHP JIT (Compilador Just in Time)
Quizás sepa que PHP se compila en el llamado opcode, que son instrucciones de bajo nivel que puede ver por ejemplo aquí y que ejecuta la máquina virtual de PHP. ¿Y qué es JIT? JIT puede compilar PHP de forma transparente directamente a código máquina, que ejecuta directamente el procesador, por lo que se evita la ejecución más lenta de la máquina virtual.
JIT, por lo tanto, tiene como objetivo acelerar PHP.
El esfuerzo por implementar JIT en PHP se remonta a 2011 y detrás de él está Dmitry Stogov. Desde entonces, ha probado 3 implementaciones diferentes, pero ninguna de ellas llegó a la versión estable de PHP por estas razones: el resultado nunca fue un aumento sustancial del rendimiento para aplicaciones web típicas; complica el mantenimiento de PHP (es decir, nadie excepto Dmitry lo entiende 😉); existían otras formas de mejorar el rendimiento sin necesidad de usar JIT.
El salto en el rendimiento de PHP en la versión 7 fue un subproducto del trabajo en JIT, aunque paradójicamente no se llegó a implementar. Esto ocurre ahora en PHP 8. Pero frenaré de inmediato las expectativas exageradas: probablemente no notará ninguna aceleración.
Entonces, ¿por qué entra JIT en PHP? Por un lado, otras vías para mejorar el rendimiento se están agotando lentamente y JIT simplemente está en la lista. Aunque no aporta aceleración en aplicaciones web comunes, acelera fundamentalmente, por ejemplo, los cálculos matemáticos. Se abre así la posibilidad de empezar a escribir estas cosas en PHP. Y, de hecho, sería posible implementar directamente en PHP funciones que hasta ahora requerían implementación directa en C por velocidad.
JIT forma parte de la extensión opcache
y se activa junto con
ella en php.ini (lea la documentación
sobre ese cuarteto de dígitos):
zend_extension=php_opcache.dll
opcache.jit=1205 ; configuración mediante el cuarteto OTRC
opcache.enable_cli=1 ; para que funcione también en CLI
opcache.jit_buffer_size=128M ; memoria reservada para el código compilado
Que JIT está funcionando lo sabrá, por ejemplo, en el panel de información en la Barra Tracy.
JIT funciona muy bien cuando todas las variables tienen tipos claramente
definidos y no pueden cambiar durante llamadas repetidas al mismo código. Por
lo tanto, tengo curiosidad por saber si algún día declararemos tipos también
para las variables en
PHP: string $s = 'Hola, esta es la conclusión de la serie';
Para enviar un comentario, inicie sesión