miércoles, 25 de junio de 2025

Implementación de Locks Distribuídos con Redis para Sincronización en PHP

Implementación de Locks Distribuídos con Redis para Sincronización en PHP

En entornos distribuidos, la necesidad de sincronizar el acceso a recursos compartidos se vuelve crucial para evitar condiciones de carrera y garantizar la integridad de los datos. Una solución popular es el uso de locks distribuidos. Redis, gracias a su atomicidad y bajo tiempo de latencia, se presenta como una excelente opción para implementar estos locks en aplicaciones PHP.

Este artículo mostrará cómo implementar un lock distribuido básico con Redis utilizando la extensión phpredis. Utilizaremos las funciones setnx (SET if Not eXists) y del (DELETE) para adquirir y liberar el lock, respectivamente. Un aspecto importante es incluir un tiempo de expiración al lock para evitar que, en caso de fallo del proceso que lo adquirió, el recurso quede bloqueado indefinidamente.


<?php

class DistributedLock {

    private $redis;
    private $lockKeyPrefix = 'lock:';

    public function __construct(Redis $redis) {
        $this->redis = $redis;
    }

    public function acquireLock(string $resourceId, int $ttl = 10): bool {
        $lockKey = $this->lockKeyPrefix . $resourceId;
        $lockValue = uniqid('', true); // Valor único para identificar el lock
        $acquired = $this->redis->setnx($lockKey, $lockValue);

        if ($acquired) {
            $this->redis->expire($lockKey, $ttl); // Establecer el tiempo de vida (TTL)
            return true;
        } else {
            return false;
        }
    }

    public function releaseLock(string $resourceId): bool {
        $lockKey = $this->lockKeyPrefix . $resourceId;
        return $this->redis->del($lockKey) > 0;
    }
}

// Ejemplo de uso:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379); // Reemplaza con tu configuración de Redis

$lock = new DistributedLock($redis);
$resourceId = 'my_resource';

if ($lock->acquireLock($resourceId)) {
    try {
        // Sección crítica: Aquí realizamos las operaciones que requieren sincronización.
        echo "Lock adquirido. Procesando recurso...\n";
        sleep(5); // Simula un proceso que tarda.
        echo "Recurso procesado.\n";
    } finally {
        if ($lock->releaseLock($resourceId)) {
            echo "Lock liberado.\n";
        } else {
            echo "Error al liberar el lock.\n";
        }
    }
} else {
    echo "No se pudo adquirir el lock. El recurso está bloqueado.\n";
}

$redis->close();
?>
    

El código anterior define una clase DistributedLock que encapsula la lógica para adquirir y liberar el lock. La función acquireLock intenta establecer una clave en Redis usando setnx. Si la clave no existe, se crea y se establece un tiempo de expiración. La función releaseLock simplemente elimina la clave de Redis.

Es importante notar que este es un ejemplo básico. Para una implementación más robusta, se deben considerar escenarios como la comprobación del valor del lock antes de liberarlo (para asegurar que el proceso actual es el que adquirió el lock) y la implementación de un mecanismo de reintento en la adquisición del lock.

Implementando Árboles Binarios de Búsqueda Autobalanceados (AVL) en PHP

Implementando Árboles Binarios de Búsqueda Autobalanceados (AVL) en PHP

Los Árboles Binarios de Búsqueda (BST) son estructuras de datos poderosas para la búsqueda, inserción y eliminación eficiente de elementos. Sin embargo, en el peor de los casos (cuando los datos están ordenados), un BST puede degenerar en una lista enlazada, perdiendo su eficiencia. Los árboles AVL son una optimización de los BST que se autobalancean para garantizar una altura logarítmica, manteniendo un rendimiento óptimo incluso en casos de inserciones y eliminaciones desfavorables. En este artículo, exploraremos cómo implementar un árbol AVL en PHP.


<?php

class AVLNode {
    public $key;
    public $value;
    public $height;
    public $left;
    public $right;

    public function __construct($key, $value) {
        $this->key = $key;
        $this->value = $value;
        $this->height = 1; // Altura inicial de un nodo hoja
        $this->left = null;
        $this->right = null;
    }
}

class AVLTree {
    private $root;

    public function __construct() {
        $this->root = null;
    }

    private function height(?AVLNode $node): int {
        return $node ? $node->height : 0;
    }

