こんにちは、@ttskch です。

6/28(土) 開催の PHP カンファレンス関西 2014 の LT で発表した内容の補足記事です。 大まかな内容だけ見たい方は、こちらの発表資料をご覧ください。

こんなサイトを作りたかった

  • 基本は普通の静的サイト
  • ページの追加や修正は割と頻繁にある
  • サイト内にブログ機能がほしい (他のページと同じデザインで)

割とよくある要件だと思います。これに対してどういうアプローチが考えられるでしょうか。

どうアプローチするか?

1. 静的サイト + WordPress

一番簡単なのは、普通の静的サイトと WordPress を別々に構築するというアプローチです。

サイトのサブドメインやサブディレクトリに WordPress をインストールする方法ですね。 かなりよく見るパターンだと思います。

この方法の問題点

静的サイト側と WordPress 側でビューのテンプレートを共有できない

同じ HTML を二重に管理する羽目になり、イケてません。

静的サイト側で WordPress タグが使えない

静的サイト側に最新記事を 10 件表示、とかをやりたい場合に、WordPress 側の RSS フィードを使うしか方法がなく、イケてません。

2. WordPress Only

テンプレートを共有したい、WordPress タグを使いたい、ならいっそのことサイト全体を WordPress で構築しちゃえばいいじゃないかというアプローチです。

WordPress には「固定ページ」という機能があり、投稿以外に静的なページを用意することもできるので、ごく真っ当な方法だと言えます。 これも割とよく見るパターンではないでしょうか。

この方法の問題点

静的ページのコンテンツがバージョン管理できない

WordPress の固定ページとして作ってしまうと、ページのコンテンツが DB に保存されるため、バージョン管理ができません。これはイケてませんね。

固定ページそれぞれでテンプレートを別個にして、テンプレートファイル内にコンテンツもろとも全部書いちゃうという荒業もありますが、それだとページの数だけ空の固定ページを作る必要があり、もっとイケてません。

3. WordPress + Silex :)

そこで WordPress + Silex です。

Silex とは、Symfony コンポーネントと Pimple をベースにした PHP のマイクロフレームワークです。マイクロフレームワークというだけあって軽量で手軽に使えるので、ルーティングだけ面倒見てほしい、みたいなときに重宝します。

今回やりたいことは、

  • WordPress をベースにしつつ
  • ファイルを追加すれば静的ページが追加できて
  • WordPress と静的ページでテンプレートを共有したい

でした。

Silex を使えば WordPress のテーマディレクトリ内にルーティング機能を持った簡易なアプリを構築 することができます。これを使って、WordPress でルーティングされたリクエストを、さらに自前でもう一段階ルーティングしてしまえばよいのです。

具体的な実装方法

全体的な流れは以下のようになります。

  1. WordPress をインストール
  2. Silex をインストール
  3. WordPress で固定ページを 1 つだけ作ってフロントページに設定
  4. テンプレートが index.php しかない テーマを作成
  5. index.php に Silex のフロントコントローラ を実装
  6. Silex のルーティングに合わせて各ページのビューを配置

[補足] テンプレートが index.php しかないってどういうこと?

WordPress のテンプレートファイルは、ファイル名によってどのファイルが使われるかが決まりますが、そのファイル名は 1 パターンだけではありません。

例えば、投稿表示テンプレートの場合、優先度順に以下のファイル名がマッチします。

  1. single-{post_type}.php
  2. single.php
  3. index.php

single-{post_type}.php にマッチするテンプレートファイルを探して、あれば使われる。 なければ single.php を探す。single.php があれば使われ、なければ index.php が使われる。 (index.php すらなければエラー)

これは投稿テンプレートだけでなく、全てのテンプレートにおいて、最低優先度のファイル名は index.php になっています。なので、テーマ内に index.php しかテンプレートファイルがなければ、 すべてのリスクエストが index.php に集まる というわけです。 [参考]

では、ステップごとに順に説明していきましょう。

実際に動作する実装例を GitHub に上げておきました ので、ローカル環境にインストールして動かしてみると手っ取り早いかもしれません。

WordPress をインストール

普通に ja.wordpress.org からダウンロードしてインストールしてください。

Silex をインストール

Silex のインストール方法はいくつかありますが、今回は composer を使います。

$ cd /path/to/wordpress
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require silex/silex "~1.2"

固定ページを 1 つだけ作ってフロントページに

中身は空でよいので、固定ページを 1 つだけ作って、表示設定でその固定ページをフロントページに設定しておきましょう。

image

image

テーマを作成

これで準備は整ったので、WordPress にテーマを追加しましょう。 最終的にはテーマ内の構成は以下のような感じになります。

image

順番にファイルを作っていきましょう。

/functions.php

さっそく index.php にルーティングを書いて…と行きたいところですが、せっかく WordPress なのでルーティング前に必要な処理は functions.php にまとめることにしましょう。

<?php
require_once __DIR__ . '/../../../vendor/autoload.php';

date_default_timezone_set('Asia/Tokyo');
mb_language('Japanese');

class SampleUtility
{
    public function renderTemplate($path, $params = [])
    {
        extract($params);
        ob_start();
        require $path;
        $html = ob_get_clean();
        return $html;
    }
}

こんな感じにしてみました。 composer の autoload.php を読み込んで、あとあと必要になりそうなタイムゾーンの設定と mb_language の設定をしています。

SampleUtility クラスは、このテーマで Silex から使いたくなる処理をまとめておくために作ってあります。差し当たり、ビューのファイルを元にレスポンスの HTML を作成する処理は何度も使うので予めメソッド化しておきました。

