PHPerの悩みどころっていうところで、
WordPressはWordPressのお作法があって、PHPの開発手法とは別だったりとか、
WordPressはMVCでなかったり、とか、スカッフォールディングあったらいいのにとか。
いろいろ悩むのである。
WordPressを拡張したり、プラグインをつくったりというのは、
WordPress使いが思うより、遥かに大変なのである。。。
 


フレームワーク使えたらなぁ

とはいえ、WordPressベースの案件というのはかなり数はある。
さてどうしたものか。
 
僕は普段Yii、Yii2というフレームワークをよく利用する。
http://www.yiiframework.com/
日本ではあまり使われないのだけれども、、
(CakePHPとか、Laravelがよく使われるのかな)
スカッフォールディングが非常に使いやすい、
既存のライブラリに頼らず、しっかりとチューニングしている、
なによりモデルの仕様が非常にわかりやすい。
などの理由でYiiを好んで使っている。
余談だが、日本における有名なYiiの使い手さんは田中ひさてるさんです。
 
閑話休題。
 
WordPressベースの案件にしても、何か簡単にデータベースの管理ができるといいなぁというのが、
PHPerの本音だったりするのでは…。
 
というわけで、どうにかしてみようと思った次第。
その話をしましょう!


Yii1.x系ならプラグインがある!

http://cornernote.github.io/yii-embed-wordpress/
YiiをWordPressのプラグインとしてインストールできるようにしてみようという試みのプラグイン。
うん。すごい。
 
使ってみた。そのまんまではスカッフォールディングなどは使えず。ちょこっと改造が必要。
それについては今度どこかで。
それでもYii1系は非常に見通しが良いフレームワークでPHP5.1.0以降をサポートしている。
この点においては、有用だと思う。
 
ただし、このプラグイン自体がメンテされていないのと、
Yii1.x系も公式には2015年末でサポートを切る予定(セキュリティ面での調整は当面続く)なので
その辺りを十分に認識した上で、利用していく必要がある。
 
やはりYii2でいきたいね。


Yii2でやる!

Yii2でやる!俺はやる!!と決めた。

どうしよう。Yii1のプラグインのようにやっても良いが…簡単ではない…
で、考えた、テーマディレクトリにインストールしたらどうか?
調べたら、codeigniterでやっている人がいた。

なるほど!single.phpなどindex.php以外のテーマファイルがなければ、自動的にindex.phpが、まず呼ばれる。
多くのフレームワークもindex.phpを起点としてルーティングしているので、
そのことを利用すれば、フレームワークをテーマディレクトリにインストールすることはできる!


テーマディレクトリにYii2をインストール

今回は、twentyfifteenテーマをコピーする形で「yii2」というテーマを作っていくことにしましょう。
子テーマで作るなどやり方はいくつかありますが、学習のためということで。
 
下準備として、WordPressをインストール後、
パーマリンク設定もGETパラメータを不要とする設定にしておいてください。
 
まず、wp-content/themes/yii2ディレクトリをつくります。
 
それでその中にYii2をインストールしますが、Yii2はcomposerでインストールします。
WordPressユーザーにはもしかするとcomposerは馴染みがないかもしれません。
composerはPHPのライブラリなどのパッケージをインストール管理をする仕組みです。
最近はアプリケーションのコアをcomposerで提供するなど利用が進んでいます。
Yii2など最近のフレームワークではインストールをcomposerでインストールすることを推奨することが多いので、
覚えておいて損はないでしょう。
 
というわけで、yii2ディレクトリを黒い画面で操作することになります。
 
まずはcomposer自体のインストールを。(Windowsの場合はまた違うの注意)

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

 
つぎにYii2をインストール

cd wp-content/themes/yii2
composer global require "fxp/composer-asset-plugin:~1.0.0"
composer create-project --prefer-dist yiisoft/yii2-app-basic .

 
つぎ、WordPressでは、テーマの構成として

  • index.php
  • style.css
  • functions.php

を最低限必要なファイルとなっています。
index.phpは、YiiによってインストールされているのでOKですから、
style.cssとfunctions.phpをtwentyfifteenフォルダからyii2フォルダにコピーします。
 
Yii2のインストール初期値では、
webという名前のディレクトリを公開ディレクトリして想定していますので、
構造を変更します。
wp-content/themes/yii2/web/
ディレクトリ内の各ファイルを
wp-content/themes/yii2/
ディレクトリへ移動し、
wp-content/themes/yii2/web/
を削除します。
 

これで「インストールの作業」は終了です。
ここからは「設定の作業」です。


style.css

WordPressでは、テーマ名などをstyle.cssの冒頭コメントから取得する仕組みになっていますので、
まずstyle.cssを変更しましょう。