    private function updateHeight(?AVLNode $node): void {
        $node->height = 1 + max($this->height($node->left), $this->height($node->right));
    }

    private function getBalance(?AVLNode $node): int {
        return $node ? $this->height($node->left) - $this->height($node->right) : 0;
    }

    private function rotateRight(?AVLNode $y): ?AVLNode {
        $x = $y->left;
        $T2 = $x->right;

        // Rotación
        $x->right = $y;
        $y->left = $T2;

        // Actualizar alturas
        $this->updateHeight($y);
        $this->updateHeight($x);

        return $x;
    }

    private function rotateLeft(?AVLNode $x): ?AVLNode {
        $y = $x->right;
        $T2 = $y->left;

        // Rotación
        $y->left = $x;
        $x->right = $T2;

        // Actualizar alturas
        $this->updateHeight($x);
        $this->updateHeight($y);

        return $y;
    }

    public function insert(?AVLNode $node, $key, $value): ?AVLNode {
        // Inserción estándar de BST
        if ($node === null) {
            return new AVLNode($key, $value);
        }

        if ($key < $node->key) {
            $node->left = $this->insert($node->left, $key, $value);
        } elseif ($key > $node->key) {
            $node->right = $this->insert($node->right, $key, $value);
        } else {
            // Clave duplicada no permitida
            return $node;
        }

        // Actualizar altura del nodo actual
        $this->updateHeight($node);

        // Obtener el factor de balance del nodo
        $balance = $this->getBalance($node);

        // Si el nodo no está balanceado, hay 4 casos
        // Caso Izquierda Izquierda
        if ($balance > 1 && $key < $node->left->key) {
            return $this->rotateRight($node);
        }

        // Caso Derecha Derecha
        if ($balance < -1 && $key > $node->right->key) {
            return $this->rotateLeft($node);
        }

        // Caso Izquierda Derecha
        if ($balance > 1 && $key > $node->left->key) {
            $node->left = $this->rotateLeft($node->left);
            return $this->rotateRight($node);
        }

        // Caso Derecha Izquierda
        if ($balance < -1 && $key < $node->right->key) {
            $node->right = $this->rotateRight($node->right);
            return $this->rotateLeft($node);
        }

        return $node;
    }

    public function insertKey($key, $value): void {
        $this->root = $this->insert($this->root, $key, $value);
    }

    // (El resto de métodos: eliminar, buscar, etc., seguirían la misma lógica AVL)
}

// Ejemplo de uso
$tree = new AVLTree();
$tree->insertKey(10, "Valor 10");
$tree->insertKey(20, "Valor 20");
$tree->insertKey(30, "Valor 30");
$tree->insertKey(40, "Valor 40");
$tree->insertKey(50, "Valor 50");
$tree->insertKey(25, "Valor 25");

// La raíz ahora apuntará al árbol AVL balanceado.
?>
    

Este código proporciona una implementación básica de la inserción en un árbol AVL. Los métodos `rotateRight` y `rotateLeft` realizan las rotaciones necesarias para mantener el árbol balanceado. El método `getBalance` determina si un nodo está balanceado basándose en la diferencia de alturas entre sus subárboles izquierdo y derecho. Es importante recordar que la eliminación de nodos en un árbol AVL es más compleja que la inserción y requerirá implementar lógica adicional para mantener el balance.

El código presentado es un punto de partida. Una implementación completa de un árbol AVL requeriría la inclusión de métodos para eliminar nodos, buscar nodos, y quizás un método para imprimir el árbol para su visualización y depuración. Además, la optimización del rendimiento podría ser considerada, especialmente si el árbol se espera que maneje grandes cantidades de datos.

Programación reactiva con SplSubject y SplObserver en PHP

Programación reactiva con SplSubject y SplObserver en PHP

La programación reactiva es un paradigma que se centra en el flujo de datos y la propagación del cambio. En PHP, aunque no tenemos librerías reactivas tan robustas como en otros lenguajes, podemos implementar un patrón observer rudimentario usando las interfaces SplSubject y SplObserver.

Este enfoque permite que un objeto (el "sujeto") notifique automáticamente a sus dependientes (los "observadores") cuando su estado cambia. Esto facilita la creación de aplicaciones con una lógica más desacoplada y reactiva a los eventos.