/index.php

では、いよいよ index.php にルーティングを書いていきましょう。

<?php
$app = new Silex\Application();
$util = new SampleUtility();

// for authors.
$app->get('/archives/author/{route}', function ($route) use ($util) {
    return $util->renderTemplate(__DIR__ . '/views/author.php');
})->assert('route', '.*');

// for posts.
$app->get('/archives/{route}', function ($route) use ($util) {
    return $util->renderTemplate(__DIR__ . '/views/single.php');
})->assert('route', '.*');

// for categories.
$app->get('/category/{route}', function ($route) use ($util) {
    return $util->renderTemplate(__DIR__ . '/views/category.php');
})->assert('route', '.*');

// for tags.
$app->get('/tag/{route}', function ($route) use ($util) {
    return $util->renderTemplate(__DIR__ . '/views/tag.php');
})->assert('route', '.*');

// default.
$app->get('/{route}', function ($route) use ($util) {
    $path = __DIR__ . '/views/pages/' . trim($route, '/') . '.php';
    if (!file_exists($path)) {
        $path = __DIR__ . '/views/pages/' . trim($route, '/') . '/index.php';
        if (!file_exists($path)) {
            return $util->renderTemplate(__DIR__ . '/views/404.php');
        }
    }
    return $util->renderTemplate($path);
})->assert('route', '.*');

$app->run();

ちょっと長いですが、やっていることは単調です。分かりやすいように一部ずつ切り出しながら見てみましょう。

導入部分

<?php
$app = new Silex\Application();
$util = new SampleUtility();

ここは特に説明不要ですね。Silex の初期化と、さっき functions.php に作った SampleUtility クラスのインスタンス化をしているだけです。

WordPress ページのルーティング部分

<?php
// for posts.
$app->get('/archives/{route}', function ($route) use ($util) {
    return $util->renderTemplate(__DIR__ . '/views/single.php');
})->assert('route', '.*');

一箇所だけ切り出してみました。 この部分は、/archives/{route} にマッチする URL が GET でリクエストされたら /views/single.php をレンダリングする、という意味です。

$util->renderTemplate はさっき functions.php に書いたメソッドですね。

->assert('route', '.*') というメソッドコールがくっついていますが、これは {route} の部分を .* でマッチングしますよ、という意味です。

普通は、

<?php
$app->get('/blog/{id}', function ($id) {
    // ...
})->assert('id', '\d+');

のように「整数列しか受け付けませんよ」とかをやりたいときに使うものですが、ここでは スラッシュも含めて丸ごとマッチさせたい という理由で使っています。 [参考]

他の部分もこれの繰り返しで、どの URL がリクエストされたらどのビューでレンダリングするか、というのを全パターン定義しているだけです。

なお、

  • /archives/*
  • /archives/author/*
  • /category/*
  • /tag/*

は WordPress 的な意味を持つ URL ですが、これらの URL が必ずこのパターンになるようにするには、予め WordPress 側で設定をしておく必要があります。

具体的には、WordPress のパーマリンク設定で、以下のように設定しています。

image

URL のパターンで正しくルーティングが振り分けられるようにしてあれば、これと全く同じ設定である必要はありません。 できる人は自分なりに上手いことやってみてください。

静的ページのルーティング部分

<?php
// default.
$app->get('/{route}', function ($route) use ($util) {
    $path = __DIR__ . '/views/pages/' . trim($route, '/') . '.php';
    if (!file_exists($path)) {
        $path = __DIR__ . '/views/pages/' . trim($route, '/') . '/index.php';
        if (!file_exists($path)) {
            return $util->renderTemplate(__DIR__ . '/views/404.php');
        }
    }
    return $util->renderTemplate($path);
})->assert('route', '.*');

'/{route}' であらゆる URL にマッチさせています。 どのルーティングにもマッチしなかったリクエストは全て静的ページと見なす、ということです。

例えば、/aaa/bbb がリクエストされた場合は、/views/pages/aaa/bbb.php がレンダリングされます。 ただし、URL に対応するファイルが存在しない場合は、/views/404.php をレンダリングします。

/style.css

style.css は、無いと WordPress 的にエラーになるので空のファイルを置いてあるだけです。特に使いません。

/views/single.php, /views/author.php, /views/category.php, /views/tag.php

これらのファイルは WordPress の「投稿ページ」「著者ページ」「カテゴリページ」「タグページ」のテンプレートファイルです。普通に WordPress タグを使っていつもどおりテンプレートを書けば OK です。

/views/404.php

先ほど説明したとおり、このファイルを 404 用のページとして作っておきましょう。

/views/pages/ 配下

/views/pages/ 配下はすべて静的ページになります。

ルーティングを適切に設定しておいたので、WordPress 側には何も手を入れなくても、 このディレクトリにファイルを追加するだけで静的ページを追加することができます

まとめ

WordPress と Silex を組み合わせることで、WordPress のブログ機能を活用しつつ、静的ページはファイルベースで管理できる構成を作ることができました。

これで、 静的ページ部分が普通にバージョン管理できますね! めでたしめでたし。

次回予告:テンプレートに Twig を使いたい

Silex には標準で Twig のサービスプロバイダがパッケージされているので、今回の例で言う /views/ 配下のテンプレートファイルで Twig を使うことも結構簡単にできます。

今回はちょっと長くなってしまったので、Twig 導入編はまた次の記事ということで…m(_ _)m