Métodos mágicos en PHP: __toString, __get, __set, __call e __invoke

Los métodos mágicos de PHP son funciones con nombres reservados que el motor del lenguaje invoca automáticamente cuando ocurren ciertas operaciones sobre un objeto. No los llamas tú directamente: PHP los llama por ti cuando conviertes un objeto a string, accedes a una propiedad que no existe, invocas un método inexistente o usas el objeto como función.

__toString: convertir un objeto en cadena

<?php
class Dinero
{
    public function __construct(
        private float  $cantidad,
        private string $moneda = 'EUR',
    ) {}

    public function __toString(): string
    {
        return number_format($this->cantidad, 2, ',', '.') . ' ' . $this->moneda;
    }
}

$precio = new Dinero(1234.5);
echo $precio;             // 1.234,50 EUR
echo "Precio: $precio";   // Precio: 1.234,50 EUR
?>

__get y __set: interceptar propiedades inexistentes

<?php
class ConfiguracionDinamica
{
    private array $datos = [];

    public function __get(string $nombre): mixed
    {
        if (!array_key_exists($nombre, $this->datos)) {
            throw new RuntimeException("Propiedad '$nombre' no definida");
        }
        return $this->datos[$nombre];
    }

    public function __set(string $nombre, mixed $valor): void
    {
        $this->datos[$nombre] = $valor;
    }

    public function __isset(string $nombre): bool
    {
        return isset($this->datos[$nombre]);
    }

    public function __unset(string $nombre): void
    {
        unset($this->datos[$nombre]);
    }
}

$config = new ConfiguracionDinamica();
$config->tema       = 'oscuro';
$config->idioma     = 'es';
$config->maxResultados = 25;

echo $config->tema;          // oscuro
echo isset($config->idioma); // 1 (true)
unset($config->idioma);
?>

__call y __callStatic: interceptar métodos inexistentes

<?php
class ConstructorSQL
{
    private string $tabla  = '';
    private array  $campos = [];
    private array  $condiciones = [];

    public function __call(string $nombre, array $args): static
    {
        if (str_starts_with($nombre, 'donde')) {
            $campo = lcfirst(substr($nombre, 5)); // 'dondeId' ? 'id'
            $this->condiciones[] = "$campo = '{$args[0]}'";
            return $this;
        }
        throw new BadMethodCallException("Método '$nombre' no existe");
    }

    public function tabla(string $tabla): static
    {
        $this->tabla = $tabla;
        return $this;
    }

    public function construir(): string
    {
        $where = $this->condiciones
            ? ' WHERE ' . implode(' AND ', $this->condiciones)
            : '';
        return "SELECT * FROM {$this->tabla}{$where}";
    }
}

$sql = (new ConstructorSQL())
    ->tabla('usuarios')
    ->dondeActivo(1)
    ->dondeRol('admin')
    ->construir();

echo $sql;
// SELECT * FROM usuarios WHERE activo = '1' AND rol = 'admin'
?>

__invoke: usar un objeto como callable

<?php
class ValidadorEmail
{
    private array $dominiosBloqueados;

    public function __construct(array $dominiosBloqueados = [])
    {
        $this->dominiosBloqueados = $dominiosBloqueados;
    }

    public function __invoke(string $email): bool
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return false;
        }
        $dominio = strtolower(substr(strrchr($email, '@'), 1));
        return !in_array($dominio, $this->dominiosBloqueados, true);
    }
}

$validar = new ValidadorEmail(['spam.com', 'basura.net']);

echo $validar('[email protected]');  // 1 (true)
echo $validar('[email protected]');      // '' (false)

// Se puede usar directamente como callback
$emails = ['[email protected]', '[email protected]', '[email protected]'];
$validos = array_filter($emails, $validar);
// ['[email protected]', '[email protected]']
?>

Otros métodos mágicos útiles

<?php
class ObjetoCloneable
{
    public array $datos;

    public function __construct(array $datos)
    {
        $this->datos = $datos;
    }

    // Se llama justo después de clone
    public function __clone(): void
    {
        // Asegurar copia profunda si hay objetos anidados
        $this->datos = array_map(
            fn($v) => is_object($v) ? clone $v : $v,
            $this->datos
        );
    }
}

$original = new ObjetoCloneable(['a' => 1, 'b' => 2]);
$copia    = clone $original;
$copia->datos['a'] = 99;

echo $original->datos['a']; // 1 (no afectado)
echo $copia->datos['a'];    // 99
?>

La documentación oficial de métodos mágicos en PHP lista todos los métodos reservados con sus firmas exactas, restricciones de visibilidad y comportamiento en contextos de serialización y clonado.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP