速習Larave3(その13)、コメントNo.1

タグ: Laravel3  

今回は記事にコメントを投稿できるようにしましょう。

関連付け

さて、いよいよ今回からEloquent ORMの関連付けを学んでいく事にしましょう。

コメントはある一つの記事に付けるものです。ついていない場合もありますし、何百も付くこともあるでしょう。

このように記事一つに対し、関連するテーブルのレコードが複数ある場合を1:多の関係と呼びます。

まずは日本語で理解しましょう。「一つの記事は複数のコメントを持つ」、そして「一つのコメントは一つの記事に所属する。」です。記事から見れば、コメントはたくさん持つものです。コメントの立場から見れば、所属先の記事は一つです。これさえ押さえておけば、1:1それと1:多の関係は理解できます。1:1は一つを持ち、一つに所属する状態ですから、1:多の限定バージョンです。

さて、理解できたか確かめてみましょう。コメントは記事だけに所属するわけでありません。ユーザーさんにも所属します。先に読み進める前に、考えてくださいね。ユーザーさんとコメントの関係を同じように言い表してみましょう。

「一人のユーザーは複数のコメントを持つ」、「一つのコメントは一人のユーザーに所属する。」

理解できましたか?

コメントに関しては1:多の関係が二つ存在しています。

データベースのドライバー

ここで悲しいお知らせです。

今までこのチュートリアルで、私の環境はSQLiteをデータベースに使用していました。

LaravelのSchemaクラスの外部束縛の生成は、SQLiteに対応していません。バグとして上がっていますが、Laravel3で対応されるか微妙です。なにせ、Laravel4ベータのリリースが近いですからね。

今まで提供してきたgithubで公開しているコードに含まれるconfig/database.phpはSQLiteを使用する設定になっています。

これから、外部キー束縛の生成法も学習しますが、SQliteご利用の方は以下の方法で、対処ください。

  • 外部キー束縛を作らない。
    1. 今回はキーの変更、削除を行わないため、外部束縛を行わなくても、動作に違いはありません。該当するコードをコメントにしてください。
  • データベースをSQLiteから変更する
    1. config/database.phpのドライバにMySQLを設定してください。そして、接続に必要なユーザー名とパスワード、使用するデータベース名を指定してください。ドライバー設定の下にあります。
    2. php artisan migrate::install、続けてphp artisan migrateを実行してください。

コメントテーブル生成

それでは、まずcommentsテーブルを作成しましょう。新しいマイグレーションが必要です。生成しましょう。

生成したマイグレーションは以下の内容に書き換え、マイグレーションを実行してください。(以降のチュートリアルでは、マイグレーションの作成コマンドを示しません。マイグレーションコマンドではファイル名を指定しますが、それにより生成されるクラス名は単語の先頭を大文字にしたものです。ファイル名とクラス名はこの規約で一致している必要があります。自分で指定したファイル名に合わせ、以降のチュートリアルのマイグレーションクラスの名前は置き換えて下さい。一番良いのは、upとdownメソッドのみをコピー&貼り付けすることです。)

<?php

class Create_Comments_Table
{

    /**
     * Make changes to the database.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments',
            function($table) {
                $table->increments('id');
                $table->integer('post_id')
                    ->unsigned();
                $table->integer('user_id')
                    ->unsigned();
                $table->string('comment', 8000);
                $table->timestamps();
            });
        // 外部キー
        Schema::table('comments',
            function($table) {
                $table
                    ->foreign('post_id')->references('id')->on('posts')
                    ->on_update('cascade')->on_delete('cascade');
                $table
                    ->foreign('user_id')->references('id')->on('users')
                    ->on_update('cascade')->on_delete('cascade');
            });
    }

    /**
     * Revert the changes to the database.
     *
     * @return void
     */
    public function down()
    {
        // リレーショナルデータベースですから、
        // 最初に外部キーを削除しないとDropできません。
        Schema::table('comments',
            function($table) {
                $table->drop_foreign('comments_user_id_foreign');
                $table->drop_foreign('comments_post_id_foreign');
            });
        Schema::drop('comments');
    }

}

