おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

PHPのstream wrapperをさわってみた

PHPには、ストリームラッパーというファイルなどへのアクセスを抽象化するしくみがあります。普段よく使う http:// とかがそうです。
組み込みで提供されるストリームラッパーは以下です。

PHP: Supported Protocols and Wrappers - Manual

それぞれどういうふうに使うものなのか試してみました。

ファイルシステム

  • /path/to/file.ext
  • relative/path/to/file.ext
  • fileInCwd.ext
  • C:/path/to/winfile.ext
  • C:\path\to\winfile.ext
  • \\smbserver\share\path\to\winfile.ext
  • file:///path/to/file.ext

PHP で使用されるデフォルトのラッパーは Filesystem で、 これはローカルファイルシステムを表します。

PHP: file:// - Manual

デフォルトで利用されているラッパーということで、何も無い場合はこれになるわけですが、明示的に指定する場合には file:// となります。

HTTP と HTTPS

HTTP 1.0 により HTTP GET メソッドを用いてファイル/リソースに読み込みのみの アクセスが可能です。

PHP: http:// - Manual

これもよく使うHTTPとHTTPS
ちなみにPOSTを利用したい場合や、ヘッダをつけてリクエストしたい場合は、fopen や file_get_contents といった関数には、contextというオプション引数があり、そこに stream_context_create関数で、メソッドやヘッダを指定することで、PEARcurlを利用することなく対応できます。
このストリームは読み込みのみに対応しているため、例えば、file_put_contentsでファイルを作成したりすることはできません。

FTP と FTPS

FTPクライアントでファイルの受け渡しをすることはあるものの、PHPで使いたいと思ったことはないですね。

PHP: ftp:// - Manual

ファイルの内容を取得する場合はこんな感じ。

<?php 
echo file_get_contents("ftp://user:pass@ftp.example.jp/home/user/tmp.php");

読み込みだけでなく、書き込み・追記が可能です。
追記は、こんな感じで (FILE_APPENDで追記)

<?php
file_put_contents("ftp://user:pass@ftp.example.jp/home/user/tmp.php",
                  "<?php phpinfo();", 
                  FILE_APPEND);'

上書きはcontextの指定が必要です。追記は普通にできるのになんだか微妙。

<?php
$context = stream_context_create(array('ftp' => array('overwrite' => true)));
file_put_contents("ftp://user:pass@ftp.example.jp/home/user/tmp.php", 
                  "<?php phpinfo();", 0, $context);

ディレクトリの走査も可能です。

<?php 
$d = dir("ftp://user:pass@example.jp/home/user");
while (false !== ($entry = $d->read())) {
  echo $entry . PHP_EOL;
}
$d->close();

PHP 入出力ストリーム

これは何かのプロトコルのラッパーというわけではなく、PHP入出力関連をまとめたものです。

  • php://stdin
  • php://stdout
  • php://stderr
  • php://output
  • php://input
  • php://filter (PHP 5.0.0 以降で使用可能)
  • php://memory (PHP 5.1.0 以降で使用可能)
  • php://temp (PHP 5.1.0 以降で使用可能)
PHP: php:// - Manual

上から5つは、あんまり利用する用途が無い感じですが、例えば、入出力の指定をstreamで受けるように実装することで抽象化して、取替可能にするとかはありかもしれません。

php://stdin、php://stdout および php://stderr

それぞれ、標準入力、標準出力、標準エラー出力に対応します。STDIN、STDOUT、STDERR という定数で、オープン済みのハンドルとしてアクセスできるものと同等です。

<?php
$fp = fopen('php://stdin', 'r');
while (!feof($fp)) {
  echo fgets($fp);
}

は以下と等価です。

<?php
while (!feof(STDIN)) {
  echo fgets(STDIN);
}
php://output

print() および echo() と同じ方法での出力バッファへの書き込み。ということで、あんまり意味ない感じ。

php://input

php://input は、POST の生データの読み込みを 許可します。これは $HTTP_RAW_POST_DATA に比べて メモリ消費量が少なく、特別な php.ini ディレクティブを設定する 必要がありません。php://input は、 enctype="multipart/form-data" に対しては 使用できません。

ということで、POSTの生データを参照する際に推奨されています。

<html>
<body>
<form method="POST">
    <input name="first"  type="text" />
    <input name="second" type="text" />
    <input type="submit" />
</form>
<?php
var_dump(file_get_contents("php://input"));
?>
</body>
</html>

php://filter

php://filter は、フィルタアプリケーションが ストリームをオープンすることを許可するために設計されたメタラッパーです。 これは、readfile()、file() および file_get_contents() のようなオールインワンのファイル関数とともに使用すると有用です。これらの関数には、コンテンツが 読み込まれる前にストリームにフィルタを適用する手段がありません。

