プログラムを作る際、しばしば一時ファイルが必要になる時があります。この一時ファイルは様々な用いられ方がありますが、この記事の中では1プロセスの中でのみ取り扱う一時ファイルのみを対象とします。1プロセスの中で一時ファイルが完結する場合、PHPではそのファイルを作るには tmpfile という関数を使うのが便利です。tmpfile は一時ファイルを作成し、そのファイルのハンドルを返してくれる関数です。tmpfile を使うと自前で一時ファイルを作る場合に必要となる面倒な制限やファイルを扱うケアをPHP側で行ってくれます。
tmpfile はファイルの名前を自動で決めてくれます。これはファイル名の衝突を気にしなくていいという点で便利ですが、特定の条件にあうファイル名が欲しい場合には困ることもあります。例えば特定の拡張子を必要とする場合や一定の命名規則に従いたい場合などです。そういった時にいい感じに tmpfile を使うためのファイル名変更方法について紹介します。
実際のコードが次です。
<?php // 一時ファイルを作成 $tmp = tmpfile(); // 一時ファイルの名前を変更するためにメタデータからフルパスを取得 $originFilePath = stream_get_meta_data($tmp)['uri']; // 一時ファイルのフルパスを加工。この際、文字を加える形にしておくと衝突の可能せいがなくて安全 $batFilePath = $originFilePath . '.bat'; rename($originFilePath, $batFilePath); // 一時ファイルの中身を書いて実行するなど、何かしら一時ファイルでやりたいことをする file_put_contents($batFilePath, <<<BAT @echo off echo Hello World! BAT ); echo shell_exec($batFilePath); // 一時ファイルの名前を元に戻す。こうするとPHPが一時ファイルの後始末をしてくれる rename($batFilePath, $originFilePath); // ↑の rename は PHP によしなにしてもらうことを期待しているけど // 別に unlink で削除するのも、tmpファイルの置き場にあるであろう自動ローテーションに任せるのも良さそう // unlink($batFilePath);
コードの説明はざっくり言うと tmpfile で一時ファイルを作り、stream_get_meta_data で一時ファイルの情報を集め、rename で名前を変える、という感じです。
上記コードに似た一時ファイルを扱うコードを作る時に起きる典型的なミスに一時ファイルが予期せぬうちに消える、というものがあります。具体的に何が起きるかというと次です。
<?php // 一時ファイルを変数に格納せず、作成してメタデータを取得 $originFilePath = stream_get_meta_data(tmpfile())['uri']; // 一時ファイルの名前を変えようとするとファイルが見つかりませんでしたエラーになる rename($originFilePath, $originFilePath . '.bat'); // File Not Found
変数に一時ファイルを格納しない場合、ファイルそのものが消えます。これはPHPがより省メモリでプログラムを動かすための機能が働いて起きます。PHPはtmpfile の返すファイルハンドルへの参照がなくなった場合はもう一時ファイルの役割は終わったと判断し、ファイルを削除します。tmpfile をファイルパスベースであれこれするというのは普通の使い方ではなく想定外の使い方なのです。この問題にあたった場合は一時ファイルのファイルハンドルを変数に格納し、一時ファイルに生きていて欲しい間は参照できる状態にしておけば解決します。これは例えば次です。
<?php // 一時ファイルを作成してファイルハンドルを変数に格納 $tmp = tmpfile(); // エラーなしで改名できる $originFilePath = stream_get_meta_data($tmp)['uri']; rename($originFilePath, $originFilePath . '.bat');