PHP 8.0: Nuevas funciones, clases y JIT (4/4)
La versión 8.0 de PHP ha sido liberada. Está llena de nuevas funciones como ninguna otra versión antes. Su introducción merecía cuatro artículos separados. En la última parte echaremos un vistazo a las nuevas funciones y clases e introduciremos el compilador Just in Time.
Nuevas funciones
La librería estándar de PHP tiene cientos de funciones y en la versión 8.0 han aparecido seis nuevas. No parece mucho, pero la mayoría de ellas remedian puntos débiles del lenguaje. Lo cual se alinea muy bien con el concepto de la versión 8.0, que refuerza y consolida PHP como ninguna versión antes. Una visión general de todas las nuevas funciones y métodos se puede encontrar en la guía de migración.
str_contains()
str_starts_with()
str_ends_with()
Funciones para determinar si una cadena comienza, termina o contiene una subcadena.
if (str_contains('Nette', 'te')) {
...
}
Con la llegada de esta trinidad, PHP define cómo manejar una cadena vacía mientras se busca, que es a lo que se adhieren todas las demás funciones relacionadas, y es que una cadena vacía se encuentra en todas partes:.
str_contains('Nette', '') // true
str_starts_with('Nette', '') // true
strpos('Nette', '') // 0 (previously false)
Gracias a esto, el comportamiento de la trinidad es completamente idéntico al de los análogos de 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 lastradas por el desarrollo histórico; no se
pueden evitar las incoherencias y los pasos en falso. Pero al mismo tiempo es un
testimonio del lenguaje respectivo. Sorprendentemente, el PHP de hace 25 años
carece de funciones para operaciones tan básicas como devolver el primer
o último elemento de un array, escapar HTML sin sorpresas desagradables
(htmlspecialchars
no escapa un apóstrofo), o simplemente buscar
una cadena en una cadena. No se sostiene que pueda saltarse de alguna
manera, porque el resultado no es un código legible y comprensible. Esta
es una lección para todos los autores de API. Cuando ves que gran parte de la
documentación de la función está ocupada por explicaciones de trampas (como
los valores de retorno de strpos
), es una clara señal para
modificar la biblioteca y añadir str_contains
.
get_debug_type()
Sustituye al ya obsoleto get_type()
. En lugar de tipos largos
como integer
, devuelve el hoy utilizado 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) |
Migración de recursos a objetos
Los valores de tipo resource provienen de una época en la que PHP aún no tenía objetos, pero en realidad los necesitaba. Así nacieron los recursos. Hoy tenemos objetos y, comparados con los recursos, funcionan mucho mejor con el recolector de basura, así que el plan es reemplazarlos gradualmente a todos con objetos.
A partir de PHP 8.0, los recursos images, curl joins, openssl, xml, etc. son cambiados a objetos. En PHP 8.1, seguirán 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 puedes instanciarlos directamente.
Hasta ahora, es sólo cuestión de deshacerse de recursos obsoletos de PHP sin
cambiar la API. Y eso es bueno, porque crear una buena API es una tarea aparte y
desafiante. Nadie desea la creación de nuevas clases PHP como SplFileObject con
métodos llamados fgetc()
o fgets()
.
PhpToken
El tokenizador y las funciones alrededor de token_get_all
también se migran a objetos. Esta vez no se trata de deshacerse de recursos,
sino que obtenemos un objeto completo que representa un token PHP.
<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0]; // instance 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
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 usan (es decir, no hay ninguna variable o propiedad que los contenga). Debido a que los hilos de PHP son de corta duración y tenemos mucha memoria disponible en nuestros servidores, usualmente no tratamos temas relacionados con la liberación efectiva de memoria. Pero para scripts de larga duración, son esenciales.
El objeto WeakMap
es similar a SplObjectStorage
Ambos utilizan objetos como claves y permiten almacenar valores arbitrarios bajo
ellos. La diferencia es que WeakMap
no impide que el objeto sea
liberado por el recolector de basura. Es decir, si el único lugar, donde el
objeto existe actualmente, es una clave en el 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? Por ejemplo, para cachear. Tengamos un método
loadComments()
al que le pasamos un artículo de un blog y nos
devuelve todos sus comentarios. Como el método se llama repetidamente para el
mismo artículo, crearemos otro 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]
}
...
}
La cuestión es que cuando el objeto $article
se libera (por
ejemplo, la aplicación empieza a trabajar con otro artículo), su entrada
también se libera de la caché.
PHP JIT (Compilador Justo a Tiempo)
Puede que sepas que PHP se compila en los llamados opcode, que son instrucciones de bajo nivel que puedes ver aquí, por ejemplo y que son ejecutadas por una máquina virtual PHP. ¿Y qué es un JIT? JIT puede compilar PHP de forma transparente directamente en código máquina, que es ejecutado directamente por el procesador, de forma que se evita la ejecución más lenta por parte de la máquina virtual.
Por lo tanto, JIT está pensado para acelerar PHP.
El esfuerzo por implementar JIT en PHP se remonta a 2011 y está respaldado por Dmitry Stogov. Desde entonces, ha probado 3 implementaciones diferentes, pero ninguna de ellas llegó a una versión final de PHP por tres razones: el resultado nunca ha sido un aumento significativo del rendimiento para aplicaciones web típicas; complica el mantenimiento de PHP (es decir, nadie excepto Dmitry lo entiende 😉); había otras formas de mejorar el rendimiento sin tener que usar un JIT.
El salto en el aumento del rendimiento observado en la versión 7 de PHP fue un subproducto del trabajo sobre el JIT, aunque paradójicamente no se desplegó. Esto sólo está ocurriendo ahora en PHP 8. Pero voy a contener las expectativas exageradas: probablemente no verás ningún aumento de velocidad.
Entonces, ¿por qué está entrando JIT en PHP? Primero, otras formas de mejorar el rendimiento se están agotando lentamente, y JIT es simplemente el siguiente paso. En aplicaciones web comunes, no aporta ninguna mejora de velocidad, pero acelera significativamente, por ejemplo, los cálculos matemáticos. Esto abre la posibilidad de empezar a escribir estas cosas en PHP. De hecho, sería posible implementar algunas funciones directamente en PHP que antes requerían una implementación directa en C debido a la velocidad.
JIT es parte de la extensión opcache
y se habilita junto con
ella en php.ini (lea la documentación
sobre esos cuatro dígitos):
zend_extension=php_opcache.dll
opcache.jit=1205 ; configuration using four digits OTRC
opcache.enable_cli=1 ; in order to work in the CLI as well
opcache.jit_buffer_size=128M ; dedicated memory for compiled code
Puede verificar que JIT está funcionando, por ejemplo, en el panel de información de Tracy Bar.
JIT funciona muy bien si todas las variables tienen tipos claramente
definidos y no pueden cambiar aunque se llame al mismo código repetidamente.
Por eso me pregunto si algún día también declararemos tipos en PHP para las
variables: string $s = 'Bye, this is the end of the series';
Para enviar un comentario, inicie sesión