Se espera que PHP 8 sea lanzado en diciembre de 2020 y nos traerá un montón de características poderosas y grandes mejoras en el lenguaje.

Ya se han aprobado e implementado muchos RFC, así que es hora de que nos sumerjamos en algunas de las adiciones más emocionantes que deberían hacer que PHP sea más rápido y fiable.

Como PHP 8 está todavía en desarrollo, podríamos ver varios cambios antes de la publicación final. Haremos un seguimiento de estos cambios y actualizaremos este post regularmente, así que asegúrate de no perderte nada de PHP 8 y revisa este post de vez en cuando.

Entonces, ¿qué características y mejoras debemos esperar con PHP 8? ¿Qué es lo más importante que viene con PHP 8, la próxima gran versión del lenguaje?

¡Vamos a sumergirnos!

PHP JIT (Just in Time Compiler)

La característica más destacada que viene con PHP 8 es el compilador Just-in-time (JIT). ¿De qué se trata JIT?

La propuesta de la RFC describe el JIT de la siguiente manera:

«PHP JIT está implementado como una parte casi independiente de OPcache. Puede ser activado/desactivado en tiempo de compilación de PHP y en tiempo de ejecución. Cuando se habilita, el código nativo de los archivos PHP se almacena en una región adicional de la memoria compartida de OPcache y los manejadores op_array→opcodes[]. mantienen punteros a los puntos de entrada del código JIT».

Entonces, ¿cómo llegamos a JIT y cuál es la diferencia entre JIT vs. OPcache?

Para entender mejor lo que es JIT para PHP, echemos un vistazo rápido a cómo PHP se ejecuta desde el código fuente hasta el resultado final.

La ejecución de PHP es un proceso de 4 etapas:

La siguiente imagen muestra una representación visual del proceso básico de ejecución de PHP.

Proceso básico de ejecución de PHP
Proceso básico de ejecución de PHP

Entonces, ¿cómo hace OPcache que PHP sea más rápido? ¿Y qué cambia en el proceso de ejecución con JIT?

La Extensión de OPcache

PHP es un lenguaje interpretado. Esto significa que cuando un script PHP se ejecuta, el intérprete analiza, compila y ejecuta el código una y otra vez en cada solicitud. Esto puede resultar en una pérdida de recursos de la CPU y de tiempo adicional.

Aquí es donde entra en juego la extensión de la OPcache:

«OPcache mejora el rendimiento de PHP almacenando el código de bytes de los scripts precompilados en la memoria compartida, eliminando así la necesidad de que PHP cargue y analice los scripts en cada solicitud».

Con OPcache habilitado, el intérprete de PHP pasa por el proceso de 4 etapas mencionado anteriormente sólo la primera vez cuando se ejecuta el script. Dado que los bytecodes de PHP se almacenan en la memoria compartida, están disponibles inmediatamente como representación intermedia de bajo nivel y pueden ser ejecutados en la VM Zend de inmediato.

Proceso de ejecución de PHP con OPcache habilitado
Proceso de ejecución de PHP con OPcache habilitado

A partir de PHP 5.5, la extensión OPcache de Zend está disponible por defecto y puedes comprobar si la tienes correctamente configurada simplemente llamando a phpinfo() desde un script en tu servidor o comprobando tu archivo php.ini (ver los ajustes de configuración de OPcache).

Lectura sugerida: ¿Cómo mejorar el límite de memoria de PHP en WordPress?

La sección Zend OPcache en una página phpinfo
La sección Zend OPcache en una página phpinfo

Precarga

Recientemente se ha mejorado OPcache con la implementación de la precarga, una nueva característica de OPcache añadida con PHP 7.4. La precarga proporciona una forma de almacenar un conjunto específico de scripts en la memoria OPcache «antes de que se ejecute cualquier código de aplicación«, pero no aporta una mejora tangible del rendimiento para las aplicaciones típicas basadas en la web.

