Lección 55 de 75 10 min de lectura

Autoloading Básico

El autoloading permite cargar clases automáticamente cuando las necesitas, sin escribir require para cada archivo. Es fundamental para proyectos organizados y escalables.

El problema de require manual

Sin autoloading, debes incluir manualmente cada archivo que contiene clases:

PHP
<?php

declare(strict_types=1);

// Sin autoloading: lista interminable de requires
require_once 'src/Models/Usuario.php';
require_once 'src/Models/Producto.php';
require_once 'src/Models/Pedido.php';
require_once 'src/Services/AuthService.php';
require_once 'src/Services/CarritoService.php';
require_once 'src/Services/EmailService.php';
require_once 'src/Controllers/HomeController.php';
require_once 'src/Controllers/ProductoController.php';
// ... y así con cada archivo

$controller = new App\Controllers\HomeController();

Esto es tedioso, propenso a errores y difícil de mantener. El autoloading lo soluciona.

spl_autoload_register

PHP proporciona spl_autoload_register() para registrar funciones que cargan clases automáticamente. Cuando usas una clase que no existe, PHP llama a estas funciones:

PHP
<?php

declare(strict_types=1);

// Registrar una función de autoload
spl_autoload_register(function (string $clase): void {
    // $clase contiene el nombre completo, ej: "App\Models\Usuario"
    echo "Intentando cargar: $clase\n";
});

// Cuando uses una clase no definida, PHP llamará a la función
$usuario = new App\Models\Usuario(); // Imprime: Intentando cargar: App\Models\Usuario

Autoloader simple

Un autoloader básico convierte el namespace en una ruta de archivo:

PHP
<?php

// autoload.php
declare(strict_types=1);

spl_autoload_register(function (string $clase): void {
    // Directorio base donde están las clases
    $baseDir = __DIR__ . '/src/';

    // Convertir namespace a ruta de archivo
    // App\Models\Usuario -> src/App/Models/Usuario.php
    $archivo = $baseDir . str_replace('\\', '/', $clase) . '.php';

    // Si el archivo existe, incluirlo
    if (file_exists($archivo)) {
        require $archivo;
    }
});
PHP
<?php

// index.php
declare(strict_types=1);

require_once 'autoload.php';  // Solo un require

// Ahora las clases se cargan automáticamente
use App\Models\Usuario;
use App\Services\AuthService;

$usuario = new Usuario(1, 'ana@example.com');
$auth = new AuthService();

Estructura de carpetas correspondiente

Para que el autoloader funcione, la estructura de carpetas debe coincidir con los namespaces:

Estructura
proyecto/
├── autoload.php
├── index.php
└── src/
    └── App/
        ├── Models/
        │   ├── Usuario.php      # namespace App\Models;
        │   └── Producto.php     # namespace App\Models;
        ├── Services/
        │   ├── AuthService.php  # namespace App\Services;
        │   └── EmailService.php # namespace App\Services;
        └── Controllers/
            └── HomeController.php  # namespace App\Controllers;

Autoloader con prefijo de namespace

Es común mapear un namespace base a un directorio específico. Por ejemplo, App\ apunta a src/:

PHP
<?php

// autoload.php
declare(strict_types=1);

spl_autoload_register(function (string $clase): void {
    // Prefijo del namespace
    $prefijo = 'App\\';

    // Directorio base para el prefijo
    $baseDir = __DIR__ . '/src/';

    // ¿La clase usa el prefijo?
    $longPrefijo = strlen($prefijo);
    if (strncmp($prefijo, $clase, $longPrefijo) !== 0) {
        // No, esta clase no es nuestra
        return;
    }

    // Obtener el nombre relativo de la clase
    $claseRelativa = substr($clase, $longPrefijo);

    // Construir la ruta del archivo
    // App\Models\Usuario -> src/Models/Usuario.php
    $archivo = $baseDir . str_replace('\\', '/', $claseRelativa) . '.php';

    if (file_exists($archivo)) {
        require $archivo;
    }
});

Con esta configuración, la estructura es más limpia:

