"for use with all the other filesystem functions"
"all" is not accurate. Unfortunately, zip_open(), and presumably others, will ignore your custom stream wrapper.
stream_wrapper_register
(PHP 4 >= 4.3.2, PHP 5)
stream_wrapper_register — Enregistre une enveloppe URL, implémentée comme une classe PHP
Description
stream_wrapper_register() permet d'implémenter des gestionnaires de protocole et de flux, à utiliser avec toutes les autres fonctions de fichiers, comme fopen(), fread() etc.
Pour implémenter une enveloppe, il faut définir une classe avec la liste des membres définie ci-dessous. Lorsque quelqu'un ouvre le flux, PHP va créer une instance de la classe classname et appeler les méthodes de cette instance. Il faut implémenter ces méthodes exactement comme décrit ci-dessous : sinon, les comportements seront indéfinis.
Note: Depuis PHP 5.0.0, l'instance de la classe classname contiendra une propriété context , faisant référence à la ressource de contexte qui peut être accédée avec la fonction stream_context_get_options(). Si aucun contexte n'est passé à la fonction de création du flux, context sera défini à NULL.
stream_wrapper_register() retourne FALSE si le protocole protocol a déjà un gestionnaire attitré.
Cette méthode est appelée immédiatement après la création de votre flux. path spécifie l'URL qui doit être passée à la fonction fopen() et ce que cet objet est supposé y lire. Vous pouvez utiliser parse_url() pour l'analyser.
mode est le mode d'ouverture du fichier, comme expliqué dans fopen(). Vous êtes responsable de la vérification de la validité du paramètre mode avec le chemin path fourni.
options contient des options supplémentaires, utilisées par les API de flux. Il peut contenir une ou plusieurs des options suivantes, combinées avec l'opérateur OR.
| Option | Description |
|---|---|
| STREAM_USE_PATH | Si path est relatif, recherche la ressources en utilisant la configuration de l'include_path. |
| STREAM_REPORT_ERRORS | Si cette option est activée, la classe est responsable de la génération d'erreurs avec trigger_error() durant l'ouverture du flux. Si cette option n'est pas activée, il ne faut générer aucune erreur. |
Si le paramètre path est ouvert avec succès, et que STREAM_USE_PATH est activé dans le paramètre options , il faut affecter à opened_path le chemin complet de la ressource ou du fichier qui a été réellement ouvert.
Si la ressource demandée a été ouverte, il faut retourner TRUE, ou, sinon, FALSE
Cette méthode est appelée lorsque le flux est fermé, grâce à la fonction fclose(). Vous devez libérer toutes les ressources verrouillées ou réservées par le flux.
Cette méthode est appelée suite à l'utilisation des fonctions fread() et fgets(). Vous devez lire jusqu'à count octets de données à partir de la position courante d'écriture ou de lecture, sous la forme de chaîne de caractères. S'il y a moins que count octets disponibles, vous devez en retourner autant que possible. Si aucune autre donnée n'est disponible, FALSE sera retourné ou une chaîne vide. Vous devez aussi tenir à jour la position du pointeur d'écriture/lecture dans le flux, en ajoutant ou retranchant le nombre d'octets lus.
Cette méthode est appelée lorsque la fonction fwrite() est utilisée. Vous devez stocker les données data dans le flux. S'il n'y a pas assez de place, essayez d'en stocker le maximum. Vous devriez aussi retourner le nombre d'octets que vous avez réussi à écrire, ou bien 0 si aucun n'a pu être stocké. Vous devez aussi tenir à jour la position du pointeur d'écriture/lecture dans le flux, en ajoutant ou retranchant le nombre d'octets écrits.
Cette méthode est appelée lorsque la fonction feof() est utilisée. Vous devez retourner TRUE si la position de lecture se situe à la fin du fichier et s'il n'y a plus de données disponibles pour la lecture, ou bien FALSE sinon.
Cette méthode est appelée lorsque la fonction ftell() est utilisée. Vous devez retourner la position actuelle du pointeur de lecture/écriture dans le flux.
Cette méthode est appelée lorsque la fonction fseek() est utilisée. Vous devez modifier la position du pointeur de lecture/écriture en fonction des paramètres d'offset offset et de direction whence . Reportez-vous à la fonction fseek() pour plus de détails sur ces paramètres. Retournez TRUE si la position a été modifiée, et FALSE sinon.
Cette méthode est appelée lorsque la fonction fflush() est utilisée. Si vous avez mis des données dans un système de cache pour votre flux, mais qu'elles ne sont pas encore stockées de manière pérenne, c'est le moment de le faire. Retournez TRUE si les données cachées ont pu être stockées avec succès (il n'y a plus de données à stocker), ou bien FALSE si les données n'ont pu être stockées.
Cette méthode est appelée en réponse à la fonction fstat() sur un flux, et retourne un tableau, avec les valeurs appropriées pour le flux.
Cette méthode est appelée en réponse à la fonction unlink() avec une URL, et doit tenter de supprimer l'objet identifié par path . La fonction doit retourner TRUE en cas de succès, et FALSE en cas d'échec. Afin de retourner le bon message d'erreur, ne définissez pas cette méthode si votre gestionnaire ne le supporte pas.
Note: La méthode enveloppe d'utilisateur unlink n'est pas supportée avant PHP 5.0.0.
Cette méthode est appelée en réponse à la fonction rename() avec une URL associée avec l'enveloppe et doit tenter de renommer l'objet identifié par path_from en path_to . Cela doit retourner TRUE en cas de succès ou FALSE en cas d'erreur. Afin de pouvoir retourner le message d'erreur approprié, ne définissez pas cette méthode si votre enveloppe ne supporte pas d'être renommé.
Note: La méthode enveloppe d'utilisateur rename n'est pas supportée avant PHP 5.0.0.
Cette méthode est appelée en réponse à la fonction mkdir() avec une URL associée avec l'enveloppe et doit tenter de créer le répertoire spécifié par path . Cela doit retourner TRUE en cas de succès ou FALSE en cas d'erreur. Afin de pouvoir retourner le message d'erreur approprié, ne définissez pas cette méthode si votre enveloppe ne supporte pas la création de répertoire. Les valeurs possibles pour le paramètre options sont STREAM_REPORT_ERRORS et STREAM_MKDIR_RECURSIVE.
Note: La méthode enveloppe d'utilisateur mkdir n'est pas supportée avant PHP 5.0.0.
Cette méthode est appelée en réponse à la fonction rmdir() avec une URL associée avec l'enveloppe et doit tenter de supprimer le répertoire spécifié par path . Cela doit retourner TRUE en cas de succès ou FALSE en cas d'erreur. Afin de pouvoir retourner le message d'erreur approprié, ne définissez pas cette méthode si votre enveloppe ne supporte pas la suppression de répertoire. La valeur possible pour le paramètre options est STREAM_REPORT_ERRORS.
Note: La méthode enveloppe d'utilisateur rmdir n'est pas supportée avant PHP 5.0.0.
Cette méthode est appelée immédiatement lorsque votre flux est créé, pour examiner le contenu d'un dossier avec opendir(). path spécifie l'URL qui est passée à opendir() et que cet objet doit explorer. Vous pouvez utiliser parse_url() pour la scinder en morceaux.
Cette méthode est appelée en réponse à la fonction stat() avec une URL associée avec l'enveloppe et doit retourner autant d'éléments en commun avec la fonction système que possible. Les valeurs non connues ou non disponibles doivent être définies en une valeur rationnelle (habituellement, 0).
flags représente les options additionnelles définies par l'API. Il peut prendre une ou plusieurs des valeurs suivantes séparées par OR.
| Option | Description |
|---|---|
| STREAM_URL_STAT_LINK | Pour les ressources avec la possibilité de relier d'autres ressources (comme par exemple HTTP Location: forward ou un lien symbolique d'un système de fichiers). Cette option spécifie que seul les informations à propos du lien lui-même peuvent être retournées, et non pas les ressources pointées par le lien. Cette option est définie en réponse à l'appel à la fonction lstat(), is_link(), ou filetype(). |
| STREAM_URL_STAT_QUIET | Si cette option est définie, votre enveloppe ne devra contenir aucune erreur. Si cette option n'est pas définie, vous êtes responsable du traitement des erreurs en utilisant la fonction trigger_error() pendant l'évaluation du chemin. |
Cette méthode est appelée en réponse à la fonction readdir() et doit retourner une chaîne représentant le prochain fichier ouvert par dir_opendir().
Cette méthode est appelée en réponse à rewinddir() et doit remettre à zéro les résultats de dir_readdir(). i.e. : le prochain appel à dir_readdir() doit retourner la première ligne située dans le dossier ouvert par dir_opendir().
Cette méthode est appelée en réponse à closedir(). Vous devez libérer toutes les ressources qui ont été réservées durant l'ouverture et l'utilisation du dossier.
L'exemple ci-dessous implémente un gestionnaire de protocole pour le protocole var://, qui permet l'écriture et la lecture de variables globales en utilisant un flux de fichier standard, et les fonctions classiques telles que fread(). Le protocole var:// implémenté ci-dessous, étant donné l'URL var://foo va écrire ou lire les données dans $GLOBALS["foo"].
Exemple #1 Une classe de flux pour accéder aux variables globales
<?php
class VariableStream {
var $position;
var $varname;
function stream_open($path, $mode, $options, &$opened_path)
{
$url = parse_url($path);
$this->varname = $url["host"];
$this->position = 0;
return true;
}
function stream_read($count)
{
$ret = substr($GLOBALS[$this->varname], $this->position, $count);
$this->position += strlen($ret);
return $ret;
}
function stream_write($data)
{
$left = substr($GLOBALS[$this->varname], 0, $this->position);
$right = substr($GLOBALS[$this->varname], $this->position + strlen($data));
$GLOBALS[$this->varname] = $left . $data . $right;
$this->position += strlen($data);
return strlen($data);
}
function stream_tell()
{
return $this->position;
}
function stream_eof()
{
return $this->position >= strlen($GLOBALS[$this->varname]);
}
function stream_seek($offset, $whence)
{
switch($whence) {
case SEEK_SET:
if ($offset < strlen($GLOBALS[$this->varname]) && $offset >= 0) {
$this->position = $offset;
return true;
} else {
return false;
}
break;
case SEEK_CUR:
if ($offset >= 0) {
$this->position += $offset;
return true;
} else {
return false;
}
break;
case SEEK_END:
if (strlen($GLOBALS[$this->varname]) + $offset >= 0) {
$this->position = strlen($GLOBALS[$this->varname]) + $offset;
return true;
} else {
return false;
}
break;
default:
return false;
}
}
}
stream_wrapper_register("var", "VariableStream")
or die("Failed to register protocol");
$myvar = "";
$fp = fopen("var://myvar", "r+");
fwrite($fp, "line1\n");
fwrite($fp, "line2\n");
fwrite($fp, "line3\n");
rewind($fp);
while(!feof($fp)) {
echo fgets($fp);
}
fclose($fp);
var_dump($myvar);
?>
stream_wrapper_register
16-Sep-2008 08:19
17-Apr-2008 10:38
Actually, I don't know if there's a better way to figure out if stream_read() was called by fgets() or e.g. fread() than this one:
public function stream_read($count)
{
$bt = debug_backtrace();
if(($bt[0]['function'] == 'stream_read') &&
($bt[1]['function'] == 'fgets'))
{
$pos = strpos($GLOBALS[$this->varname], "\n", $this->position);
$retval = substr($GLOBALS[$this->varname], $this->position, ($pos > $count) ? $count : $pos+1);
}
else
{
$retval = substr($GLOBALS[$this->varname], $this->position, $count);
}
$this->position += strlen($retval);
return (string)$retval;
}
20-Dec-2007 12:47
I have created a POP3 stream wrapper, available at http://www.potato-people.com/code/pop3.stream/
15-Oct-2007 04:30
on using dir_opendir on PHP5 make sure you not return a resource object on success. A resource object is diferent from false but php make a cast to bool to dir_opendir return value and modify the value of your resource to 1.
example:
class myclass{
private $mysqlHandler;
public function dir_opendir(....)
{
$this->mysqlHandler = mysql_connect(....);
return $this->mysqlHandler; //this is wrong, next
//time you use
//$this->mysqlHandler
// the value is 1
}
}
24-Sep-2007 06:34
I have written a new SMB Stream Wrapper for Unix systems using smbclient (SAMBA project).
http://freshmeat.net/smb4php
13-Aug-2007 02:59
Updated. I figured there's no need to store the variable name if we're dereferenceing; we can just store the pointer and not have to dereference in each function for brevity.
Also, I added the assertion that the stream is a string, since we're behaving basically like it has to be, and I changed the name to GlobalStream and global://, as that's a more descriptive moniker than VariableName/var://.
<?php
class GlobalStream {
private $pos;
private $stream;
public function stream_open($path, $mode, $options, &$opened_path) {
$url = parse_url($path);
$this->stream = &$GLOBALS[$url["host"]];
$this->pos = 0;
if (!is_string($this->stream)) return false;
return true;
}
public function stream_read($count) {
$p=&$this->pos;
$ret = substr($this->stream, $this->pos, $count);
$this->pos += strlen($ret);
return $ret;
}
public function stream_write($data){
$l=strlen($data);
$this->stream =
substr($this->stream, 0, $this->pos) .
$data .
substr($this->stream, $this->pos += $l);
return $l;
}
public function stream_tell() {
return $this->pos;
}
public function stream_eof() {
return $this->pos >= strlen($this->stream);
}
public function stream_seek($offset, $whence) {
$l=strlen($this->stream);
switch ($whence) {
case SEEK_SET: $newPos = $offset; break;
case SEEK_CUR: $newPos = $this->pos + $offset; break;
case SEEK_END: $newPos = $l + $offset; break;
default: return false;
}
$ret = ($newPos >=0 && $newPos <=$l);
if ($ret) $this->pos=$newPos;
return $ret;
}
}
stream_wrapper_register('global', 'GlobalStream') or die('Failed to register protocol global://');
$myvar = "";
$fp = fopen("global://myvar", "r+");
fwrite($fp, "line1\n");
fwrite($fp, "line2\n");
fwrite($fp, "line3\n");
rewind($fp);
while (!feof($fp)) {
echo fgets($fp);
}
fclose($fp);
var_dump($myvar);
?>
13-Aug-2007 02:32
Updated for PHP 5, and optimized for readability, low line count, and minimum memory use:
<?php
class VariableStream {
private $position;
private $varname;
public function stream_open($path, $mode, $options, &$opened_path) {
$url = parse_url($path);
$this->varname = $url["host"];
$this->position = 0;
return true;
}
public function stream_read($count) {
$p=&$this->position;
$ret = substr($GLOBALS[$this->varname], $p, $count);
$p += strlen($ret);
return $ret;
}
public function stream_write($data){
$v=&$GLOBALS[$this->varname];
$l=strlen($data);
$p=&$this->position;
$v = substr($v, 0, $p) . $data . substr($v, $p += $l);
return $l;
}
public function stream_tell() {
return $this->position;
}
public function stream_eof() {
return $this->position >= strlen($GLOBALS[$this->varname]);
}
public function stream_seek($offset, $whence) {
$l=strlen(&$GLOBALS[$this->varname]);
$p=&$this->position;
switch ($whence) {
case SEEK_SET: $newPos = $offset; break;
case SEEK_CUR: $newPos = $p + $offset; break;
case SEEK_END: $newPos = $l + $offset; break;
default: return false;
}
$ret = ($newPos >=0 && $newPos <=$l);
if ($ret) $p=$newPos;
return $ret;
}
}
stream_wrapper_register("var", "VariableStream")
or die("Failed to register protocol");
$myvar = "";
$fp = fopen("var://myvar", "r+");
fwrite($fp, "line1\n");
fwrite($fp, "line2\n");
fwrite($fp, "line3\n");
rewind($fp);
while (!feof($fp)) {
echo fgets($fp);
}
fclose($fp);
var_dump($myvar);
?>
10-Jun-2007 05:10
In response to Anonymouse at Coward dot com:
The manual says "Reading stops when up to length bytes have been read, [...] or (after opening userspace stream) when 8192 bytes have been read whichever comes first."
I tested it and fread($filehandle, 4096) returns 4096 bytes, so it's working as the manual says it should. You're right when you say "8192 bytes is always passed to stream_read as count", but that doesn't mean fread will return 8192 bytes. If you call fread twice with length 4096, PHP calls stream_read passing 8192 as count on the first fread, and doesn't call it on second fread. On both cases, fread returns the correct amount of bytes.
<?php
class VariableStream {
var $position;
var $varname;
function stream_open($path, $mode, $options, &$opened_path)
{
$url = parse_url($path);
$this->varname = $url["host"];
$this->position = 0;
return true;
}
function stream_read($count)
{
echo "stream_read called asking for $count bytes\n";
$ret = substr($GLOBALS[$this->varname], $this->position, $count);
$this->position += strlen($ret);
return $ret;
}
function stream_eof()
{
return $this->position >= strlen($GLOBALS[$this->varname]);
}
}
stream_wrapper_register("var", "VariableStream")
or die("Failed to register protocol");
$myvar = "";
$l=range('a','z');
for($i=0;$i<65536;$i++) {
$myvar .= $l[array_rand($l)];
}
$fp = fopen("var://myvar", "r+");
while (!feof($fp)) {
$out = fread($fp,1000);
echo "fread returned ",strlen($out)," bytes\n";
}
fclose($fp);
?>
26-Apr-2007 06:18
Use caution with writing code that may use stream wrappers with fread, as fread behaviour is 'inconsistent' with normal file operations because of the 8192 bytes internal buffer used by PHP ( >= 5.0.5 IIRC ).
ie:
fread($filehandle, filesize($filename))
will not work correctly if the file is larger than 8KB, it will only get you the first 8192 bytes. Also, it seems that:
fread($filehandle, 4096)
will still give you 8KB (if the file is larger than 8KB) as 8192 bytes is always passed to stream_read as count.
This makes it somewhat impossible to just 'drop in' a stream where normally a file would be used without taking special care.
Yes, it IS mentioned in the documentation here if you read it really well, but I for one spent some time scratching my head over it, and looking at the bug tracker, I am not the only one. The dev's say this inconsistancy is a feature though, even if it does make stream wrappers pretty much useless 'out of the box'.
file_get_contents and stream_get_contents seem to work ok though.
10-Nov-2006 01:35
In case someone else starts scratching their head like I was, you should change the VariableStream::stream_eof() function to something like this:
function stream_eof()
{
$eof = ($this->position >= strlen($GLOBALS[$this->varname]));
if(version_compare(PHP_VERSION,'5.0','>=') && version_compare(PHP_VERSION,'5.1','<'))
{
$eof = !$eof;
}
return $eof;
}
PHP 5.0 introduced a bug that wasn't fixed until 5.1
21-Jun-2005 04:37
It is worth noting that if your wrapper supports stream_flush() then when you flcose() your stream this function will be called prior to closing the stream.
13-Apr-2005 04:45
If you plan to use your wrapper in a require_once you need to define stream_stat(). If you plan to allow any other tests like is_file()/is_dir(), you have to define url_stat().
stream_stat() must define the size of the file, or it will never be included. url_stat() must define mode, or is_file()/is_dir()/is_executable(), and any of those functions affected by clearstatcache() simply won't work.
It's not documented, but directories must be a mode like 040777 (octal), and files a mode like 0100666. If you wish the file to be executable, use 7s instead of 6s. The last 3 digits are exactly the same thing as what you pass to chmod. 040000 defines a directory, and 0100000 defines a file. It would be really helpful to add this to the official manual!
04-Apr-2005 08:07
The current URL standard is RFC 3986 - available at www.ietf.org/rfc/rfc3986.txt
13-Apr-2004 07:53
using streams to use the ever useful fgetcsv() on a variable where explode() would not work (and would otherwise require regex(though that may be easier;)))
$explode_this="yak, llama, 'big llama', 'wobmat, with a comma in it', bandycoot";
<?php
class csvstream{
var $position;
var $varname;
function stream_open($path, $mode, $options, &$opened_path){
$url = parse_url($path);
$this->varname = $url['host'] ;
$this->position = 0;
return true;
}
function stream_read($count){
$ret = substr($GLOBALS[$this->varname], $this->position, $count);
$this->position += strlen($ret);
return $ret;
}
function stream_eof(){
return $this->position >= strlen($GLOBALS[$this->varname]);
}
function stream_tell(){
return $this->position;
}
}
stream_wrapper_register("csvstr", "csvstream") ;
$str="yak, llama, 'big llama', 'wobmat, with a comma in it', bandycoot";
$fp = fopen("csvstr://str", "r+");
print_r(fgetcsv($fp,100,",","'"));
?>
