ソースコードで学ぶWebプログラミング

PHPでデータを出力する8つの方法

Webプログラムをライフサイクルで見た場合、処理内容を3つに分類できます。

Webプログラムのライフサイクル

・入力データの受取 ・データの変換 ・データの出力処理

これはWebサイトでの処理が「ブラウザからの入力」「出力するデータの作成」「データの出力」で構成されるためです。 このページではWebプログラムの基本となる「データの出力」を習得できるように解説します。

テキストファイルに保存したい時

変数内に入っている値をテキストファイルに保存する処理です。ファイルを新規作成して書き込む方法、ファイルの末尾に書き込む方法があります。ファイル内の特定の位置に書き込んだり、特定の位置のデータを書き換えるには、ファイルを読み込みながら保存用データを作成し、その作成したデータでファイルを書き換えることになります。

ファイル内容を読み込みつつ、データ処理を行ってから書き込みたい場合は fopen(),fwrite(),fclose() 、ファイル内容を触らない場合は file_put_contents() がよく使われます。

<?php /* 保存するファイル名 */ define('FILE_NAME', 'test.txt'); /* 新規ファイルとして書き込む */ $fp = fopen(FILE_NAME, 'w'); fwrite($fp, $lines); fclose($fp); /* ファイルに追記で書き込む */ $fp = fopen(FILE_NAME, 'a'); fwrite($fp, $line); fclose($fp); /* 配列をCSV形式で追記書込したい場合 */ $fp = fopen(FILE_NAME, 'a'); fputcsv($fp, $c); fclose($fp); /* CSVファイルを新規作成で書込したい場合 */ $fp = fopen(FILE_NAME, 'w'); /* $csv は2次元配列 */ foreach($csv as $c){ fputcsv($fp, $c); } fclose($fp); /* 新規ファイルとして書き込む */ file_put_contents(FILE_NAME, $lines); /* 追記で書き込む */ file_put_contents(FILE_NAME, $line, FILE_APPEND); /* 追記でファイルロックをして書き込む */ file_put_contents(FILE_NAME, $line, FILE_APPEND | LOCK_EX);

ファイル処理には、ファイルロックという概念があります。 Webサイトでは複数人により同時アクセスが行われることがあります。 例えば、掲示板の削除処理が同時に行われた場合、書込するデータがユーザー毎に異なる状態になり、 片方の削除処理が反映されないケースが発生することになります。

/* Aさんの削除処理 */ $lines = 'Bさんの書込'; fwrite($fp, $lines); /* Bさんの削除処理 */ $lines = 'Aさんの書込'; fwrite($fp, $lines);

Aさん、Bさんがそれぞれ自分の書込を削除した場合、新しいデータが異なる状態になります。削除に限らず、変更処理でも同様の結果となります。これを避けるために一人だけがデータ変更や削除を行えるように制限を行います。 これがファイルロックの排他制御です。

<?php /* ファイルが無い時、空のファイルを作成する */ if (!file_exists(FILE_NAME)) touch(FILE_NAME); /* 読込書込の r+ で開く */ $fp = fopen(FILE_NAME,'r+'); /* ファイルの書き込みをロック */ flock($fp, LOCK_EX); /* ファイル内容を検査、変更・削除など */ while($line = fgets($fp)){ } /* ファイル位置を制御 0で先頭に */ fseek($fp, 0); /* ファイルに書き込む */ fwrite($fp, $data); /* ファイルを指定サイズに丸める */ ftruncate($fp, ftell($fp)); /* ロックを解除 */ flock($fp, LOCK_UN); fclose($fp);

新規書込の fopen('','w') や 追加書込の fopen('','a') ではファイルがない時、ファイルが自動作成されますが、 読込/書込のfopen('','r+') ではファイルがない時、エラーとなるため、あらかじめファイルを設置しておくか、 ない場合の作成コードを記述しておきます。

ファイルの書込処理で、もうひとつ注意すべき点があります。ファイル全体のサイズの問題です。 例えばデータの変更では、変更前よりもファイルサイズが小さくなる事があります。 特にデータの削除時は、ファイルサイズは必ず小さくなるため、ファイルに書き込んだ後、 そのサイズ差の分だけファイルの末尾に元データが残ります。上記ではこれを除去するため、ftruncate() を利用しています。

データベースに保存したい時

PHPでは、PDOオブジェクトでデータベース操作が簡単に行えます。主にMySQLPostgreSQLSQLiteが利用できます。データベースで行う処理はSQL文として記述します。PHPでは、データベースにSQL文を安全に受け渡して、結果を受け取るコーディングになります。

