PHP/ファイル入出力

出典: フリー教科書『ウィキブックス(Wikibooks)』
< PHP
ナビゲーションに移動 検索に移動

概要[編集]

PHPには、サーバー側にあるファイルに読み書きをする機能がある。

PHPにかぎらず一般に、プログラム言語によるファイルを読み書きの処理を「ファイル入出力」という。

PHPがファイル入出力で操作できる対象は、サーバー側のファイルと、例外的にアクセスしているwebブラウザのクッキーのみである。

考えてみればセキュリティ的に当然であり、もしアクセスしているクライアント側のファイルを読み書きできてしまったら、盗聴やウイルス・サイトなどとして悪用されてします。

ともかく、PHPでファイル入出力できる対象は原則的にサーバー側のファイルだけである。(例外はクッキー。)


読み込み[編集]

たとえば下記のようになる。

<?php
	echo "読み込みテスト<br />" ; 
	$fp = fopen("readTest.txt","r") ;
	$readString = fgets($fp);	

 	echo $readString ;
 		
	fclose($fp);

?>

C言語と似ていて、 fopen でファイルを開き、 fgets などで読みこめる。


たとえば、「readTest.txt」に

我輩はネコである。 

と書いておけば(そしてこれをドキュメントルートに置いておけば)、

上述のPHPファイルから造られたwebページでは

読み込みテスト
我輩はネコである。 

と表示される。

書き込み[編集]

前準備[編集]

Apacheなどのサーバー上でPHPによるファイル書き込みをする場合には、後述のようにセキュリティ上の設定や準備が、必要になる。

まず、ドキュメントルート( /var/www/html )のフォルダの所有者がもし root だと、Fedora系の場合、一般ユーザー上でのOS実行時にはファイル書き込みに失敗するので、フォルダ所有者を変更する(Fedora32 で2020年4月21日に確認)。

コマンド端末にて

$ sudo chown ログインユーザー名 /var/www/html

で、変更できる。

chown とは所有者(オーナー owner)を変更(チェンジ change)しろ、という命令である。


さらに、保存先となるファイルを、あらかじめ作成しておいて、ドキュメントルートにアップロードしないといけない(そうしないと、apache上でのPHPでは、なぜかファイル書き込みをしてくれない)。

もし、たとえば「saveData.txt」みたいなファイルを、あらかじめ作成しておいて、それをドキュメントルートにコピーするのである。

つまり

$ sudo cp saveData.txt /var/www/html

のように、書き込まれるファイルを、ドキュメントルートにコピーして置いておく。


こうして、ようやく、ファイル書き込みをできる。

コードと解説[編集]

コード
<?php
	echo "書き込みテスト<br />" ; 
	file_put_contents("saveData.txt", "Hello 書き込み"."\n", FILE_APPEND);	
?>
実行結果

これをドキュメントルートにアップロードし、ブラウザ上で「書き込みテスト」と書かれてあるローカルホストのサブページを閲覧すると、ファイル「saveData.txt」に

Hello 書き込み

と、書き込まれているハズである。

解説

file_put_contents の書き込み先は、ブラウザではなくテキストファイルなので、文字列を改行したい場合にはPHPそのものの改行文字「\n」を使わなければならない。

file_put_contents の 第三引数の FILE_APPEND は、書き込み時に、文章の最後に追記書き込みをするための命令。(これを使わないと、白紙から書き直してしまい、前の保存内容が消えてしまう。)


なお、改行文字を追加する場合には、."\n" のように追加する。

たとえば、もし

た
て
が
き

と書き込みたいなら、

<?php
	echo "書き込みテスト<br />" ; 
	file_put_contents("saveData.txt", "た"."\n"."て"."\n"."が"."\n"."き", FILE_APPEND);	
?>

をアップロードして、ブラウザ上から閲覧すれば、「saveData.txt」ファイルに、「たてがき」と、1文字ずつ改行して、末尾のほうに書いてあるハズ。


しかし、保存先のフォルダを、このPHPファイルと同じフォルダに置いてしまうと、webbbbbbbbbbbブラウザ側から、閲覧できてしまう。

このため、プライバシーなど非公開の情報を扱うときは、対策が必要になる。


対策として簡単な方法は、保存先のファイルを、ドキュメントルートより上にフォルダにしておけばいい。そうすると、ユーザーからはアクセスできない。

?php
	echo "書き込みテスト(保存先は非公開ファイル)<br />" ; 
	file_put_contents("../saveDataUp.txt", "た"."\n"."て"."\n"."よ"."\n"."み", FILE_APPEND);	
?>

のように、保存先ファイル名の手前に ../ をつければいい。この ../ は単に、「(PHPなど実行プログラムにとっての)現在のフォルダの、一つ上」という意味である。


なお、もし2つ上のフォルダにある twoUp.txt に対して読みかきしたいなら、"../saveDataUp.txt" の代わりに ../../twoUp.txt のように、すればいい。

