FuelPHPでプログラマーボックスを再構築したので、このフレームワークの良さを伝えてみる。

By@mtoksuyOn
FuelPHPでプログラマーボックスを再構築したので、このフレームワークの良さを伝えてみる。

サーバの移行も完了して前々からローカルでちょこちょこ作成していたFuelPHPで完全書き換えしたプログラマーボックスを本番に上げて、めでたく通常運用になりましたのでFuelPHPとはなんぞや!?というところからFuelPHPの新しい情報の収集する時のサイトなんかを紹介しつつ、どんな感じでFuelPHPで書き換えたかを書いていきたいと思います。

FuelPHPとは?

FuelPHP はシンプルで、フレキシブルな、コミュニティにより開発されている PHP 5 用の Web フレームワークです。現在のフレームワークに対する不満から産まれ、開発者のコミュニティからサポート、支持されています。 FuelPHP はとても移植性が高く、多くのサーバで動き、そして、とても綺麗な構文のフレームワークです。

引用元:FuelPHP ドキュメント

書籍関連のリスト

2012/07 電子書籍 達人出版会

はじめてのフレームワークとしての FuelPHP

友人に勧められ、この電子書籍でFuelPHPと出会いました。内容は優しく構築方法を説明していて基本となる情報が多くて勉強が捗ります。

2013/12 電子書籍 達人出版会

FuelPHP Advent Calendar 2013

無料。2011年、2012年と続き継続されてる事にビックリしました。内容もかなり良くて実践よりのお話が多いので必読です。

2012/12 電子書籍 達人出版会

FuelPHP Advent Calendar 2012

無料。様々な観点からFuelPHPの情報が載っていますのでオススメです。もう少ししたら2013年版も出ると思いますので是非是非読みましょう。(記事公開寸前で出てました)

2012/03 電子書籍 技術評論社

FuelPHP Advent Calendar 2011

無料。EPUB形式なのが残念ですがまだダウンロードして読めますので最初の時点の情報も読んでおくと良いかもしれません。

2012/06 ソーテック社

FuelPHP 入門

ごめんなさい、読んでいません。

2013/12 電子書籍 Kindle

fuelphp 逆引きガイド

FuelPHP Advent Calendar 2013に参加している、みかかねさんが出した本です。いつのまに笑

2012/07 電子書籍Leanpub

FuelPHP に貢献する方法

ごめんなさい、読んでいません。

2013/11 [英語] Packt Publishing[サイト]

Learning FuelPHP for Effective PHP Development

ごめんなさい、読んでいません。

2013/02 [英語] 電子書籍Kindle

FuelPHP Guide

ごめんなさい、読んでいません。

FuelPHP情報を知ろう

本家を元に出来るだけ最小限のサイトセレクトにしました。

FuelPHP[本家]

FuelPHP » Blogs[本家]

FuelPHP[github]

FuelPHP[github][ライブラリ]

FuelPHP.JP 日本語ドキュメント

書籍はじめてのフレームワークとしての FuelPHPサポートサイト

FuelPHP Advent Calendar 2013

FuelPHPの良さを伝えるスライド

数多くあるフレームワークの中で何故FuelPHPが良いのか、文章をずらずら書いても伝わりにくいので素敵なスライドをご紹介します。

プログラマーボックスを構築した際のポイント

実際に、このブログシステムをどのようにFuelPHPで構築したのかをポイント別に説明していきたいと思います。通常のコントローラーの使い方をしていませんので違和感を感じる方もいるかもしれませんがご容赦下さい。

想定ディレクトリ構成

初めにセキュリティーの面でディレクトリ構成をこんな感じにします。

想定ディレクトリ構成
※ バーチャルホストにする場合
www
├ ...
├ ...
├ ...
├ html(デフォルトはここ)
└ vhosts
	├ ...(他のドメイン)
	└ programmerbox.com
	   ├ ...
	   ├ ...
	   ├ httpdocs(FuelPHPを使わない場合はここ)
	   └ app
           ├ ...(他のフレームワークやアプリを入れる場合はここ)
           └ fuelphp(明示的にfuelphpとしていた方がいい。)
              ├ docs
              ├ fuel
              └ public(FuelPHPを導入する場合、ここをドキュメントルートにする。)