Estructura
proyecto/
├── autoload.php
├── index.php
└── src/                         # App\ apunta aquí
    ├── Models/
    │   └── Usuario.php          # App\Models\Usuario
    ├── Services/
    │   └── AuthService.php      # App\Services\AuthService
    └── Controllers/
        └── HomeController.php   # App\Controllers\HomeController

Múltiples autoloaders

Puedes registrar varios autoloaders. PHP los llama en orden hasta que uno cargue la clase:

PHP
<?php

declare(strict_types=1);

// Autoloader para App\
spl_autoload_register(function (string $clase): void {
    $prefijo = 'App\\';
    $baseDir = __DIR__ . '/src/';

    if (strncmp($prefijo, $clase, strlen($prefijo)) === 0) {
        $claseRelativa = substr($clase, strlen($prefijo));
        $archivo = $baseDir . str_replace('\\', '/', $claseRelativa) . '.php';

        if (file_exists($archivo)) {
            require $archivo;
        }
    }
});

// Autoloader para Tests\
spl_autoload_register(function (string $clase): void {
    $prefijo = 'Tests\\';
    $baseDir = __DIR__ . '/tests/';

    if (strncmp($prefijo, $clase, strlen($prefijo)) === 0) {
        $claseRelativa = substr($clase, strlen($prefijo));
        $archivo = $baseDir . str_replace('\\', '/', $claseRelativa) . '.php';

        if (file_exists($archivo)) {
            require $archivo;
        }
    }
});

Ejemplo práctico completo

PHP
<?php

// src/Models/Producto.php
namespace App\Models;

declare(strict_types=1);

class Producto
{
    public function __construct(
        public readonly int $id,
        public readonly string $nombre,
        public readonly float $precio
    ) {}

    public function precioConIva(): float
    {
        return $this->precio * 1.21;
    }
}
PHP
<?php

// src/Services/CatalogoService.php
namespace App\Services;

declare(strict_types=1);

use App\Models\Producto;

class CatalogoService
{
    /** @var Producto[] */
    private array $productos = [];

    public function agregar(Producto $producto): void
    {
        $this->productos[$producto->id] = $producto;
    }

    public function buscar(int $id): ?Producto
    {
        return $this->productos[$id] ?? null;
    }

    /** @return Producto[] */
    public function listar(): array
    {
        return array_values($this->productos);
    }
}
PHP
<?php

// autoload.php
declare(strict_types=1);

spl_autoload_register(function (string $clase): void {
    $mapa = [
        'App\\' => __DIR__ . '/src/',
    ];

    foreach ($mapa as $prefijo => $baseDir) {
        $longPrefijo = strlen($prefijo);

        if (strncmp($prefijo, $clase, $longPrefijo) === 0) {
            $claseRelativa = substr($clase, $longPrefijo);
            $archivo = $baseDir . str_replace('\\', '/', $claseRelativa) . '.php';

            if (file_exists($archivo)) {
                require $archivo;
                return;
            }
        }
    }
});
PHP
<?php

// index.php
declare(strict_types=1);

require_once __DIR__ . '/autoload.php';

use App\Models\Producto;
use App\Services\CatalogoService;

// Las clases se cargan automáticamente al usarlas
$catalogo = new CatalogoService();

$catalogo->agregar(new Producto(1, 'Laptop', 999.99));
$catalogo->agregar(new Producto(2, 'Mouse', 29.99));
$catalogo->agregar(new Producto(3, 'Teclado', 79.99));

foreach ($catalogo->listar() as $producto) {
    echo "{$producto->nombre}: {$producto->precioConIva()}€\n";
}
// Laptop: 1209.99€
// Mouse: 36.29€
// Teclado: 96.79€
En producción

Este autoloader manual es didáctico. En proyectos reales, usa Composer que implementa PSR-4 de forma optimizada. Lo veremos en la siguiente lección.

Ejercicios

Ejercicio 1: Autoloader básico

Crea un proyecto con la estructura src/Models/Libro.php y src/Services/BibliotecaService.php. Implementa un autoloader que mapee App\ a src/. La clase Libro debe tener título, autor e ISBN. La clase BibliotecaService debe poder agregar y buscar libros.

