Start · Sprachen · PHP · Referenz · streamWrapper

streamWrapper

Klasse

Prototyp-Klasse für benutzerdefinierte Stream-Wrapper, die eigene Protokolle und Streams für PHP-Dateisystemfunktionen implementieren.

seit PHP 4.3.2 Kategorie: io

Signatur

class streamWrapper

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');
Hallo Welt!

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
R2VoZWltdGV4dA==

// 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.