Puedes leer más sobre la precarga en nuestra introducción a PHP 7.4.

Con JIT, PHP da un paso adelante.

JIT – El compilador Just in Time

Incluso si los opcodes están en forma de representación intermedia de bajo nivel, todavía tienen que ser compilados en código de máquina. JIT «no introduce ninguna forma adicional de IR (Representación Intermedia)», sino que utiliza DynASM (Dynamic Assembler para motores de generación de código) para generar código nativo directamente desde el código de bytes de PHP.

En resumen, JIT traduce las partes calientes del código intermedio a código de máquina. Pasando por alto la compilación, sería capaz de traer considerables mejoras en el rendimiento y el uso de la memoria.

Zeev Surasky, co-autor de la propuesta PHP JIT, muestra cuánto más rápidos serían los cálculos con JIT:

Pero, ¿mejoraría el JIT efectivamente el rendimiento de WordPress?

JIT para aplicaciones web en vivo

Según el JIT RFC, la implementación del compilador «just in time» debería mejorar el rendimiento de PHP. ¿Pero realmente experimentaríamos tales mejoras en aplicaciones de la vida real como WordPress?

Las primeras pruebas muestran que el JIT haría que las cargas de trabajo intensivas de la CPU se ejecuten significativamente más rápido, sin embargo, el RFC advierte:

«… como los intentos anteriores – actualmente no parecen mejorar significativamente las aplicaciones de la vida real como WordPress (con opcache.jit=1235 326 req/sec vs 315 req/sec).

Se planea proporcionar un esfuerzo adicional, mejorando el JIT para las aplicaciones de la vida real, usando perfiles y optimizaciones especulativas».

Con el JIT activado, el código no sería ejecutado por el VM de Zend, sino por la propia CPU, y esto mejoraría la velocidad de cálculo. Las aplicaciones web como WordPress también dependen de otros factores como TTFB, optimización de bases de datos, peticiones HTTP, etc.

Así que, cuando se trata de WordPress y aplicaciones similares, no deberíamos esperar un gran aumento en la velocidad de ejecución de PHP. Sin embargo, JIT podría traer varios beneficios para los desarrolladores.

Según Nikita Popov:

«Los beneficios del compilador de JIT son aproximadamente (y como ya se ha señalado en el RFC):

  • Un rendimiento significativamente mejor para el código numérico.
  • Un rendimiento ligeramente mejor para el código «típico» de una aplicación web PHP.
  • El potencial para mover más código de C a PHP, porque PHP será ahora suficientemente rápido.»

Así que, mientras que JIT difícilmente traerá grandes mejoras en el rendimiento de WordPress, estará actualizando el PHP al siguiente nivel, convirtiéndolo en un lenguaje en el que muchas funciones podrían ser escritas directamente.

El inconveniente, sin embargo, sería la mayor complejidad que puede llevar a un aumento de los costos de mantenimiento, estabilidad y depuración. De acuerdo con Dmitry Stogov:

«El JIT es extremadamente simple, pero de todas formas aumenta el nivel de toda la complejidad del PHP, el riesgo de nuevos tipos de errores y el costo de desarrollo y mantenimiento.»

La propuesta de incluir JIT en PHP 8 fue aprobada con 50 votos a favor y 2 en contra.

Mejoras y nuevas características de PHP 8

Aparte de JIT, podemos esperar muchas características y mejoras con PHP 8. La siguiente lista es nuestra selección de las próximas adiciones y cambios que deberían hacer a PHP más confiable y eficiente.

Promoción de la propiedad de los constructores

Como resultado de una discusión en curso sobre cómo mejorar la ergonomía de los objetos en PHP, la RFC del Constructor property promotion propone una nueva y más concisa sintaxis que simplificará la declaración de la propiedad, haciéndola más corta y menos redundante.

Esta propuesta solo se refiere a los parámetros promovidos, es decir, los parámetros de método prefijados con palabras clave de visibilidad pública, protegidas y privadas.

