phpでphpファイルを生成する
はじめに
phpでphpファイルを生成する必要があったので、方法についてまとめました。
実現したかったのは、apiから取得したデータを含むphpファイルを動的に生成することで、仕様上、vue, reactでの対応が難しかったため、phpでapiを叩いて、取得したコンテンツの内容を埋め込んだ状態でphpファイルを生成しました。
やりたかったこと
- phpでapiを叩く
- apiを叩いて取得したデータを出力するphpファイルに埋め込む
- 出力されたphpファイルにphpで処理を記述する
- ログイン判定
- 別のphpファイルのinclude
- 環境変数の読み込み etc...
実現のための前提知識
出力バッファリング (Output Buffering)
PHPの出力を一時的にメモリ上に保存する機能。
普段phpは、下記のようなタイミングでメモリ上の処理結果の出力を行なっています。
- PHPスクリプトの処理が全て終わったとき
- 出力バッファが一杯になったとき
今回は出力バッファリングを操作し、メモリ上の処理結果の出力タイミング、出力先をコントロールすることで、「phpの処理結果をphpファイルとして出力」を行います。
今回はこの機能を利用して、「出力結果をファイルに出力」をします。
以下の流れで動作します。
- ob_start(): 出力のバッファリングを開始
- 出力の生成: echo文やHTML出力など
- ob_get_contents(): バッファの内容を取得
- ob_end_clean(): バッファをクリアして終了
出力バッファリングのコントロール例
即時出力(出力を制御)する場合
echo "処理を開始します...<br>";
flush(); // 即座に出力
// 重い処理をシミュレート
for ($i = 1; $i <= 3; $i++) {
sleep(1); // 1秒待機
echo "{$i}番目の処理が完了しました<br>";
flush(); // 即座に出力
}
echo "全ての処理が完了しました<br>";
ブラウザからファイルにアクセスすると、「{$i}番目の処理が完了しました」の文字が1秒ごとにブラウザ上に出力されます。
出力をバッファに蓄積して一括処理する場合
// ===== 出力をバッファに蓄積して一括処理する場合 =====
ob_start();
echo "バッファリング処理を開始します...<br>";
// 重い処理をシミュレート
for ($i = 1; $i <= 3; $i++) {
sleep(1); // 1秒待機
echo "{$i}番目の処理が完了しました<br>";
}
echo "全ての処理が完了しました<br>";
// すべての出力をまとめて表示
ob_end_flush();
3秒後に一気に画面に出力されます。
メモリに保存されている出力を一気にブラウザに出力しています。
実装
出力バッファリングの機能を用いて、処理結果をメモリに保存し、最終的にphpファイルに出力することでphpファイルを生成する例。
<?php
/**
* HTMLファイルを生成する関数
* @return string 生成されたHTML内容
*/
function makeHtmlFile(): string
{
// 出力バッファリングを開始
ob_start();
$title = 'ページタイトル';
$body = 'ここに本文が入ります';
// PHPコードを文字列として定義
$phpCode = <<<PHP
<?php
\$title = "$title" . "test";
\$var1 = 'varvar';
?>
PHP;
echo $phpCode;
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $title; ?></title>
<title><?php echo '<?php echo $title; ?>'; ?></title>
</head>
<body>
<div class="content">
<div class="variables">
<p><?php echo $var1 ?? ''; ?></p>
<p><?php echo '<?php echo $var1; ?>'; ?></p>
</div>
<div class="body-content">
<?php echo $body; ?>
</div>
</div>
</body>
</html>
<?php
// バッファの内容を取得
$content = ob_get_clean();
return $content;
}
try {
$generatedHtml = makeHtmlFile();
// ファイルに保存する場合
$filename = 'generated_' . date('Y-m-d_His') . '.php';
if (file_put_contents($filename, $generatedHtml) === false) {
throw new Exception('ファイルの保存に失敗しました。');
}
} catch (Exception $e) {
error_log($e->getMessage());
}
上記のファイルにブラウザからアクセスすると下記のファイルが生成されます。
生成されるファイル
<?php
$title = "ページタイトル" . "test";
$var1 = 'varvar';
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ページタイトル</title>
<title><?php echo $title; ?></title>
</head>
<body>
<div class="content">
<div class="variables">
<p></p>
<p><?php echo $var1; ?></p>
</div>
<div class="body-content">
ここに本文が入ります </div>
</div>
</body>
</html>
実装の詳細
- 出力バッファリングの制御
function makeHtmlFile(): string
{
ob_start(); // バッファリング開始
// ... コンテンツ生成 ...
$content = ob_get_clean(); // バッファの取得とクリア
return $content;
}
この部分で、全ての出力をメモリ上に一時保存し、最後にまとめて取得しています。
取得したメモリ上のデータは最後にまとめてファイルに出力しています。
try {
$generatedHtml = makeHtmlFile();
// ファイルに保存する場合
$filename = 'generated_' . date('Y-m-d_His') . '.php';
if (file_put_contents($filename, $generatedHtml) === false) {
throw new Exception('ファイルの保存に失敗しました。');
}
} catch (Exception $e) {
error_log($e->getMessage());
}
- 変数の二段階の展開
即時展開される変数
$title = 'ページタイトル';
$body = 'ここに本文が入ります';
これらの変数は、ファイル生成時に値が展開されます。
生成されたファイルで実行時に展開される変数
// PHPコードを文字列として定義
$phpCode = <<<PHP
<?php
\$title = "$title" . "test";
\$var1 = 'varvar';
?>
PHP;
これらはファイル生成時に「文字列」から「phpのコード」に展開されます。
展開されたコードは生成されたファイルの先頭に配置され、そのファイルが実行される時に処理されます。
- 変数の展開タイミングの使い分け
ファイル生成時に展開される例
<title><?php echo $title; ?></title>
// =>
<title>ページタイトル</title>
ファイル実行時に展開される例
<title><?php echo '<?php echo $title; ?>'; ?></title>
// =>
<title><?php echo $title; ?></title>
この実装方法により、以下のような柔軟な対応が可能になります:
- APIから取得したデータの埋め込み
- 実行時の動的な処理
- テンプレートベースのコンテンツ生成
- 条件分岐や環境変数に基づく出力の制御
まとめ
PHPの出力バッファリング機能を利用することで、APIから取得したデータを含むPHPファイルを動的に生成することができます。
実際のプロジェクトでは、生成ファイルの配置場所やパーミッションの設定、APIデータのサニタイズなど、セキュリティ面の考慮が必要になります。
Discussion