※ Linux における、フォルダ(ディレクトリ)の位置の指定方法については、wikibooks『UNIX/Linux入門』で、cdコマンドなどを例に書いてある。cdコマンド以外でも、フォルダ位置の指定方法は同様である。

HTMLフォーム入力との連動[編集]

HTMLには、フォーム入力という機能がある。(※ 詳しくは『HTML/フォーム』)

たとえば、ネット企業の会員登録などのページでよくある、ユーザ名の登録画面のようなものを、作ってみよう。

原理的には、下記のようなhtmlファイルで form タグ と input タグを使えば、フォーム入力欄のあるhtmlのwebページを作れる。

<form action="catchTest.php" method="post">
ユーザー名を登録: <input type="text" name="username">
<input type="submit" value="登録">
</form>

さて、市販のHTMLの入門書にも、HTMLでのフォーム表示画面の作り方は書いてあるが、しかし、せっかくユーザーに入力してもらった内容を、どうやって保存するかは、なかなか、そういった入門書には書いてない。


実は、入力内容の保存は、PHPなどで行い、file_put_contentsなどの関数を使う。

そもそも html ファイルは、セキュリティー上の理由により、htmlファイルではパソコンに保存書き込みをできない。なので、PHPなどを使ってサーバー側に保存書き込みを行う。


そおためには、まず、PHPファイルとhtmlファイルとのデータ通信が必要であるが、このための機能がhtmlの form タグにある。

<form action="catchTest.php" method="post">とあるが、actionプロパティにより、送信先ファイルを指定でき、通信できる。


また、受信側のファイルは当然、その catchTest.php というファイル名にする必要がある。

よって、上記コードを受け取るために catchTest.php というファイル名で下記のコードを作る。

<?php
	echo $_POST['username'] ,"と入力されました。" ; 
	echo "入力されたユーザー名を保存しています。" ; 
	file_put_contents("../saveDataUp.txt", htmlspecialchars($_POST['username']) , FILE_APPEND);	
?>

上記コードのようにhtmlファイル post メソッドで送られた情報は、

PHPファイルで $_POST[' '] というのを使うと、受け取ることができる。

htmlspecialcharsというのは単に、webページからの文字列コピーペーストでよくある、サイズの大きくなったりした文字などを、通常の文字に直す命令である。


あとは、/var/www フォルダにある、保存ファイルを見に行けばいい。

保存ファイルに、さきほどフォームに入力した文字列が、追記で保存されているハズである。file_put_contents 関数の第一引数で指定した名前のファイル(上記コードの例の場合なら saveDataUp.txt というファイル)に、フォーム入力したユーザー名が登録されるハズである。


なお、上記コードの画面でwebブラウザのソースコード表示の機能を使ってコード表示しても、表示されるのは

自動生成のhtmlソースコード例
 test54321と入力されました。入力されたユーザー名を保存しています。

といった、変数部分が置き換わったHTMLコードである。

たとえローカルホストのアドレス名がたとえば http://localhost/catchTest.php のような末尾に「php」とついた形式であっても、表示されるコードは(PHPでなくて)html形式である。

サーバーからのダウンロード[編集]

概要[編集]

PHPでダウンロードをブラウザに問いかけるには、下記のように header 関数というのを使って、ブラウザに問い掛けできる。

コード例
<?php

    // 画像のパスとファイル名 (拡張子ごと)
    $fpath = "/var/www/html/phpgra2.png";
    $fname = "phpgra2.png";

    // ヘッダーの設定
    header('Content-Type: application/octet-stream');
    header('Content-Length: ' . filesize($fpath));
    header('Content-Disposition: attachment; filename="' . $fname . '"');


    // 環境によっては必要
    ob_end_clean(); 
    
    // 画像のダウンロード
    readfile($fpath);

?>

書式は

<?php

    $パス変数 = 'パスのアドレス';
    $ファイル変数 = 'ファイル名';

    header('Content-Type: application/octet-stream');
    header('Content-Length: ' . filesize($パス変数));
    header('Content-Disposition: attachment; filename="' . $ファイル変数 . '"');

    ob_end_clean(); 

    readfile($パス変数);

?>

です。


実験のさいには、あらかじめ画像データを作成しておいてください。

そして、ブラウザから、上記のPHPを実行します。(コマンドラインから実行しても、意味不明の文字列が表示されるだけです。)


成功すれば、ページ起動時に

「次のファイルを開こうとしています:」

と出て、「キャンセル」または「OK」のボタンが出てきます。


Content-Type: application/octet-stream の 「octet-stream 」は、種類を特定しないバイナリデータであることを宣言しています。画像データなどをダウンロードさせたい場合は画像ならバイナリ形式ですので、この 「octet-stream」を指定してもダウンロード可能です。

ダウンロードしたいファイルのファイル形式によっては、Content-Type で具体的に指定することもできます。


画像の場合、