通常の場合はこんな感じ
www
├ ...
├ ...
├ ...
├ html
	 └ fuelphp(明示的にfuelphpとしていた方がいい。)
	    ├ docs
	    ├ fuel
	    └ public(FuelPHPを導入する場合、ここをドキュメントルートにする。)

当たり前なのですが、ドキュメントルート上にFuelPHPを置くという事は全てのファイルを実行されてしまうのでFuelPHPのpublicをドキュメントルートにします。

なお、ローカルで開発する場合は様々な環境があるとは思いますが本番環境とローカルのURL構成をまったく同じにしないとセグメントがズレるので同じにします。

(例)

ローカル: http://localhost/programmerbox/
本番: http://programmerbox.com/

独自のセグメント判定機能を作る

多分プログラマーボックスのシステムはここが一番大事な機能です。ここの判定で全ての挙動を変えています。簡単に説明しますと、サイト内で階層別のセグメントを調べて、そのセグメントが存在するかしないかを判定する関数を作ります。

ルーティングで制御を司るようにする

サイトとユーザーの橋渡し役みたいな感じでエラーならエラーを吐き出したり行かせたいコントローラーに行かせて最終的にviewを表示します。

【追記】2014年01月08日
現在、別のプロジェクトをFuelPHPで構築していますが、このルーティング使用法はあまり宜しくないような気がして来ました。root.php内で制御しつつ、コントローラールールに沿って作成していき、特別なセグメントのみルーティングで回避していくのがベストです。

routes.php
ソースコード表示のSyntaxHighlighterを通すとズレてしまいます。。見づらくてすいません。



// 独自セグメント情報取得
$segment_info_get_array = Model_Info_Basis::segment_info_get();

// エラーページ
if($segment_info_get_array["segment_error"] === FALSE) {
  return array(
    '.*?'  => 'error/404', 
  );
}
	else if($segment_info_get_array["article_judgment"] === true && $segment_info_get_array["article_url_error"] === false) {
		return array(
			'.*?'  => 'error/404', 
		);
	}
	  else {
	    return array(
	      '_root_'                                                     => 'root',          // The default route
	      '_404_'                                                      => 'error/404',     // The main 404 route
	      'about'                                                      => 'about',
	      'contact'                                                    => 'contact',
	      'login'                                                      => 'login',
	      'login/admin'                                                => 'login/admin',
	      'login/admin/post'                                           => 'login/admin/post',
	      'login/admin/logout'                                         => 'login/admin/logout',
	      '(([0-9]{0,4})(-|_)([0-9]{0,2})(-|_)([0-9]{0,2})(-|_)(.*))'  => 'article/index', // 記事
	      '[0-9]+?$'                                                   => 'root',          // トップ ページング
	      '.*?/.*?/[0-9].*?$'                                          => 'root',          // 子セグメントページング
	      '.*?/[0-9].*?$'                                              => 'root',          // 親セグメントページング
	      '.*?/.*?'                                                    => 'root',          // 子セグメント
	      '.*?'                                                        => 'root',          // 親セグメント
	    );
	  }

コントローラーを肥大化させない為に

通常FuelPHPではセグメント上でコントローラーを作っていきます。ですが、このままコントローラーを作りますと必ず肥大化してしまいますので、ここは機能別に切り分ける事にしました。

  1. トップ + カテゴリー + ページング
    root.php
  2. 記事機能
    article.php
  3. aboutページ
    about.php
  4. contctページ
    contct.php
  5. ログイン機能
    login.php

もっとくっ付ける事は出来るとは思いますが、関連しない機能を同じにする事はオススメ出来ません。とにかくこれで綺麗なディレクトリになって管理もしやすくなりました。ただ、この形にするにはルーティングをかなりいじる事になりますので、通常はこんな事はしないのかもしれません。(ボクは絶対にした方が良いと思いますが)

通常のコントローラーの使い方

通常のセグメント分けとしてコントローラーを作成する場合はこんな感じになります。(あくまでボクの場合)