/*
Theme Name: Yii2
Theme URI: http://agilmente.com/
Author: the Kiyonori Ito
Author URI: http://agilmente.com/
Description: Yii2 on WordPress Test!
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Tags: black, 
Text Domain: Yii2

This theme, like WordPress, is licensed under the GPL.
Use it to make something cool, have fun, and share what you've learned with others.
*/

冒頭をこのように自分のテーマのものとして書き換えます。
 
OK!NEXT!


functions.php

とくにやることなし!
OK!NEXT!


index.php

index.phpにはちょっと仕掛けが必要です。

  • yiiの公開ディレクトリを変更したための書き換え
  • WordPressがmagic_quotesを使うことを前提としている

特に後者が厄介で、PHP5.3では非推奨となっていますが、WordPressのお作法としては必須で、
WordPress自前のmagic_quotesがグローバルに影響を与えています。
(この辺が WordPress ≠PHPなわけなんですが)
 
というわけで、これを回避しましょう。

// comment out the following two lines when deployed to production
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require(__DIR__ . '/vendor/autoload.php');
require(__DIR__ . '/vendor/yiisoft/yii2/Yii.php');

$config = require(__DIR__ . '/config/web.php');

//wordpressのマジッククォーツを回避
$_GET = stripslashes_deep($_GET);
$_POST = stripslashes_deep($_POST);
//stripslashes_deep($_REQUEST);//リクエスト全体も必要な場合。

(new yii\web\Application($config))->run();

このように書き換えます。

OK!NEXT!


config/web.php

Yii2の設定ファイルを変更します。
 
まず、$config[‘components’]に

        //ショートカットURL
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'enableStrictParsing' => false,
            'rules' => [
                '' => 'wordpress/index',
            ],
        ],

を追加。デフォルトインストール状態では、Yii2はPrettyUrlになっていませんので、これを有効にします。
また、一旦どんなURLだろうとも、wordpressというYii2のコントローラーにいれてしまう設定をします。
これについては後述。
 
次に、同じく$config[‘components’]に

        //アセットの場所を設定
        'assetManager' => [
            'basePath' => get_template_directory() . '/assets/',
            'baseUrl' => get_template_directory_uri() . '/assets/',
        ],

を設定します。
これは、Yiiが認識するwebrootと、実際のYiiのインストールディレクトリにズレがあるのを修正します。
 
次、$config[‘components’][‘request’]に

    'enableCsrfValidation' => false,

を設定します。いまのところまだ研究中なんですが、Yii2のCSRF対策がうまく効かなくなるので、自動化は中止します。
必要であれば自前のヘルパを用意するのがよいでしょう。
 
OK!NEXT!


config/db.php

データベース設定です。これはWPの設定を引き継ぎたいところです。

return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=' . DB_CHARSET,
    'username' => DB_USER,
    'password' => DB_PASSWORD,
    'charset' => 'utf8',
	'tablePrefix' => $wpdb->prefix
];

このように設定します。WPの定数とプリフィックスを取得して設定すればOK。

OK!これで設定は完了です。
次に必要なのはコントローラーの設置です。


controllers/WordpressController.php

controllersフォルダの中にWordpressController.phpを作成し

namespace app\controllers;
use Yii;

class WordPressController extends \yii\web\Controller
{
    public function actionIndex()
    {
		//ルーティングを書き換える
		$route = $this->checkMethod();
		if( $route !== FALSE )
		{
			return Yii::$app->runAction($route);
		}
		return $this->wp();
    }
	
	// ----------------------------------------------------
	/**
	 * WP用の外出し
	 */
	protected function wp()
	{
		
		if ( is_robots() ) {
			do_action('do_robots');
			return;
		}
		else if ( is_feed() ) {
			do_feed();
			return;
		}
		else if ( is_trackback() ) {
			include(ABSPATH . 'wp-trackback.php');
			return;
		}
		else if ( is_404() ) {
			return $this->renderPartial('404' );
		}
		else if ( is_search() )
		{
			return $this->renderPartial('search' );
		}
		else if ( is_tax() )
		{
			return $this->renderPartial('taxonomy' );
		}
		else if ( is_home() )
		{
			return $this->renderPartial('index' );
		}
		else if ( is_attachment() )
		{
			//remove_filter('the_content', 'prepend_attachment');
			return $this->renderPartial('attachment' );
		}
		else if ( is_single() )
		{
			return $this->renderPartial('single' );
		}
		else if ( is_page() )
		{
			return $this->renderPartial('page' );
		}
		else if ( is_category() )
		{
			return $this->renderPartial('archive' );
		}
		else if ( is_tag() )
		{
			return $this->renderPartial('archive' );
		}
		else if ( is_author() )
		{
			return $this->renderPartial('author' );
		}
		else if ( is_date() )
		{
			return $this->renderPartial('archive' );
		}
		else if ( is_archive() )
		{
			return $this->renderPartial('archive' );
		}
		else if ( is_comments_popup() )
		{
			return $this->renderPartial('comments_popup' );
		}
		else if ( is_paged() )
		{
			return $this->renderPartial('paged' );
		}
		else
		{
			return $this->renderPartial('index' );
		}
	}
	