El siguiente ejemplo muestra cómo podemos usar SplSubject y SplObserver para construir un sistema simple de notificación de eventos. Imagina un objeto que representa un artículo de blog; cuando se actualiza el contenido del artículo, queremos notificar a los observadores (por ejemplo, para actualizar un índice de búsqueda o enviar una notificación por correo electrónico).


<?php

class BlogPost implements SplSubject {
    private $title;
    private $content;
    private $observers = [];

    public function __construct(string $title, string $content) {
        $this->title = $title;
        $this->content = $content;
    }

    public function attach(SplObserver $observer): void {
        $this->observers[] = $observer;
    }

    public function detach(SplObserver $observer): void {
        $key = array_search($observer, $this->observers, true);
        if ($key !== false) {
            unset($this->observers[$key]);
        }
    }

    public function notify(): void {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function updateContent(string $newContent): void {
        $this->content = $newContent;
        $this->notify();
    }

    public function getContent(): string {
        return $this->content;
    }

    public function getTitle(): string {
        return $this->title;
    }
}

class SearchIndexUpdater implements SplObserver {
    public function update(SplSubject $subject): void {
        if ($subject instanceof BlogPost) {
            echo "Actualizando el índice de búsqueda para el artículo: " . $subject->getTitle() . " con contenido: " . substr($subject->getContent(), 0, 50) . "...\n";
            // Aquí iría la lógica para actualizar el índice de búsqueda
        }
    }
}

// Ejemplo de uso
$blogPost = new BlogPost("Mi primer artículo", "Este es el contenido inicial del artículo.");
$searchIndexUpdater = new SearchIndexUpdater();

$blogPost->attach($searchIndexUpdater);
$blogPost->updateContent("Este es el nuevo contenido actualizado del artículo.");
?>
    

En el código anterior, BlogPost implementa SplSubject, lo que le permite adjuntar, separar y notificar a los observadores. SearchIndexUpdater implementa SplObserver y define la lógica para reaccionar a los cambios en el BlogPost.

Este es un ejemplo básico, pero ilustra el principio. Puedes extender este patrón para crear sistemas de eventos más complejos y robustos, permitiendo que diferentes partes de tu aplicación reaccionen a los cambios de estado de manera desacoplada y eficiente.

Implementando el Patrón Specification en PHP con Doctrine Criteria

Implementando el Patrón Specification en PHP con Doctrine Criteria

El Patrón Specification es un patrón de diseño que encapsula la lógica de negocio necesaria para validar si un objeto satisface un criterio particular. Combinado con el poder de Doctrine Criteria, podemos crear consultas dinámicas y altamente mantenibles para nuestras entidades. Este enfoque separa la lógica de selección de datos del acceso a datos, mejorando la flexibilidad y la reutilización del código.


<?php

use Doctrine\ORM\Query\Expr\Comparison;
use Doctrine\ORM\QueryBuilder;

interface SpecificationInterface
{
    public function modifyQuery(QueryBuilder $qb, string $alias): void;
}

class AndSpecification implements SpecificationInterface
{
    private SpecificationInterface $left;
    private SpecificationInterface $right;

    public function __construct(SpecificationInterface $left, SpecificationInterface $right)
    {
        $this->left = $left;
        $this->right = $right;
    }

    public function modifyQuery(QueryBuilder $qb, string $alias): void
    {
        $this->left->modifyQuery($qb, $alias);
        $this->right->modifyQuery($qb, $alias);
    }
}

class IsActiveSpecification implements SpecificationInterface
{
    public function modifyQuery(QueryBuilder $qb, string $alias): void
    {
        $qb->andWhere($alias . '.isActive = :isActive')
           ->setParameter('isActive', true);
    }
}

class ProductNameContainsSpecification implements SpecificationInterface
{
    private string $searchTerm;

    public function __construct(string $searchTerm)
    {
        $this->searchTerm = $searchTerm;
    }