コントローラー
※ loginだけにスポットをあてた場合

controller
├ login.php
└ login
	├ admin.php
	└ admin
	   ├ logout.php
	   ├ post.php
	   ├ logout
	   └ post

uriとしてはこんな感じになりますね。
programmerbox.com/login/
programmerbox.com/login/admin/
programmerbox.com/login/admin/post/
programmerbox.com/login/admin/logout/

一つのコントローラーファイルでactionを作成していってもいいのですが
ここも切り分け思想の元、こんな感じの形が望ましいです。

独自のテンプレートを作る

テンプレートを作らないとコントローラーの意味が半減してしまいますので、何パターンかテンプレートを作成します。テンプレートを作成する際はcontroller側とviews側が密な関係になりますのでそこらへんを気をつけて繋げていきます。

controller側

初めにこんなTemplateを作成します。

Template

ソースコード表示のSyntaxHighlighterを通すとズレてしまいます。。見づらくてすいません。

/**
 * コントローラーレイアウトテンプレート
 * 
 * Viewのbasic群をforgeしてテンプレートに流し込み値を返す。
 * 
 * 
 */

class Controller_Basic_Template extends Controller {
	public function __construct(Request $request) {
		$this->request = $request;
	}

	public function before() {
		require APPPATH.'classes/library/autoload.php';
		$this->basic_template = View::forge('basic/template');

		$this->basic_template->view_data = array(
			'title'        => 'ProgrammerBOX -プログラマーボックス-',
			'meta'         => View::forge('basic/meta'),
			'external_css' => View::forge('basic/externalcss'),
			'header'       => View::forge('basic/header'),
			'content'      => View::forge('basic/content'),
			'paging'       => View::forge('basic/paging'),
			'side_bar'     => View::forge('basic/sidebar'),
			'footer'       => View::forge('basic/footer'),
			'script'       => View::forge('basic/script'),
		);
		// カテゴリーHTML生成
		$category_ul_html = Model_Sidebar_Basis::sidebar_category_html_create();
		// 人気記事HTML生成
		$popular_li_html = Model_Sidebar_Basis::sidebar_popular_html_create();

		// サイドバーカテゴリーセット
		$this->basic_template->view_data["side_bar"]->set('sidebar_data', array(
			'category_ul_html' => $category_ul_html,
			'popular_li_html'  => $popular_li_html,
		), false);
	}
	public function after($response) {
		if($response === null) {
			$response = $this->basic_template;
		}
		return parent::after($response);
	}
}

このTemplateを継承します。

root.php
/**
 * コントローラー root.php
 * 
 * 
 * 
 * 
 */


class Controller_Root extends Controller_Basic_Template {
	public function before() {
		parent::before();
	}

	public function action_index() {
		// セグメント情報取得
		$segment_info_get_array = Model_Info_Basis::segment_info_get();

		// タイトルセット
		$this->basic_template->view_data["title"] = $segment_info_get_array["title_segment"].TITLE;

		// メタセット(トップのみ)
		if($segment_info_get_array["top_judgment"] == true) {
			$this->basic_template->view_data["meta"] = View::forge('root/meta');
		}
		// 記事一覧データ取得
		$list_query             = Model_Article_Basis::list_get($segment_info_get_array);
		// 記事一覧HTML生成
		$article_list_html      = Model_Article_Basis::list_html_create($list_query);
		// コンテンツデータセット
		$this->basic_template->view_data["content"]->set('content_data', array(
			'summary_query' => $list_query,
			'value'         => $article_list_html,
		), false);

		// ページングHTML追加
		$paging_html = Model_Paging_Basis::paging_html_create($segment_info_get_array);
		// ページングセット
		$this->basic_template->view_data["paging"]->set('paging_data', array(
			'paging_html' => $paging_html,
		), false);
	}
}

その時その時のセグメントで必要なデータをModelからもらって来てViewに渡します。たったこれだけで記事やプライマリーなページ以外をこの2つだけで表示出来るようになりました。便利。

views側

Controller_Basic_Templateで使おうとしているViewファイルを用意しなくては行けませんので用意します。なお、中身はHTMLと渡している変数だけなので省略させて頂きます。

