PHP 8. Todas las novedades de esta nueva versión (II)
Un repaso a fondo de las novedades que tendremos disponibles a partir del 26 de noviembre para trabajar con PHP
La semana publiqué un artículo hablando de las principales novedades que incorpora la versión 8 de PHP que estará disponible a partir del día 26 de noviembre.
En esta segunda parte quiero repasar el resto de nuevas características que tendremos disponibles ya que todavía quedaba por hablar de algunas muy interesantes.
Así que, no nos entretengamos más y ¡comencemos!.
Attributes
Esta es una de las características más interesantes que incorpora PHP 8 y sobre la que más me extenderé, ya que da “muchísimo juego”.
Para entender cómo funcionan los atributos creo que lo mejor es verlo con un ejemplo:
<?phpnamespace App\Attribute;use Attribute;#[Attribute]
class FooAttribute
{
public function __construct(
public string $foo
) {}
}
Aquí ya aparecen varias cosas interesantes que nos muestran cómo crear nuestros propios atributos:
- Debemos declarar al comienzo del archivo
use Attribute
y colocar la instrucción#[Attribute]
delante de la clase que contiene nuestro atributo. - Por lo demás, un atributo es una clase normal de PHP.
Veamos ahora la forma en que podemos usarlo:
<?phpnamespace App\Service;#[FooAttribute('some value')]
class MyService
{}
Puesto que el constructor del atributo FooAttribute
recibía un string como argumento, cuando declaremos el atributo que usará la clase debemos especificarlo con la misma sintaxis que usamos cuando creamos objeto.
Ahora, para recuperar al atributo necesitamos recurrir a las ReflectionClass
que nos permitirá acceder al método para recuperar los atributos de la clase y su valor:
<?phpuse App\Service\MyService;$reflectionClass = new ReflectionClass(MyService::class);$attributes = $reflectionClass->getAttributes();
Esta variable $attributes
contiene la lista de atributos asignados a la clase en la forma de objetos de la clase ReflectionAttribute
.
Esta clase nos permite obtener el atributo asignado a la clase del siguiente modo:
<?phpuse App\Service\MyService;$reflectionClass = new ReflectionClass(MyService::class);$attributes = $reflectionClass->getAttributes();$firstAttribute = $attributes[0];var_dump($attributes[0]->newInstance());
Lo cual ahora ya sí nos mostrará el atributo FooAttribute
con el valor para su propiedad foo
con el que fue creado en la clase MyService
:
App\Attribute\FooAttribute
$foo -> "some value" (string)
Al ser la propiedad foo
de FooAttribute
pública, podemos acceder a ella del modo habitual:
<?phpuse App\Service\MyService;$reflectionClass = new ReflectionClass(MyService::class);$attributes = $reflectionClass->getAttributes();$firstAttribute = $attributes[0];var_dump($attributes[0]->newInstance()->foo); // some value
Por supuesto podemos añadir a una clase tantos atributos como queramos:
<?phpnamespace App\Service;use App\Attribute\{FooAttribute, BarAttribute};#[FooAttribute('some value'), BarAttribute('other value')]
class MyService
{}
¿Dónde puedo usar los atributos?
Los atributos que declaremos podemos usarlos en multitud de sitios. El que hemos creado en el ejemplo anterior lo hemos asignado a una clase pero, sin embargo, podemos crear atributos que se puedan asignar a métodos, propiedades e incluso “closures”.
Por ejemplo, si quieres crear un atributo que sólo se pueda asignar a una propiedad puedes hacerlo del siguiente modo:
<?phpnamespace App\Attribute;use Attribute;#[Attribute::TARGET_METHOD]
class FooAttribute
{
public function __construct(
public string $foo
} {}
}
Y si quieres crear un atributo para asignarlo a una propiedad:
<?phpnamespace App\Attribute;use Attribute;#[Attribute::TARGET_PROPERTY]
class FooAttribute
{
public function __construct(
public string $foo
} {}
}
👉🏼 Como habrás sospechado, por defecto los atributos se crean con TARGET_CLASS.
¿Y para qué sirven?
Los atributos son una de esas características que parecen muy potentes pero a las que resulta difícil ver al principio la utilidad.
Es como tener en las manos un super ordenador pero no saber qué hacer con él. Bien, déjame que te muestre algunos ejemplos.
Symfony por ejemplo ya tiene integración completa con los atributos de PHP 8 a partir de la versión 5.2. Esto significa que vamos a poder declarar las rutas de nuestros controladores con ellos en vez de recurrir a las anotaciones como hacíamos hasta ahora:
use Symfony\Component\Routing\Annotation\Route;
class SomeController
{
#[Route('/path', name: 'action')]
public function someAction()
{
// ...
}
}
Tienes el articulo completo sobre los primeros pasos en esta integración en el siguiente enlace.
Por otra parte en el siguiente vídeo puedes ver otro ejemplo sobre los atributos a partir del minuto 6:50. En el vídeo el creador crea un proyecto paso a paso donde usa los atributos para definir las distintas rutas del proyecto. Muy muy interesante.
Mejoras en las excepciones
Otra interesante característica que nos trae PHP 8 es una mejora en la forma en que trabajamos con excepciones.
A partir de ahora, si no vamos a usar la variable que captura la excepción no es necesario especificarla. Es decir, pasamos de:
try {
throw new \Exception();
} catch (\Exception $exception) {
print_r('An error ocurred');
}
A:
try {
throw new \Exception();
} catch (\Exception) {
print_r('An error ocurred');
}
Mucho mejor, ¿no?
“Trailing commas” en la definición de funciones
A partir de PHP 8 es posible añadir una coma final en la lista de argumentos de una función y en los elementos de un array:
function foo(int $arg1, int $arg2,) {
}$numbers = [
1,
2,
3,
];
¿Para qué puede servir esto? La sorpresa te va a sorprender: para que cuando añadamos un nuevo elemento al array o un nuevo argumento a la función GIT sólo detecte el cambio en una línea y no en dos:
$numbers = [
1,
2,
3,
4,
];
Cambios detectados por GIT:
$numbers = [
1,
2,
3,
+ 4,
];
Nuevas funciones para trabajar con strings
PHP8 incorpora 3 nuevas funciones para trabajar con strings las cuales conseguirán que resulte mucho más cómodo trabajar con este tipo de datos.
str_contains
La primera de ellas se trata de str_contains
y como su propio nombre indica permite comprobar si el string pasado como primer argumento ( haystack
) contiene al segundo ( needle
):
$included = str_contains('hola mundo', ' mundo'); // true$included = str_contains('hola mundo', 'adiós); // false
str_starts_with
Esta nueva función devuelve true
si el string pasado como primer argumento ( haystack
) empieza con el segundo ( needle
):
$included = str_starts_with('hola mundo', 'hola '); // true$included = str_starts_with('hola mundo', 'mundo)'; // false
str_ends_with
Esta nueva función devuelve true
si el string pasado como primer argumento ( haystack
) termina con el segundo ( needle
):
$included = str_ends_with('hola mundo', 'mundo'); // true$included = str_ends_with('hola mundo', 'hola)'; // false
La interfaz Stringable
La interfaz Stringable, nativa a partir de PHP8, permite indicar que una clase implementa el método __toString()
.
Supongamos que tenemos la siguiente clase:
class Foo
{
public function __toString(): string
{
return 'foo class';
}
}
Podemos definir una función que acepte tanto un string
como una clase que implemente la nueva interfaz Stringable
:
function log(string|Stringable $stringable)
{
print_r (string)$stringable;
}log(new Foo()); // 'foo class'
Una característica muy interesante de Stringable
es que no es necesario declarar explícitamente esta interfaz en la clase. PHP8 la añadirá automáticamente cuando detecte que la clase implementa el método __toString()
.
WeakMaps
PHP8 añade una nueva clase nativa muy interesante: WeakMap
. Esta clase se comporta como un array asociativo. La diferencia es que en un WeakMap
las claves deben ser objetos, los cuales son referenciados de forma débil:
$weakMap = new WeakMap();class Foo {};$foo = new Foo();
$otherFoo = new Foo();$weakMap[$foo] = 'foo';
$weakMap[$otherFoo] = 'other foo';print_r($weakMap[$otherFoo]); // 'other foo'print_r(count($weakMap)); // 2
Es decir, dentro de un WeakMap podemos almacenar cualquier valor, pero las claves deben ser objetos. ¿Qué ventaja tiene esta nueva clase?
Imagina que partiendo del ejemplo anterior, eliminamos uno de los objetos mediante la instrucción unset
:
unset($foo);
Automáticamente el WeakMap pasará a tener un solo objeto:
print_r(count($weakMap)); // 1
Esto se debe a que las referencias que guarda de los objetos son débiles. Dicho de otro modo, estas referencias no cuentan a la hora de eliminar objetos, por lo que en el ejemplo anterior, tras realizar unset($foo)
no existe ninguna referencia “fuerte” más hacia el objeto $foo
y por tanto este se elimina de memoria (y el WeakMap pasa a tener un único elemento).
Por tanto, los escenarios donde tiene sentido usar la clase WeakMap
es en aquellos donde cacheamos objetos, ya que almacenándolos aquí no estaremos generando nuevas referencias hacia ellos, por lo que no tendremos que preocuparnos de eliminarlos.
Conclusiones
Y ahora sí, finalizamos el repaso de todas las novedades que tendremos disponibles en PHP8 a partir del día 26. Como has podido ver a lo largo de estos dos artículos son muy interesantes y suponen una interesante renovación de este lenguaje que cada vez más da la sensación de ir en la dirección correcta.
Por supuesto todavía queda hablar de una nueva herramienta que tendremos disponible para optimiza nuestras aplicaciones: el JIT (Just In Time Compiler). Espero preparar en unas semanas un nuevo artículo donde poder mostrar claramente la optimización que supone.
Vídeo resumen
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.