<?php /* MySQLに接続 */ /* ホスト名設定 */ define('DB_HOST', 'localhost'); /* データベース名 */ define('DB_NAME', ''); /* 接続ユーザー名 */ define('DB_USER', ''); /* 接続パスワード */ define('DB_PASS', ''); /* エラーを代入する変数 */ $ERROR = array(); /* 接続エラーを取得するための記述 */ try { /* データベース操作用のオブジェクトを作成 */ $db = new PDO('mysql:dbname='.DB_NAME.';host='.DB_HOST.';charset=utf8', DB_USER, DB_PASS); /* データベースを操作するSQL文 */ $sql = 'INSERT INTO posts (title,content,date) VALUES (?,?,?)'; /* SQL文として渡す入力データ */ $q = array($title, $content, $date); /* SQL文を実行するための準備 */ $sth = $db->prepare($sql); /* SQL文を実行 */ $sth->execute($q); /* 接続エラーの例外を処理 */ } catch(PDOException $e) { $ERROR[] = $e->getMessage(); }

PostgreSQLSQLiteでも同様に接続できます。SQLiteでは、ファイルに保存されるためファイル名を指定します。 あらかじめファイルを用意する必要はなく、自動的に作成されます。SQLiteのファイルは、ブラウザでアクセスできない領域に設置するか、 アクセスできないようにします。

/* PostgreSQLに接続 */ $db = new PDO('pgsql:dbname='.DB_NAME.';host='.DB_HOST.';charset=utf8', DB_USER, DB_PASS); /* SQLiteで保存するファイル名 */ define('DB_FILE', 'sqlite.db'); /* SQLiteに接続 */ $db = new PDO('sqlite:'.DB_FILE);

データベースに複数の更新処理を行なう必要があるが、一貫したデータとするために処理をまとめたい場合があります。例えば、あるユーザーのポイントを別のユーザーのポイントへと移動するケースなどが考えられます。このケースでは、SQL文が一つでも失敗した場合、更新処理をすべて取り消す必要があります。

/* ポイントの移動 */ /* Aさんのポイントを更新 */ $sql = 'UPDATE users SET point=? WHERE id=?'; /* Bさんのポイントを更新 */ $sql = 'UPDATE users SET point=? WHERE id=?';

この整合性のための機能は、トランザクションと呼ばれます。MySQLでは、ストレージエンジンがInnoDBの時のみ利用でき、MyISAMでは利用できません。

/* トランザクションを開始する */ $db->beginTransaction(); /* レコードの追加や更新処理 */ /* ロールバック:条件に合わない時、データベースへの変更をなかった事にする */ if () { $db->rollBack(); /* コミット:変更を反映する */ } else { $db->commit(); }

HTMLで画面として表示したい時

PHPでは、HTMLタグの中に処理コードを埋め込むことが出来ます。一般的に、メンテナンスの効率上なるべく処理を分ける必要があるので、表示できる直前の状態まで前処理を行っておいて、出力側では変数から取り出して表示するだけにします。

表示用の変数は、データ毎の配列で、データ名と値の連想配列を入れておくと便利です。 データベースへの保存では ->prepare() が使われますが、エスケープされた状態で入っているため、 取り出して表示などを行なう前に、エスケープ除去を行なう必要があります。

<?php /* エスケープ関数 */ function h($v){ return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); } function eh($v){ echo h($v); } /* DB取得のデータからエスケープ除去 */ function gDATA($d,$k=''){ global $DATA; if (is_array($d)) { return isset($d[$k]) ? stripslashes($d[$k]): ''; } else { return isset($DATA[$d]) ? stripslashes($DATA[$d]): stripslashes($d); } } function ehDATA($d,$k=''){ eh(gDATA($d,$k)); } function eDATA($d,$k=''){ echo gDATA($d,$k); } /* DB取得処理など */ $DATA = $DB->get('list_items'); $DATA['is_sale'] = $DB->get('is_sale'); /* サンプル用表示データ */ $DATA = array( array( 'name'=>'商品A' ), array( 'name'=>'商品B' ), 'is_sale'=>true, ); ?> <?php /* 条件分岐 */ ?> <?php if (gDATA('is_sale')) : ?> <b>セール中</b> <?php else: ?> <b>セール準備中</b> <?php endif; ?> <table> <?php /* 表示用データを順番に処理 */ ?> <?php foreach($DATA as $d) : ?> <?php if (!is_array($d)) continue; ?> <tr> <td><?php eDATA($d,'name'); ?></td> </tr>> <?php endforeach; ?> </table> <select name="month"> <?php /* 繰り返し処理 */ ?> <?php for ($i=1;$i<13;$i++) : ?> <option value="<?php echo $i; ?>"><?php echo $i; ?>月</option> <?php endfor; ?> </select> <?php /* 条件処理 */ ?> <?php switch($mode): case 'editor': ?> <?php /* 編集者用のselect */ ?> <?php break; ?> <?php case 'admin': ?> <?php /* 管理者用のselect */ ?> <?php break; ?> <?php default: ?> <?php /* 一般用のselect */ ?> <?php endswitch; ?>