views

views
└ basic
   ├ content.php
   ├ externalcss.php
   ├ footer.php
   ├ header.php
   ├ meta.php
   ├ paging.php
   ├ script.php
   ├ sidebar.php
   └ template.php

機能毎にmodelで切り分ける

Modelの部分では機能毎に切り分けて関数を作成していきました。

Model構成
model
├ article
├ info
├ login
├ mail
├ paging
└ sidebar

DBのキャッシュ機能を使う

様々なキャッシュ機能がありますが、今回はDBクラスのキャッシュ機能のみ使用しています。

キャッシュ(例)
// 同じqueryを一日だけキャッシュする
		$query = DB::query("
			SELECT * 
			FROM user")->cached(86400)->execute();

ブログという性質上ずっと同じデータを吐き出しますので、キャッシュ機能を存分に使った方が表示スピードが速くなります。ただ、記事の内容が変更された場合にキャッシュを読み込まないようにした方がいいのですが、その機能は作ってないので改善の余地はあります。

assetsフォルダに対応するべくパッチファイルを作成

あまり関係ないのですが、FuelPHPでは画像やcssをassetsというフォルダにいれる習慣?があったのでここも踏襲したくて、データ移行するときのDBの記事データをそのまま表示しますと画像のパスが間違っていますので、全ての記事データを直す為に一括で変更するパッチファイルを作成して実行しました。

スモールWordPressを作る

サイト上から記事の更新や編集をする為にログイン機能を作成して、取り敢えず更新だけ出来るようにしました。もしかすると、このままバージョンアップしていけばFuelPHP仕様のブログ用CMSをリリースするかもしれません。

FuelPHPで再構築した結果

本来ならばチームやプロジェクト内で使用してこそのフレームワークなので一人で扱ってもあまり意味はないとは思ったのですが、それでも良い結果を得られたと実感しています。

サーバサイドの実行スピードは遅くなる

当たり前ですがフルスクラッチでオレオレフレームワークで構築していた頃よりも若干ですが、遅くなりました。というかサーバも変えたのでどのくらいってなると数字が出せないのですが大体10ms〜20msくらいフレームワークを通した分は遅くなりました。

記事を作成してもディレクトリを生成しないので管理が楽になる

前のシステムでは記事を更新する時に全自動ですが、全てのディレクトリ(記事、カテゴリー、ページング)をわざわざ生成してたので、ここの部分はかなり管理が楽になったように感じます。

このブログはまだまだ記事数がないのにも関わらずそう思ってしまったので早めに解決できて良かったです。

機能毎にきっちり分けるので管理と共にアップデートも楽になる

前のブログシステムでは関数の場所やクラスの在り方が曖昧でしたので、機能毎にきっちり分けられて管理が物凄く楽になりました。

DBのキャッシュ機能でかなりパフォーマンスを上げている

実は前のシステムではアクセスがある度に毎回queryを投げてDBを圧迫していました。一日のquery回数で表すと前は10000回queryを投げていたと仮定して相対的に計算すると現在では100〜200回queryを投げているくらいなのでここの部分のパフォーマンスは上がりました。

まとめ

FuelPHPと言ったら世界ではまだそんなに流行っていなくて逆に日本では流行る兆しがあるような流れがあります。

Ruby on Rails、Zend Framework、CakePHP、Symfonyの「設定よりも規約」とFuelPHPの「規約より設定」のどちらが正しいかはわかりませんが、FuelPHPの現状は超軽量なので「規約より設定」でも問題なくて、その自由さが日本人には良かったのかもしれません。

実はFuelPHPを勉強するのにかなり時間がかかってしまいました。最初の頃は本当に扱いづらいフレームワークだなって感じていたのですが、一つ一つの機能を覚える毎に逆に扱いやすいフレームワークになっていきました。今でも全ての機能を使った事がないので最適な設計をしたとは言い難いですが、現状、満足しています。次のプロジェクトを開発する時もFuelPHPで構築して、FeulPHPの良い使い方を身につけ発信出来れば良いと思っております。

webサービス