PHP 8.0 no solo trajo match y los union types: también llegaron el operador nullsafe ?-> para encadenar llamadas sobre objetos que pueden ser null, las funciones de cadena str_contains(), str_starts_with() y str_ends_with(), y la posibilidad de usar throw como expresión dentro de ternarios, coalescing y arrow functions.
El operador nullsafe ?->
Antes de PHP 8, encadenar llamadas sobre objetos opcionales obligaba a comprobar manualmente cada eslabón con if o ?? null. El operador nullsafe ?-> devuelve null en cuanto encuentra un valor null en la cadena, sin lanzar excepción:
<?php
class Pais
{
public function __construct(public string $nombre) {}
}
class Ciudad
{
public function __construct(
public string $nombre,
public ?Pais $pais = null,
) {}
}
class Usuario
{
public function __construct(
public string $nombre,
public ?Ciudad $ciudad = null,
) {}
public function getCiudad(): ?Ciudad
{
return $this->ciudad;
}
}
$usuario = new Usuario('Ana', new Ciudad('Madrid', new Pais('España')));
$sinCiudad = new Usuario('Bot');
// PHP 7: varios if anidados
$pais = null;
if ($usuario->getCiudad() !== null && $usuario->getCiudad()->pais !== null) {
$pais = $usuario->getCiudad()->pais->nombre;
}
// PHP 8: una sola línea con nullsafe
$pais1 = $usuario?->getCiudad()?->pais?->nombre;
$pais2 = $sinCiudad?->getCiudad()?->pais?->nombre;
echo $pais1; // España
var_dump($pais2); // NULL sin excepción
?>
str_contains(), str_starts_with() y str_ends_with()
<?php
$url = 'https://www.ejemplo.com/productos/teclado-mecanico?color=negro';
// Antes: strpos() con === false, confuso
if (strpos($url, 'productos') !== false) {
echo "Es URL de producton";
}
// PHP 8: mucho más legible
if (str_contains($url, 'productos')) {
echo "Es URL de producton";
}
if (str_starts_with($url, 'https://')) {
echo "URL seguran";
}
if (str_ends_with($url, 'color=negro')) {
echo "Filtrado por color negron";
}
// Ejemplos con cadenas vacías (comportamiento definido)
var_dump(str_contains('hola', '')); // true (toda cadena contiene la vacía)
var_dump(str_starts_with('hola', '')); // true
?>
throw como expresión
<?php
// Antes: throw solo era una instrucción, no una expresión
// Ahora puede usarse en ternarios, null coalescing, arrow functions...
// En operador ternario
function getEnv(string $nombre): string
{
return getenv($nombre)
?: throw new RuntimeException("Variable de entorno '$nombre' no definida");
}
// En null coalescing
function getConfig(array $config, string $clave): mixed
{
return $config[$clave]
?? throw new InvalidArgumentException("Clave '$clave' requerida en config");
}
// En arrow function
$validar = fn(int $n) => $n > 0
? $n
: throw new RangeException("Debe ser positivo, se recibió: $n");
echo $validar(5); // 5
$validar(-1); // RangeException
?>
Nullsafe con métodos que devuelven objetos anidados
<?php
class Repositorio
{
private array $datos = [
1 => ['nombre' => 'Ana', 'pais_id' => 1],
2 => ['nombre' => 'Luis', 'pais_id' => null],
];
public function find(int $id): ?object
{
$d = $this->datos[$id] ?? null;
return $d ? (object)$d : null;
}
}
class PaisRepositorio
{
public function find(?int $id): ?object
{
if ($id === null) return null;
return (object)['nombre' => 'España'];
}
}
$repo = new Repositorio();
$paisRepo = new PaisRepositorio();
// Encadenamiento nullsafe con resultado de métodos
$usuario = $repo->find(2);
$pais = $paisRepo->find($usuario?->pais_id);
var_dump($pais); // NULL Luis no tiene pais_id
?>
La documentación oficial del operador nullsafe y las funciones str_contains(), str_starts_with() detallan los casos límite con cadenas vacías y el comportamiento del short-circuit del operador nullsafe.