Actualmente, todas las propiedades tienen que ser repetidas varias veces (al menos cuatro veces) antes de que podamos usarlas con los objetos. Consideremos el siguiente ejemplo de la RFC:

class Point {
    public int $x;
    public int $y;
    public int $z;

    public function __construct(
        int $x = 0,
        int $y = 0,
        int $z = 0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

De acuerdo con Nikita Popov, el autor del RFC, tenemos que escribir el nombre de la propiedad al menos cuatro veces en tres lugares diferentes: la declaración de propiedad, los parámetros del constructor y la asignación de la propiedad. Esta sintaxis no es particularmente utilizable, sobre todo en clases con un buen número de propiedades y nombres más descriptivos.

Esta RFC propone fusionar el constructor y la definición de los parámetros. Así, a partir de PHP 8, tenemos una forma más utilizable de declarar los parámetros y el código visto anteriormente puede cambiar como se muestra a continuación:

class Point {
    public function __construct(
        public int $x = 0,
        public int $y = 0,
        public int $z = 0,
    ) {}
}

Y eso es todo. Así que tenemos una nueva forma de promover las propiedades que es más corta, más legible y menos propensa a errores. De acuerdo con Nikita:

Estamos haciendo una simple transformación sintáctica. Pero esto reduce la cantidad de código que tienes que escribir para los objetos de valor en particular…

La declaración de la propiedad se transforma, ya que habíamos declarado explícitamente esas propiedades y podemos utilizar la API Reflection para introspectar las definiciones de las propiedades antes de la ejecución (véase Desugaring):

Reflection (y otros mecanismos de introspección) observará el estado después del desugaring. Esto significa que las propiedades promovidas aparecerán de la misma manera que las propiedades explícitamente declaradas, y los argumentos de construcción promovidos aparecerán como argumentos de construcción ordinarios.

// before desugaring
class Point {
    public function __construct(public int $x = 0) {}
}

// after desugaring
class Point {
    public int $x;

    public function __construct(int $x = 0) {
        $this->x = $x;
    }
}

Herencia

No tenemos ninguna limitación en el uso de la herencia en conjunto con los parámetros promovidos. De todos modos, no hay una relación particular entre los padres y los hijos constructores de clase. Según Nikita:

Normalmente decimos que los métodos siempre tienen que ser compatibles con el método de los padres. […] pero esta regla no se aplica para el constructor. Así que el constructor realmente pertenece a una sola clase, y los constructores entre la clase padre e hijo no tienen que ser compatibles de ninguna manera.

 

Aquí tienes un ejemplo:

class Test {
    public function __construct(
        public int $x = 0
    ) {}
}

class Child extends Test {
    public function __construct(
        $x, 
        public int $y = 0,
        public int $z = 0,
    ) {
        parent::__construct($x);
    }
}

Lo que no se permite con las propiedades promocionadas

Las propiedades promocionadas están permitidas en los constructores y rasgos no abstractos, pero hay varias limitaciones que vale la pena mencionar aquí.

Los constructores abstractos

Las propiedades promocionadas no están permitidas en las clases e interfaces abstractas:

abstract class Test {
    // Error: Abstract constructor.
    abstract public function __construct(private $x);
}
 
interface Test {
    // Error: Abstract constructor.
    public function __construct(private $x);
}
Nulidad

Una de las limitaciones más notables está relacionada con la nulidad. Anteriormente, cuando utilizábamos un tipo que no era explícitamente anulable, pero con un valor predeterminado nulo, el tipo era implícitamente anulable. Pero con los tipos de propiedad, no tenemos este comportamiento implícito porque los parámetros promovidos requieren una declaración de propiedad, y el tipo anulable debe ser declarado explícitamente. Véase el siguiente ejemplo del RFC:

class Test {
    // Error: Using null default on non-nullable property
    public function __construct(public Type $prop = null) {}

    // Correct: Make the type explicitly nullable instead
    public function __construct(public ?Type $prop = null) {}
}
Tipo de llamada

Como el llamable no es un tipo soportado para las propiedades, no se nos permite usar el tipo llamable en las propiedades promocionadas:

class Test {
    // Error: Callable type not supported for properties.
    public function __construct(public callable $callback) {}
}
La palabra clave var no está permitida

Solo se puede utilizar una palabra clave de visibilidad con los parámetros promocionados, por lo que no se permite declarar las propiedades del constructor con la palabra clave var (véase el siguiente ejemplo de la RFC):

class Test {
    // Error: "var" keyword is not supported.
    public function __construct(var $prop) {}
}
No se permiten duplicaciones

Podemos combinar las propiedades promocionadas y las propiedades explícitas en la misma clase, pero las propiedades no pueden ser declaradas dos veces:

class Test {
    public string $prop;
    public int $explicitProp;

    // Correct
    public function __construct(public int $promotedProp, int $arg) {
        $this->explicitProp = $arg;
    }

    // Error: Redeclaration of property.
    public function __construct(public string $prop) {}
}
No se permiten parámetros variables

La razón aquí es que el tipo declarado es diferente del parámetro variable, que en realidad es un conjunto:

class Test {
    // Error: Variadic parameter.
    public function __construct(public string ...$strings) {}
}

Otras lecturas

Para ver más de cerca el Constructor property promotion, escucha esta entrevista con Nikita Popov. Para una visión en profundidad de la ergonomía de los objetos en PHP, lee este post y la siguiente entrevista con Larry Garfield.

Validación de los métodos de rasgos abstractos

Los rasgos se definen como «un mecanismo para la reutilización del código en lenguajes de herencia única como el PHP». Típicamente, se usan para declarar métodos que pueden ser usados en múltiples clases.

Un rasgo también puede contener métodos abstractos. Estos métodos simplemente declaran la firma del método, pero la implementación del método debe hacerse dentro de la clase que utiliza el rasgo.

De acuerdo con el manual de PHP,

«Los rasgos apoyan el uso de métodos abstractos para imponer requisitos a la clase expositora.»

Esto también significa que las firmas de los métodos deben coincidir. En otras palabras, el tipo y el número de argumentos requeridos deben ser los mismos.

De todos modos, según Nikita Popov, autor de la RFC, la validación de la firma se aplica actualmente sólo de manera puntual:

El siguiente ejemplo de Nikita se refiere al primer caso (firma no ejecutada):

trait T {
	abstract public function test(int $x);
}
 
class C {
	use T;

	// Allowed, but shouldn't be due to invalid type.
	public function test(string $x) {}
}

Dicho esto, este RFC propone lanzar siempre un error fatal si el método de implementación no es compatible con el método del rasgo abstracto, independientemente de su origen:

Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10

Esta RFC ha sido aprobada por unanimidad.

Firmas de métodos incompatibles

En PHP, los errores de herencia debidos a firmas de métodos incompatibles arrojan un error fatal o una advertencia, dependiendo de lo que esté causando el error.

Si una clase está implementando una interfaz, las firmas de métodos incompatibles arrojan un error fatal. Según la documentación de las Interfaces de Objetos:

«La clase que implemente la interfaz debe utilizar una firma de método que sea compatible con el LSP (Principio de Sustitución de Liskov). No hacerlo resultará en un error fatal.»

He aquí un ejemplo de un error de herencia con una interfaz:

interface I {
	public function method(array $a);
}
class C implements I {
	public function method(int $a) {}
}

En PHP 7.4, el código de arriba daría el siguiente error:

Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7

Una función en una clase de hijos con una firma incompatible lanzaría una advertencia. Ve el siguiente código del RFC:

class C1 {
	public function method(array $a) {}
}
class C2 extends C1 {
	public function method(int $a) {}
}

En PHP 7.4, el código de arriba simplemente lanzaría una advertencia:

Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Ahora, este RFC propone lanzar siempre un error fatal para las firmas de métodos incompatibles. Con PHP 8, el código que vimos anteriormente arriba indicaría lo siguiente:

Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Los arreglos que comienzan con un índice negativo

En PHP, si una matriz comienza con un índice negativo (start_index < 0), los siguientes índices comenzarán desde 0 (más sobre esto en la documentación de array_fill). Observa el siguiente ejemplo:

$a = array_fill(-5, 4, true);
var_dump($a);

En PHP 7.4 el resultado sería el siguiente:

array(4) {
	[-5]=>
	bool(true)
	[0]=>
	bool(true)
	[1]=>
	bool(true)
	[2]=>
	bool(true)
}

Ahora, RFC propone cambiar las cosas para que el segundo índice sea start_index + 1, cualquiera que sea el valor de start_index.

En PHP 8, el código de arriba resultaría en la siguiente matriz:

array(4) {
	[-5]=>
	bool(true)
	[-4]=>
	bool(true)
	[-3]=>
	bool(true)
	[-2]=>
	bool(true)
}

Con PHP 8, las matrices que comienzan con un índice negativo cambian su comportamiento. Lee más sobre las incompatibilidades hacia atrás en el RFC.

Tipos de Unión 2.0

Los tipos de unión aceptan valores que pueden ser de diferentes tipos. Actualmente, PHP no provee soporte para tipos de unión, con la excepción de la sintaxis del ?Type y el tipo especial iterable.

Antes de PHP 8, los tipos de unión sólo podían ser especificados en anotaciones phpdoc, como se muestra en el siguiente ejemplo del RFC:

class Number {
	/**
	 * @var int|float $number
	 */
	private $number;

	/**
	 * @param int|float $number
	 */
	public function setNumber($number) {
		$this->number = $number;
	}

	/**
	 * @return int|float
	 */
	public function getNumber() {
		return $this->number;
	}
}

Ahora, el RFC de los tipos de unión 2.0 propone añadir soporte para los tipos de unión en las firmas de las funciones, de manera que no dependeremos más de la documentación en línea, sino que definiremos los tipos de unión con una sintaxis T1|T2|... en su lugar:

class Number {
	private int|float $number;

	public function setNumber(int|float $number): void {
		$this->number = $number;
	}

	public function getNumber(): int|float {
		return $this->number;
	}
}

Como explicó Nikita Popov en el RFC,

«Apoyar los tipos de unión en el lenguaje nos permite pasar más información de tipos de phpdoc a firmas de funciones, con las ventajas habituales que esto conlleva:

  • Los tipos se aplican realmente, así que los errores se pueden detectar a tiempo.
  • Debido a que se hacen cumplir, es menos probable que la información tipo se vuelva anticuada o que se pierda en los bordes de los casos.
  • Los tipos se comprueban durante la herencia, aplicando el principio de sustitución de Liskov.
  • Los tipos están disponibles a través de Reflection.
  • La sintaxis es mucho menos «boilerplate-y» que «phpdoc».

Los tipos de unión apoyan todos los tipos disponibles, con algunas limitaciones:

  • El tipo de void no podría formar parte de una unión, ya que void significa que una función no devuelve ningún valor.
  • El tipo null sólo se admite en los tipos de unión, pero su uso como tipo independiente no está permitido.
  • La notación de tipo null (?T) también está permitida, es decir, T|null, pero no se nos permite incluir la notación ?T en los tipos de unión (?T1|T2 no está permitida y debemos usar T1|T2|null en su lugar).
  • Como muchas funciones (es decir, strpos(), strstr(), substr(), etc.) incluyen false entre los posibles tipos de retorno, el false pseudo-tipo también está soportado.

Puedes leer más sobre los Tipos de Unión V2 en el RFC.

Errores de tipo consistentes para las funciones internas

Al pasar un parámetro de tipo ilegal, las funciones internas y las definidas por el usuario se comportan de manera diferente.

Las funciones definidas por el usuario arrojan un error TypeError, pero las funciones internas se comportan de diversas maneras, dependiendo de varias condiciones. De todos modos, el comportamiento típico es lanzar una advertencia y volver null. Ve el siguiente ejemplo en PHP 7.4:

var_dump(strlen(new stdClass));

Esto resultaría en la siguiente advertencia:

Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4
NULL

Si se habilita la opción strict_types, o la información del argumento especifica los tipos, el comportamiento sería diferente. En tales escenarios, el error de tipo se detecta y resulta en un TypeError.

Esta situación daría lugar a una serie de problemas bien explicados en sección de cuestiones de la RFC.

Para eliminar estas inconsistencias, este RFC propone hacer que las APIs de análisis de parámetros internos generen siempre un ThrowError en caso de una falta de coincidencia de tipo de parámetro.

En PHP 8, el código de arriba arroja el siguiente error:

Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 {main}
  thrown in /path/to/your/test.php on line 4

throw Expresión

En PHP, throw es una declaración, por lo que no es posible usarla en lugares donde sólo se permite una expresión.

Este RFC propone convertir la declaración de throw en una expresión para que pueda ser utilizada en cualquier contexto donde se permitan las expresiones. Por ejemplo, funciones de flecha, operador de coalescencia nula, operadores ternario y elvis, etc.

Vea los siguientes ejemplos de la RFC:

$callable = fn() => throw new Exception();

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
 
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

Mapas débiles

Un mapa débil es una colección de datos (objetos) en la que las claves están débilmente referenciadas, lo que significa que no son impedidos que recojan la basura.

PHP 7.4 añadió soporte para referencias débiles como una forma de retener una referencia a un objeto que no evita que el objeto mismo sea destruido. Como señaló Nikita Popov,

«Las referencias débiles en bruto sólo tienen una utilidad limitada por sí mismas y los mapas débiles se utilizan mucho más comúnmente en la práctica. No es posible implementar un mapa débil eficiente encima de las referencias débiles del PHP porque no se proporciona la capacidad de registrar una llamada de destrucción.»

Por eso este RFC introduce una clase de WeakMap para crear objetos para ser usados como claves de mapas débiles que pueden ser destruidos y eliminados del mapa débil si no hay más referencias al objeto clave.

En los procesos de larga duración, esto evitaría las fugas de memoria y mejoraría el rendimiento. Como se ve en el siguiente ejemplo de la RFC:

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);

Con PHP 8, el código anterior produciría el siguiente resultado (ver el código en acción aquí):

object(WeakMap)#1 (1) {
	[0]=>
	array(2) {
		["key"]=>
		object(stdClass)#2 (0) {
		}
		["value"]=>
		int(42)
	}
}