Ver solución
<?php

// autoload.php
declare(strict_types=1);

spl_autoload_register(function (string $clase): void {
    $prefijo = 'App\\';
    $baseDir = __DIR__ . '/src/';

    if (strpos($clase, $prefijo) !== 0) {
        return;
    }

    $claseRelativa = substr($clase, strlen($prefijo));
    $archivo = $baseDir . str_replace('\\', '/', $claseRelativa) . '.php';

    if (file_exists($archivo)) {
        require $archivo;
    }
});

// src/Models/Libro.php
namespace App\Models;

declare(strict_types=1);

class Libro
{
    public function __construct(
        public readonly string $titulo,
        public readonly string $autor,
        public readonly string $isbn
    ) {}
}

// src/Services/BibliotecaService.php
namespace App\Services;

declare(strict_types=1);

use App\Models\Libro;

class BibliotecaService
{
    private array $libros = [];

    public function agregar(Libro $libro): void
    {
        $this->libros[$libro->isbn] = $libro;
    }

    public function buscar(string $isbn): ?Libro
    {
        return $this->libros[$isbn] ?? null;
    }
}

Ejercicio 2: Múltiples prefijos

Extiende el autoloader del ejercicio 1 para soportar dos prefijos: App\ que apunte a src/ y Tests\ que apunte a tests/. Crea una clase Tests\Models\LibroTest que verifique que el autoloader funciona correctamente para ambos prefijos.

Ver solución
<?php

// autoload.php
declare(strict_types=1);

$prefijos = [
    'App\\' => __DIR__ . '/src/',
    'Tests\\' => __DIR__ . '/tests/',
];

spl_autoload_register(function (string $clase) use ($prefijos): void {
    foreach ($prefijos as $prefijo => $baseDir) {
        if (strpos($clase, $prefijo) === 0) {
            $claseRelativa = substr($clase, strlen($prefijo));
            $archivo = $baseDir . str_replace('\\', '/', $claseRelativa) . '.php';

            if (file_exists($archivo)) {
                require $archivo;
                return;
            }
        }
    }
});

// tests/Models/LibroTest.php
namespace Tests\Models;

declare(strict_types=1);

use App\Models\Libro;

class LibroTest
{
    public function testCrearLibro(): bool
    {
        $libro = new Libro('PHP Moderno', 'Juan', '978-123');

        return $libro->titulo === 'PHP Moderno'
            && $libro->autor === 'Juan'
            && $libro->isbn === '978-123';
    }
}

// Uso
$test = new \Tests\Models\LibroTest();
var_dump($test->testCrearLibro()); // true

Ejercicio 3: Autoloader con cache

Modifica el autoloader para que guarde en un array estático las rutas de archivos ya resueltas. Si se intenta cargar la misma clase dos veces, debe usar la ruta cacheada sin volver a calcularla. Añade un contador para verificar cuántas veces se resuelven rutas.

Ver solución
<?php

// autoload.php
declare(strict_types=1);

class Autoloader
{
    private static array $cache = [];
    private static int $resoluciones = 0;

    private static array $prefijos = [
        'App\\' => __DIR__ . '/src/',
    ];

    public static function cargar(string $clase): void
    {
        // Verificar cache primero
        if (isset(self::$cache[$clase])) {
            require self::$cache[$clase];
            return;
        }

        foreach (self::$prefijos as $prefijo => $baseDir) {
            if (strpos($clase, $prefijo) === 0) {
                $claseRelativa = substr($clase, strlen($prefijo));
                $archivo = $baseDir . str_replace('\\', '/', $claseRelativa) . '.php';

                if (file_exists($archivo)) {
                    self::$resoluciones++;
                    self::$cache[$clase] = $archivo;
                    require $archivo;
                    return;
                }
            }
        }
    }

    public static function getResoluciones(): int
    {
        return self::$resoluciones;
    }
}

spl_autoload_register([Autoloader::class, 'cargar']);

// Uso
$libro1 = new \App\Models\Libro('PHP', 'Ana', '123');
echo Autoloader::getResoluciones(); // 1

¿Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte.

Descubrir cursos premium