Día a día, nos estamos enterando de nuevas características en PHP 8 que marcarán un antes y un después con esta próxima versión del lenguaje. Y en este artículo vamos a ver como se verían las cosas si implementaramos PHP 8 💪.
Este artículo es una traducción del artículo de Brent con algunas modificaciones mías. Si no conoces el blog de Brent, te lo recomiendo mucho https://stitcher.io/blog/php-8-before-and-after.
Suscripción de eventos y los Atributos en PHP 8
Si estas desarrollando un sistema dirigido por eventos, donde tenes que mapear los eventos con su handler y tienes algo así:
// Con PHP 7.4
class CartsProjector implements Projector
{
use ProjectsEvents;
protected array $handlesEvents = [
CartStartedEvent::class => 'onCartStarted',
CartItemAddedEvent::class => 'onCartItemAdded',
CartItemRemovedEvent::class => 'onCartItemRemoved',
CartExpiredEvent::class => 'onCartExpired',
CartCheckedOutEvent::class => 'onCartCheckedOut',
CouponAddedToCartItemEvent::class => 'onCouponAddedToCartItem',
];
public function onCartStarted(CartStartedEvent $event): void
{ /* … */ }
public function onCartItemAdded(CartItemAddedEvent $event): void
{ /* … */ }
public function onCartItemRemoved(CartItemRemovedEvent $event): void
{ /* … */ }
public function onCartCheckedOut(CartCheckedOutEvent $event): void
{ /* … */ }
public function onCartExpired(CartExpiredEvent $event): void
{ /* … */ }
public function onCouponAddedToCartItem(CouponAddedToCartItemEvent $event): void
{ /* … */ }
}
Con PHP 8 podríamos aplicar Atributos y se verá de la siguiente forma:
class CartsProjector implements Projector
{
use ProjectsEvents;
@@SubscribesTo(CartStartedEvent::class)
public function onCartStarted(CartStartedEvent $event): void
{ /* … */ }
@@SubscribesTo(CartItemAddedEvent::class)
public function onCartItemAdded(CartItemAddedEvent $event): void
{ /* … */ }
@@SubscribesTo(CartItemRemovedEvent::class)
public function onCartItemRemoved(CartItemRemovedEvent $event): void
{ /* … */ }
@@SubscribesTo(CartCheckedOutEvent::class)
public function onCartCheckedOut(CartCheckedOutEvent $event): void
{ /* … */ }
@@SubscribesTo(CartExpiredEvent::class)
public function onCartExpired(CartExpiredEvent $event): void
{ /* … */ }
@@SubscribesTo(CouponAddedToCartItemEvent::class)
public function onCouponAddedToCartItem(CouponAddedToCartItemEvent $event): void
{ /* … */ }
}
Beneficios de utilizar los atributos:
- No debemos desplazarnos hacia la parte superior del archivo para saber si un listener está correctamente configurado.
- No debemos escribir y administrar nombres de métodos como strings. Ahora, el IDE podrá completarlos automáticamente, sin necesidad de análisis estático sobre errores tipográficos y sin que el cambio de nombre del método no rompa nada.
Tipo de retorno estatico vs. Docblocks
Algo que odio (yo Matías, no Brent) es utilizar docblocks para que alguna herramienta valide tipos de retornos o tipos de parámetros, cuando debería ser algo que haga el lenguaje desde su core. Pero por suerte, desde PHP 7.x, esto viene cambiando y en PHP 8 se avanza un poquito más agregando el tipo de retorno estático.
Con PHP 7.4 tenemos que hacer esto:
/**
* @return static
*/
public static function new()
{
return new static();
}
Pero con PHP 8, podremos hacer esto:
public static function new(): static
{
return new static();
}
Utilizando Constructor property promotion y named arguments
Un caso de uso donde veremos los beneficios de los constructor property promotion y named arguments podría ser en los DTOs (Data Transfer Object). Este tipo de objetos son altamente utilizados en programación orientada a objetos (por ejemplo, los API Resources de Laravel son DTOs).
En PHP 7.X un DTO tendría la siguiente forma:
class CustomerDataDto
{
public string $name;
public string $email;
public int $age;
public static function fromRequest(
CustomerRequest $request
): self {
return new self([
'name' => $request->get('name'),
'email' => $request->get('email'),
'age' => $request->get('age'),
]);
}
}
$data = CustomerData::fromRequest($customerRequest);
Pero con PHP 8 el ejemplo anterior se podría escribir de la siguiente forma:
class CustomerDataDto
{
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}
$data = new CustomerData(...$customerRequest->validated());
Belleza! 😍
Si queres saber más sobre que son los DTOs y como se utilizan, te recomiendo mi video de YouTube https://www.youtube.com/watch?v=ip0lxeJOUzk.
Enums y la expresión match.
Los tipos Enums existen en muchos lenguajes pero no en PHP, hasta este momento. Por lo tanto, teníamos que hacer cosas como estas:
/**
* @method static self PENDING()
* @method static self PAID()
*/
class InvoiceState extends Enum
{
private const PENDING = 'pending';
private const PAID = 'paid';
public function getColour(): string
{
return [
self::PENDING => 'orange',
self::PAID => 'green',
][$this->value] ?? 'gray';
}
}
Pero con PHP 8 y la expresión match podemos hacerlo así:
/**
* @method static self PENDING()
* @method static self PAID()
*/
class InvoiceState extends Enum
{
private const PENDING = 'pending';
private const PAID = 'paid';
public function getColour(): string
{
return match ($this->value) {
self::PENDING => 'orange',
self::PAID => 'green',
default => 'gray',
};
}
Union types en lugar de Docblocks
Una característica más que desplaza a mis odiados docblocks, gracias a los Union Types que vendrán integrado en PHP 8.
En PHP 7.x:
/**
* @param string|int $input
*
* @return string
*/
public function sanitize($input): string;
Con PHP 8:
public function sanitize(string|int $input): string;
Lanzando excepciones en expresiones
Antes de PHP 8, no se podía usar throw
en una expresión, lo que significa que tendría que hacer comprobaciones explícitas así:
public function (array $input): void
{
if (! isset($input['bar'])) {
throw BarIsMissing::new();
}
$bar = $input['bar'];
// …
}
En PHP 8, throw
se ha convertido en una expresión, lo que significa que vamos a poder hacer cosas como estas:
public function (array $input): void
{
$bar = $input['bar'] ?? throw BarIsMissing::new();
// …
}
¡Chau IF! 🚀
El operador nullsafe
El operador de fusión nula es muy bueno pero tienee sus defectos: no funciona en llamadas a métodos. Por lo tanto, teníamos que hacer validaciones en el medio o usar la función optional()
de Laravel:
$startDate = $booking->getStartDate();
$dateAsString = $startDate ? $startDate->asDateTimeString() : null;
Pero con la adición del operador nullsafe, ahora vamos a poder tener un comportamiento nulo en los métodos encadenados.
$dateAsString = $booking->getStartDate()?->asDateTimeString();
Conclusión
Espero que te haya gustado estas implementaciones. Cualquier duda, la puedes dejar en los comentarios. Y se vienen muchas características para PHP 8 y estaremos atentos para comunicarlas a toda la comunidad de Laravel Tip. Así que estate atento! Nos vemos en la próxima ✊.