Si desajustas el objeto, la llave se elimina automáticamente del mapa débil:

unset($obj);
var_dump($map);

Ahora el resultado sería el siguiente:

object(WeakMap)#1 (0) {
}

Para ver más de cerca los mapas débiles, mira el RFC. La propuesta fue aprobada por unanimidad.

Coma de arrastre en la lista de parámetros

Las comas finales son comillas que se añaden a las listas de artículos en diferentes contextos. PHP 7.2 introdujo las comillas en la sintaxis de las listas, PHP 7.3 introdujo las comillas en las llamadas a funciones.

PHP 8 introduce ahora comillas en las listas de parámetros con funciones, métodos y cierres, como se muestra en el siguiente ejemplo:

class Foo {
	public function __construct(
		string $x,
		int $y,
		float $z, // trailing comma
	) {
		// do something
	}
}

Esta propuesta fue aprobada con 58 votos a favor y 1 en contra.

Allow ::class syntax en los objetos

Para buscar el nombre de una clase, podemos usar la sintaxis de FooBar::class. Este RFC propone extender la misma sintaxis a los objetos, de modo que ahora es posible obtener el nombre de la clase de un objeto dado, como se muestra en el siguiente ejemplo:

$object = new stdClass;
var_dump($object::class); // "stdClass"
 
