PHPファイル操作機能一覧

PHPマニュアル[ファイルシステム関数]には、さまざまな函数が雑多に並んでますが、この記事では機能を分類しながら、PHPのファイル操作方法について具体的に説明していきます。

## 機能の種類

PHPでファイルを操作するための機能には、大きく分けて3種類あります。

 * **ファイル名型** - ファイル名を引数として取る函数
 * **ストリーム型** - ストリーム(ファイルポインタ)を引数としてとる函数
 * **オブジェクト型** - [`SplFileObject`](http://php.net/SplFileObject)([`SplFileInfo`](http://php.net/manual/ja/class.splfileinfo.php))のメソッド

この3種類の機能にはそれぞれ特徴があります。どれか一つの型だけに統一することは**できない**のですが、向く場面向かない場面があるので表に整理します。

さらに**てっとり早く知りたい**人は[PHPでファイルを開いて読み込む]を読んでね。

### 早見表

| 機能 ＼ 引数                 | ファイル名型                           | ストリーム型                                                | オブジェクト型                                          |
|------------------------------+----------------------------------------+-------------------------------------------------------------+---------------------------------------------------------|
| ファイルを操作する準備       | 不要                                   | [`fopen()`]                                                 | [`new SplFileObject()`]                                 |
| 内容の全体を文字列として取得 | [`file_get_contents()`]                | [`stream_get_contents()`]                                   | [`SplFileObject::fread()`] + [`SplFileInfo::getSize()`] |
| 内容の一部を文字列として取得 | [`file_get_contents()`] (第4・第5引数) | [`fread()`]                                                 | [`SplFileObject::fread()`]                                |
| 文字列をファイルに書き込み   | [`file_put_contents()`]                | [`fwrite()`]                                                | [`SplFileObject::fwrite()`]                             |
| 内容を行区切りで取得         | [`file()`]                             | [`stream_get_line()`] / [`fgets()`]                         | [`SplFileObject::fgets()`]                              |
| 内容をバッファに出力         | [`readfile()`]                         | [`fpassthru()`]                                             | [`SplFileObject::fpassthru()`]                          |
| ファイルロック               | [`file_put_contents()`] の `LOCK_EX`   | [`flock()`]                                                 | [`SplFileObject::flock()`]                              |
| 一時ファイル生成             | [`tempnam()`] \(ファイル名生成のみ)     | `fopen('php://temp', 'rw')` または [`tmpfile()`] (自動削除) | [`new SplTempFileObject()`]                             |
| ファイルコピー               | [`copy()`]                             | 読み込みと書き込みの複合で実現                              | 読み込みと書き込みの複合で実現                          |

## おまけ: 「ファイル名」とは

PHPで「ファイル名」として利用できるのは、以下のような文字列です。

 * ローカルファイルシステムのフルパス
 * `include_path`内のファイル名
 * [プロトコル/ラッパー](http://php.net/manual/ja/wrappers.php)

### ローカルファイルシステムのフルパス

あまり説明することもないですが、普通のファイル名のパスです。UNIX系OSでは`/`から始まり、Windowsでは`C:\`のようなドライブレターから始まる文字列です。

### `include_path`内のファイル名

あまりおすすめしないです。

`include_path`は`php.ini`ファイルまたは`ini_set()`関数(または`set_include_path()`)でセットできる設定です。

```php
<?php
var_dump(get_include_path());
//=> string(44) ".:/usr/local/Cellar/php/7.3.0/share/php/pear"
```

しれっと`.`が入ってるので、ディレクトリを指定しなくてもファイルを開けるように見えるのは、そのせいです。

### プロトコル／ラッパー

PHPマニュアルに掲載されてるのは以下の通りです。

 * [`php://`] — さまざまな入出力ストリームへのアクセス
 * [`file://`] — ローカルファイルシステムへのアクセス
 * [`http://`] — HTTP(s) URL へのアクセス
 * [`ftp://`] — FTP(s) URL へのアクセス
 * [`zlib://`] — 圧縮ストリーム
 * [`data://`] — データ (RFC 2397)
 * [`glob://`] — パターンにマッチするパス名の検索
 * [`phar://`] — PHP アーカイブ
 * [`ssh2://`] — Secure Shell 2
 * [`rar://`] — RAR
 * [`ogg://`] — オーディオストリーム
 * [`expect://`] — 対話的プロセスストリーム

これらは普通のファイルシステムと同じように操作することができます。

ただし全ての操作が実装されてるわけではなく、読み込みしかできないプロトコルも多いです。

```php
// file_get_contents("http://httpbin.org/get") と同じこと
$f = fopen("http://httpbin.org/get", "r");
var_dump(stream_get_contents($f));
// string(158) "{
//   "args": {},
//   "headers": {
//     "Connection": "close",
//     "Host": "httpbin.org"
//   },
//   "origin": "111.97.185.93",
//   "url": "http://httpbin.org/get"
// }
// "
```

このプロトコルは**ユーザーが拡張できます**。PHPテストに利用できる仮想ファイルシステムである[vfsStream](https://github.com/mikey179/vfsStream)は、この仕組みの上に構築されてます。

## 結局どれがいいの？

表に挙げたように、実際どれもほとんど同じことができます。ただ、関数をまたいでファイルのようなものを扱うときに、どれを使うかはある程度は一貫性を持たせた方がよいでしょう。

 * **ファイル名型**
    * 初期化の必要がないので操作が簡単
    * ファイル名は型としては単なる文字列なので型として区別して扱いにくい
 * **ストリーム型** (`resource`)
    * `STDIN`, `STDOUT`, `STDERR` など定義済みの定数がある
        * これらの入出力を差し替えるものとしてテストに利用可能
    * [`proc_open()`](http://php.net/manual/ja/function.proc-open.php)と相互運用できる
    * 型名としては`resource`だが、これは**型宣言に記述できない**
 * **オブジェクト型**
    * [`SplFileObject`](http://php.net/SplFileObject)や([`SplFileInfo`](http://php.net/manual/ja/class.splfileinfo.php))などがある
    * すべての操作がオブジェクトのメソッドとして実装されてるのでわかりやすい (＝メソッド名の補完ができる)
    * ストリーム型の機能と相互運用できない

アプリケーションの設計に属する問題なので好きなものを選んでください。

[`file://`]: http://php.net/manual/ja/wrappers.file.php
[`http://`]: http://php.net/manual/ja/wrappers.http.php
[`ftp://`]: http://php.net/manual/ja/wrappers.ftp.php
[`php://`]: http://php.net/manual/ja/wrappers.php.php
[`zlib://`]: http://php.net/manual/ja/wrappers.zlib.php
[`data://`]: http://php.net/manual/ja/wrappers.data.php
[`glob://`]: http://php.net/manual/ja/wrappers.glob.php
[`phar://`]: http://php.net/manual/ja/wrappers.phar.php
[`ssh2://`]: http://php.net/manual/ja/wrappers.ssh2.php
[`rar://`]: http://php.net/manual/ja/wrappers.rar.php
[`ogg://`]: http://php.net/manual/ja/wrappers.ogg.php
[`expect://`]: http://php.net/manual/ja/wrappers.expect.php
[`copy()`]: http://php.net/manual/ja/function.copy.php
[`fgets()`]: http://php.net/manual/ja/function.fgets.php
[`file()`]: http://php.net/manual/ja/function.file.php
[`file_get_contents()`]: http://php.net/manual/ja/function.file-get-contents.php
[`file_put_contents()`]: http://php.net/manual/ja/function.file-put-contents.php
[`fopen()`]: http://php.net/manual/ja/function.fopen.php
[`flock()`]: http://php.net/manual/ja/function.flock.php
[`fpassthru()`]: http://php.net/manual/ja/function.fpassthru.php
[`fread()`]: http://php.net/manual/ja/function.fread.php
[`fwrite()`]: http://php.net/manual/ja/function.fwrite.php
[`readfile()`]: http://php.net/manual/ja/function.readfile.php
[`stream_get_contents()`]: http://php.net/manual/ja/function.stream-get-contents.php
[`stream_get_line()`]: http://php.net/manual/ja/function.stream-get-line.php
[`tempnam()`]: http://php.net/manual/ja/function.tempnam.php
[`tmpfile()`]: http://php.net/manual/ja/function.tmpfile.php
[`SplFileInfo::getSize()`]: http://php.net/manual/ja/splfileinfo.getsize.php
[`SplFileObject::fgets()`]: http://php.net/manual/ja/splfileobject.fgets.php
[`SplFileObject::flock()`]: http://php.net/manual/ja/splfileobject.flock.php
[`SplFileObject::fpassthru()`]: http://php.net/manual/ja/splfileobject.fpassthru.php
[`SplFileObject::fread()`]: http://php.net/manual/ja/splfileobject.fread.php
[`SplFileObject::fwrite()`]: http://php.net/manual/ja/splfileobject.fwrite.php
[`new SplFileObject()`]: http://php.net/manual/ja/splfileobject.construct.php
[`new SplTempFileObject()`]: http://php.net/manual/ja/spltempfileobject.construct.php

[ファイルシステム関数]: http://php.net/manual/ja/ref.filesystem.php

[PHPでファイルを開いて読み込む]: https://qiita.com/tadsan/items/bbc23ee596d55159f044