Uno de los puntos importantes de cara a prepararse la certificación de PHP es la comprensión de lo que significa un stream y lo que le rodea. A muchos nos suenan, sabemos más o menos lo que son pero no acabamos de entenderlos al 100%. Este post pretende ser una introducción de conceptos clave necesarios para entender los streams, vamos a ello.

¿Qué son los streams?

Los streams son una característica introducida en PHP desde la versión 4.3, y pretenden ser una solución para facilitarnos la vida a la hora de trabajar con diferentes recursos como ficheros, sockets… Con ellos tenemos un conjunto de funciones comunes que podemos usar de la misma manera con cada uno de ellos, de manera que, por ejemplo, leer el contenido de un archivo local será lo mismo que leer el contenido de un archivo que se encuentra en un servidor remoto, para nosotros será transparente el como lo hace, lo importante es que para nosotros será igual. No obstante, si queremos ser más formales, los streams son el modo en que PHP maneja el acceso a archivos y servicios o recursos de red.

¿Cómo hacemos referencia a un stream?

La menera que tenemos de hacer referencia a un stream será: scheme://target,donde scheme es el nombre del wrapper que vamos a usar (luego veremos que es un wrapper) y target es el destino donde se encuentra el recurso al que queremos acceder, por ejemplo:

echo file_get_contents("http://www.php.net");
echo file_get_contents("file:///home/...");

En este par de ejemplos vemos como accedemos al contenido de la web php.net de la misma manera a la que podemos acceder a un archivo de nuestro sistema local. Podéis consultar el listado de los protocolos y wrappers soportados aquí.

Streams Contexts

Con las peticiones que hemos visto en los ejemplos del punto anterior, simplemente podemos hacer una petición al recurso que queremos y recuperarlo, pero también tenemos la opción de enviar más información o modificar el comportamiento de esa petición del recurso, para ello usaremos los streams contexts.

Vamos a ver de nuevo un ejemplo en que modificaremos los headers que le enviaremos a la petición.

$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar\r\n"
)
);

$context = stream_context_create($opts);

echo file_get_contents("http://www.php.net", false, $context);

Como podemos ver en el ejemplo, se trata de la misma llamada pero pasándole unos headers adicionales. La clave aquí está en la función stream_context_create() que será la encargada de crear el contexto que le vamos a pasar a la llamada al stream.

Podéis consultar el listado de opciones que se pueden usar para crear un contexto aquí.

Stream metadata

Trabajando con streams también nos podemos encontrar con la necesidad de recuperar algo de información extra relacionada con nuestra petición más allá del retorno de esta propiamente dicho. Con la función stream_get_meta_data() seremos capaces de esto. Esta función nos devolverá los headers devueltos por la petición así como otra información de interés como puede ser el tipo de wrapper que hemos usado, si la petición ha dado un timeout… Vamos a variar el ejemplo anterior con el uso de esta función:

$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar\r\n"
)
);

$context = stream_context_create($opts);

$fp = fopen("http://www.php.net", 'r', false, $context);

print_r(stream_get_meta_data($fp));

Como podéis observar la función stream_get_meta_data espera un recurso como parámetro con lo que tenemos que crear el strem con la función fopen. La salida de este código debería de ser algo como:


Array
(
[wrapper_data] => Array
(
[0] => HTTP/1.1 200 OK
[1] => Date: Thu, 01 Dec 2011 19:41:57 GMT
[2] => Server: Apache/1.3.41 (Unix)
[3] => X-Powered-By: PHP/5.2.17
[4] => Content-language: en
[5] => Set-Cookie: COUNTRY=ESP%2C85.52.24.60; expires=Thu, 08-Dec-2011 19:41:57 GMT; path=/; domain=.php.net
[6] => Last-Modified: Thu, 01 Dec 2011 22:40:23 GMT
[7] => Connection: close
[8] => Content-Type: text/html;charset=utf-8
)
[wrapper_type] => http
[stream_type] => tcp_socket/ssl
[mode] => r
[unread_bytes] => 1096
[seekable] =>
[uri] => http://www.php.net
[timed_out] =>
[blocked] => 1
[eof] =>
)

Stream filters

Los streams filters son operaciones que las podemos ejecutar contra los datos que hemos leído de la petición de lectura que hemos realizado o bien operaciones contra los datos que vamos a escribir en la petición de escritura que vamos a realizar.

Podemos usar filtros ya existentes (podéis obtener los filtros que ya existen con la función stream_get_filters()) o incluso podemos crear un filtro a nuestra medida usando la funciónstream_filter_register()

Vamos a a ampliar un poco más nuestro ejemplo:

$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar\r\n"
)
);

$context = stream_context_create($opts);

$fp = fopen("http://www.php.net", 'r', false, $context);
<code>stream_filter_append($fp, "string.rot13", STREAM_FILTER_READ);</code>
echo stream_get_contents($fp);

En este ejemplo aplicamos el filtro “string.rot13″ (desplaza cada letra 13 posiciones en el abecedario) al contenido que recuperamos, si queréis, ejecutad el código y le echais un ojo al resultado, es curioso. Por otro lado, si nos fijamos en el tercer parámetro de la función stream_filter_append  podemos ver como he puesto en el ejemplo que filtre en modo lectura el recurso. También se podría especificar que fuera en modo escritura o ambos.

Wrappers

Los wrappers son un punto fundamental en nuestra batalla contra los streams. Ellos son los responsables de que para nosotros sea transparente si estamos accediendo al sistema de archivos local, o bien estamos accediendo a un ftp. Nosotros llamaremos a las mismas funciones tanto para una cosa como para la otra, la implementación que hay tras cada uno de los wrappers nos permite que eso sea posible.

Una cosa muy importante que tiene que quedar clara es que siempre que accedamos a un recurso estaremos usando un wrapper. La gran mayoría de wrappers vienen ya creados por defecto (aquí tenéis la lista de los disponibles), pero una cosa muy útil es que podemos crear nuestros propios wrappers para acceder o realizar las tareas que nosotros queramos que realicen.

Para poder implementar nuestro propio wrapper deberemos crear una clase que implemente diferentes métodos que serán a los que acceda el stream para realizar las operaciones de lectura, escritura…

Una vez creada la clase, para definir este nuevo wrapper usaremos la función stream_wrapper_register, en la que definiremos el scheme  y la clase que definirá su comportamiento.


stream_wrapper_register("var","myWrapper");

$fp = fopen("var://mivar", "r");

En este ejemplo podemos observar como se define el nuevo wrapper y como le hacemos una petición.

A continuación vemos como definir la clase que definirá su comportamiento:


class myWrapper {

function stream_open($ruta, $modo, $opciones, &$ruta_abierta)
{

...

}

function stream_write($data)
{

...

}

...

}

En el ejemplo podemos observar como en esta clase se definen diferentes funciones que serán llamadas cada vez que se realice alguna acción contra el recurso. Por ejemplo, cuando se crea un recurso (con fopen por ejemplo) se llamará a la función stream_open, cuando se haga un fseek se llamará stream_seek ...

Podéis revisar todos los métodos que se pueden/tienen que implementar en la clase en este enlace.

Toda la información necesaria y los ejemplos de este artículo los podéis encontrar en php.net

Ilustración extraída de la Zend PHP Certification Guide.

 

Leave a reply

 

Your email address will not be published.