PNG画像なら image/png をつかって Content-Type: image/png と指定しても、かまいません。
もしGIF画像なら image/gif で Content-Type: image/gif とも書けます。
JPEG画像なら Content-Type: image/jpeg とも書けます。

画像以外でも、

もしPDFをダウンロードさせるなら application/pdf のようになり、 Content-Type: application/pdf とも書けます。
あるいは、もしテキストファイルなら text/plain で、 Content-Type: text/plain とも書けます。


さて、

   header('Content-Disposition: attachment; filename="' . $fname . '"');

は、ダウンロードしたときのファイル名を上記コードでは $fname で指定しています。なので、ほかの名称でも構いません。たとえば

   header('Content-Disposition: attachment; filename="' . "test" . '"');

とすれば、ダウンロードされたファイル名は「test」になります。


ダウンロード開始は readfile関数でなくても、 file_get_contents 関数でもダウンロード問い掛けを出来ます。

環境や、アップロードするファイルの種類によっては

ob_end_clean(); 

が必要です。

これがないと、ファイルにバッファ内の余計なデータがついたままブラウザに送信されてしまい、ダウンロード自体はできても、読み込みエラーになってしまい、さっかくダウンロードした価値が無くなってしまいます。


豆知識など[編集]

PHPのここらへんの仕様は、あまり論理的ではなく、規則としてこう決まっていると思ったほうがいい。

たとえば、下記のように、ヘッダーの一部をコメントアウトすると、ブラウザ画面上に読み込んだPNG画像が表示される。

要するに、readfile() という関数は、本来ならば読み込んだファイル内容を表示する関数なのだが、PHPは readfile() 関数をダウンローダーとして流用しているのである。

コード例
<?php

    // 画像のパスとファイル名 (拡張子ごと)
    $fpath = "/var/www/html/phpgra2.png";
    $fname = "phpgra2.png";

    // ヘッダーの設定
    header('Content-Type: application/octet-stream');
    // header('Content-Length: ' . filesize($fpath));
    // header('Content-Disposition: attachment; filename="' . $fname . '"');


    // 環境によっては必要
    ob_end_clean(); 
    
    // 画像のダウンロード
    readfile($fpath);

?>


なお

// header('Content-Type: application/octet-stream');

のようにヘッダーの最初の行もコメントアウトすると、機械語のような文字列が画面に表示される。


PHPの header() 関数でいう「ヘッダ」とは何かというと、ブラウザとサーバーとの間で、通信のために やりとり している HTTPヘッダ という情報のことである。[1]


  • ソケット通信

高度なので説明を省略するが「ソケット通信」というパソコンどうしのネットワーク通信の際、HTTPサーバーとの通信方法は国際規格などで方式が決まっており、HTTPヘッダといわれるメッセージで命令を送受信する仕組みである。

PHPはソケット通信のための関数をサポートしており、そのソケット通信の関数の一部としてHTTPヘッダの関数も用意されており、本ダウンロードのコードではそれらの機能を流用している。


なお、サーバー系の言語としてPHP同様に有名な Perl という別言語もまた、ソケット通信の関数をいろいろとサポートしている。


実際の例[編集]

上記のコードだと、ページが表示される前にダウンロードが始まってしまう。そのため、とても見づらくなる。

上記のPHPコードにprintなどの命令を書いても、うまく動作しない。(基本的に、ダウンロード用のリンクでは、画像表示や文字表示は、あまり機能しない。)


実務的な方法としては、別のHTMLファイルで上記PHPコードにアクセスするリンクを配置し、

コード例
    <a href="dlTest.php">ダウンロード</a>
(※ これはHTMLファイルです。PHPではないです。)

のようにして、このHTMLファイルに先にリンクしてもらうようにするのが良い。

すると、先にこのHTMLだけが表示される。

そして、「ダウンロード」リンクをクリックすると、ページはそのままで(このHTMLが表示されたままで)、ダウンロードのポップアップが出るので、あとはブラウザ側でユーザーにダウンロードしてもらえば済む。

HTMLのdownloadプロパティ[編集]

2020年の近年、じつはダウンロードするだけなら、PHPなどを用いなくても、HTML5で追加されたdownload 属性だけで実装できるようになっている。

HTML5では a タグに download 属性が追加された。


コード例
    <a href="gazou.png"  download>ダウンロード</a>
(2020年5月18日 に Fedora 32 上でのブラウザ Firefox76 で動作確認。)

のようにhrefのハイパーリンクのあるタグに download 属性を追加するだけでも、実は簡単にダウンロードを実装できるようになっている。

なお、href で、コンテンツ名およびコンテンツの場所を指定する。コンテンツの場所がともにHTMLとともにドキュメントルートにあるなら、単にコンテンツのファイル名だけでよい。

上記コードの場合、単に「ダウンロード」をクリックするだけで、画像のダウンロードが開始する。


参考文献[編集]

  1. ^ "PHP: header - Manual" 2020年5月16日に閲覧して確認.