	// ----------------------------------------------------
	/**
	 * コントローラーメソッドの存在チェック
	 */
	private function checkMethod()
	{
		$pathInfo = explode( '/', Yii::$app->request->pathInfo );
		$controllerList = [];
		while( count($pathInfo) > 0 )
		{
			$controllerList[] = array_shift( $pathInfo );
			$method = $pathInfo[0];
			$controllerListSet = $controllerList;
			$contorollerName = array_pop($controllerListSet);
			$contorollerName = ucfirst( $contorollerName ) . 'Controller';
			
			$controllerFile = 
				Yii::getAlias('@app')
				. '/controllers/'
				. (
					(
						empty($controllerListSet)
					)?'':implode( '/', $controllerListSet ) . '/' 
				  )
				. $contorollerName
				. '.php';
			$method = $pathInfo[0];
			if( $method == '')
			{
				$method = 'index';
			}
			
			if(file_exists($controllerFile))
			{
				$space = 'app\\controllers\\' . $contorollerName;
				$setMethod = 'action' . ucfirst($method);
				$methodExists = method_exists( $space, $setMethod );
				if( $methodExists === true )
				{
					$return = implode( '/', $controllerList ) . '/' . $method;
					return $return;
				}
			}
		}
		return false;
	}
	
	// ----------------------------------------------------

}

と書きます。
ここでは何をやっているかと言いますと、上述のweb.php設定の中で、
いかなるURLであろうとも、一旦wordpressコントローラーのindexアクションを呼ぶように設定しています。
このアクションの中で、WPの投稿であればsingle.phpなどを呼び、そうでなければ、
Yiiのコントローラーアクションを呼ぶという設定をします。
YiiではHMVCというデザインパターンを採用されており、
コントローラアクション内から別のコントローラアクションをコールできるようにしています。
 
そのことを利用して

      URLからYiiのコントローラーアクションがあればHMVCコール
      WPのページならWPのテンプレート

という順番でルーティングを処理するようにしています。
ここのところで少々速度を食われてしまうわけですが…
 
現時点ではGETパラメータをどうするのかという点については、
考慮していません。
ここについてはURL構造の問題ですので、続編をお待ちください。
最低限YiiがWPの中で動くことを想定しています。


ビューファイルをコピー

次に、WPのsingle.phpなどをYiiのビューとしてコピーします。
このとき注意しなくてはならないのは、
single.phpやarchive.phpなどWPから最初にコールされるファイルは
Yiiのビューファイルとして配置。
header.phpなどのテーマファイルの中からコールされるファイルは、
Yiiのルートに置く必要があります。
 
yii2/views/wordpressフォルダを作成し、
その中に

  • 404.php
  • archive.php
  • author-bio.php
  • image.php
  • index.php
  • page.php
  • search.php
  • single.php
  • sidebar.php

をコピー。それ以外のファイルはYiiのindex.phpと並ぶように配置します。
 
よっしゃ次最後!


views/layout/main.php

最後に、Yiiのレイアウトファイルから余分なタグを削ります。
views/layout/main.php

<?php

/* @var $this \yii\web\View */
/* @var $content string */

use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs;
use app\assets\AppAsset;

AppAsset::register($this);
?>
<?php $this->beginPage() ?>
    
    
    
    <?= Html::encode($this->title) ?>
    <?php $this->head() ?>
	
	
	<?php $this->beginBody() ?>

isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], ]) ?>
<?php $this->endBody() ?> <?php $this->endPage() ?>

とします。
 
本来であれば、WP側のheader.phpとの兼ね合いでタイトルを設定したりは必要だと思いますが、
まずはこれだけあれば大丈夫でしょうという動きを。
 


以上で、なんとかWPの中でYii2を動かすという流れでした。
この状態で
http://domain/gii
を動かすとYiiのスカッフォールディングが動きます。


 
▲上記のような画面構成になります。
なんとかWPの中でYiiが動くようになりました。
これで複雑な検索を必要とするページを作ったり、
WPではまかない切れない難しい一覧ページを出力したりできます。
WPのデータベース設定を継承するようにつくられているので、
WPのデータベーステーブルに対してモデルファイルをつくっておけば、
WPの複雑なリレーションに対して簡単なデータ呼び出しも可能になるでしょう。
 
次回は、

  • GETパラメータの処理
  • header.phpの最適化
  • WPのログオン情報を継承してYiiのコントローラにアクセス制限

をやってみます。
 


P.S.
上述の通り、ほとんどのWEBアプリケーションフレームワークは
一旦index.phpが呼ばれる前提になっていますので、
Yii以外でもHMVCがサポートされていれば、
同じアプローチができるとおもいます!
 
出てこい!俺より強いやつ!!