$object = null;
var_dump($object::class); // TypeError

Con PHP 8, $object::class proporciona el mismo resultado que get_class($object). Si $object no es un objeto, lanza una excepción TypeError.

Esta propuesta fue aprobada por unanimidad.

Atributos v2

Los atributos, también conocidos como anotaciones, son una forma de metadatos estructurados que pueden utilizarse para especificar las propiedades de los objetos, elementos o archivos.

Hasta PHP 7.4, los doc-comentarios eran la única forma de añadir metadatos a las declaraciones de clases, funciones, etc. Ahora, el RFC Attributes v2 introduce atributos para PHP definiéndolos como una forma de metadatos estructurados y sintácticos que pueden ser agregados a declaraciones de clases, propiedades, funciones, métodos, parámetros y constantes.

Los atributos se añaden antes de las declaraciones a las que se refieren. Mira los siguientes ejemplos de la RFC:

<<ExampleAttribute>>
class Foo
{
	<<ExampleAttribute>>
	public const FOO = 'foo';

	<<ExampleAttribute>>
	public $x;

	<<ExampleAttribute>>
	public function foo(<<ExampleAttribute>> $bar) { }
}

$object = new <<ExampleAttribute>> class () { };

<<ExampleAttribute>>
function f1() { }

$f2 = <<ExampleAttribute>> function () { };

