Elige la herramienta de diseño adecuada - un contrato, un esqueleto de algoritmo o la reutilización horizontal - conociendo los compromisos de cada una.
Abrir esta lección en KodokonPHP te ofrece cuatro herramientas de diseño que se solapan en parte: la herencia concreta, las clases abstractas, las interfaces y los traits. La elección se reduce a una pregunta: ¿qué quieres compartir? Un contrato: interfaz. Un contrato más un esqueleto de implementación: clase abstracta. Código compartido entre clases sin vínculo jerárquico: trait. La herencia concreta, por su parte, es cada vez más difícil de justificar: acopla con fuerza, y PHP solo permite un único padre.
<?php
declare(strict_types=1);
abstract class Exporter
{
public function export(array $rows): string
{
return $this->head() . $this->body($rows);
}
abstract protected function head(): string;
abstract protected function body(
array $rows
): string;
}
final class CsvExporter extends Exporter
{
protected function head(): string
{
return "id;name\n";
}
protected function body(array $rows): string
{
$lines = array_map(
fn (array $r): string => implode(';', $r),
$rows,
);
return implode("\n", $lines);
}
}
$csv = new CsvExporter();
echo $csv->export([[1, 'Ada'], [2, 'Linus']]);La interfaz es el contrato puro: firmas y constantes, sin estado ni implementación. Su ventaja decisiva sobre la clase abstracta: una clase puede implementar varias de ellas, y cualquier clase existente puede adoptar el contrato a posteriori. La clase abstracta, en cambio, lleva estado y código compartido, pero consume tu única ranura de herencia. En la práctica: tipa tus parámetros con interfaces (Cache, no ArrayCache) para poder intercambiar implementaciones, tanto en las pruebas como en producción.
<?php
declare(strict_types=1);
interface Cache
{
public function get(string $key): ?string;
public function set(
string $key,
string $value
): void;
}
final class ArrayCache implements Cache
{
private array $items = [];
public function get(string $key): ?string
{
return $this->items[$key] ?? null;
}
public function set(
string $key,
string $value
): void {
$this->items[$key] = $value;
}
}
$cache = new ArrayCache();
$cache->set('lang', 'PHP');
echo $cache->get('lang');Los traits resuelven el reparto horizontal: inyectar los mismos métodos en clases sin ningún ancestro común. Cuando dos traits aportan un método con el mismo nombre, PHP se niega a elegir por ti y exige una resolución explícita: insteadof designa la versión que se conserva, as crea un alias para mantener accesible la otra. Es verboso a propósito - el conflicto debe permanecer visible en el código.
<?php
declare(strict_types=1);
trait FileLogger
{
public function log(string $message): void
{
echo "[file] $message\n";
}
}
trait AuditLogger
{
public function log(string $message): void
{
echo "[audit] $message\n";
}
}
final class PaymentService
{
use FileLogger, AuditLogger {
FileLogger::log insteadof AuditLogger;
AuditLogger::log as audit;
}
}
$service = new PaymentService();
$service->log('Payment received');
$service->audit('Payment received');log(). ¿Qué ocurre?insteadof