    public function modifyQuery(QueryBuilder $qb, string $alias): void
    {
        $qb->andWhere($qb->expr()->like($alias . '.name', ':searchTerm'))
           ->setParameter('searchTerm', '%' . $this->searchTerm . '%');
    }
}
    

En el ejemplo anterior, definimos una interfaz SpecificationInterface con un método modifyQuery que permite modificar un QueryBuilder de Doctrine. Implementamos especificaciones concretas como IsActiveSpecification para filtrar entidades activas y ProductNameContainsSpecification para buscar productos por nombre. También incluimos una especificación AndSpecification para combinar especificaciones. La clave está en usar el QueryBuilder de Doctrine para construir las consultas dinámicamente.


<?php

use Doctrine\ORM\EntityManagerInterface;

class ProductRepository
{
    private EntityManagerInterface $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function findBySpecification(SpecificationInterface $specification): array
    {
        $qb = $this->entityManager->createQueryBuilder();
        $qb->select('p')
           ->from('App\Entity\Product', 'p');

        $specification->modifyQuery($qb, 'p');

        return $qb->getQuery()->getResult();
    }
}

// Uso:
// $isActiveSpec = new IsActiveSpecification();
// $nameContainsSpec = new ProductNameContainsSpecification('Producto');
// $combinedSpec = new AndSpecification($isActiveSpec, $nameContainsSpec);

// $products = $productRepository->findBySpecification($combinedSpec);

    

Finalmente, en el repositorio, el método findBySpecification recibe una instancia de SpecificationInterface, crea un QueryBuilder, aplica la especificación y ejecuta la consulta. El código de uso muestra cómo combinar especificaciones para crear consultas complejas de forma concisa y mantenible. Este enfoque promueve la separación de preocupaciones, facilitando las pruebas unitarias y la evolución del sistema.

Implementación de Colas Prioritarias con SplPriorityQueue en PHP para Tareas Asíncronas

Implementación de Colas Prioritarias con SplPriorityQueue en PHP para Tareas Asíncronas

En el desarrollo de aplicaciones PHP complejas, la gestión eficiente de tareas asíncronas es crucial. Una forma poderosa de lograrlo es mediante el uso de colas prioritarias. PHP proporciona la clase SplPriorityQueue para implementar estas colas, permitiendo asignar prioridades a las tareas y ejecutarlas en el orden adecuado.

Este artículo explora cómo utilizar SplPriorityQueue para gestionar tareas asíncronas en un contexto PHP, proporcionando un ejemplo práctico y listo para usar.


<?php

/**
 * Clase para representar una tarea.
 */
class Tarea {
    public $id;
    public $descripcion;

    public function __construct($id, $descripcion) {
        $this->id = $id;
        $this->descripcion = $descripcion;
    }

    public function ejecutar() {
        // Simulación de la ejecución de la tarea.
        echo "Ejecutando tarea " . $this->id . ": " . $this->descripcion . PHP_EOL;
        sleep(1); // Simula un proceso que tarda un segundo.
    }
}

/**
 * Clase para gestionar la cola de prioridades de tareas.
 */
class GestorTareas {
    private $colaPrioridad;

    public function __construct() {
        $this->colaPrioridad = new SplPriorityQueue();
    }

    /**
     * Agrega una tarea a la cola con una prioridad específica.
     *
     * @param Tarea $tarea La tarea a agregar.
     * @param int $prioridad La prioridad de la tarea (mayor valor = mayor prioridad).
     */
    public function agregarTarea(Tarea $tarea, int $prioridad) {
        $this->colaPrioridad->insert($tarea, $prioridad);
    }

