FuelPHP での「管理パネル」チュートリアル
この記事は FuelPHP 1.1 を前提にしています。また、Build an Admin Panel with the Fuel PHP Framework を基にしています。
このチュートリアルでは、ブログの管理ページを作成し管理者が記事を投稿できるようにし、フロントエンドの記事一覧ページと個別の記事表示ページを作成し、読者が記事にコメントを投稿できるようにします。
FuelPHP の oil コマンドでコードを生成し、ORM を使います。
FuelPHP のインストール
最初に oil コマンドをインストールします。
$ curl get.fuelphp.com/oil | sh
上記のコマンドは、/usr/bin/ に oil コマンドをインストールします。
続いて、FuelPHP をインストールします。
$ oil create blog
上記のコマンドは、カレントフォルダに blog フォルダを作成し、その中に FuelPHP のリポジトリを GitHub から clone します。
Note: コマンドラインを使わずに 手動でインストールする方法 もあります。
Apache の設定
blog/public/ を http://localhost/blog/ でアクセスできるように Apache を設定します。
http://localhost/blog/ に Web ブラウザでアクセスすると以下のように表示されるはずです。
データベースの設定
MySQL にデータベースを作成します。
CREATE DATABASE `blog_example` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
fuel/app/config/development/db.php を以下のように変更します。
<?php /** * The development database settings. */ return array( 'default' => array( 'type' => 'mysql', 'connection' => array( 'hostname' => 'localhost', 'port' => '3306', 'database' => 'blog_example', 'username' => 'root', 'password' => '', ), 'table_prefix' => 'ap_', ), );
FuelPHP の設定
FuelPHP の設定ファイルである config.php を変更します。
oil コマンドで生成するコードが、ビューに Validation オブジェクトを渡すため、Fuel\Core\Validation クラスを security の whitelisted_classes に追加します。
Auth、Orm の 2つのパッケージを使うため、always_load の packages に設定します。
--- a/fuel/app/config/config.php +++ b/fuel/app/config/config.php @@ -136,6 +136,7 @@ return array( 'Fuel\\Core\\Response', 'Fuel\\Core\\View', 'Fuel\\Core\\ViewModel', + 'Fuel\\Core\\Validation', 'Closure', ) ), @@ -185,7 +185,8 @@ return array( * ); */ 'packages' => array( - //'orm', + 'auth', + 'orm', ), /**
Note: 安易に whitelisted_classes に追加し、ビューの中でオブジェクトを使うと XSS 脆弱性ができる可能性がありますので、注意してください。
SimpleAuth の設定
Auth パッケージには、SimpleAuth というドライバが含まれています。その設定をします。
fuel/packages/auth/config/simpleauth.php を fuel/app/config/simpleauth.php にコピーします。
そして、fuel/app/config/simpleauth.php の groups を以下のように変更します。この設定は、コメントアウトされている Example そのままです。これは、グループの定義になります。
'groups' => array( -1 => array('name' => 'Banned', 'roles' => array('banned')), 0 => array('name' => 'Guests', 'roles' => array()), 1 => array('name' => 'Users', 'roles' => array('user')), 50 => array('name' => 'Moderators', 'roles' => array('user', 'moderator')), 100 => array('name' => 'Administrators', 'roles' => array('user', 'moderator', 'admin')), ),
User モデルの作成
ここから、oil コマンドを使って、コードを生成していきます。
まず、User モデルと users テーブルを作成します。引数は users とします。
$ oil generate model users username:varchar[50] password:string group:int email:string last_login:int login_hash:string profile_fields:text
以下のファイルが生成されます。
fuel/app/classes/model/user.php:
<?php class Model_User extends \Orm\Model { protected static $_properties = array( 'id', 'username', 'password', 'group', 'email', 'last_login', 'login_hash', 'profile_fields', 'created_at', 'updated_at' ); protected static $_observers = array( 'Orm\Observer_CreatedAt' => array( 'events' => array('before_insert'), 'mysql_timestamp' => false, ), 'Orm\Observer_UpdatedAt' => array( 'events' => array('before_save'), 'mysql_timestamp' => false, ), ); }
fuel/app/migrations/001_create_users.php:
<?php namespace Fuel\Migrations; class Create_users { public function up() { \DBUtil::create_table('users', array( 'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true), 'username' => array('constraint' => 50, 'type' => 'varchar'), 'password' => array('constraint' => 255, 'type' => 'varchar'), 'group' => array('constraint' => 11, 'type' => 'int'), 'email' => array('constraint' => 255, 'type' => 'varchar'), 'last_login' => array('constraint' => 11, 'type' => 'int'), 'login_hash' => array('constraint' => 255, 'type' => 'varchar'), 'profile_fields' => array('type' => 'text'), 'created_at' => array('constraint' => 11, 'type' => 'int'), 'updated_at' => array('constraint' => 11, 'type' => 'int'), ), array('id')); } public function down() { \DBUtil::drop_table('users'); } }
マイグレーションを実行し、実際にテーブルを作成します。
$ oil refine migrate
これで、データベースにテーブルが作成されました。
管理者の登録
oil console コマンドを使うと、コンソールから FuelPHP へコードを渡し実行することができます。この機能を使い、管理者を登録しておきます。
コンソールから Auth パッケージの create_user() メソッドを実行し、ユーザを作成します。
$ oil console Fuel 1.1 - PHP 5.3.8 (cli) (Sep 19 2011 13:26:12) [Linux] >>> Auth::create_user('admin', 'password', 'admin@example.jp', 100); Warning - Cannot modify header information - headers already sent by (output started at .../blog/fuel/core/classes/cli.php:127) in COREPATH/classes/cookie.php on line 95 1 >>> ^C
コンソールからは Ctrl + C で抜けます。
管理ページの生成
FuelPHP 1.1 では、管理ページを生成する oil generate admin コマンドが追加されました。
このコマンドは、scaffold にユーザ認証を追加したコードを生成します。
これで、Post モデルと posts テーブル、関連するコントローラとビューを生成します。
$ oil generate admin posts title:string slug:string summary:text body:text user_id:int
生成されるファイルは以下の通りです。
Creating controller: .../blog/fuel/app/classes/controller/base.php
Creating controller: .../blog/fuel/app/classes/controller/admin.php
Creating views: .../blog/fuel/app/views/admin/template.php
Creating views: .../blog/fuel/app/views/admin/dashboard.php
Creating views: .../blog/fuel/app/views/admin/login.php
Creating migration: .../blog/fuel/app/migrations/002_create_posts.php
Creating model: .../blog/fuel/app/classes/model/post.php
Creating controller: .../blog/fuel/app/classes/controller/admin/posts.php
Creating view: .../blog/fuel/app/views/admin/posts/index.php
Creating view: .../blog/fuel/app/views/admin/posts/view.php
Creating view: .../blog/fuel/app/views/admin/posts/create.php
Creating view: .../blog/fuel/app/views/admin/posts/edit.php
Creating view: .../blog/fuel/app/views/admin/posts/_form.php
Creating view: .../blog/fuel/app/views/template.php
マイグレーションを実行し、実際にテーブルを作成します。
$ oil refine migrate
作成された管理ページを見てみる
http://localhost/blog/index.php/admin/posts にアクセスします。
http://localhost/blog/index.php/admin/login へリダイレクトされました。
先ほど作成した管理者の ID とパスワードでログインします。
Dashboard が表示されました。
記事投稿ページ (Post) の CRUD フォームを改良する
CRUD フォームを改良します。
slug フィールドを title から自動的に生成するようにし、user_id フィールドはユーザのリストをセレクトメニューから選択できるようにします。
Controller_Admin_Posts クラスの action_create() メソッドを変更します。
--- a/fuel/app/classes/controller/admin/posts.php +++ b/fuel/app/classes/controller/admin/posts.php @@ -29,7 +29,7 @@ class Controller_Admin_Posts extends Controller_Admin { $post = Model_Post::forge(array( 'title' => Input::post('title'), - 'slug' => Input::post('slug'), + 'slug' => Inflector::friendly_title(Input::post('title'), '-', true), 'summary' => Input::post('summary'), 'body' => Input::post('body'), 'user_id' => Input::post('user_id'), @@ -53,9 +53,13 @@ class Controller_Admin_Posts extends Controller_Admin } } - $this->template->title = "Posts"; - $this->template->content = View::forge('admin/posts/create'); + $this->template->title = "Create Post"; + $view = View::forge('admin/posts/create'); + + // Set some data + $view->set_global('users', Arr::assoc_to_keyval(Model_User::find('all'), 'id', 'username')); + $this->template->content = $view; } public function action_edit($id = null)
Note: Inflector::friendly_title() メソッドは日本語では使えません。
同じように action_edit() メソッドを変更します。
@@ -66,7 +70,7 @@ class Controller_Admin_Posts extends Controller_Admin if ($val->run()) { $post->title = Input::post('title'); - $post->slug = Input::post('slug'); + $post->slug = Inflector::friendly_title(Input::post('title'), '-', true); $post->summary = Input::post('summary'); $post->body = Input::post('body'); $post->user_id = Input::post('user_id'); @@ -100,9 +104,13 @@ class Controller_Admin_Posts extends Controller_Admin $this->template->set_global('post', $post, false); } - $this->template->title = "Posts"; - $this->template->content = View::forge('admin/posts/edit'); + $this->template->title = "Edit Post"; + $view = View::forge('admin/posts/edit'); + + // Set some data + $view->set_global('users', Arr::assoc_to_keyval(Model_User::find('all'), 'id', 'username')); + $this->template->content = $view; } public function action_delete($id = null)
ビューファイルを変更します。
--- a/fuel/app/views/admin/posts/_form.php +++ b/fuel/app/views/admin/posts/_form.php @@ -10,14 +10,6 @@ </div> </div> <div class="clearfix"> - <?php echo Form::label('Slug', 'slug'); ?> - - <div class="input"> - <?php echo Form::input('slug', Input::post('slug', isset($post) ? $post->slug : ''), array('class' => 'span6')); ?> - - </div> - </div> - <div class="clearfix"> <?php echo Form::label('Summary', 'summary'); ?> <div class="input"> @@ -37,7 +29,7 @@ <?php echo Form::label('User id', 'user_id'); ?> <div class="input"> - <?php echo Form::input('user_id', Input::post('user_id', isset($post) ? $post->user_id : ''), array('class' => 'span6')); ?> + <?php echo Form::select('user_id', Input::post('user_id', isset($post) ? $post->user_id : $current_user->id), $users, array('class' => 'span6')); ?> </div> </div>
最後に、Model_Post クラスの validate() メソッドで定義されているバリデーションのルールを変更します。slug はフォームからは投稿されませんのでコメントアウトしておきます。
--- a/fuel/app/classes/model/post.php +++ b/fuel/app/classes/model/post.php @@ -27,7 +27,7 @@ class Model_Post extends \Orm\Model { $val = Validation::forge($factory); $val->add_field('title', 'Title', 'required|max_length[255]'); - $val->add_field('slug', 'Slug', 'required|max_length[255]'); + //$val->add_field('slug', 'Slug', 'required|max_length[255]'); $val->add_field('summary', 'Summary', 'required'); $val->add_field('body', 'Body', 'required'); $val->add_field('user_id', 'User Id', 'required|valid_string[numeric]');
わかりやすいように、ユーザを 1人追加しておきましょう。
$ oil console Fuel 1.1 - PHP 5.3.8 (cli) (Sep 19 2011 13:26:12) [Linux] >>> Auth::create_user('user1', 'password', 'user1@example.jp', 1);
http://localhost/blog/index.php/admin/posts/create にアクセスします。
フロントエンドの作成
フロントエンドの記事一覧ページを作成します。
fuel/app/classes/controller/blog.php:
<?php class Controller_Blog extends Controller_Base { public function action_index() { $view = View::forge('blog/index'); $view->posts = Model_Post::find('all'); $this->template->title = 'My Awesome Blog'; $this->template->content = $view; } }
fuel/app/views/blog/index.php:
<h2>Recent Posts</h2> <?php foreach ($posts as $post): ?> <h3><?php echo Html::anchor('blog/view/'.$post->slug, $post->title) ?></h3> <p><?php echo $post->summary ?></p> <?php endforeach; ?>
http://localhost/blog/index.php/blog にアクセスします。
続いて、記事を表示するページも作成します。
--- a/fuel/app/classes/controller/blog.php +++ b/fuel/app/classes/controller/blog.php @@ -11,4 +11,14 @@ class Controller_Blog extends Controller_Base $this->template->title = 'My Awesome Blog'; $this->template->content = $view; } + + public function action_view($slug) + { + $post = Model_Post::find_by_slug($slug); + + $this->template->title = $post->title; + $this->template->content = View::forge('blog/view', array( + 'post' => $post, + )); + } }
fuel/app/views/blog/view.php:
<h2><?php echo $post->title ?></h2> <p><strong>Posted: </strong><?php echo date('jS F, Y', $post->created_at) ?> (<?php echo Date::time_ago($post->created_at) ?>)</p> <p><?php echo nl2br($post->body) ?></p>
記事にアクセスしてみます。
ORM を使ってみる
ORM にリレーションを追加します。
記事 (Post) は 1人のユーザ (User) により作成されますので、関係は、
a post belongs to a user
となります。
Model_Post モデルに $_belongs_to プロパティを追加します。
--- a/fuel/app/classes/model/post.php +++ b/fuel/app/classes/model/post.php @@ -1,6 +1,8 @@ <?php class Model_Post extends \Orm\Model { + protected static $_belongs_to = array('user'); + protected static $_properties = array( 'id', 'title',
一方、ユーザ (User) は複数の記事 (Post) を投稿できますので、関係は、
a user has many posts
となります。
Model_User モデルに $_has_many プロパティを追加します。
--- a/fuel/app/classes/model/user.php +++ b/fuel/app/classes/model/user.php @@ -2,6 +2,8 @@ class Model_User extends \Orm\Model { + protected static $_has_many = array('posts'); + protected static $_properties = array( 'id', 'username',
oil console から確認してみます。
$ oil console Fuel 1.1 - PHP 5.3.8 (cli) (Sep 19 2011 13:26:12) [Linux] >>> $post = Model_Post::find('first'); >>> $post->user->username; admin
うまくユーザ名が取得できました。
view.php を変更し、ユーザ名を表示するようにします。
--- a/fuel/app/views/blog/view.php +++ b/fuel/app/views/blog/view.php @@ -1,5 +1,9 @@ <h2><?php echo $post->title ?></h2> -<p><strong>Posted: </strong><?php echo date('jS F, Y', $post->created_at) ?> (<?php echo Date::time_ago($post->created_at) ?>)</p> +<p> + <strong>Posted: </strong><?php echo date('jS F, Y', $post->created_at) ?> + (<?php echo Date::time_ago($post->created_at) ?>) + by <?php echo $post->user->username ?> +</p> <p><?php echo nl2br($post->body) ?></p>
データベースクエリのプロファイル
データベースのクエリをプロファイルに表示させてみましょう。
設定ファイルを変更します。
--- a/fuel/app/config/config.php +++ b/fuel/app/config/config.php @@ -43,7 +43,7 @@ return array( */ 'index_file' => 'index.php', - 'profiling' => false, + 'profiling' => true, /** * Settings for Cache class
--- a/fuel/app/config/development/db.php +++ b/fuel/app/config/development/db.php @@ -14,5 +14,6 @@ return array( 'password' => '', ), 'table_prefix' => 'ap_', + 'profiling' => true, ), );
以下のクエリが実行されたことがわかります。
SELECT * FROM `ap_users` WHERE `username` = 'admin'
Type: ALL ·Rows: 2 ·Speed: 0.430 ms
SELECT `ap_t0`.`id` AS `t0_c0`, `ap_t0`.`username` AS `t0_c1`, `ap_t0`.`password` AS `t0_c2`, `ap_t0`.`group` AS `t0_c3`, `ap_t0`.`email` AS `t0_c4`, `ap_t0`.`last_login` AS `t0_c5`, `ap_t0`.`login_hash` AS `t0_c6`, `ap_t0`.`profile_fields` AS `t0_c7`, `ap_t0`.`created_at` AS `t0_c8`, `ap_t0`.`updated_at` AS `t0_c9` FROM `ap_users` AS `ap_t0` WHERE (`ap_t0`.`username` = 'admin') ORDER BY `ap_t0`.`id` ASC, `ap_t0`.`id` ASC LIMIT 1
Type: ALL ·Rows: 2 ·Speed: 0.535 ms
SELECT `ap_t0`.`id` AS `t0_c0`, `ap_t0`.`title` AS `t0_c1`, `ap_t0`.`slug` AS `t0_c2`, `ap_t0`.`summary` AS `t0_c3`, `ap_t0`.`body` AS `t0_c4`, `ap_t0`.`user_id` AS `t0_c5`, `ap_t0`.`created_at` AS `t0_c6`, `ap_t0`.`updated_at` AS `t0_c7` FROM `ap_posts` AS `ap_t0` WHERE (`ap_t0`.`slug` = 'fuelphp-is-awesome') ORDER BY `ap_t0`.`id` ASC, `ap_t0`.`id` ASC LIMIT 1
Type: ALL ·Rows: 2 ·Speed: 0.391 ms
SELECT `ap_t0`.`id` AS `t0_c0`, `ap_t0`.`username` AS `t0_c1`, `ap_t0`.`password` AS `t0_c2`, `ap_t0`.`group` AS `t0_c3`, `ap_t0`.`email` AS `t0_c4`, `ap_t0`.`last_login` AS `t0_c5`, `ap_t0`.`login_hash` AS `t0_c6`, `ap_t0`.`profile_fields` AS `t0_c7`, `ap_t0`.`created_at` AS `t0_c8`, `ap_t0`.`updated_at` AS `t0_c9` FROM `ap_users` AS `ap_t0` WHERE `ap_t0`.`id` = '1' LIMIT 1
Possible keys: PRIMARY ·Key Used: PRIMARY ·Type: const ·Rows: 1 ·Speed: 0.475 ms
Eager Loading を使ってみる
先ほどのデータの取得方法は Lazy Loading であり、Eager Loading に変更することで SQL クエリの回数を減らしパフォーマンスを改善できると、Build an Admin Panel with the Fuel PHP Framework に記載されています。
そして、以下のように変更すると、Eager Loading になると記述されていますが、
--- a/fuel/app/classes/controller/blog.php +++ b/fuel/app/classes/controller/blog.php @@ -14,7 +14,7 @@ class Controller_Blog extends Controller_Base public function action_view($slug) { - $post = Model_Post::find_by_slug($slug); + $post = Model_Post::find_by_slug($slug, array('related' => array('user'))); $this->template->title = $post->title; $this->template->content = View::forge('blog/view', array(
この結果は、以下のエラーになってしまいました。
Notice! ErrorException [ Notice ]: Trying to get property of non-object APPPATH/classes/controller/blog.php @ line 20: 19: 20: $this->template->title = $post->title; 21: $this->template->content = View::forge('blog/view', array(
仕方がないので、Build an Admin Panel with the Fuel PHP Framework のコメント欄にあった別の方法に変更します。
--- a/fuel/app/classes/controller/blog.php +++ b/fuel/app/classes/controller/blog.php @@ -14,7 +14,8 @@ class Controller_Blog extends Controller_Base public function action_view($slug) { - $post = Model_Post::find_by_slug($slug); + //$post = Model_Post::find_by_slug($slug, array('related' => array('user'))); + $post = Model_Post::find()->where('slug', $slug)->related('user')->get_one(); $this->template->title = $post->title; $this->template->content = View::forge('blog/view', array(
これだとうまく動作しました。また、クエリが 1つ減りました。
SELECT * FROM `ap_users` WHERE `username` = 'admin'
Type: ALL ·Rows: 2 ·Speed: 0.444 ms
SELECT `ap_t0`.`id` AS `t0_c0`, `ap_t0`.`username` AS `t0_c1`, `ap_t0`.`password` AS `t0_c2`, `ap_t0`.`group` AS `t0_c3`, `ap_t0`.`email` AS `t0_c4`, `ap_t0`.`last_login` AS `t0_c5`, `ap_t0`.`login_hash` AS `t0_c6`, `ap_t0`.`profile_fields` AS `t0_c7`, `ap_t0`.`created_at` AS `t0_c8`, `ap_t0`.`updated_at` AS `t0_c9` FROM `ap_users` AS `ap_t0` WHERE (`ap_t0`.`username` = 'admin') ORDER BY `ap_t0`.`id` ASC, `ap_t0`.`id` ASC LIMIT 1
Type: ALL ·Rows: 2 ·Speed: 0.574 ms
SELECT `ap_t0`.`id` AS `t0_c0`, `ap_t0`.`title` AS `t0_c1`, `ap_t0`.`slug` AS `t0_c2`, `ap_t0`.`summary` AS `t0_c3`, `ap_t0`.`body` AS `t0_c4`, `ap_t0`.`user_id` AS `t0_c5`, `ap_t0`.`created_at` AS `t0_c6`, `ap_t0`.`updated_at` AS `t0_c7`, `ap_t1`.`id` AS `t1_c0`, `ap_t1`.`username` AS `t1_c1`, `ap_t1`.`password` AS `t1_c2`, `ap_t1`.`group` AS `t1_c3`, `ap_t1`.`email` AS `t1_c4`, `ap_t1`.`last_login` AS `t1_c5`, `ap_t1`.`login_hash` AS `t1_c6`, `ap_t1`.`profile_fields` AS `t1_c7`, `ap_t1`.`created_at` AS `t1_c8`, `ap_t1`.`updated_at` AS `t1_c9` FROM (SELECT `ap_t0`.`id`, `ap_t0`.`title`, `ap_t0`.`slug`, `ap_t0`.`summary`, `ap_t0`.`body`, `ap_t0`.`user_id`, `ap_t0`.`created_at`, `ap_t0`.`updated_at` FROM `ap_posts` AS `ap_t0` WHERE `ap_t0`.`slug` = 'fuelphp-is-awesome' LIMIT 1) AS `ap_t0` LEFT JOIN `ap_users` AS `ap_t1` ON (`ap_t0`.`user_id` = `ap_t1`.`id`)
Type: system ·Rows: 1 ·Speed: 0.958 ms
コメント欄を追加する
oil generate admin コマンドでコメント欄に関するコードを生成します。
$ oil generate admin comments name:string email:string website:string message:text post_id:int
Creating migration: .../blog/fuel/app/migrations/003_create_comments.php
Creating model: .../blog/fuel/app/classes/model/comment.php
Creating controller: .../blog/fuel/app/classes/controller/admin/comments.php
Creating view: .../blog/fuel/app/views/admin/comments/index.php
Creating view: .../blog/fuel/app/views/admin/comments/view.php
Creating view: .../blog/fuel/app/views/admin/comments/create.php
Creating view: .../blog/fuel/app/views/admin/comments/edit.php
Creating view: .../blog/fuel/app/views/admin/comments/_form.php
$ oil refine migrate
リレーションを設定します。
--- a/fuel/app/classes/model/comment.php +++ b/fuel/app/classes/model/comment.php @@ -1,6 +1,8 @@ <?php class Model_Comment extends \Orm\Model { + protected static $_belongs_to = array('post', 'user'); + protected static $_properties = array( 'id', 'name',
--- a/fuel/app/classes/model/post.php +++ b/fuel/app/classes/model/post.php @@ -2,6 +2,7 @@ class Model_Post extends \Orm\Model { protected static $_belongs_to = array('user'); + protected static $_has_many = array('comments'); protected static $_properties = array( 'id',
--- a/fuel/app/classes/model/user.php +++ b/fuel/app/classes/model/user.php @@ -2,7 +2,7 @@ class Model_User extends \Orm\Model { - protected static $_has_many = array('posts'); + protected static $_has_many = array('posts', 'comments'); protected static $_properties = array( 'id',
http://localhost/blog/index.php/admin/comments/create にアクセスしてコメントを追加します。Post id は 1 にします。
oil console で確認します。
$ oil console Fuel 1.1 - PHP 5.3.8 (cli) (Sep 19 2011 13:26:12) [Linux] >>> Model_Post::find(1)->comments; array ( 1 => Model_Comment::__set_state(array( '_is_new' => false, '_frozen' => false, '_data' => array ( 'id' => '1', 'name' => 'Who am I', 'email' => 'test@example.jp', 'website' => 'http://example.jp/', 'message' => 'This is a comment, even if you believe or not. <s>test</s> ', 'post_id' => '1', 'created_at' => '1328005079', 'updated_at' => '1328005079', ), '_original' => array ( 'id' => '1', 'name' => 'Who am I', 'email' => 'test@example.jp', 'website' => 'http://example.jp/', 'message' => 'This is a comment, even if you believe or not. <s>test</s> ', 'post_id' => '1', 'created_at' => '1328005079', 'updated_at' => '1328005079', ), '_data_relations' => array ( ), '_original_relations' => array ( ), '_view' => NULL, '_iterable' => array ( ), )), )
Controller_Blog クラスのクエリに comments へのリレーションを追加します。
--- a/fuel/app/classes/controller/blog.php +++ b/fuel/app/classes/controller/blog.php @@ -15,7 +15,7 @@ class Controller_Blog extends Controller_Base public function action_view($slug) { //$post = Model_Post::find_by_slug($slug, array('related' => array('user'))); - $post = Model_Post::find()->where('slug', $slug)->related('user')->get_one(); + $post = Model_Post::find()->where('slug', $slug)->related('user')->related('comments')->get_one(); $this->template->title = $post->title; $this->template->content = View::forge('blog/view', array(
ビューファイルにコメントの表示とコメントを投稿するフォームを追加します。
--- a/fuel/app/views/blog/view.php +++ b/fuel/app/views/blog/view.php @@ -7,3 +7,43 @@ </p> <p><?php echo nl2br($post->body) ?></p> + +<hr /> + +<h3 id="comments">Comments</h3> + +<?php foreach ($post->comments as $comment): ?> + + <p><?php echo Html::anchor($comment->website, $comment->name) ?> said "<?php echo $comment->message ?>"</p> + +<?php endforeach; ?> + +<h3>Write a comment</h3> + +<?php echo Form::open('blog/comment/'.$post->slug) ?> + +<div class="row"> + <label for="name">Name:</label> + <div class="input"><?php echo Form::input('name'); ?></div> +</div> + +<div class="row"> + <label for="website">Website:</label> + <div class="input"><?php echo Form::input('website'); ?></div> +</div> + +<div class="row"> + <label for="email">Email:</label> + <div class="input"><?php echo Form::input('email'); ?></div> +</div> + +<div class="row"> + <label for="message">Comment:</label> + <div class="input"><?php echo Form::textarea('message'); ?></div> +</div> + +<div class="row"> + <div class="input"><?php echo Form::submit('submit'); ?></div> +</div> + +<?php echo Form::close() ?>
投稿されたコメントを処理する action_comment() メソッドをコントローラに追加します。
--- a/fuel/app/classes/controller/blog.php +++ b/fuel/app/classes/controller/blog.php @@ -22,4 +22,42 @@ class Controller_Blog extends Controller_Base 'post' => $post, )); } + + public function action_comment($slug) + { + $post = Model_Post::find_by_slug($slug); + + // Lazy validation + if (Input::post('name') AND Input::post('email') AND Input::post('message')) + { + // Create a new comment + $post->comments[] = new Model_Comment(array( + 'name' => Input::post('name'), + 'website' => Input::post('website'), + 'email' => Input::post('email'), + 'message' => Input::post('message'), + 'user_id' => $this->current_user->id, + )); + + // Save the post and the comment will save too + if ($post->save()) + { + $comment = end($post->comments); + Session::set_flash('success', 'Added comment #'.$comment->id.'.'); + } + else + { + Session::set_flash('error', 'Could not save comment.'); + } + + Response::redirect('blog/view/'.$slug); + } + + // Did not have all the fields + else + { + // Just show the view again until they get it right + $this->action_view($slug); + } + } }
このメソッドはバリデーションをきちんとしてませんし、バリデーションにパスしなかった場合、入力値が消えてしまいますが、とりあえず、これで、コメントを投稿できるようになりました。
ORM の生成する SQL のデバッグ
先ほどのエラーが出た場合の動作を、確認しておきます。
実は、最初の記事ではエラーは出ずに記事が正常に表示されましたが、2つ目の記事ではエラーが表示されました。
Debug::dump() メソッドで変数を表示してみると、エラーが出た場合は null になっていました。
それでは、最終的なコメントのリレーションも含まれた以下のコードを確認してみます。
$post = Model_Post::find_by_slug($slug, array('related' => array('user', 'comments')));
上記の場合の SQL をプロファイラで取得し、整形すると、
SELECT `ap_t0`.`id` AS `t0_c0`, `ap_t0`.`title` AS `t0_c1`, `ap_t0`.`slug` AS `t0_c2`, `ap_t0`.`summary` AS `t0_c3`, `ap_t0`.`body` AS `t0_c4`, `ap_t0`.`user_id` AS `t0_c5`, `ap_t0`.`created_at` AS `t0_c6`, `ap_t0`.`updated_at` AS `t0_c7`, `ap_t1`.`id` AS `t1_c0`, `ap_t1`.`username` AS `t1_c1`, `ap_t1`.`password` AS `t1_c2`, `ap_t1`.`group` AS `t1_c3`, `ap_t1`.`email` AS `t1_c4`, `ap_t1`.`last_login` AS `t1_c5`, `ap_t1`.`login_hash` AS `t1_c6`, `ap_t1`.`profile_fields` AS `t1_c7`, `ap_t1`.`created_at` AS `t1_c8`, `ap_t1`.`updated_at` AS `t1_c9`, `ap_t2`.`id` AS `t2_c0`, `ap_t2`.`name` AS `t2_c1`, `ap_t2`.`email` AS `t2_c2`, `ap_t2`.`website` AS `t2_c3`, `ap_t2`.`message` AS `t2_c4`, `ap_t2`.`post_id` AS `t2_c5`, `ap_t2`.`created_at` AS `t2_c6`, `ap_t2`.`updated_at` AS `t2_c7` FROM (SELECT `ap_t0`.`id`, `ap_t0`.`title`, `ap_t0`.`slug`, `ap_t0`.`summary`, `ap_t0`.`body`, `ap_t0`.`user_id`, `ap_t0`.`created_at`, `ap_t0`.`updated_at` FROM `ap_posts` AS `ap_t0` ORDER BY `ap_t0`.`id` ASC LIMIT 1) AS `ap_t0` LEFT JOIN `ap_users` AS `ap_t1` ON (`ap_t0`.`user_id` = `ap_t1`.`id`) LEFT JOIN `ap_comments` AS `ap_t2` ON (`ap_t0`.`id` = `ap_t2`.`post_id`) WHERE (`ap_t0`.`slug` = 'first-post') ORDER BY `ap_t0`.`id` ASC
となりました。
FROM 句の SQL が明らかにおかしいですね。ということで、このクエリはやはりバグってました。
うまく動作した場合の以下のコードでは、
$post = Model_Post::find()->where('slug', $slug)->related('user')->related('comments')->get_one();
生成される SQL は以下のようになりました。
SELECT `ap_t0`.`id` AS `t0_c0`, `ap_t0`.`title` AS `t0_c1`, `ap_t0`.`slug` AS `t0_c2`, `ap_t0`.`summary` AS `t0_c3`, `ap_t0`.`body` AS `t0_c4`, `ap_t0`.`user_id` AS `t0_c5`, `ap_t0`.`created_at` AS `t0_c6`, `ap_t0`.`updated_at` AS `t0_c7`, `ap_t1`.`id` AS `t1_c0`, `ap_t1`.`username` AS `t1_c1`, `ap_t1`.`password` AS `t1_c2`, `ap_t1`.`group` AS `t1_c3`, `ap_t1`.`email` AS `t1_c4`, `ap_t1`.`last_login` AS `t1_c5`, `ap_t1`.`login_hash` AS `t1_c6`, `ap_t1`.`profile_fields` AS `t1_c7`, `ap_t1`.`created_at` AS `t1_c8`, `ap_t1`.`updated_at` AS `t1_c9`, `ap_t2`.`id` AS `t2_c0`, `ap_t2`.`name` AS `t2_c1`, `ap_t2`.`email` AS `t2_c2`, `ap_t2`.`website` AS `t2_c3`, `ap_t2`.`message` AS `t2_c4`, `ap_t2`.`post_id` AS `t2_c5`, `ap_t2`.`created_at` AS `t2_c6`, `ap_t2`.`updated_at` AS `t2_c7` FROM (SELECT `ap_t0`.`id`, `ap_t0`.`title`, `ap_t0`.`slug`, `ap_t0`.`summary`, `ap_t0`.`body`, `ap_t0`.`user_id`, `ap_t0`.`created_at`, `ap_t0`.`updated_at` FROM `ap_posts` AS `ap_t0` WHERE `ap_t0`.`slug` = 'fuelphp-is-awesome' LIMIT 1) AS `ap_t0` LEFT JOIN `ap_users` AS `ap_t1` ON (`ap_t0`.`user_id` = `ap_t1`.`id`) LEFT JOIN `ap_comments` AS `ap_t2` ON (`ap_t0`.`id` = `ap_t2`.`post_id`)
本家フォーラムを検索したところ、
FuelPHP » Forums にて、__callStatic() メソッドのバグだとのコメントがありました。そして、以下ならうまく動作すると。
$post = Model_Post::query()->where('slug', $slug')->related('user')->get_one(); // or $post = Model_Post::find('first', array('where' => array('slug' => $slug), 'related' => array('user')));