Categoría: Arquitectura de software

  • Capas invisibles de seguridad en PHP que bloquean ataques

    Capas invisibles de seguridad en PHP que bloquean ataques

    Resumen: las “capas invisibles” son defensas integradas en el runtime o en la capa infraestrutural que bloquean clases completas de ataques antes de que lleguen al negocio. Este artículo describe seis capas prácticas y muestra ejemplos de implementación en PHP.

    Introducción

    Los atacantes buscan huecos repetibles y fáciles: salidas sin escapar, sesiones que no cambian, consultas sin parámetros. Las capas invisibles obligan al runtime o a la infraestructura a aplicar defensas por defecto, reduciendo la carga mental del desarrollador y cerrando vectores comunes.

    Prerrequisitos

    Antes de integrar estas capas conviene contar con un entorno que permita soluciones como hashing moderno, plantillas con auto-escaping y mecanismos de almacenamiento para límites por IP.

    • PHP con soporte para Argon2id (mencionado en el texto base).
    • Motor de plantillas con auto-escaping (por ejemplo, Twig o Blade, o equivalente).
    • Acceso a la base de datos vía ORM o biblioteca que permita prepared statements.
    • Sistema de caché o store (Redis, memcached, etc.) para rate-limiting y rotación de tokens.

    Desarrollo

    Procedimiento

    Capa 1 — Auto-escaping a nivel de motor de plantillas: configure el motor para escapar por defecto las salidas. Así, incluso si un desarrollador olvida sanitizar una variable, el motor evita XSS a la salida.

    Capa 2 — Hashing memory-hard para credenciales: use algoritmos que consuman memoria (Argon2id o scrypt). Según el texto base, Argon2id es la opción recomendada para evitar que dumps de contraseñas sean fáciles de crackear.

    Capa 3 — Rotación automática de tokens: regenere identificadores de sesión en eventos críticos (login, elevación de privilegios) y mantenga un tiempo de validez corto para tokens expuestos.

    Capa 4 — Firewall de consultas dentro del ORM: haga que el acceso a la BD use solo consultas parametrizadas. Si el ORM obliga a parámetros, las inyecciones se eliminan en la práctica.

    Capa 5 — Middleware adaptativo a la tasa (rate-adaptive): aumente la latencia o limite intentos según patrones sospechosos. El objetivo es hacer el brute force económicamente inviable sin afectar a usuarios legítimos.

    Capa 6 — Content Security Policy (CSP): entregue cabeceras CSP estrictas para limitar fuentes de scripts, iframes y orígenes. Esto reduce el impacto de XSS y evita exfiltración desde el navegador.

    Ejemplos

    A continuación hay ejemplos concisos para poner en práctica las capas descritas.

    <?php
    // Hashing con Argon2id
    $password = 'usuario-password';
    $options = ['memory_cost' => 1<<17, 'time_cost' => 4, 'threads' => 2];
    $hash = password_hash($password, PASSWORD_ARGON2ID, $options);
    Lenguaje del código: PHP (php)

    Ejemplo de consultas parametrizadas con PDO (ORMs modernos aplican esto por defecto).

    <?php
    $stmt = $pdo->prepare('SELECT id, email FROM users WHERE email = :email');
    $stmt->execute([':email' => $email]);
    $user = $stmt->fetch();
    Lenguaje del código: PHP (php)

    Rotación de token/ID de sesión en eventos clave.

    <?php
    session_start();
    // Tras login o cambio de privilegios
    session_regenerate_id(true);
    $_SESSION['rotated_at'] = time();
    // Verificar caducidad en cada petición
    if (isset($_SESSION['rotated_at']) && time() - $_SESSION['rotated_at'] > 3600) {
        // forzar logout o revalidación
    }
    Lenguaje del código: PHP (php)

    Cabecera CSP mínima para reducir ejecución de scripts externos.

    <?php
    header("Content-Security-Policy: default-src 'self'; script-src 'self'");
    Lenguaje del código: PHP (php)

    Middleware simple para introducir fricción adaptativa en intentos de login.

    <?php
    $ip = $_SERVER['REMOTE_ADDR'];
    $attempts = $cache->get("login:{$ip}") ?? 0;
    if ($attempts > 5) {
        // añadir demora creciente (ej. usleep) para frustrar ataques automatizados
        usleep(min(500000 * ($attempts - 5), 2000000));
    }
    $cache->set("login:{$ip}", $attempts + 1, 300);
    Lenguaje del código: PHP (php)

    Checklist

    1. Habilitar auto-escaping en el motor de plantillas.
    2. Usar hashing memory-hard (Argon2id) para contraseñas.
    3. Forzar consultas parametrizadas desde el ORM o capa de datos.
    4. Implementar rotación de tokens y control de caducidad.
    5. Agregar middleware rate-adaptive para endpoints sensibles.
    6. Entregar cabeceras CSP estrictas y revisar políticas periódicamente.

    Conclusión

    Las capas invisibles eliminan vectores enteros de ataque al aplicar buenas prácticas desde el runtime y la infraestructura. Implementarlas reduce la superficie de ataque y permite a los equipos centrarse en la lógica de negocio con mayor confianza.

  • Autoload adaptativo en PHP: reducir tiempo de descubrimiento de archivos

    Autoload adaptativo en PHP: reducir tiempo de descubrimiento de archivos

    Este artículo explica cómo reducir el tiempo de descubrimiento de archivos en aplicaciones PHP mediante un autoload adaptativo que convierte mapas estáticos en una caché predictiva.

    Incluye patrón mínimo, integración con Composer y ejemplos prácticos para precargar y persistir la caché.

    Introducción

    Un autoloader estándar (por ejemplo PSR-4 con Composer) mapea namespaces a directorios y busca secuencialmente hasta encontrar un archivo. En proyectos grandes esa búsqueda repetida agrega latencia acumulada.

    El autoload adaptativo crea y mantiene una caché de rutas resueltas (en memoria o en disco) que se actualiza cuando cambia el árbol de clases, transformando búsquedas costosas en accesos determinísticos y rápidos.

    Prerrequisitos

    • Proyecto PHP que use autoload (Composer o autoloader propio).
    • Permiso para escribir un archivo de caché en disco (si se persiste entre peticiones).
    • Posibilidad de ejecutar scripts de Composer (si se integra con ganchos post-autoload-dump).
    • Opcional: soporte para opcache.preload para precargar rutas en entornos que lo permiten.

    Desarrollo

    Procedimiento

    La idea central es mantener un mapa (hash) de clase => ruta. En runtime se consulta primero esa caché; si la entrada existe se incluye la ruta inmediatamente. Si no, el autoloader realiza la búsqueda clásica, almacena el resultado en la caché y continúa.

    <?php
    spl_autoload_register(function ($class) {
        static $cache = [];
    
        if (isset($cache[$class])) {
            require $cache[$class];
            return;
        }
    
        $file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
        if (file_exists($file)) {
            $cache[$class] = $file;
            require $file;
        }
    });
    Lenguaje del código: PHP (php)

    Ese ejemplo mantiene la caché en memoria. Para persistir entre procesos, vuelca el array a JSON en disco después de cambios relevantes y vuelves a cargarlo al arrancar.

    Usar opcache.preload junto con una caché prellenada reduce aún más I/O en solicitudes recurrentes: el mapa pre-cargado evita lecturas repetidas desde disco.

    Integración con Composer

    Puedes enganchar un script a post-autoload-dump para reconstruir la caché adaptativa cuando Composer actualice el autoload. Ejemplo de snippet en composer.json:

    {
      "scripts": {
        "post-autoload-dump": [
          "php scripts/build-classmap-cache.php"
        ]
      }
    }
    Lenguaje del código: JSON / JSON con comentarios (json)

    En el script de construcción puedes combinar composer/autoload_classmap.php con tu caché adaptativa para obtener una base precisa y añadir hits observados en producción.

    <?php
    // scripts/build-classmap-cache.php
    $classmap = [];
    $composerMap = __DIR__ . '/../vendor/composer/autoload_classmap.php';
    if (file_exists($composerMap)) {
        $classmap = include $composerMap; // devuelve array clase => ruta
    }
    // Aquí podrías mergear con una caché existente o con datos de telemetría
    file_put_contents(__DIR__ . '/../storage/classmap-cache.json', json_encode($classmap, JSON_PRETTY_PRINT));
    Lenguaje del código: PHP (php)

    El ejemplo escribe un archivo JSON que el autoloader puede cargar al inicio para evitar búsquedas en disco en la primera petición.

    Ejemplos

    Lectura de la caché pre-generada en el arranque de la aplicación antes de registrar el autoloader dinámico:

    <?php
    $cacheFile = __DIR__ . '/storage/classmap-cache.json';
    $cache = [];
    if (file_exists($cacheFile)) {
        $cache = json_decode(file_get_contents($cacheFile), true);
    }
    
    spl_autoload_register(function ($class) use (&$cache) {
        if (isset($cache[$class])) {
            require $cache[$class];
            return;
        }
    
        // Fallback: búsqueda clásica y almacenamiento en caché
        $file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
        if (file_exists($file)) {
            $cache[$class] = $file;
            require $file;
            // Opcional: persistir incrementalmente o marcar para persistencia en shutdown
        }
    });
    Lenguaje del código: PHP (php)

    Este flujo permite que la primera petición use la caché en disco y las siguientes usen la versión en memoria, con posibilidad de actualizar el archivo cuando cambien las rutas.

    Checklist

    1. Generar una base de classmap (composer/autoload_classmap.php) o similar.
    2. Implementar caché en memoria y opcional persistencia en disco (JSON).
    3. Registrar autoloader que consulte la caché antes de buscar en disco.
    4. Integrar con Composer: script post-autoload-dump para reconstruir la caché cuando cambien dependencias.
    5. Considerar opcache.preload y precargar la caché en entornos que lo permitan.
    6. Medir y validar: comparar tiempo de arranque y perfil de I/O antes y después.

    Conclusión

    Un autoload adaptativo reduce búsquedas innecesarias convirtiendo mapas estáticos en una caché viva que aprende patrones de carga. El resultado: menos I/O, resolución de clases casi instantánea y mayor predictibilidad en perfiles de rendimiento.

    Implementa la caché en memoria con persistencia opcional, engancha la reconstrucción a Composer y considera opcache.preload para optimizar el comportamiento en producción.

  • Dejar que PHP narre su runtime: guía práctica

    Dejar que PHP narre su runtime: guía práctica

    \n

    Resumen: este artículo muestra cómo aplicar un patrón de \”Narrator\” en PHP para que la propia aplicación narre su flujo de ejecución. Incluye un diseño básico, integración y ejemplos de código listos para adaptar.

    \n\n\n\n\n\n\n\n

    Introducción

    \n\n\n\n

    Las soluciones tradicionales de observabilidad entregan señales reactivas: métricas, trazas y logs que requieren correlación externa. En lugar de depender exclusivamente de herramientas externas, podemos hacer que la aplicación misma produzca una narración del runtime: eventos con contexto e intención unidos en hilos causales.

    \n\n\n\n

    Este enfoque complementa (no necesariamente reemplaza) la observabilidad convencional: su objetivo es mejorar la comprensibilidad interna —por ejemplo, por qué se tomó una decisión— y facilitar la depuración rápida sin saltar entre múltiples herramientas.

    \n\n\n\n

    Prerrequisitos

    \n\n\n\n
    • Entorno PHP donde puedas anexar objetos al ciclo de vida de la petición (middleware o front controller).
    • Un logger compatible PSR-3 (por ejemplo Monolog) o un adaptador propio para exportar hilos de eventos cuando sea necesario.
    • Mínima estructura para identificar contexto: request id, session id o user id para correlación.
    \n\n\n\n

    Desarrollo

    \n\n\n\n

    Procedimiento

    \n\n\n\n
    1. Diseñar una clase Narrator que viaje con la petición y acumule eventos con metadatos (tipo, intención, contexto, timestamp).
    2. Instrumentar puntos clave: routing, validaciones, intentos de reintento, decisiones que afecten el flujo.
    3. Decidir retención: caducar segmentos de memoria cuando ya no aporten valor, o agrupar eventos en hilos causales.
    4. Exportar o exponer los hilos en un formato que un visor timeline pueda consumir (JSON, evento estructurado a logger, API interna).
    \n\n\n\n

    A continuación se muestra una implementación mínima de una clase Narrator que acumula eventos y permite serializarlos. Es un punto de partida para adaptar a frameworks o middlewares.

    \n\n\n\n
    <?php\nnamespace App\Observability;\n\nclass Narrator\n{\n    private array $threads = [];\n    private array $currentContext = [];\n\n    public function startThread(string $id, array $meta = []): void\n    {\n        $this->threads[$id] = [\n            'id' => $id,\n            'meta' => $meta,\n            'events' => [],\n        ];\n    }\n\n    public function annotate(string $threadId, string $type, string $message, array $context = []): void\n    {\n        $this->threads[$threadId]['events'][] = [\n            'ts' => microtime(true),\n            'type' => $type,\n            'message' => $message,\n            'context' => $context,\n        ];\n    }\n\n    public function setContext(array $context): void\n    {\n        $this->currentContext = $context;\n    }\n\n    public function expireThread(string $threadId): void\n    {\n        unset($this->threads[$threadId]);\n    }\n\n    public function export(): array\n    {\n        return $this->threads;\n    }\n}\n
    \n\n\n\n

    Integrar el Narrator con el flujo de la petición (por ejemplo como middleware) permite que cada controlador o servicio registre decisiones y motivos, y al final de la petición exporte el hilo a un logger o lo envíe a un endpoint interno.

    \n\n\n\n
    <?php\n// Ejemplo simplificado de middleware (framework-agnóstico)\nuse App\\Observability\\Narrator;\nuse Psr\\Log\\LoggerInterface;\n\nclass NarratorMiddleware\n{\n    private Narrator $narrator;\n    private LoggerInterface $logger;\n\n    public function __construct(Narrator $narrator, LoggerInterface $logger)\n    {\n        $this->narrator = $narrator;\n        $this->logger = $logger;\n    }\n\n    public function handle($request, $next)\n    {\n        $requestId = $request->getAttribute('request_id') ?? uniqid('req_', true);\n        $this->narrator->startThread($requestId, ['path' => $request->getUri()]);\n        $this->narrator->setContext(['request_id' => $requestId]);\n\n        $response = $next($request);\n\n        // Al final de la petición exportamos el hilo\n        $threads = $this->narrator->export();\n        $this->logger->info('narrator.threads', ['threads' => $threads]);\n\n        return $response;\n    }\n}\n
    \n\n\n\n

    En el ejemplo anterior, el logger recibe la estructura completa; un visor de timeline o un consumidor puede mostrar los eventos como una historia por petición. También puede enviarse a un índice o guardarse en almacenamiento temporal.

    \n\n\n\n
    {\n  \"req_606e2a\": {\n    \"id\": \"req_606e2a\",\n    \"meta\": {\"path\": \"/checkout\"},\n    \"events\": [\n      {\"ts\": 1690000000.123, \"type\": \"decision\", \"message\": \"cookie override - route B\", \"context\": {\"cookie\": \"promo\"}},\n      {\"ts\": 1690000000.456, \"type\": \"intent\", \"message\": \"calculate delivery estimate\", \"context\": {\"address\": \"...\"}},\n      {\"ts\": 1690000000.789, \"type\": \"notice\", \"message\": \"missing delivery estimate - user exited\", \"context\": {}}\n    ]\n  }\n}\n
    \n\n\n\n

    Ejemplos

    \n\n\n\n

    Ejemplo de uso dentro de una función de negocio: anotar la intención antes de ejecutar y el resultado después. Así se preserva la causalidad entre intención y efecto.

    \n\n\n\n
    <?php\n// Dentro de un servicio\nfunction applyCoupon(Narrator $narrator, string $threadId, array $couponData)\n{\n    $narrator->annotate($threadId, 'intent', 'apply coupon', ['coupon' => $couponData['code']]);\n\n    // Lógica real\n    $applied = false;\n    // ... comprobar validaciones, límites, fecha ...\n\n    if ($applied) {\n        $narrator->annotate($threadId, 'result', 'coupon applied', ['discount' => 10]);\n    } else {\n        $narrator->annotate($threadId, 'result', 'coupon rejected', ['reason' => 'expired']);\n    }\n\n    return $applied;\n}\n
    \n\n\n\n

    Con esta información, los equipos de producto y soporte pueden leer la cadena de eventos y comprender qué decisión tomó la aplicación y por qué, sin reconstruir el estado a partir de múltiples fuentes.

    \n\n\n\n

    Checklist

    \n\n\n\n
    1. Decidir el alcance de narración: qué tipos de eventos y qué contexto incluir.
    2. Agregar un objeto Narrator accesible en el ciclo de vida de la petición.
    3. Instrumentar puntos críticos: routing, validaciones, retries, fallos y decisiones de negocio.
    4. Definir política de expiración o agregación para evitar ruido innecesario.
    5. Elegir destino de exportación: logger estructurado, índice temporal o UI de timeline.
    6. Validar que los eventos incluyan identificadores para correlación (request id, session id).
    \n\n\n\n

    Conclusión

    \n\n\n\n

    Hacer que PHP narre su propio runtime reduce la necesidad de interpretar trazas desconectadas y acelera la resolución de problemas. Empieza por una implementación pequeña: una clase Narrator, puntos de instrumentación selectos y un canal para exportar hilos.

    \n\n\n\n

    Este patrón no elimina herramientas de observabilidad, pero aporta una capa de contexto y causalidad que hace a las aplicaciones más autoexplicativas y a los equipos menos dependientes de correlaciones externas.

    \n\n
  • Guía rápida de attributes en PHP 8+: uso y ejemplos

    Guía rápida de attributes en PHP 8+: uso y ejemplos

    Los attributes en PHP 8+ permiten adjuntar metadatos nativos a clases, métodos, propiedades y parámetros. Esta guía muestra cómo definirlos, aplicarlos y leerlos con Reflection, incluyendo ejemplos prácticos.

    Introducción

    Antes de PHP 8 se usaban docblocks y parsing de comentarios para añadir metadatos (por ejemplo, rutas). Los attributes son nativos: son clases usadas como metadatos mediante sintaxis literal, p. ej. #[Route('/users')].

    Prerrequisitos

    Necesitas PHP 8.0+ (los attributes son una característica introducida con PHP 8). También conviene familiaridad básica con clases y la API de Reflection para leer atributos en tiempo de ejecución.

    Desarrollo

    Procedimiento

    1. Crear la clase que representará el attribute y marcarla con #[Attribute].
    2. Aplicar el attribute sobre clases, métodos, propiedades o parámetros según correspondan.
    3. Leer los atributos en tiempo de ejecución usando Reflection y, si hace falta, instanciarlos.

    A continuación hay snippets que ilustran cada paso: definición, uso y lectura por Reflection.

    <?php
    #[Attribute(Attribute::TARGET_METHOD)]
    class Route
    {
        public function __construct(
            public string $path,
            public array $methods = ['GET']
        ) {}
    }
    Lenguaje del código: PHP (php)
    <?php
    class UserController
    {
        #[Route('/users', methods: ['GET'])]
        public function index() {}
    }
    Lenguaje del código: PHP (php)
    <?php
    $method = new ReflectionMethod(UserController::class, 'index');
    $attributes = $method->getAttributes();
    
    foreach ($attributes as $attribute) {
        $instance = $attribute->newInstance();
        var_dump($instance->path); // '/users'
    }
    Lenguaje del código: PHP (php)

    Atributos nativos

    PHP incluye varios attributes útiles. Aquí hay ejemplos prácticos tomados de la sintaxis nativa que aparecen en el lenguaje.

    Ejemplo: #[ReturnTypeWillChange] — se usa para compatibilidad de firmas en bibliotecas.

    <?php
    class MyIterator implements Iterator
    {
        #[ReturnTypeWillChange]
        public function current() {
            return 'foo';
        }
    }
    Lenguaje del código: PHP (php)

    Ejemplo: #[Deprecated] — marca elementos en desuso y puede incluir un motivo.

    <?php
    #[Deprecated(reason: 'Use newFunction() instead.')]
    function oldFunction() {
        return 'legacy';
    }
    Lenguaje del código: PHP (php)

    Ejemplo: #[AllowDynamicProperties] — re-habilita propiedades dinámicas en clases legadas.

    <?php
    #[AllowDynamicProperties]
    class User {}
    
    $user = new User();
    $user->name = 'Lukasz'; // funciona
    Lenguaje del código: PHP (php)

    Ejemplo: #[SensitiveParameter] — oculta valores en trazas y dumps para parámetros sensibles.

    <?php
    function login(#[SensitiveParameter] string $password) {
        throw new Exception('Oops!');
    }
    
    login('super-secret-password');
    Lenguaje del código: PHP (php)

    Ejemplo: #[Override] — asegura que un método realmente sobrescribe uno del padre.

    <?php
    class Base {
        public function greet() {}
    }
    
    class Child extends Base {
        #[Override]
        public function greet() {
            echo "Hello!";
        }
    }
    Lenguaje del código: PHP (php)

    Ejemplos

    Combinar atributos con frameworks o bibliotecas propias suele reducir la necesidad de parsear docblocks y simplifica extractores de metadatos. Un patrón común: definir attributes para rutas, validar parámetros sensibles y marcar API obsoletas.

    Ejemplo práctico rápido: definir una ruta y extraerla desde un bootstrap de aplicación para registrar endpoints.

    <?php
    // Definición ya mostrada arriba: Route
    // En bootstrap de la app:
    $rc = new ReflectionClass(UserController::class);
    foreach ($rc->getMethods() as $method) {
        foreach ($method->getAttributes() as $attr) {
            $route = $attr->newInstance();
            // registrar $route->path y $route->methods en el router
        }
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Reemplazar parsing de docblocks por classes-attribute cuando sea posible.
    2. Marcar las classes de attribute con #[Attribute] y ajustar TARGET_* según uso.
    3. Actualizar extractores para usar Reflection::getAttributes().
    4. Probar casos de compatibilidad: #[ReturnTypeWillChange] y #[AllowDynamicProperties] si persisten dependencias legadas.
    5. Marcar parámetros sensibles con #[SensitiveParameter] para proteger trazas.

    Conclusión

    Los attributes son una mejora clara frente a los docblocks: son nativos, más seguros y fáciles de consumir por código. Migrar a attributes reduce parsing ad hoc y coloca la metadata junto al código que la usa.

    Si estás manteniendo una librería o framework en PHP, prueba a implementar un pequeño atributo y su extractor: la reducción de complejidad suele notarse rápido.

  • Errores comunes de DDD en PHP y cómo evitarlos

    Resumen: este artículo recopila los errores más comunes al aplicar Domain-Driven Design en PHP y propone alternativas prácticas para mejorar nombres, estructura de código, modelos y límites entre capas.

    Introducción

    DDD no es una receta fija: son decisiones de modelado. Este artículo identifica malas prácticas frecuentes en proyectos PHP y muestra alternativas que ayudan a mantener el dominio expresivo, testable y consistente.

    Prerrequisitos

    Se asume familiaridad básica con PHP moderno, PSR, namespaces y conceptos OOP (clases, interfaces, visibilidad). También conviene conocer el vocabulario básico de DDD (aggregate, repository, value object).

    Desarrollo

    A continuación se resumen problemas comunes y recomendaciones concretas extraídas del análisis de proyectos reales.

    Procedimiento

    1. Forzar y usar un lenguaje ubicuo (ubiquitous language) consistente en nombres, namespaces y propiedades.
    2. Preferir una organización de dominio orientada al modelo (Model) en lugar de carpetas técnicas por patrón.
    3. Usar repositorios que trabajen con agregados; evitar DAOs que exponen CRUD arbitrario.
    4. Generar identidad en el dominio (UUID/ULID o una estrategia de nextIdentity en el repositorio).
    5. Diferenciar claramente PO (Parameter Object), VO (Value Object) y DTO y aplicar cada uno en su lugar.

    Cada punto se ilustra con ejemplos y patrones a continuación.

    Ejemplos

    Namespaces y raíz significativa — evite App si puede usar un namespace alineado al dominio:

    <?php
    namespace App\IAM\Domain\User;
    
    // Mejor: nombre alineado al contexto de negocio
    
    namespace ECommerce\IAM\Domain\User;
    Lenguaje del código: PHP (php)

    Organización de carpetas: agrupar por Model en lugar de fragmentar por patrones tácticos mejora la navegación y la expresividad.

    <?php
    namespace ECommerce\IAM\Domain\Model\User;
    namespace ECommerce\IAM\Domain\Model\Credentials;
    Lenguaje del código: PHP (php)

    Nombre de repositorio y propiedad inyectada: prefiera nombres del dominio en la propiedad para leer mejor el código.

    <?php
    namespace ECommerce\IAM\Application\CreateUser;
    
    use ECommerce\IAM\Domain\Model\UserRepository;
    
    class CreateUserHandler
    {
        public function __construct(private readonly UserRepository $users) {}
    
        public function __invoke(CreateUserCommand $command)
        {
            $user = User::create($command->email, $command->password);
    
            $this->users->add($user);
        }
    }
    Lenguaje del código: PHP (php)

    Getters: en DDD es preferible usar nombres que reflejen el dominio (username() en vez de getUsername()).

    <?php
    final class User
    {
        private string $username;
    
        public function username(): string
        {
            return $this->username;
        }
    }
    Lenguaje del código: PHP (php)

    Anémico vs rico: coloque comportamiento y reglas en la entidad para proteger invariantes.

    <?php
    // Anémico (anti-pattern)
    final class User
    {
        public function __construct(
            private Uuid $id,
            private string $name,
            private string $email,
        ) {}
    
        public function getName(): string { return $this->name; }
        public function setName(string $name): void { $this->name = $name; }
    }
    
    // Rico (recomendado)
    final class User
    {
        public function __construct(
            private Uuid $id,
            private string $name,
            private string $email,
        ) {}
    
        public function changeName(string $newName): void
        {
            // validar reglas de negocio
            $this->name = $newName;
        }
    }
    Lenguaje del código: PHP (php)

    DAO vs Repository: un DAO expone CRUD de bajo nivel; un Repository trabaja con agregados y oculta la persistencia.

    <?php
    // DAO (operaciones SQL, arreglos)
    final class UserDAO
    {
        public function find(int $id): array { /* ... */ }
        public function insert(array $data): void { /* ... */ }
        public function update(int $id, array $data): void { /* ... */ }
    }
    
    // Repository (trabaja con agregados)
    final class MySQLUserRepository implements UserRepository
    {
        public function byId(UserId $id): ?User { /* ... */ }
        public function add(User $user): void { /* ... */ }
    }
    Lenguaje del código: PHP (php)

    Identidad: evite depender de auto-increment del motor. Genere identidad en el dominio (UUID/ULID) o exponga nextIdentity() desde el repositorio.

    <?php
    use Ramsey\Uuid\Uuid;
    
    final class UserId
    {
        private function __construct(private string $value) {}
    
        public static function generate(): self
        {
            return new self(Uuid::uuid4()->toString());
        }
    
        public function asString(): string
        {
            return $this->value;
        }
    }
    Lenguaje del código: PHP (php)

    DTO, Parameter Object y Value Object: defínalos por su propósito y no los confundas.

    <?php
    // Parameter Object: agrupa parámetros para llamadas
    deconstructing
    readonly class Credentials
    {
        public function __construct(public string $email, public string $password) {}
    }
    
    // DTO: usado para exponer datos fuera del dominio
    final readonly class UserShowDTO
    {
        public function __construct(public string $email, public string $name) {}
    }
    
    // Value Object: inmutable y con invariantes
    final readonly class FullName
    {
        private function __construct(private string $firstName, private string $lastName) {}
    
        public static function create(string $firstName, string $lastName): self
        {
            // validar invariantes aquí
            return new self($firstName, $lastName);
        }
    
        public function asString(): string
        {
            return $this->firstName . ' ' . $this->lastName;
        }
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Revisar nombres y namespaces: ¿hablan el lenguaje del dominio?
    2. ¿La estructura de carpetas refleja conceptos del negocio (Model) en vez de patrones técnicos?
    3. ¿Las entidades encapsulan comportamiento o son solo DTOs con setters?
    4. ¿Los repositorios trabajan con agregados y no exponen update() genérico?
    5. ¿La identidad se genera en el dominio (UUID/nextIdentity) en lugar de setId() posterior a la persistencia?
    6. ¿Se diferencian claramente PO, VO y DTO en los límites del sistema?

    Conclusión

    DDD efectivo en PHP depende de decisiones de modelado: imponer lenguaje ubicuo, poner comportamiento en las entidades, usar repositorios centrados en agregados y mantener fronteras claras entre PO/VO/DTO. Aplicar estas prácticas mejora claridad, seguridad y testabilidad.

    Si quieres, puedo preparar una segunda parte con más ejemplos prácticos o refactorizaciones paso a paso.

  • Seguridad de sesiones y cookies en PHP: guía práctica

    Seguridad de sesiones y cookies en PHP: guía práctica

    Resumen: Esta guía práctica resume las medidas esenciales para gestionar sesiones y cookies en PHP de forma segura: inicio de sesión, regeneración de ID, cookies seguras, timeouts, almacenamiento eficiente y limpieza al cerrar sesión.

    Introducción

    Sesiones y cookies permiten mantener estado en aplicaciones web PHP: autenticación, preferencias y datos temporales. Usadas correctamente mejoran la experiencia; mal configuradas, introducen riesgos de seguridad.

    Prerrequisitos

    Antes de aplicar las prácticas descritas asegúrate de servir las páginas sensibles por HTTPS y de tener control sobre el código que inicia y destruye sesiones. No afirmaré compatibilidades concretas; valida esto según tu entorno.

    Conocimiento mínimo necesario: saber dónde llamar a session_start() y cómo enviar cookies desde PHP. Evita exponer identificadores de sesión en URLs o registros.

    Desarrollo

    Procedimiento

    Pasos prácticos y concisos para asegurar sesiones y cookies en PHP. Aplique cada paso según el riesgo y la arquitectura de su aplicación.

    1. Iniciar sesiones de forma controlada y consistente.
    2. Regenerar el identificador de sesión tras la autenticación.
    3. Enviar cookies con flags Secure, HttpOnly y SameSite cuando corresponda.
    4. Implementar timeouts por inactividad y limpieza al logout.
    5. Almacenar solo identificadores en la sesión; datos pesados en base de datos o cache.
    6. Opcional: usar un session handler personalizado para almacenamiento centralizado.

    Detalles clave: regenerar el ID reduce el riesgo de session fixation; Secure y HttpOnly protegen el cookie en tránsito y frente a JavaScript; SameSite mitiga ciertas CSRF.

    Pro tip: Usa sesiones para identificar entidades (IDs) y no para persistir grandes objetos o datos sensibles sin cifrado.

    Ejemplos

    A continuación se incluyen ejemplos prácticos adaptados desde patrones comunes. Escapa los marcadores de apertura PHP en tu código según la plantilla del bloque.

    <?php
    // Start the session
    session_start();
    
    // Store some data in the session
    $_SESSION['username'] = 'JohnDoe';
    
    // Retrieve data from the session
    echo 'Hello, ' . $_SESSION['username']; // Outputs: Hello, JohnDoe
    Lenguaje del código: PHP (php)

    Regenerar el ID de sesión inmediatamente después de un login exitoso:

    <?php
    session_start();
    
    // After a successful login
    if ($_POST['username'] == 'user' && $_POST['password'] == 'password') {
        session_regenerate_id(true);  // Regenerate the session ID for added security
        
        $_SESSION['username'] = 'user';
        echo "Login successful, and your session ID has been regenerated!";
    }
    Lenguaje del código: PHP (php)

    Ejemplo de cookie segura con atributos recomendados (transmisión segura y protección contra acceso por JS):

    <?php
    // Set a cookie that only works on secure connections (HTTPS)
    setcookie('user_preference', 'dark_mode', [
        'expires' => time() + 3600, // 1 hour expiration
        'path' => '/',
        'domain' => 'yourdomain.com',
        'secure' => true,  // Send cookie only over HTTPS
        'httponly' => true,  // Prevent JavaScript access to cookie
        'samesite' => 'Strict' // Helps prevent CSRF attacks
    ]);
    
    echo 'Your preference cookie is set securely.';
    Lenguaje del código: PHP (php)

    Timeout de sesión por inactividad (mecanismo simple basado en marcas temporales):

    <?php
    session_start();
    
    // Set timeout period (10 minutes)
    $timeout = 600;
    
    if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $timeout)) {
        // If the session has expired, destroy it
        session_unset();
        session_destroy();
        echo 'Session expired. Please log in again.';
    } else {
        $_SESSION['LAST_ACTIVITY'] = time(); // Update last activity time
        echo 'Your session is still active.';
    }
    Lenguaje del código: PHP (php)

    Esqueleto de un session handler personalizado para almacenar sesiones en base de datos o cache centralizada:

    <?php
    class CustomSessionHandler extends SessionHandler {
        public function read($session_id) {
            // Read session data from the database
        }
    
        public function write($session_id, $data) {
            // Write session data to the database
        }
    }
    
    // Register custom session handler
    $handler = new CustomSessionHandler();
    session_set_save_handler($handler, true);
    session_start();
    Lenguaje del código: PHP (php)

    Al cerrar sesión, destruye la sesión y expira las cookies relevantes para evitar reutilización:

    <?php
    session_start();
    
    // Destroy the session and clear all session data
    session_unset();
    session_destroy();
    
    // Expire session and preference cookies
    setcookie(session_name(), '', time() - 3600, '/');
    setcookie('user_preference', '', time() - 3600, '/');
    
    echo 'Session and cookies have been cleared.';
    Lenguaje del código: PHP (php)

    Checklist

    1. Servir páginas sensibles por HTTPS.
    2. Iniciar sesión con session_start() solo donde sea necesario.
    3. Regenerar ID de sesión tras autenticación (session_regenerate_id).
    4. Enviar cookies con Secure, HttpOnly y SameSite adecuados.
    5. Implementar timeout por inactividad y actualizar LAST_ACTIVITY.
    6. Almacenar solo identificadores en la sesión; consultar DB para datos grandes.
    7. Destruir sesión y expirar cookies en logout.

    Conclusión

    La seguridad de sesiones y cookies es una combinación de buenas prácticas: transporte seguro (HTTPS), configuración correcta de cookies, rotación de identificadores, timeouts y limpieza al logout. Estas medidas reducen riesgos comunes como hijacking y XSS.

    Empieza aplicando los pasos del checklist y adapta las implementaciones (por ejemplo, handlers personalizados) según la escala y arquitectura de tu aplicación.

  • PHP vs Node.js: cómo elegir el backend en 2025

    PHP vs Node.js: cómo elegir el backend en 2025

    Resumen: Comparativa técnica entre PHP y Node.js para elegir backend según requisitos de rendimiento, ecosistema y experiencia del equipo.

    Este artículo sintetiza ventajas, limitaciones y ejemplos mínimos para ayudar a arquitectos y desarrolladores a decidir qué tecnología usar.

    Introducción

    PHP y Node.js son tecnologías maduras para backend con enfoques distintos: PHP tradicionalmente síncrono y orientado a request-response; Node.js basado en I/O asíncrono y event-driven.

    Prerrequisitos

    Antes de elegir, evalúa: cargas concurrentes previstas, necesidad de tiempo real, experiencia del equipo y requisitos de despliegue.

    1. Definir el patrón de tráfico (p. ej. muchas conexiones concurrentes vs pocas por usuario).
    2. Identificar dependencias y ecosistema (CMS, bibliotecas, integraciones).
    3. Revisar capacidades de hosting y costos operativos.

    Desarrollo

    Comparar en cuatro ejes: rendimiento, ecosistema, curva de aprendizaje y despliegue.

    Procedimiento

    1) Analiza el caso de uso. 2) Mide concurrencia y latencia objetivo. 3) Selecciona la pila que minimice riesgos técnicos y de operación.

    Resumen de diferencias clave:

    • PHP: buen soporte para sitios de contenido, despliegue sencillo en hosting compartido, amplio ecosistema (WordPress, Composer, Laravel).
    • Node.js: mejor para I/O intensivo y tiempo real; ecosistema moderno con npm y frameworks como Express o Fastify.

    Ejemplos

    Ejemplos mínimos que ilustran el modelo de ejecución en cada plataforma.

    <?php
    // Ejemplo PHP: manejador básico en entorno tradicional (request-response)
    header('Content-Type: application/json');
    $response = ['time' => date('c'), 'message' => 'Hola desde PHP'];
    echo json_encode($response);
    Lenguaje del código: PHP (php)

    En PHP el ciclo típico abre, procesa y cierra la petición; es sencillo y efectivo para sitios de contenido.

    const http = require('http');
    
    const server = http.createServer((req, res) => {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ time: new Date().toISOString(), message: 'Hola desde Node.js' }));
    });
    
    server.listen(3000, () => {
      console.log('Server listening on port 3000');
    });
    Lenguaje del código: JavaScript (javascript)

    Node.js mantiene un loop de eventos que permite gestionar muchas conexiones concurrentes con eficiencia en I/O.

    Checklist

    Lista rápida para decidir tecnología según el proyecto.

    1. ¿El proyecto requiere tiempo real o muchas conexiones concurrentes? → Considerar Node.js.
    2. ¿Priman CMS o contenidos estáticos y despliegue rápido? → PHP/WordPress puede ser suficiente.
    3. ¿El equipo domina JavaScript o PHP/Laravel? → Elegir según experiencia para reducir riesgos.
    4. ¿Necesitas microservicios o arquitectura basada en eventos? → Node.js suele facilitarlo.

    Conclusión

    No existe una respuesta universal: PHP sigue siendo válido para sitios de contenido y despliegues rápidos; Node.js destaca en aplicaciones en tiempo real y arquitecturas orientadas a I/O.

    La recomendación práctica: prioriza requisitos técnicos, experiencia del equipo y costos operativos al seleccionar entre PHP y Node.js.

  • Laravel a escala: 5 trucos para consultar millones de registros

    Laravel a escala: 5 trucos para consultar millones de registros

    Este artículo resume cinco técnicas prácticas para consultar millones de filas en Laravel sin agotar memoria ni tiempo CPU. Cada técnica incluye una explicación breve y ejemplos de código listos para usar.

    Aplica estas prácticas en procesos de reporting, ETL o consultas masivas para mantener tiempos de respuesta y uso de recursos controlados.

    Introducción

    En consultas a tablas masivas, prácticas comunes como SELECT * o cargar todo con get() provocan consumo excesivo de RAM y CPU. Las siguientes cinco estrategias reducen uso de recursos y aceleran procesos.

    Prerrequisitos

    Antes de aplicar las técnicas: asegúrate de contar con un esquema de base de datos razonable y mecanismos de cacheo/colas si vas a usar preagregados o jobs en segundo plano.

    • Acceso al código de tus modelos/Eloquent.
    • Permiso para crear índices y migraciones en la base de datos.
    • Un sistema de cache (Redis, Memcached) y, si procede, soporte para jobs/colas.

    Desarrollo

    Procedimiento

    A continuación se presentan las cinco técnicas: seleccionar columnas esenciales, procesar por chunks o stream, indexar y optimizar WHERE, preagregar datos y cachear resultados pesados.

    1. Seleccionar sólo las columnas esenciales

    Evita SELECT * cuando sólo necesitas unas pocas columnas. Reducir columnas reduce CPU, memoria y ancho de banda.

    <?php
    // ❌ Evitar: carga todas las columnas
    $orders = Order::all();
    
    // ✅ Seleccionar sólo lo necesario
    $orders = Order::select('id', 'amount', 'created_at')
        ->whereBetween('created_at', [$start, $end])
        ->get();
    Lenguaje del código: PHP (php)

    En el ejemplo base, reducir de 20 columnas a 3 puede bajar el consumo de memoria de ~1.2 GB a ~200 MB en un conjunto grande de filas.

    2. Procesar por chunks o con cursor (stream)

    No uses get() para millones de filas. chunk() y cursor() procesan en partes y mantienen baja la memoria.

    <?php
    // Procesar en bloques de 10.000
    Order::where('status', 'completed')
        ->select('id', 'amount')
        ->chunk(10000, function ($orders) {
            foreach ($orders as $order) {
                // generar fila de reporte
            }
        });
    
    // O procesar con cursor para stream perezoso
    $orders = Order::cursor()->filter(fn ($order) => $order->amount > 1000);
    Lenguaje del código: PHP (php)

    En pruebas citadas, procesar 5M de filas con cursor/chunks reduce consumo de memoria a decenas de MB frente a cientos o más.

    3. Índices y cláusulas WHERE eficientes

    Filtros y ordenamientos sin índices provocan full table scans. Indexa columnas usadas en WHERE, JOIN y ORDER BY y evita patrones ineficientes como LIKE ‘%…%’.

    <?php
    // Ejemplo de migración para agregar índices
    Schema::table('orders', function (Blueprint $table) {
        $table->index('status');
        $table->index('created_at');
    });
    Lenguaje del código: PHP (php)

    Un ejemplo reportado mostró una reducción de tiempo de consulta de 120 s a 0.8 s tras indexar columnas utilizadas en filtros.

    4. Preagregar datos para reportes pesados

    Evita agrupar o sumar decenas de millones de filas en tiempo real. Precalcula resúmenes con jobs nocturnos o usa vistas/materialized views si la BD lo permite.

    <?php
    class GenerateOrderSummary implements ShouldQueue {
        public function handle() {
            $summary = Order::selectRaw('
                DATE(created_at) AS date,
                COUNT(*) AS total_orders,
                SUM(amount) AS revenue
            ')
            ->whereDate('created_at', today()->subDay())
            ->groupBy('date')
            ->first();
    
            Report::updateOrCreate(['date' => $summary->date], (array)$summary);
        }
    }
    Lenguaje del código: PHP (php)

    Con preagregados, la carga de un reporte puede pasar de minutos a decenas de milisegundos; en el ejemplo base, un reporte cargó en ~50 ms.

    5. Cachear consultas costosas

    Para consultas repetidas, guarda el resultado en cache con expiración y/o tags. Invalida o refresca la cache cuando los datos subyacentes cambien.

    <?php
    $report = Cache::remember("daily_sales_report:{$date}", 3600, fn() =>
        Order::whereDate('created_at', $date)
            ->selectRaw('COUNT(*) AS orders, SUM(amount) AS revenue')
            ->first()
    );
    Lenguaje del código: PHP (php)

    Usa Redis o Memcached según tu infraestructura y define políticas claras de invalidación para evitar datos obsoletos.

    Ejemplos

    Ejemplos concretos ya mostrados: selección de columnas, chunk/cursor, migración para índices, job para preagregados y cacheo con Cache::remember.

    Adapta los tamaños de chunk y la expiración del cache a la carga real y al patrón de acceso de tu sistema.

    Checklist

    1. Revisar consultas y eliminar SELECT *.
    2. Usar chunk() o cursor() para procesado masivo.
    3. Indexar columnas usadas en WHERE, JOIN y ORDER BY.
    4. Preagregar resultados pesados con jobs o vistas.
    5. Cachear consultas repetidas y definir estrategia de invalidación.

    Conclusión

    Combinando selección precisa de columnas, procesamiento por partes, buenos índices, preagregados y cacheo, puedes consultar millones de registros en Laravel con uso de recursos razonable y tiempos de respuesta aceptables.

    Empieza por los cambios menos invasivos (select, chunk, cache) y mide impacto antes de introducir cambios más estructurales como materialized views o rediseño de esquemas.

  • Evolución de las colas en Laravel: workers, deferred y background

    Evolución de las colas en Laravel: workers, deferred y background

    Este artículo resume la evolución del sistema de colas en Laravel: desde los workers tradicionales hasta el driver background basado en concurrencia. Explica usos, diferencias y ejemplos prácticos de implementación.

    Introducción

    Laravel ha transitado de un sistema de colas basado en workers persistentes a opciones que reducen la complejidad operativa: drivers deferred y background (impulsado por concurrencia).

    Prerrequisitos

    Antes de aplicar cualquiera de las estrategias descritas, confirma lo siguiente:

    • Proyecto Laravel con entorno PHP configurado.
    • Driver de colas definido (Redis, database, etc.) si planeas usar workers tradicionales.
    • En caso de background driver, la configuración de conexiones en config/queue.php debe incluir la conexión ‘background’ (ver ejemplo).

    Desarrollo

    Procedimiento

    A continuación se describen las tres aproximaciones principales y cómo elegir entre ellas según la carga y la tolerancia a fallos.

    1. Workers tradicionales: ejecutan jobs mediante procesos persistentes (p. ej. php artisan queue:work redis), adecuados para cargas pesadas y con control avanzado de reintentos y fallos.
    2. Driver deferred: ejecuta el job después de enviar la respuesta HTTP, sin worker. Adecuado para tareas ligeras y no críticas.
    3. Driver background (concurrencia): ejecuta jobs en procesos PHP separados, sin worker persistente; equilibrio entre robustez y simplicidad operativa.

    Ventajas y limitaciones resumidas:

    • Workers tradicionales: alto rendimiento y control; requieren DevOps para gestionar procesos.
    • Deferred: baja sobrecarga; limitado en reintentos y duración de tarea.
    • Background: sin workers dedicados, más robusto que deferred; útil para tareas de duración media.
    php artisan queue:work redis
    Lenguaje del código: Bash (bash)
    <?php
    return [
        'connections' => [
            'background' => [
                'driver' => 'background',
            ],
        ],
    ];
    Lenguaje del código: PHP (php)

    El ejemplo anterior muestra cómo declarar una conexión ‘background’ en config/queue.php. Su dispatch usa la conexión para ejecutar el job en otro proceso PHP.

    Ejemplos

    Ejemplo de clase Job que implementa ShouldQueue y se serializa para su ejecución en background o por un worker.

    <?php
    namespace App\Jobs;
    
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Bus\Dispatchable;
    use Illuminate\Queue\SerializesModels;
    
    class RecordDelivery implements ShouldQueue
    {
        use Dispatchable, Queueable, SerializesModels;
    
        public function __construct(public $order)
        {
        }
    
        public function handle()
        {
            // lógica de procesamiento
        }
    }
    Lenguaje del código: PHP (php)

    Dispatch desde un controlador usando la conexión background para evitar bloquear la petición HTTP.

    <?php
    use App\Jobs\RecordDelivery;
    
    class DeliveryController
    {
        public function store(Request $request)
        {
            $order = Order::create([...]);
            RecordDelivery::dispatch($order)->onConnection('background');
    
            return response()->json(['status' => 'processing'], 202);
        }
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Determina la criticidad de la tarea (alta → worker tradicional).
    2. Si la tarea es ligera y no crítica, considera deferred.
    3. Para equilibrio operativo (sin workers pero más robusto que deferred), usa el driver background.
    4. Configura reintentos, timeouts y monitoreo según el método elegido.

    Conclusión

    La evolución de las colas en Laravel ofrece alternativas para distintos perfiles de carga operativa. Elegir entre workers, deferred o background depende de la carga, la tolerancia a fallos y la capacidad operativa del equipo.

    Si buscas simplificar la operación sin renunciar a capacidad de procesamiento, el driver background —impulsado por concurrencia— es una opción intermedia interesante.

  • AsyncRequestContext en Laravel: reducir middleware con contexto

    AsyncRequestContext en Laravel: reducir middleware con contexto

    Este artículo explica cómo reemplazar middleware que solo propagan datos por petición con un contexto scoped (AsyncRequestContext) en Laravel. El objetivo: reducir la complejidad de rutas, mejorar la claridad de dependencias y facilitar pruebas unitarias.

    Incluye el diseño del contexto, el enlace en el service container y ejemplos de uso en acciones, controladores, rutas y pruebas.

    Introducción

    El patrón AsyncRequestContext propone un contenedor ligero por petición para almacenar estado que actualmente se inyecta en Illuminate\Http\Request mediante middleware.

    En lugar de ensuciar el Request con claves arbitrarias, se inicializa un contexto por ciclo de petición y se usa desde acciones y bindings del contenedor.

    Prerrequisitos

    Conocimientos básicos de Laravel: service container, providers, uso de acciones/servicios y rutas. No se requiere configuración adicional en el framework más allá de registrar el provider.

    Desarrollo

    La idea central es exponer un RequestContext scoped que se inicializa al enlazar en el container y se limpia en termination. Evita ‘request pollution’ y hace explícitas las dependencias.

    <?php
    namespace App\Context;
    
    class RequestContext
    {
        private static ?array $data = null;
    
        public static function initialize(): void
        {
            self::$data = [];
        }
    
        public static function set(string $key, mixed $value): void
        {
            self::$data[$key] = $value;
        }
    
        public static function get(string $key): mixed
        {
            return self::$data[$key] ?? null;
        }
    
        public static function flush(): void
        {
            self::$data = null;
        }
    }
    Lenguaje del código: PHP (php)

    En el service provider se enlaza RequestContext para inicializarlo por petición y se registra un hook de terminating para limpiar el estado.

    <?php
    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use App\Context\RequestContext;
    
    class ContextServiceProvider extends ServiceProvider
    {
        public function boot()
        {
            $this->app->bind(RequestContext::class, function () {
                RequestContext::initialize();
                return new RequestContext();
            });
    
            $this->app->terminating(function () {
                RequestContext::flush();
            });
        }
    }
    Lenguaje del código: PHP (php)

    Procedimiento

    En lugar de usar middleware que sólo añade datos al Request, crea acciones que reciben el Request y escriben en el RequestContext. Eso permite invocarlas donde y cuando hagan falta.

    <?php
    namespace App\Actions;
    
    use Illuminate\Http\Request;
    use App\Context\RequestContext;
    use App\Models\Tenant;
    
    class ResolveTenant
    {
        public function __construct(private RequestContext $context) {}
    
        public function execute(Request $request): void
        {
            $tenant = Tenant::fromDomain($request->host());
            $this->context->set('tenant', $tenant);
        }
    }
    Lenguaje del código: PHP (php)

    Consume el contexto explícitamente en controladores o servicios, lo que revela dependencias y facilita pruebas unitarias.

    <?php
    namespace App\Http\Controllers;
    
    use App\Context\RequestContext;
    
    class ReportController
    {
        public function index(RequestContext $context)
        {
            $tenant = $context->get('tenant');
            return $tenant->reports()->paginate();
        }
    }
    Lenguaje del código: PHP (php)

    Las rutas pueden simplificarse ejecutando acciones de resolución dentro de la closure y pasando el contexto al controlador.

    <?php
    use App\Actions\ResolveTenant;
    use App\Context\RequestContext;
    use App\Http\Controllers\ReportController;
    use Illuminate\Support\Facades\Route;
    
    Route::get('/reports', function (
        ResolveTenant $resolve,
        RequestContext $context
    ) {
        $resolve->execute(request());
        return (new ReportController)->index($context);
    })->middleware('auth:api');
    Lenguaje del código: PHP (php)

    Con este enfoque mantienes middleware para filtrado (autenticación, throttling) y usas acciones/contexto para propagar estado.

    Ejemplos

    Ejemplo de binding contextual que elige una implementación según un valor en RequestContext.

    <?php
    // En AppServiceProvider.php
    $this->app->bind(ReportExporter::class, function () {
        $format = RequestContext::get('export_format') ?? 'csv';
        return match ($format) {
            'excel' => new ExcelExporter,
            'pdf'   => new PDFExporter,
            default => new CSVExporter,
        };
    });
    Lenguaje del código: PHP (php)

    Prueba unitaria sin levantar el HTTP layer inicializando el contexto manualmente:

    <?php
    test('exports tenant reports in excel', function () {
        RequestContext::initialize();
        RequestContext::set('tenant', Tenant::factory()->create());
        RequestContext::set('export_format', 'excel');
    
        $exporter = app(ReportExporter::class);
        $this->assertInstanceOf(ExcelExporter::class, $exporter);
    });
    Lenguaje del código: PHP (php)

    Checklist

    1. Auditar middleware que solo escriben datos en el Request.
    2. Refactorizar esas responsabilidades en acciones (ResolveX classes).
    3. Sustituir $request->get(‘foo’) por $context->get(‘foo’) donde corresponda.
    4. Reducir arrays de middleware en rutas dejando filtros reales (auth, throttle).
    5. Asegurar que RequestContext::flush() se ejecuta en terminating.

    Conclusión

    AsyncRequestContext ofrece una alternativa práctica al uso excesivo de middleware para propagar estado por petición. Mejora la claridad, facilita pruebas y mantiene middleware para filtrado.

    Middleware es para filtrado, no para propagar datos. Deja que el contexto lleve tu estado cross‑cutting.