API de Flujos para Autores de Extensiones de PHP

Nota:

Las funciones de este capítulo son para usarlas en el código fuente de PHP y no son funciones de PHP. Se puede encontrar información sobre las funciones de flujos del entorno de usuario en la Referencia de Flujos.

Visión General

La API de Flujos de PHP introduce una aproximación unificada para el manejo de archivos y sockets de extensiones de PHP. Usando una API única con funciones estándar para operaciones comunes, la API de flujos permite a sus extensiones acceder a archivos, sockets, URLs, memoria y objetos definidos en scripts. Streams (Flujos) es una API extensible en tiempo de ejecución que permite la carga dinámica de módulos (¡y scripts!) para registrar nuevos flujos.

El objetivo de la API de Flujos es hacerla confortable para desarrolladores para abrir ficheros, URLs y otras fuentes de datos que se pueden utilizar como flujos con una API unificada que es fácil de entender. La API está más o menos basada en la familia de funciones stdio de ANSI C (con semántica idéntica para la mayoría de las funciones principales), por lo que los programadores de C estarán familiarizados con los flujos.

La API de flujos opera en dos niveles diferentes: en el nivel básico, la API define objetos php_stream para representar fuentes de datos que se pueden usar como flujos. En un nivel ligeramente superior, la API define objetos php_stream_wrapper que "envuelven" al nivel inferior de la API para proporcionar soporte para recuperar información y meta-información desde URLs. Un parámetro adicional, context, aceptado por la mayoría de las funciones de creación de flujos, es pasado al método stream_opener de la envoltura para mejorar el comportamiento de la envoltura.

Un flujo, una vez abierto, también puede tener cualquier número de filtros aplicados a él, que procesan la información que es leída desde/escrita en el flujo.

Los flujos pueden ser convertidos (cast) en otros tipos de gestores de archivos, por lo que se pueden usar con bibliotecas de terceros sin grandes dificultades. Esto permite a estas bibliotecas acceder a la información directamente desde fuentes de URL. Si su sistema tiene la función fopencookie() o funopen(), ¡puede incluso pasar cualquier flujo de PHP a cualquier biblioteca de use stdio de ANSI!

Flujos: lo básico

Usar flujos es muy parecido a usar las funciones de stdio de ANSI. La principal diferencia está en la manera en de obtiener el gestor de flujo para empezar con él. En la mayoría de los casos se usará php_stream_open_wrapper() para obtener el gestor de flujo. Esta función trabaja de manera similar a fopen, como se puede ver en el ejemplo de abajo:

Ejemplo #1 ejemplo sencillo de flujo que muestra la página de inicio de PHP

php_stream * stream = php_stream_open_wrapper("http://www.php.net", "rb", REPORT_ERRORS, NULL);
if (stream) {
    while(!php_stream_eof(stream)) {
        char buf[1024];
        
        if (php_stream_gets(stream, buf, sizeof(buf))) {
            printf(buf);
        } else {
            break;
        }
    }
    php_stream_close(stream);
}

La tabla de abajo muestra la equivalencia entre Flujos y las funciones stdio de ANSI más comunes. A menos que se anote lo contrario, la semántica de las funciones son idénticas.

Funciones stdio de ANSI equivalentes en la API de Flujos
Función Stdio de ANSI Función de Flujos de PHP Notas
fopen php_stream_open_wrapper Los Flujos incluyen parámetros adicionales
fclose php_stream_close  
fgets php_stream_gets  
fread php_stream_read El parámetro nmemb se asume que tiene un valor de 1, por lo que el prototipo se parece más a read(2)
fwrite php_stream_write El parámetro nmemb se asume que tiene un valor de 1, por lo que el prototipo se parece más a write(2)
fseek php_stream_seek  
ftell php_stream_tell  
rewind php_stream_rewind  
feof php_stream_eof  
fgetc php_stream_getc  
fputc php_stream_putc  
fflush php_stream_flush  
puts php_stream_puts La misma semántica que puts, NO fputs
fstat php_stream_stat Los flujos tienen una estructura de estadísticas más abundante

Flujos como Recursos

Todos los flujos están registrados como recursos cuando son creados. Esto asegura que serán limpiados de manera apropiada incluso si hubiera un error fatal. Todas las funciones de sistema de archivos de PHP operan sobre recursos de flujos - esto significa que sus extensiones pueden aceptar punteros a archivos de PHP habituales como parámetros, y devovler flujos desde sus funciones. La API de flujos realiza este proceso de la forma más sencilla posible:

Ejemplo #2 Cómo aceptar un flujo como parámetro

PHP_FUNCTION(example_write_hello)
{
    zval *zstream;
    php_stream *stream;
    
    if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream))
        return;
    
    php_stream_from_zval(stream, &zstream);

    /* ahora se puede usar el flujo. Sin embargo, usted no es el "propietario" del
        flujo; lo es el script. Esto significa que NO DEBE cerrar el
        flujo, ¡ya que causaría que PHP se colgara! */

    php_stream_write(stream, "hello\n");
        
    RETURN_TRUE();
}

Ejemplo #3 Cómo devolver un flujo desde una función

