Nullsafe Operator
El operador nullsafe (?->) permite encadenar llamadas
a metodos o propiedades de forma segura. Si alguna parte de la
cadena es null, toda la expresion retorna null.
El problema: verificaciones anidadas
Antes de PHP 8, acceder a propiedades de objetos que podian ser null requeria multiples verificaciones:
<?php
declare(strict_types=1);
// Sin nullsafe: codigo verboso
$pais = null;
if ($usuario !== null) {
$direccion = $usuario->getDireccion();
if ($direccion !== null) {
$pais = $direccion->getPais();
}
}
// O con operador ternario (sigue siendo largo)
$pais = $usuario !== null
? ($usuario->getDireccion() !== null
? $usuario->getDireccion()->getPais()
: null)
: null;
La solucion: operador nullsafe
Con ?->, PHP evalua la cadena y retorna null
si encuentra un valor null en cualquier punto:
<?php
declare(strict_types=1);
class Direccion
{
public function __construct(
public string $calle,
public string $pais
) {}
public function getPais(): string
{
return $this->pais;
}
}
class Usuario
{
public function __construct(
public string $nombre,
public ?Direccion $direccion = null
) {}
public function getDireccion(): ?Direccion
{
return $this->direccion;
}
}
$usuario1 = new Usuario('Ana', new Direccion('Gran Via', 'Espana'));
$usuario2 = new Usuario('Luis'); // Sin direccion
// Nullsafe: una linea, seguro
$pais1 = $usuario1?->getDireccion()?->getPais(); // 'Espana'
$pais2 = $usuario2?->getDireccion()?->getPais(); // null (no hay error)
Encadenar multiples llamadas
<?php
declare(strict_types=1);
class Empresa
{
public function __construct(
public string $nombre,
public ?Usuario $ceo = null
) {}
}
$empresa = new Empresa('TechCorp');
// Cadena larga: si cualquier parte es null, retorna null
$paisCeo = $empresa?->ceo?->getDireccion()?->getPais();
// Equivale a:
// if ($empresa !== null && $empresa->ceo !== null
// && $empresa->ceo->getDireccion() !== null) { ... }
Con propiedades y métodos
<?php
declare(strict_types=1);
class Pedido
{
public function __construct(
public string $id,
public ?Usuario $cliente = null
) {}
}
$pedido = new Pedido('PED-001');
// Acceso a propiedad
$nombreCliente = $pedido->cliente?->nombre; // null
// Llamada a metodo
$direccionCliente = $pedido->cliente?->getDireccion(); // null
// Combinando con null coalescing para valor por defecto
$pais = $pedido->cliente?->getDireccion()?->getPais() ?? 'Desconocido';
echo $pais; // 'Desconocido'
Caso practico: APIs y datos externos
<?php
declare(strict_types=1);
class ApiResponse
{
public function __construct(
public ?array $data = null,
public ?string $error = null
) {}
public function getUser(): ?object
{
return isset($this->data['user'])
? (object) $this->data['user']
: null;
}
}
function fetchApi(): ?ApiResponse
{
// Simula respuesta de API que puede fallar
return random_int(0, 1) === 1
? new ApiResponse(['user' => ['name' => 'Ana', 'email' => 'ana@example.com']])
: null;
}
$response = fetchApi();
// Sin nullsafe: muchas verificaciones
// Con nullsafe: limpio y seguro
$userName = $response?->getUser()?->name ?? 'Invitado';
echo "Hola, $userName";
El operador nullsafe solo funciona para lectura. No puedes
usarlo para asignar valores: $obj?->prop = 'valor'
genera un error.
Ejercicios
Ejercicio 1: Obtener email de forma segura
Dado un array de pedidos donde cada uno puede o no tener cliente, obtener los emails de los clientes usando nullsafe.
Ver solucion
<?php
declare(strict_types=1);
class Cliente
{
public function __construct(
public string $nombre,
public string $email
) {}
}
class Pedido
{
public function __construct(
public string $id,
public ?Cliente $cliente = null
) {}
}
$pedidos = [
new Pedido('P1', new Cliente('Ana', 'ana@example.com')),
new Pedido('P2'), // Sin cliente
new Pedido('P3', new Cliente('Luis', 'luis@example.com')),
];
$emails = [];
foreach ($pedidos as $pedido) {
$email = $pedido->cliente?->email;
if ($email !== null) {
$emails[] = $email;
}
}
print_r($emails); // ['ana@example.com', 'luis@example.com']
Ejercicio 2: Configuracion anidada
Crea una estructura de configuracion anidada y usa nullsafe para acceder a valores profundos con un valor por defecto.
Ver solucion
<?php
declare(strict_types=1);
class DatabaseConfig
{
public function __construct(
public string $host = 'localhost',
public int $port = 3306
) {}
}
class AppConfig
{
public function __construct(
public ?DatabaseConfig $database = null
) {}
}
class Config
{
public function __construct(
public ?AppConfig $app = null
) {}
}
$config = new Config(); // Sin configuracion
$dbHost = $config->app?->database?->host ?? 'localhost';
$dbPort = $config->app?->database?->port ?? 3306;
echo "Conectando a $dbHost:$dbPort";
¿Has encontrado un error o tienes una sugerencia?
Escribenos