PHP 7 introdujo las clases anónimas: clases que se definen y se usan en el mismo punto del código, sin nombre y sin fichero propio. Se crean con new class { ... } y pueden extender clases, implementar interfaces y recibir argumentos en el constructor, igual que cualquier clase normal.
Sintaxis básica
<?php
$saludo = new class('Mundo') {
public function __construct(private string $nombre) {}
public function saludar(): string {
return "Hola, {$this->nombre}!";
}
};
echo $saludo->saludar(); // Hola, Mundo!
Caso de uso 1: implementar una interfaz puntualmente
Cuando necesitas pasar un objeto que implemente una interfaz a una función y no vas a reutilizar esa implementación en ningún otro lugar, una clase anónima evita crear un fichero entero para ese único uso:
<?php
interface Logger {
public function log(string $mensaje): void;
}
function procesarPedido(int $id, Logger $logger): void {
$logger->log("Procesando pedido $id");
// ... lógica del pedido
$logger->log("Pedido $id completado");
}
// En producción usarías un logger real (Monolog, etc.)
// Para una prueba rápida o script de consola:
procesarPedido(42, new class implements Logger {
public function log(string $mensaje): void {
echo date('H:i:s') . " $mensajen";
}
});
Caso de uso 2: test doubles rápidos
Las clases anónimas son perfectas para crear mocks o stubs inline en tests sin depender de un framework de mocking:
<?php
use PHPUnitFrameworkTestCase;
interface Mailer {
public function enviar(string $destinatario, string $asunto, string $cuerpo): bool;
}
class RegistroService {
public function __construct(private Mailer $mailer) {}
public function registrar(string $email): bool {
// ... guarda en BD ...
return $this->mailer->enviar($email, 'Bienvenido', 'Cuenta creada');
}
}
class RegistroServiceTest extends TestCase {
public function testRegistrarEnviaEmail(): void {
$enviados = [];
$mailerFalso = new class($enviados) implements Mailer {
public function __construct(private array &$enviados) {}
public function enviar(string $dest, string $asunto, string $cuerpo): bool {
$this->enviados[] = compact('dest', 'asunto');
return true;
}
};
$service = new RegistroService($mailerFalso);
$service->registrar('[email protected]');
$this->assertCount(1, $enviados);
$this->assertSame('[email protected]', $enviados[0]['dest']);
}
}
Caso de uso 3: callbacks con estado
Cuando necesitas una closure que mantenga estado mutable entre llamadas, una clase anónima puede ser más clara que una closure con use (&$var):
<?php
// Con closure y referencia: funciona pero menos legible
$contador = 0;
$incrementar = function() use (&$contador) { return ++$contador; };
// Con clase anónima: el estado está encapsulado
$contador = new class {
private int $valor = 0;
public function incrementar(): int { return ++$this->valor; }
public function valor(): int { return $this->valor; }
public function reset(): void { $this->valor = 0; }
};
echo $contador->incrementar(); // 1
echo $contador->incrementar(); // 2
echo $contador->valor(); // 2
$contador->reset();
echo $contador->valor(); // 0
Caso de uso 4: extender una clase concreta
Las clases anónimas pueden extender clases y sobreescribir métodos, lo que permite crear variantes puntuales sin herencia formal:
<?php
class BaseController {
protected function render(string $vista, array $datos = []): string {
return "Renderizando $vista con " . json_encode($datos);
}
public function responder(): string {
return $this->render('base');
}
}
// Variante anónima que sobreescribe render para tests
$controller = new class extends BaseController {
protected function render(string $vista, array $datos = []): string {
return "TEST:$vista:" . implode(',', array_keys($datos));
}
};
echo $controller->responder(); // TEST:base:
El error típico: intentar acceder a variables externas sin pasarlas
A diferencia de las closures, las clases anónimas no pueden usar use para capturar variables del scope. Debes pasarlas por el constructor:
<?php
$prefijo = 'ERROR';
// INCORRECTO: las clases anónimas no tienen use
// $obj = new class { public function log($m) { echo "$prefijo: $m"; } }; // Error
// CORRECTO: pasar por el constructor
$obj = new class($prefijo) {
public function __construct(private string $prefijo) {}
public function log(string $m): void { echo "{$this->prefijo}: $mn"; }
};
$obj->log('Conexión fallida'); // ERROR: Conexión fallida
