Signatur
Beschreibung
streamWrapper ist eine Vorlage (kein verpflichtendes Interface), die beschreibt, welche Methoden eine eigene Wrapper-Klasse implementieren kann, um ein benutzerdefiniertes Protokoll (z. B. myproto://) in PHP zu registrieren. Sobald ein Wrapper über stream_wrapper_register() registriert ist, reagiert PHP automatisch auf alle Dateioperationen, die das jeweilige Protokoll verwenden – also fopen(), fread(), file_get_contents(), opendir() und viele weitere.
Die Klasse muss nicht von streamWrapper erben. PHP erwartet nur, dass die benötigten Methoden (z. B. stream_open(), stream_read(), stream_eof()) vorhanden sind. Welche Methoden tatsächlich implementiert werden müssen, hängt davon ab, welche Operationen auf dem Stream unterstützt werden sollen.
Typische Anwendungsfälle sind: Transparente Verschlüsselung/Kompression beim Schreiben und Lesen, virtuelle Dateisysteme im Arbeitsspeicher, Cloud-Storage-Adapter (S3, GCS), Test-Doubles für Dateisystemoperationen in Unit-Tests sowie Logging oder Caching von Dateioperationen.
Wichtig: Die Instanz der Wrapper-Klasse wird von PHP intern für jeden Stream-Vorgang neu erzeugt. Der öffentliche Property $context wird dabei automatisch von PHP gesetzt und enthält den Stream-Kontext.
Beispiele
Minimaler In-Memory-Stream-Wrapper
<?php
class MemoryStreamWrapper
{
/** @var resource|null Stream-Kontext, wird von PHP gesetzt */
public $context;
private string $buffer = '';
private int $position = 0;
/** Globaler Speicher für alle geöffneten Pfade */
private static array $storage = [];
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
{
$key = parse_url($path, PHP_URL_HOST) . parse_url($path, PHP_URL_PATH);
if (str_contains($mode, 'w')) {
self::$storage[$key] = '';
}
$this->buffer = self::$storage[$key] ?? '';
$this->position = str_contains($mode, 'a') ? strlen($this->buffer) : 0;
return true;
}
public function stream_read(int $count): string|false
{
$data = substr($this->buffer, $this->position, $count);
$this->position += strlen($data);
return $data;
}
public function stream_write(string $data): int
{
$left = substr($this->buffer, 0, $this->position);
$right = substr($this->buffer, $this->position + strlen($data));
$this->buffer = $left . $data . $right;
$this->position += strlen($data);
return strlen($data);
}
public function stream_eof(): bool
{
return $this->position >= strlen($this->buffer);
}
public function stream_tell(): int
{
return $this->position;
}
public function stream_seek(int $offset, int $whence = SEEK_SET): bool
{
$length = strlen($this->buffer);
$this->position = match ($whence) {
SEEK_SET => $offset,
SEEK_CUR => $this->position + $offset,
SEEK_END => $length + $offset,
};
return $this->position >= 0;
}
public function stream_close(): void
{
// Puffer zurückschreiben (Schlüssel ermitteln)
// vereinfacht: wird durch stream_flush abgehandelt
}
public function stream_flush(): bool
{
return true;
}
public function stream_stat(): array
{
return ['size' => strlen($this->buffer)];
}
public function url_stat(string $path, int $flags): array|false
{
return false;
}
}
stream_wrapper_register('mem', MemoryStreamWrapper::class);
// Schreiben
file_put_contents('mem://test/hello.txt', 'Hallo Welt!');
// Lesen
echo file_get_contents('mem://test/hello.txt');
Stream-Wrapper für automatisches Base64-Encoding beim Schreiben
<?php
class Base64WriteWrapper
{
public $context;
private string $buffer = '';
private int $pos = 0;
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
{
// Eigentlichen Pfad ohne Protokoll extrahieren
$this->realPath = substr($path, strlen('b64file://'));
$this->mode = $mode;
$this->buffer = '';
$this->pos = 0;
return true;
}
public function stream_write(string $data): int
{
$this->buffer .= $data;
$this->pos += strlen($data);
return strlen($data);
}
public function stream_flush(): bool
{
// Base64-kodiert auf Disk schreiben
return file_put_contents($this->realPath, base64_encode($this->buffer)) !== false;
}
public function stream_read(int $count): string|false { return false; }
public function stream_eof(): bool { return true; }
public function stream_tell(): int { return $this->pos; }
public function stream_stat(): array { return []; }
public function url_stat(string $p, int $f): array|false { return false; }
public function stream_close(): void { $this->stream_flush(); }
public function stream_seek(int $o, int $w): bool { return false; }
}
stream_wrapper_register('b64file', Base64WriteWrapper::class);
file_put_contents('b64file:///tmp/encoded.txt', 'Geheimtext');
echo file_get_contents('/tmp/encoded.txt'); // zeigt Base64-kodierten Inhalt
// Wichtig · Fallstricke
Methoden-Übersicht: PHP ruft je nach Operation unterschiedliche Methoden auf. Die wichtigsten sind: stream_open(), stream_read(), stream_write(), stream_eof(), stream_tell(), stream_seek(), stream_flush(), stream_close(), stream_stat(), url_stat(), dir_opendir(), dir_readdir(), dir_closedir(), mkdir(), rmdir(), rename(), unlink().
Kontext-Property: Der öffentliche Property $context muss deklariert sein (oder wird implizit gesetzt). Er enthält den via stream_context_create() erzeugten Kontext oder null.
Protokoll-Kollisionen: Es ist nicht möglich, ein bereits registriertes PHP-internes Protokoll (z. B. file://, http://) einfach zu überschreiben, ohne es vorher mit stream_wrapper_unregister() zu entfernen. Danach kann es mit stream_wrapper_restore() wiederhergestellt werden.
Sicherheit: Benutzerdefinierte Wrapper, die Benutzereingaben als Pfad akzeptieren, müssen Path-Traversal-Angriffe (../) explizit abwehren. Außerdem sollten sensible Daten im Speicher-Puffer beim Schließen sicher gelöscht werden.