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
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
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
// 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
// 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:
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
// 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:
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
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
// 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
// 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
// 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
// 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€
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
¿Has encontrado un error o tienes sugerencias para esta lección?
Enviar feedback¿Te está gustando el curso?
Tenemos cursos premium con proyectos reales y soporte.
Descubrir cursos premium