if():endif;、foreach():endforeach;for():endfor;switch():endswitch;のほか、while():endwhile;も使えます。switch():のみ使い方に注意が必要で、一つ目のcase:を<?php ?>で分けずに、switch(): と同じブロックに記述します。

また、処理コードと表示の分離や簡略化を目指すと、変数内に表示用のタグも入れてしまいがちですが、なるべく表示文字列までとなるようにします。理由はエスケープ分けが難しいのと、表示デザインの開発者と、処理コードの開発者分けが難しくなるためです。

JSONやXMLデータを返したい時

javascriptによるAjax通信を受け取って、JSONデータを返したい場合、API開発でXMLデータを返したい場合などの処理です。

<?php /* クロスドメイン対策 指定ドメインを許可 */ header('Access-Control-Allow-Origin: https://example.com'); /* すべてのドメインを許可 */ header('Access-Control-Allow-Origin: *'); /* 連想配列をJSONデータに */ $json = json_encode($a); /* ヘッダーでレスポンスのタイプ application/json を指定 */ header('Content-Type: application/json; charset=utf-8'); header('Content-Length:'.strlen($json)); echo $json; exit; /* XMLデータを作成 */ $xml = ''; /* ヘッダーでレスポンスのタイプ text/xml を指定 */ header('Content-Type: text/xml; charset=utf-8'); header('Content-Length:'.strlen($xml)); echo $xml; exit;

データをダウンロードさせたい時

ブラウザからユーザにデータをダウンロードさせたい場合の処理です。データベースのエクスポートやファイル群をzip圧縮してダウンロードさせたりなどがあります。

<?php /* ファイルパス */ $fpath = ''; /* サーバに存在するファイルをダウンロードさせる */ header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="保存ファイル名"'); header('Content-Length: '.filesize($fpath)); readfile($fpath); exit; /* ダウンロードさせるデータ */ $data = ''; /* 変数に入っているデータをダウンロードさせる */ header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="保存ファイル名"'); header('Content-Length: '.strlen($data)); echo $data; exit;

クッキーを出力したい時

ユーザーのブラウザにデータを記録する機能としてクッキーがあります。クッキーの内容はユーザーが自由に編集できます。想定外の文字列が入っている事もありえるので取り扱いには注意が必要です。一つのクッキー名に対して、一つの文字列を最大4KBほど保存できます。配列を保存したい場合は、json_encode() が利用できます。

またクッキーは、ヘッダー情報となるので、コンテンツを表示する前に出力する必要があります。

<?php /* クッキーを発行 */ setcookie('クッキー名','値','有効期限','パス','サブドメイン','https制限','javascript制限'); /* ブラウザを閉じるまで有効なクッキー */ setcookie('name','value'); /* 1時間有効なクッキー */ setcookie('name','value',time()+3600); /* 全てのサブドメインで有効なブラウザを閉じると消えるクッキー */ setcookie('name','value',0,'/','example.com'); /* https接続のみでjavascriptでアクセスできないブラウザを閉じるまで有効なクッキー */ setcookie('name','value',0,'/','',true,true); /* 連想配列を文字列に */ $val = json_encode($a); /* URLエンコードせずにクッキーを発行 */ setrawcookie('name', $val); /* クッキーから取得 */ $val = isset($_COOKIE['name']) ? json_decode($_COOKIE['name']): ''; /* クッキーを削除する 過去の時間を設定で削除 */ setcookie('name','',0);

セッションを出力したい時

PHPでは、セッションを管理する機能がついています。セッション内の変数の値などはサーバ側に保存され、セッションにアクセスできる識別子(セッションID)がクッキーでユーザーのブラウザに保存されます。

ユーザーに変更されたり、見せたくないデータを保持する場合などにセッション機能が利用されます。

<?php /* セッションを開始する */ session_start(); /* 現在のセッションID */ $sid = session_id(); /* セッションIDを再発行する */ session_regenerate_id(); /* ユーザーがPOSTしたデータを別ページでも使う場合 */ $_SESSION['post'] = $_POST; /* OAuthで別サイトに移動した後、戻った時のデータ保持など */ $_SESSION['oauth_token'] = $token; /* セッションデータを削除する */ unset($_SESSION['name']);

リダイレクトさせたい時

特定のURLへ移動させる時の処理です。記事が存在しないページへのアクセスをトップ画面へ飛ばしたり、未ログイン状態のユーザーをログイン画面に移動させるなどがあります。リダイレクト後の処理は不要なので exit するようにします。

サーバーの移転などでは、ステータスコード 301 で転送を行なうと、検索エンジンの元サイトへの評価を移転先に引き継げます。モバイル端末からのアクセスでURLを振り分ける場合などは、ステータスコード 302 で元URLの情報を検索エンジンに残すことが出来ます。

<?php /* 遷移させるURL */ $url = ''; /* ドメイン移転時などの場合 */ http_response_code(301); /* モバイル向けのURL変更 */ http_response_code(302); /* URLへ遷移させる */ header('Location: '.$url); exit;