$f3 = <<ExampleAttribute>> fn () => 1;

Los atributos pueden ser añadidos antes o después de un comentario del bloque de documentos:

<<ExampleAttribute>>
/** docblock */
<<AnotherExampleAttribute>>
function foo() {}

Cada declaración puede tener uno o más atributos y cada atributo puede tener uno o más valores asociados:

<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}

Mira el RFC para una visión más profunda de los atributos de PHP, casos de uso y sintaxis alternativa.

Nuevas funciones PHP

PHP 8 trae varias funciones nuevas al lenguaje:

str_contains

Antes de PHP 8, strstr y strpos eran las opciones típicas de los desarrolladores para buscar una aguja dentro de una cadena dada. El problema es que ambas funciones no son consideradas muy intuitivas y su uso puede ser confuso para los nuevos desarrolladores de PHP. Ve el siguiente ejemplo:

$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
$pos = strpos($mystring, $findme);

if ($pos !== false) {
	echo "The string has been found";
} else {
	echo "String not found";
}

En el ejemplo anterior utilizamos el operador de comparación !==, que también comprueba si dos valores son del mismo tipo. Esto evita que obtengamos un error si la posición de la aguja es 0:

«Esta función puede devolver FALSE Booleano, pero también puede devolver un valor no booleano que evalúa a FALSE. […] Usa el operador === para probar el valor de retorno de esta función.»

