レガシーPHPリファクタ 14:コンテンツがほぼ同じエンドポイントPHPの重複をどう解消するか?

今回は、第4章(4.8章)の 「すべて同じ内容のエンドポイントPHPファイルが並んでいる問題」 の解消についての解説です。

ターゲット読者

  • レガシーなSmartyアプリケーションの保守・運用を任されてしまったエンジニア
  • 変更点やページ数が多い一方で、大規模なフレームワーク移行やDB化の工数・予算が取れない現場の担当者

背景:なぜ同じ内容のエンドポイントPHPが乱立するのか?

書籍の執筆当時(2007年)、チュートリアルとしてコードをシンプルに見せるため、あえて .htaccess によるURLルーティングを使用せず、「必要なページごとにPHPファイルとテンプレートファイルを愚直に用意する」という構成にしていました。 .htaccess の解説に紙面を取られるのを避けるという意図もありました。

しかし、この構成は現実のWebサイトのメンテナンスにおいて、次のような「あるあるな課題」を抱えがちです。

  • よくある現場のシチュエーション
    • URLルーティングの仕組みが入っていない
    • FAQ、初めての方へ、サービスの使い方の解説など、「コンテンツ領域だけが違う」多数の静的・半静的ページが存在する
    • 「DB化して管理画面を通したいが工数がない」「外部CMSやサービスに移行したいが予算がない」
    • そのため、しかたなく該当するPHPやテンプレートファイルの文言を直接エディタで編集し、コミット・デプロイを繰り返している

今回のプルリクエスト(#721)では、この「ファイルは別々にあるが、中身は全く同じPHPファイル群」をどのようにスッキリ整理するかを取り上げています。


採用したアプローチの検討

このような重複を解消するため、3つのアプローチを検討しました。

案1:common_render.php 方式(★今回採用)

共通の描画処理を別ファイル(src/common_render.php など)に切り出し、各エンドポイントからはそれを require するだけの構成にする方式です。

  • メリット:
    • .htaccess の設定変更が不要。サーバーの環境差異に強く、安全に導入できる。
    • ファイルの存在有無がそのまま「公開してよいページのホワイトリスト」になるため、セキュリティ事故が起きにくい。
  • デメリット:
    • 2行だけとはいえ、エンドポイントごとのPHPファイル自体は残り続けるため、ファイルの保守が必要。

案2: .htaccess + PHP側での動的検証

すべてのリクエストを index.php 1ファイルに集約し、パスに応じてテンプレートを切り替えます。 index.php 側で渡ってきたパスのホワイトリスト判定やディレクトリトラバーサル対策を行います。

<IfModule mod_rewrite.c>
    RewriteEngine On

    # すでに存在するファイルやディレクトリへのリクエストは、そのまま通す
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]

    # それ以外のすべてのリクエストを index.php に転送し、パス情報を渡す
    RewriteRule ^(.*)$ index.php?path=$1 [QSA,L]
</IfModule>
  • メリット:
    • エンドポイントの物理ファイルが index.php の1つだけで済む。
    • 柔軟なパス制御や一元的なエラー処理が可能。
  • デメリット:
    • PHP側でディレクトリトラバーサルなどの脆弱性に配慮した厳格な実装とテストが必要。
    • サーバーの引っ越しや、Nginxなどの別Webサーバーへ移行する際、URLルーティング設定の書き換えコストが生じる。

案3: .htaccess による静的ホワイトリスト

.htaccess 側に許可するページ(page1.php 〜 page4.php など)を RewriteCond で明示的に列挙し、それ以外を弾く方式です。

<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} "page1.php" [OR]
    RewriteCond %{REQUEST_FILENAME} "page2.php" [OR]
    RewriteCond %{REQUEST_FILENAME} "page3.php" [OR]
    RewriteCond %{REQUEST_FILENAME} "page4.php"
    RewriteRule ^(.*)$ index.php?path=$1 [QSA,L]
</IfModule>
  • メリット:
    • エンドポイントのファイルが index.php の1つだけで済む。
    • 不正なリクエストをPHPアプリケーション層に到達させる前に、Webサーバー層で完全にブロックできる。
  • デメリット:
    • ページを追加・削除するたびに、.htaccess 側の修正・メンテナンスが必要になる。
    • サーバーの引っ越しや、Nginxなどの別Webサーバーへ移行する際、URLルーティングの設定が壊れるリスクや学習コストが生じる。

実際のコード変更

修正前後のコードを見比べてみましょう。

修正前:各ファイルにロジックが重複していた状態

index.php から page4.php まで、すべてのファイルにまったく同じブートストラップ読み込みとSmartyの初期化・アサイン・描画処理が書かれていました。

<?php

use App\Smarty\AppSmarty as Smarty;

require_once dirname(__DIR__, 2) . '/bootstrap/app.php';

// ----- 入力値受取・前処理 -----
$php = basename($_SERVER['SCRIPT_NAME']);
$tpl = preg_replace('/\.php$/', '.tpl', $php);

// ----- テンプレートエンジンの初期化とアサイン・描画 -----
$smarty = new Smarty();
$smarty->assign('currentPage', $php);
$smarty->display('pages/chapter4_8/' . $tpl);

修正後:エンドポイントを極限までシンプルに、処理を共通化

各エンドポイントPHPは、共通の処理を読み込むだけの1行にスリム化しました。

各エンドポイントファイル (index.php 〜 page4.php)

<?php

require __DIR__ . '/src/common_render.php';

共通描画スクリプト (src/common_render.php)

<?php

use App\Smarty\AppSmarty as Smarty;

require_once dirname(__DIR__, 2) . '/bootstrap/app.php';

// ----- 入力値受取・前処理 -----
$php = basename($_SERVER['SCRIPT_NAME']);
$tpl = preg_replace('/\.php$/', '.tpl', $php);

// ----- テンプレートエンジンの初期化とアサイン・描画 -----
$smarty = new Smarty();
$smarty->assign('currentPage', $php);
$smarty->display('pages/chapter4_8/' . $tpl);

まとめ

今回のリファクタリングによって、次のようなメリットが得られました。

  1. 重複コードの排除: 共通の初期化処理や描画ロジックが一箇所にまとまり、将来的な仕様変更(例えば、共通のヘッダー変数を追加するなど)が容易になった。
  2. 保守性の向上: 各ページのPHPファイルが数行の require だけになったため、ノイズが減り、実質的なテンプレートとコンテンツの管理に集中できるようになった。
  3. 低リスク: .htaccess をいじらないため、レンタルサーバー環境や既存のデプロイ手順を一切変更せずに安全に適用できた。

参考PR

refactor(chapter4_8): 各ページの共通描画処理を src/common_render.php へ抽出し $menuTree を一元管理 by ninton · Pull Request #721 · ninton/smartybook
概要各ページ(index.php〜page4.php)で重複していた Smarty の初期化・アサイン・描画処理を src/common_render.php に集約し、メニュー構造データ($menuTree)を一元管理する設計へリファクタ...
レガシーPHPリファクタ 14:コンテンツがほぼ同じエンドポイントPHPの重複をどう解消するか?
Smarty動的Webサイト構築入門サンプルソース. Contribute to ninton/smartybook development by creating an account on GitHub.
タイトルとURLをコピーしました