メタラッパーというなんだか大げさな名前ですが、ストリームにフィルタをかませることができます。
例えば、読み込みにstring.strip_tags フィルタをかませて、タグを抜く場合、このようになります。

<?php
echo file_get_contents("php://filter/read=string.strip_tags/resource=http://www.yahoo.co.jp");

逆にfopenなど、オールインワンで無い場合は、stream_filter_appendstream_filter_prepend で、フィルタを適用できます。stream_filter_appendの例を参照してください。
ストリームフィルタで何が使えるかは、stream_get_filters関数で知ることができます。もちろんフィルタは自作することができます。

参考

php://memory、php://temp

php://memory ラッパーは、データをメモリに保存します。

<?php
$fp = fopen("php://memory", 'r+');
fputs($fp, "hello!");
rewind($fp);
echo stream_get_contents($fp);

stream_get_contents() は、file_get_contentsと同じようなもので、引数にファイル名でなくオープン済みのストリーム (ファイルハンドル)を渡すところが違います。

php://temp も同様にメモリに保存しますが、メモリの制限(デフォルトは 2 MB)を超過した際にはテンポラリファイルを使用します。制限値を変更する場合は、/maxmemory:1000 とバイト単位で指定します。

<?php
$fiveMBs = 5 * 1024 * 1024;
$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');

fputs($fp, "hello\n");

// 先ほど書き込んだデータを読み込みます。
rewind($fp);
echo stream_get_contents($fp);
?>
参考

圧縮ストリーム

  • compress.zlib://
  • compress.bzip2://

compress.zlib:// および compress.bzip2:// は、それぞれ gzopen() および bzopen() と等価です。

PHP: zlib:// - Manual

ということで、これもあんまり意味の無い感じ。gzopen()、bzopen()は、fopen()の圧縮ファイル版という感じのもので、それぞれ gzip、bzipファイルを開いたファイルハンドルを返却します。

<?php
$fp = fopen("compress.zlib:///tmp/file.gz", "r");
?>

は、以下と等価

<?php
$fp = gzopen("/tmp/file.gz", "r");
?>

データ (RFC 2397)

いわゆるdata URLスキームに対応するものです。たとえば、画像データなんかを

<img src="data:image/gif;base64,R0lGODlhEAAQAPcAAIZcD..."/>

とHTML内で完結して読み込ませることができるあれです。
このように、デコードして表示できます。(読み込みのみで、書き出しはサポートしていません)

<?php
// "I love PHP" と表示します
echo file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=');
?>

デコードしてbodyだけ表示するならあんまり意味が無いですが、以下のようにしてmediatypeを取得できます。

<?php
$fp   = fopen('data://text/plain;base64,', 'r');
$meta = stream_get_meta_data($fp);

// "text/plain" と表示します
echo $meta['mediatype'];
?>

ちなみに、data:// で始まるのはPHPのストリームラッパーの仕様で、RFCでは、data:text/plain... と // は含みません。
マニュアルには記載が無く、User Contributed Notesで補足されていますが、 // を抜いた状態でも正常に動作するように実装されています。

Glob

PHP5.3から利用可能です。globというのは、あのglob関数と同じようなものです。
これは、fopenやfile_get_contentsでは利用できず、dir() や SPL の DirectoryIterator などのディレクトリ走査系で利用可能となっています。
PHPドキュメントの例は、DirectoryIteratorで利用しているものです。

<?php
// ext/spl/examples/ ディレクトリのすべての *.php ファイルについて、
// そのファイル名とサイズを表示します
$it = new DirectoryIterator("glob://ext/spl/examples/*.php");
foreach($it as $f) {
    printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>

dir()の場合

<?php
$d = dir("glob://*.php");
while (false !== ($entry = $d->read())) {
      echo $entry . PHP_EOL;
}
$d->close();

Phar

PHP 5.3 からもしくは PECL の phar を導入時に有効です。
Pharは、PHP Archiveの略で、phar形式に固められたアーカイブを参照する際に phar:// を利用します。
Phar形式のアーカイブに限らず、tar gzip、bzipのアーカイブから直接、中のファイルを参照することができます。仕組みについての詳しいことは別に書きます。
アーカイブをそのまま指定すると、Pharのスタブが読み込まれます。

<?php
include 'phar://hoge.phar';
?>

デフォルトのラッパーはファイルだと一番上で書きましたが、ファイル名に.pharが含まれる場合は、pharとして扱われます。なので以下のように、phar://を省略できます。

<?php
include 'hoge.phar';
?>

アーカイブ内のファイルも参照できます。

<?php
echo file_get_contents("phar://foo.phar.bz/dir/bar.txt");
?>