Además, varios marcos ofrecen funciones de ayuda para buscar un valor dentro de una cadena determinada (ver la documentación de Laravel Helpers como ejemplo).

Ahora, este RFC propone la introducción de una nueva función que permite buscar dentro de una cadena: str_contains.

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

Su uso es bastante sencillo. str_contains comprobaciones si se encuentra $needle en $haystack y devuelve true o false en consecuencia.

Así que, gracias a str_contains, podemos escribir el siguiente código:

$mystring = 'Managed WordPress Hosting';
$findme   = 'WordPress';

if (str_contains($mystring, $findme)) {
	echo "The string has been found";
} else {
	echo "String not found";
}

Que es más legible y menos propenso a errores (ver este código en acción aquí).
En el momento de escribir esto, str_contains es sensible a las mayúsculas y minúsculas, pero esto podría cambiar en el futuro.

La propuesta de str_contains aprobada con 43 a 9 votos.

str_starts_with() y str_ends_with()

Además de la función str_contains, dos nuevas funciones permiten buscar una aguja dentro de una cadena dada: str_starts_with y str_ends_with.

Estas nuevas funciones comprueban si una determinada cadena comienza o termina con otra cadena:

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

Ambas funciones devuelven false si $needle es más larga que el $haystack.

Según Will Hudgins, el autor de este RFC,