    /**
     * Ejecuta las tareas en la cola por orden de prioridad.
     */
    public function ejecutarTareas() {
        // SplPriorityQueue itera de menor a mayor prioridad, por lo que necesitamos extraer
        // en orden inverso. Esto lo hacemos creando una copia.
        $colaCopia = clone $this->colaPrioridad;
        $colaCopia->setExtractFlags(SplPriorityQueue::EXTR_DATA); // Extraer sólo los datos.
        $colaCopia->rewind(); // Regresar al inicio.

        while ($colaCopia->valid()) {
            $tarea = $colaCopia->extract();
            $tarea->ejecutar();
        }
    }
}

// Ejemplo de uso
$gestor = new GestorTareas();

$gestor->agregarTarea(new Tarea(1, "Enviar correo electrónico"), 2);
$gestor->agregarTarea(new Tarea(2, "Generar informe"), 1);
$gestor->agregarTarea(new Tarea(3, "Actualizar base de datos"), 3);

$gestor->ejecutarTareas();

// Salida esperada:
// Ejecutando tarea 3: Actualizar base de datos
// Ejecutando tarea 1: Enviar correo electrónico
// Ejecutando tarea 2: Generar informe

El código anterior define una clase `Tarea` simple y un `GestorTareas` que utiliza `SplPriorityQueue` para gestionar las tareas. La función `agregarTarea` inserta tareas en la cola, asignando una prioridad. La función `ejecutarTareas` extrae y ejecuta las tareas en el orden de prioridad definido. Es importante notar que `SplPriorityQueue` itera sobre las prioridades de menor a mayor, por lo que se clona la cola para invertir el orden de extracción.

Este patrón puede ser extendido para integrar con sistemas de colas de mensajes como RabbitMQ o Redis para una gestión más robusta y distribuida de tareas asíncronas. Además, se puede integrar con sistemas de logging para monitorear la ejecución de las tareas y detectar posibles errores.

Extracción Dinámica de Dependencias con ReflectionFunctionAbstract en PHP

Extracción Dinámica de Dependencias con ReflectionFunctionAbstract en PHP

La inyección de dependencias es un patrón de diseño fundamental en el desarrollo moderno de PHP. Si bien generalmente se maneja a través de contenedores de inyección de dependencias, a veces necesitamos una solución más ligera para analizar y resolver dependencias de funciones o métodos de manera dinámica, sin depender de un contenedor completo. ReflectionFunctionAbstract nos permite inspeccionar funciones y métodos y determinar sus dependencias de manera programática.

Este enfoque es especialmente útil en escenarios donde necesitas generar automáticamente la resolución de dependencias en tiempo de ejecución, como la creación de pipelines de procesamiento de datos o la implementación de middlewares personalizados.


<?php

/**
 * Función de utilidad para resolver dependencias dinámicamente.
 *
 * @param ReflectionFunctionAbstract $reflection La función o método a analizar.
 * @param array $providedDependencies Un array asociativo de dependencias ya resueltas (opcional).
 * @return array Un array de dependencias resueltas.
 * @throws ReflectionException Si una dependencia no se puede resolver.
 */
function resolveDependencies(ReflectionFunctionAbstract $reflection, array $providedDependencies = []): array
{
    $dependencies = [];

    foreach ($reflection->getParameters() as $parameter) {
        $name = $parameter->getName();
        $type = $parameter->getType();

        if ($type === null) {
            if(array_key_exists($name, $providedDependencies)) {
               $dependencies[] = $providedDependencies[$name];
               continue;
            }
            throw new ReflectionException("No se puede resolver la dependencia: {$name} (sin tipo especificado).");
        }

        $typeName = $type instanceof ReflectionNamedType ? $type->getName() : (string)$type; // PHP 7.1+
        // Simulación de la resolución de dependencias (reemplazar con la lógica real).
        if ($typeName === 'MyService') {
            // Aquí iría la lógica para obtener una instancia de MyService, por ejemplo, desde un contenedor DI.
            $dependencies[] = new MyService();
        } elseif (array_key_exists($typeName, $providedDependencies)) {
            $dependencies[] = $providedDependencies[$typeName];
        } else {
            throw new ReflectionException("No se puede resolver la dependencia: {$typeName}.");
        }
    }

    return $dependencies;
}

class MyService {
    public function doSomething() {
        return "Hice algo!";
    }
}

function myFunc(MyService $service, $parametroSinTipo) {
    return $service->doSomething() . " " . $parametroSinTipo;
}

$reflection = new ReflectionFunction('myFunc');

try {
    $resolvedDependencies = resolveDependencies($reflection, ['parametroSinTipo' => 'Valor del parámetro']);
    $result = $reflection->invokeArgs($resolvedDependencies);
    echo $result; // Imprime "Hice algo! Valor del parámetro"
} catch (ReflectionException $e) {
    echo "Error: " . $e->getMessage();
}

El código anterior define una función resolveDependencies que, dado un objeto ReflectionFunctionAbstract, analiza los parámetros de la función o método. Intenta resolver cada parámetro basándose en su tipo declarado. En el ejemplo, simulamos la resolución de la dependencia MyService. En una aplicación real, esto implicaría obtener una instancia del servicio desde un contenedor de inyección de dependencias o a través de alguna otra lógica de fábrica. El parámetro sin tipo explícito se resuelve mediante el array de dependencias provistas.

La función myFunc demuestra cómo se puede utilizar esta técnica para inyectar dependencias resueltas dinámicamente. La invocación de $reflection->invokeArgs() ejecuta la función con las dependencias inyectadas.

En resumen, ReflectionFunctionAbstract ofrece una forma poderosa de analizar y resolver dependencias de funciones y métodos en tiempo de ejecución, permitiendo soluciones flexibles y dinámicas para la inyección de dependencias en PHP.

Manipulación Avanzada de Streams con Wrappers Personalizados en PHP

Manipulación Avanzada de Streams con Wrappers Personalizados en PHP

En PHP, los streams proveen una forma consistente de interactuar con datos, ya sea desde un archivo, una red, o incluso una cadena de texto. Pero la verdadera potencia de los streams se revela cuando creamos wrappers personalizados. Estos wrappers nos permiten interceptar y modificar el flujo de datos, habilitando funcionalidades complejas como la encriptación/desencriptación transparente, la compresión/descompresión sobre la marcha, o el filtrado de datos en tiempo real.


<?php

class Rot13Stream {