ポイントは3つです。外部キーの生成はテーブルの生成と同時にはできません。もちろんSQLを実行するならば可能です。Laravel3ではできません。そのため、まずテーブルを生成し、その後に外部キーを付け加えます。

逆に削除する場合は、外部キーを削除してから、テーブル本体を削除します。これはSQLと同じ手順ですね。

役割の導入では説明しただけで活用しませんでしたが、Laravelが生成する主キー項目は符号なし整数として生成されます。ですから、それに紐付けるカラムも符号なし整数にします。

ルート定義

まず、コメントの投稿ルートを定義しましょう。

コメントを投稿する新しいビューは作成しません。コメントは普通、記事の表示ページに表示されるものです。そのコメントを読んだ人に続けて入力してもらえるよう、同じページに表示しましょう。

誰にコメントの投稿を許可するかですが、今回は登録者(register)です。ちなみに今registerが、登録(する)という意味で、登録者という意味とはちょっと違うと気が付きましたが、いまさらなので変更せず続けます。

routes.phpをご覧ください。登録者さんには何も特典がなく、いままでゲストと同じ扱いでした。それではかわいそうなのでちょっとだけやれることを増やそうというわけです。

// 登録者権限が必要な領域
Route::group(array( 'before' => 'register' ),
    function() {
        // コメント投稿
        //  フォームの表示ルートは無い。記事の表示(post-show)でフォームが表示される。
        //  フォーム処理
        Route::post('/comment/add/(:num)', array('as' => 'comment-add', 'before' => 'csrf', 'uses' => 'comment@add' ));
    });

いつもはGETとPOSTの組み合わせですが、今回表示は記事のビューで行うため、POSTメソッドに対するコントローラー/アクションだけを登録しています。

コメントモデルと関係付けの定義

コメントモデルを作成しつつ、関係づけも定義しましょう。

まずはコメントのモデルです。application/models/comment.phpです。

<?php

class Comment extends Eloquent {

    public static $timestamps = true;

    public function user()
    {
        return $this->belongs_to('User');
    }

    public function post()
    {
        return $this->belongs_to('Post');
    }
}

コメントの立場を思い出してください。コメントはユーザーと記事に所属していました。

その所属しているを英語で書くと、"belongs_to"になります。ですから所属を表すのはbelong_to()メソッドになります。引数には、所属するEloqunetモデルを記述します。

まずuser()メソッドですが、これが関連を呼び出す時の名前になります。そこでLaravelの世界では「関係名」とか「関連名」とか呼ばれます。リレーション・ネームです。まあ、まだ若いフレームワークなので、多少用語の使用にブレがあり、統一感に欠けていますが、段々改善されるでしょう。

$this->belongs_to('User');は$thisがコメントモデルのインスタンスなので、「この(コメント)は、ユーザーモデルに所属する」と読めます。同じように「このコメントは、記事モデルに所属する」ですね。

仕組みを解説すると、LaravelのEloqunetで使用するテーブルの主キーはデフォルトでidと名づけます。関連付けを使用する場合、あるテーブルのidは「テーブル名の単数形_id」という多用されるネーミングがデフォルトです。ですから、関係づけるEloquntモデル名から、テーブル名が分かりますし、カラム名も推測されるため、余計な定義を行わなくて済みます。こうした規則から外れても、それを指定すればちゃんと使えます。しかし、たいした命名規則でも無いですし、従っていれば、とてもシンプルに記述できますので、そのまま採用されることをおすすめします。

今度は所有するほうを見てみましょう。まずはPostモデルです。

<?php

class Post extends Eloquent
{
    public static $timestamps = true;

    public function comments()
    {
        return $this->has_many('Comment');
    }
}

今回のメソッド名はcommentsと複数形にしました。

関係づけをhas_many()で表していることのほかはCommentモデルの定義と同じですね。"has_many"が「多くを持つ」ですから、$this->has_many('Comment')は「この記事モデルは、多くのコメントモデルを所有する」と読めます。

最後にUserモデルにも関連付けを定義しましょう。

<?php

class User extends Eloquent
{
    public static $timestamps = true;

    public function set_password($password)
    {
        $this->set_attribute('password', Hash::make($password));
    }

    public function comments()
    {
        return $this->has_many('Comment');
    }
}

関連付けが追加されている他は、変わっていません。