«La funcionalidad str_starts_with y str_ends_with es tan comúnmente necesaria que muchos de los principales marcos PHP la soportan, incluyendo Symfony, Laravel, Yii, FuelPHP, y Phalcon

Gracias a ellos, ahora podemos evitar el uso de funciones subóptimas y menos intuitivas como substr, strpos. Ambas funciones son sensibles a las mayúsculas y minúsculas:

$str = "WordPress";
if (str_starts_with($str, "Word")) echo "Found!";

if (str_starts_with($str, "word")) echo "Not found!";

Puedes ver este código en acción aquí.

Este RFC ha sido aprobado con 51 votos a favor y 4 en contra.

get_debug_type

get_debug_type es una nueva función PHP que devuelve el tipo de una variable. La nueva función funciona de manera bastante similar a la función gettype, pero get_debug_type devuelve nombres de tipo nativos y resuelve nombres de clase.

Es una buena mejora para el lenguaje, ya que gettype() no es útil para la comprobación de tipos.

El RFC proporciona dos ejemplos útiles para entender mejor la diferencia entre la nueva función get_debug_type() y gettype(). El primer ejemplo muestra a gettype trabajando:

$bar = [1,2,3];

if (!($bar instanceof Foo)) { 
	throw new TypeError('Expected ' . Foo::class . ', got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}

Con PHP 8, podríamos usar get_debug_type, en su lugar:

if (!($bar instanceof Foo)) { 
	throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}

La siguiente tabla muestra los valores de retorno de get_debug_type y gettype:

Valor gettype() get_debug_type()
1 integer int
0.1 double float
true boolean bool
false boolean bool
null NULL null
«WordPress» string string
[1,2,3] array array
Una clase con el nombre de «Foo\Bar» object Foo\Bar
Una clase anónima object class@anonymous

RFC adicionales

En el momento de redactar este documento, varios RFC destinados a PHP 8 están todavía en borrador y/o pendientes de implementación. Las añadiremos tan pronto como su estado cambie a «Implementadas».

Aquí hay una lista rápida de mejoras adicionales aprobadas que serán parte de PHP 8:

  1. Interfaz encadenable: este RFC introduce una interfaz encadenable que se añade automáticamente a las clases que implementan el método __to String(). El objetivo principal aquí es usar el tipo de unión string|Stringable.
  2. Nuevas APIs de DOM Living Standard en ext/dom: este RFC propone implementar el actual DOM Living Standard a la extensión PHP DOM introduciendo nuevas interfaces y propiedades públicas.
  3. Tipo de retorno estático: PHP 8 introduce el uso de la static como tipo de retorno junto a los tipos self y parent.
  4. Ajustes en la sintaxis variable: este RFC resuelve algunas inconsistencias residuales en la sintaxis variable de PHP.

Resumen

¡Qué paseo! En este post, cubrimos todos los cambios y mejoras claves que se esperan con el lanzamiento de PHP 8. La más esperada de ellas es seguramente el compilador Just in Time, pero hay mucho más que viene con PHP 8.

Asegúrate de marcar esta entrada del blog ya que añadiremos nuestros favoritos a la lista tan pronto como sean aprobados. 🤓

Ahora es tu turno: ¿estás listo para probar las próximas características de PHP? ¿Cuál es tu favorita? Deja una línea en la sección de comentarios abajo.

Carlo Daniele Kinsta

Carlo es un diseñador y desarrollador de front-end freelance. Cuando escribe artículos y tutoriales, Carlo se ocupa principalmente de los estándares web, pero cuando juega con sitios web, su mejor compañero de trabajo es WordPress.