    private $fp;
    private $mode;

    public function stream_open(string $path, string $mode, int $options, string &$opened_path): bool
    {
        $url = parse_url($path);
        $this->fp = fopen($url["scheme"], $mode);
        $this->mode = $mode;

        if (!$this->fp) {
            return false;
        }

        return true;
    }

    public function stream_read(int $count): string
    {
        $data = fread($this->fp, $count);
        return str_rot13($data); // Encripta con ROT13 al leer.
    }

    public function stream_write(string $data): int
    {
        $data_encoded = str_rot13($data); // Encripta con ROT13 al escribir.
        return fwrite($this->fp, $data_encoded);
    }

    public function stream_close(): void
    {
        fclose($this->fp);
    }

    public function stream_eof(): bool
    {
        return feof($this->fp);
    }
}

stream_wrapper_register("rot13", "Rot13Stream");

// Ejemplo de uso:
$path = "rot13://data.txt"; // data.txt existe y tiene contenido en texto plano.

// Escribir datos encriptados con ROT13
file_put_contents($path, "Este es un mensaje secreto!");

// Leer datos desencriptados con ROT13
$secretMessage = file_get_contents($path);
echo "Mensaje secreto: " . $secretMessage . PHP_EOL; // Imprimirá "Rgr vf ha zrafntr frperg!"

?>
    

El ejemplo anterior demuestra la creación de un wrapper que aplica la codificación ROT13 a los datos que se leen o escriben. La función `stream_wrapper_register` registra el wrapper, asociando el protocolo "rot13" con la clase `Rot13Stream`. Luego, al usar `rot13://data.txt`, PHP utilizará nuestro wrapper personalizado para manipular los datos, encriptándolos al escribir y desencriptándolos al leer. Es crucial implementar correctamente las funciones `stream_open`, `stream_read`, `stream_write`, `stream_close` y `stream_eof` para que el wrapper funcione correctamente.

Este tipo de wrappers pueden ser extendidos para implementar algoritmos de encriptación más robustos, compresión con gzip o bzip2, o incluso para acceder a APIs web de manera transparente, convirtiendo una solicitud HTTP en una operación de lectura/escritura.


<?php

// Ejemplo de un wrapper que simplemente registra la actividad
class LoggingStream {
    private $fp;

    public function stream_open(string $path, string $mode, int $options, string &$opened_path): bool {
        $this->fp = fopen(substr($path, 9), $mode); // Quitamos el prefijo "log://"
        error_log("Stream abierto: " . $path . " en modo " . $mode);
        return (bool) $this->fp;
    }

    public function stream_read(int $count): string {
        $data = fread($this->fp, $count);
        error_log("Stream leido: " . strlen($data) . " bytes");
        return $data;
    }

    public function stream_write(string $data): int {
        $bytes = fwrite($this->fp, $data);
        error_log("Stream escrito: " . $bytes . " bytes");
        return $bytes;
    }

    public function stream_close(): void {
        fclose($this->fp);
        error_log("Stream cerrado.");
    }
}

stream_wrapper_register("log", "LoggingStream");

// Usar el wrapper de logging:
$file = "log://realfile.txt";
$handle = fopen($file, 'w');
fwrite($handle, "Esto sera registrado.");
fclose($handle);

?>
    

En resumen, los wrappers personalizados de streams en PHP ofrecen una manera poderosa y flexible de manipular el flujo de datos. Permiten abstraer la complejidad de operaciones de E/S y aplicar transformaciones personalizadas de manera transparente. Su uso adecuado puede simplificar el código, mejorar la seguridad y extender las capacidades de la aplicación.