PHP_FUNCTION(example_open_php_home_page)
{
    php_stream *stream;
    
    stream = php_stream_open_wrapper("http://www.php.net", "rb", REPORT_ERRORS, NULL);
    
    php_stream_to_zval(stream, return_value);

    /* después de este punto, el flujo es "propiedad" del script.
        ¡Si lo cierra ahora PHP se colgará! */
}

Ya que los flujos se limpian automáticamente, es tentador pensar que podemos zafarnos y ser unos programadores descuidados y no molestarnos en cerrar los flujos cuando hayamos terminado con ellos. Aunque tal enfoque podría funcionar, no es una buena idea por varias razones: los flujos permanecen bloqueados en los recursos del sistema mientras están abiertos, por lo que dejar un archivo abierto después de que se haya terminado con él podría impedir que otros procesos accedan al mismo. Si un script trata con un gran número de archivos, la acumulación de recursos usados, tanto en términos de memoria como en el número de archivos abiertos, puede causar que las peticiones al servidor web fallen. Suena mal, ¿verdad? La API de flujos incluye algo de magia que le puede ayudar a mantener su código limpio - si un flujo es cerrado por su código cuando debería hacerlo, puede encontrar alguna información de depuración útil en el registro de errores de su servidor web.

Nota: Use siempre uan construcción de depuración de PHP al desarrollar una extensión (--enable-debug cuando se ejecute configure), ya que se ha realizado un mucho esfuerzo para advertirle de filtraciones de memoria y flujos.

En algunos casos es útil mantener un flujo abierto durante una petición, para actuar como un archivo de registro o de seguimiento, por ejemplo. Escribir código para limpiar de forma segura un flujo no es difícil, pero requiere varias líneas de código que no son estrictamente necesarias. Para ahorrarse el problema de escribir el código puede marcar un flujo como OK para su autolimpieza. Esto significa que la API de flujos no emitirá una advertencia cuando es hora de autolimpiar un flujo. Para hacer esto puede usar php_stream_auto_cleanup().

Opciones de apertura de flujos

Estas constantes afectan a la operación de funciones de flujos de fábrica.

IGNORE_PATH
Esta es la opción predeterminada para los flujos; solicita que no se va a buscar en include_path el archivo pedido.
USE_PATH
Solicita que el archivo pedido se va a buscar en include_path.
IGNORE_URL
Solicita que las envolturas de URL registradas van a ser ignoradas cuando se abra el flujo. Las envolturas distintas de URL se tomarán en consideración cuando se decodifique la ruta. No hay una bandera opuesta a esta; la API de flujos usará todas las envolturas registradas por omisión.
IGNORE_URL_WIN
Es sistemas Windows esto es equivalente a IGNORE_URL. En los demás sistemas esta bandera no tiene efecto.
ENFORCE_SAFE_MODE
Solicita que la implementación de flujo subyacente realice una comprobación de safe_mode en el archivo antes de abrirlo. Si se omite esta bandera se saltará la comprobación de safe_mode y permitirá la apertura de cualquier archivo que el proceso de PHP tenga derecho a acceder.
REPORT_ERRORS
Si esta bandera está establecida y hubo un error durante la apertura del archivo o URL, la API de flujos llamará a la función php_error por usted. Esto es útil porque la ruta puede contener información sobre nombre de usuario/contraseña que no debería ser mostrada en la salida del navegador (sería un riesgo de seguridad hacerlo). Cuando la API de flujos provoca el error, primero elimina la información sobre el nombre de usuario/contraseña de la ruta, haciendo que el mensaje de error sea seguro para mostrarlo en el navegador.
STREAM_MUST_SEEK
Esta bandera es útil cuando su extensión realmente debe ser capaz de buscar aleatoriamente en un flujo. Algunos flujos pueden no ser "buscables" en su forma nativa, por lo que esta bandera pregunta a la API de flujos para verificar si el flujo soporta la búsqueda. Si no la soporta, copiará el flujo a un lugar temporalmente (que puede ser un archivo temporal o un flujo de memoria) que soporta búsquedas. Por favor, observe que esta bandera no es útil cuando se quiere buscar el flujo y escribir en él, ya que el flujo al que se está accediendo no debería estar limitado al recurso real que se registró.

Nota: Si el recurso solicitado está basado en una red, esta bandera causará que el abridor bloquee hasta que el contenido completo haya sido descargado.

STREAM_WILL_CAST
Si su extensión está usando una biblioteca de terceros que supone un FILE* o descriptor de archivo, se puede usar esta bandera para solicitar a la API de flujos que abra el recurso pero que evite almacenarlo en el buffer. Entonces se puede usar php_stream_cast() para recuperar el FILE* o descriptor de archivo que la biblioteca necesita. Esto es particularmente útil cuando se accede a URLs de HTTP donde el inicio de la información del flujo real se encuentra despuiés de un índice indeterminado dentro del flujo. Ya que esta opción deshabilita el uso del buffer al nivel de la API de flujos, se puede experimentar una bajada de rendimiento cuando se usan funciones de flujos sobre el flujo; esto es aceptable ya que se le tiene que indicar a los flujos que se usarán las funciones para comparar al implementación del flujo subyacente. Sólo use esta opción cuando esté seguro de que la necesita.