こんにちは、山本です。

前回はPHP7を取り扱ったので、今回はそのフレームワークを扱おうかと思います。
何を、と思ったのですが、Symfonyは仕事でよく使いますし、Cakeは2系も、3系も何度か使ったことがある状態です。

どうもフレームワークというものは、ある程度の学習コストがかかるもので、使い慣れたものに固執してしまい、中々新しいものに手を出すのが面倒になってしまいます。
そういったことを考えると、記事を書くのは良い機会だったりするので、よく知らないものを扱いたいと思います。

触ったことのないPHPのフレームワークはたくさんあるのですが、どうも最近は「Laravel」が人気があるらしいですね。
あと、先日ご同業の方との打ち合わせをした際に、フレームワークの話題になり「Laravel」が良いらしいとの話を聞きました。

そこまで来ると、もう「Laravel」で書くしかないですね。

人気

Googleトレンド調べ。
(過去5年間の推移)

世界

005

どうも、世界では2013年から伸び始め、2015年辺りにCodeIgniterを抜きTOPになっていますね。
あと、Cakeの人気が思ったほど無いんですね。

国内

006

Cakeが一強ではありますが、徐々に伸び始め、Cakeの位置を伺うところまで来ていますね。

インストール

まず、Laravelですが4系から5系にバージョンアップした際、随分と仕様が変わったようです。

あえて、4系を選ぶ理由もないので、5系で始めようと思います。

Lavel5.1のインストール手順が公式にあったので、それを参考にします。

インストールドキュメント

composerを入れて

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

インストーラーを入れます。

composer global require "laravel/installer=~1.1"

そして、プロジェクト作成します。

作成は、以下のコマンドで作成出来るようです。

laravel new project_name

今回は、testプロジェクトを作ります。

~/.composer/vendor/bin/laravel new test
[adjust@localhost laravel]$ ll
合計 4
drwxrwxr-x 11 adjust adjust 4096  4月 25 17:15 2016 test

次に、キャッシュ等のディレクトリに書き込み権限を与えます。

chmod -R 777 storage bootstrap/cache

最後に、publicをドキュメントルートとします。

001

随分簡潔なようこそ画面が表示されました。

ドキュメントではそれ以降も、あれこれと書いていますが、とりあえず動かしてみて必要となったらまた読んでみましょう。

チュートリアル

インストールを行ったので、次に進みます。
ドキュメントのメニューが分かりにくいのですが、チュートリアルがあるようでした。

  • 初級チュートリアル
  • 中級チュートリアル

この違いは、同じアプリケーションを簡易版と、通常版?で作るかの違いのようでした。

今回は中級を行いつつ、疑問を解決しながらやっていこうと思います。

まず、インストールの項目がありますがこれは既にインストール済みなので読み飛ばして、データベースの準備から始めます。

データベースマイグレーション

マイグレーションからの開始です。

php artisan migrate

がコマンドのようです。

なんというか、最初にテーブルを作成する時からmigrateを使うというのはなんとも違和感を覚えてしますのですが。

[adjust@localhost test]$ php artisan migrate

  [PDOException]
  SQLSTATE[HY000] [1045] Access denied for user 'homestead'@'localhost' (using password: YES)

で、実行してみました。
まあ、DBの設定を行っていないので当然の結果です。

さて、DB設定を行います。

configの下に設定ファイルがあるので、それを編集しようと思ったのですが。。。

vi config/database.php

            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),

ん?forge?
出ていた、エラーはhomesteadだった気がするんですけど。

どうも、envで呼び出すと、.envファイルから該当のキーの値を取り出してくれるようですね。

DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

2番めの引数は、該当のキーがなかった時のものでしょう。

データベースを作成して、.envの設定を変更します。

そして、再度実行します。

[adjust@localhost test]$ php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table

エラーがなくなるだけかと思ったのですが、何かテーブルがインストールされましたね。

DBを見てみると

mysql> show tables;
+-------------------+
| Tables_in_laravel |
+-------------------+
| migrations        |
| password_resets   |
| users             |
+-------------------+
3 rows in set (0.00 sec)

何かテーブルが出来てますね。

さてチュートリアルに戻って、Migrateファイルを自動作成するコマンドがあるようです。

php artisan make:migration create_tasks_table --create=tasks

とすると

[adjust@localhost test]$ php artisan make:migration create_tasks_table --create=tasks
Created Migration: 2016_04_25_092535_create_tasks_table
[adjust@localhost test]$ ll database/migrations/
-rw-rw-r-- 1 adjust adjust 699  4月 25 17:13 2016 2014_10_12_000000_create_users_table.php
-rw-rw-r-- 1 adjust adjust 633  4月 25 17:13 2016 2014_10_12_100000_create_password_resets_table.php
-rw-rw-r-- 1 adjust adjust 539  4月 25 18:25 2016 2016_04_25_092535_create_tasks_table.php

ファイルが生成されています。

他に、create_usersとcreate_passwordがありますが、先程マイグレートで生成されたものと同じですね。

mysql> select * from migrations;
+------------------------------------------------+-------+
| migration                                      | batch |
+------------------------------------------------+-------+
| 2014_10_12_000000_create_users_table           |     1 |
| 2014_10_12_100000_create_password_resets_table |     1 |
+------------------------------------------------+-------+

migrationsテーブルを見てみると、ファイル名でレコードが生成されています。
batchは恐らく済みかどうかのフラグでしょう。

次に、生成したcreate_tasks_tableを見てみます。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTasksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('tasks');
    }
}

ファイル名で、クラス名が出来ており、upとdownが定義されています。

$table->timestamps();

の記述がありますが、これは同じく使用しているusersテーブルを見ると

mysql> desc users;
+----------------+------------------+------+-----+---------+----------------+
| Field          | Type             | Null | Key | Default | Extra          |
+----------------+------------------+------+-----+---------+----------------+
| id             | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name           | varchar(255)     | NO   |     | NULL    |                |
| email          | varchar(255)     | NO   | UNI | NULL    |                |
| password       | varchar(255)     | NO   |     | NULL    |                |
| remember_token | varchar(100)     | YES  |     | NULL    |                |
| created_at     | timestamp        | YES  |     | NULL    |                |
| updated_at     | timestamp        | YES  |     | NULL    |                |
+----------------+------------------+------+-----+---------+----------------+

created_atとupdated_atを追加してくれるもののようです。

さて、チュートリアルに戻って進めます

このファイルを編集し、タスクの名前を保存するstringカラムを追加しましょう。

        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->index();
            $table->string('name');
            $table->timestamps();
        });

user_idとnameの行を追加して、再度実行します。

[adjust@localhost test]$ php artisan migrate
Migrated: 2016_04_25_092535_create_tasks_table
mysql> desc tasks;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| user_id    | int(11)          | NO   | MUL | NULL    |                |
| name       | varchar(255)     | NO   |     | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

mysql> show index from tasks;
+-------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name            | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tasks |          0 | PRIMARY             |            1 | id          | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
| tasks |          1 | tasks_user_id_index |            1 | user_id     | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)

tasksテーブルが生成されました。
また、indexも張られています。

Eloquentモデル

Eloquentとは、チュートリアルを読むと、Laravelの標準ORMのようです。

そして、Laravelではテーブル作成時に合わせて自動でモデルを作るのではなく、手動で作る仕様のようです。
どうせ使うので、自動で作ってくれればいいのにと思ってしまいます。

で、作成のコマンドですが

php artisan make:model Task

を実行すると

[adjust@localhost test]$ php artisan make:model Task
Model created successfully.

Appの直下にモデルが生成されました。

[adjust@localhost test]$ ll app/
合計 40
drwxrwxr-x 3 adjust adjust 4096  4月 25 17:13 2016 Console
drwxrwxr-x 2 adjust adjust 4096  4月 25 17:13 2016 Events
drwxrwxr-x 2 adjust adjust 4096  4月 25 17:13 2016 Exceptions
drwxrwxr-x 5 adjust adjust 4096  4月 25 17:13 2016 Http
drwxrwxr-x 2 adjust adjust 4096  4月 25 17:13 2016 Jobs
drwxrwxr-x 2 adjust adjust 4096  4月 25 17:13 2016 Listeners
drwxrwxr-x 2 adjust adjust 4096  4月 25 17:13 2016 Policies
drwxrwxr-x 2 adjust adjust 4096  4月 25 17:13 2016 Providers
-rw-rw-r-- 1 adjust adjust  100  4月 25 18:49 2016 Task.php
-rw-rw-r-- 1 adjust adjust  449  4月 25 17:13 2016 User.php

中身を見てみると

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    //
}

大枠だけのファイルが生成されるようです。

対象となるテーブル名の指定が無いと思ったのですが

データベーステーブルはモデルの複数形の名前だと想定されているため、Eloquentモデルがどのテーブルに対応するかを明確に宣言する必要はありません。
ですから、この場合Taskモデルはtasksデータベーステーブルと対応していると想定しています。

テーブル名は複数形で、モデルは単数型にするのが前提のようです。

一応、ドキュメントを読むとテーブル名の指定方法もあるようでした。

    protected $table = 'my_flights';

そして、チュートリアルの指示では

このモデルにいくらか追加しましょう。最初にモデルに対し「複数代入」ができるようname属性を登録します。

protected $fillable = ['name'];

複数代入って何?という疑問もありますが、指示に従って進めて行きます。

Eloquentリレーション

モデルが出来たので、次はリレーションです。

一対多で、UserとTaskのリレーションを張るようです。

//vi app/User.php
namespace App;

class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;

    public function tasks()
    {
        return $this->hasMany(Task::class);
    }
}
namespace App;

use App\User;
use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    protected $fillable = ['name'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

と、これで出来るらしいんですが、、、
どこで外部キーを指定しているんでしょうか?

見落としがあったかとも思ったのですが、ドキュメントを読むと

Eloquentはリレーションメソッドの名前に_idのサフィックスをつけた名前をデフォルトの外部キーとします。

デフォルトでそういった仕様のようです。

また、そのルール外で定義する場合は

return $this->belongsTo('App\Post', 'foreign_key', 'other_key');

第2引数で外部キーを指定でき、id以外とリレーションする場合は、第3引数の指定が出来るようです。

リレーションが張りやすいのは、便利ですね。

Routing

次に、ルーティングです。

チュートリアルでは色々と説明を書いていますが、ログイン限定のページを作る方法についてのようです。

認証

まず、認証用のルートを登録。

//vi app/Http/routes.php
// 認証ルート…
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');

// 登録ルート…
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');

該当のルートに、呼び出すコントローラー、メソッドとアクション名の指定か?

そして、認証画面のビューを作成する必要があるようです。

チュートリアルでは、特定のフィールドがあれば良いとのことしか書いていませんでした。
一から書くのも手間なので、サンプルを使っていきます。

layouts
login
register

layouts/app.blade.phpはauthのテンプレートに依存していたので、合わせて入れておきます。

これで、会員登録が出来るようになったぽいので、早速会員を作ってみます。

メニューから、registerを押してみると、、、なぜか404ぽい画面が表示されます。

003

どうやら、URLが /register となっており、ルーティングの auth/register とは異なることが原因のようでした。
なので、間違っているようなので、調整します。

007

そして、必須項目を埋めて登録すると。

003

なんで?
何か、手順を飛ばしたかと思い、読み返してみたんですが、特に見落としはないようでした。。。

う~ん、進めていけば何か答えが見つかるかとも思うので、先進めて行きます。

タスクコントローラー

タスクコントローラーを作ります。

php artisan make:controller TaskController --plain

が指示なんですが

  [Symfony\Component\Console\Exception\RuntimeException]  
  The "--plain" option does not exist.                    

とエラーになってしまいます。

コントローラーのドキュメントを見ると、plainのオプションがありませんでした。
どうも、海外のフォーラムを見ると5.2からは、デフォルトがplainでresourceコントローラーを作りたいときはresourceオプションをように変更されたようです。

resourceコントローラーとは何か?という疑問もあるのですが。

さて、plainの除いて実行すると

[adjust@localhost Controllers]$ ll
合計 12
drwxrwxr-x 2 adjust adjust 4096  4月 25 17:13 2016 Auth
-rw-rw-r-- 1 adjust adjust  441  4月 25 17:13 2016 Controller.php
-rw-rw-r-- 1 adjust adjust  145  4月 26 15:33 2016 TaskController.php

TaskControllerが生成されました。

そして、ルーティングに追加します。

//vi app/Http/routes.php
Route::get('/tasks', 'TaskController@index');
Route::post('/task', 'TaskController@store');
Route::delete('/task/{task}', 'TaskController@destroy');

全タスクルートの認証

今度は、タスクをログイン必須にします。

ですから、タスクルートは認証済みユーザーのみアクセスできるように制限する必要があります。Laravelではミドルウェアを使えばあっという間です。

とのことですですが、ミドルウェアとはなんでしょう?

ドキュメントを読むと

HTTPミドルウェアはアプリケーションにやってきたHTTPリクエストをフィルタリングする、便利なメカニズムを提供します。

何かフィルターらしいですね。

//vi app/Http/Kernel.php
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];

Karnelで定義したものが使用出来るようになるみたいです。

さて、タスクでauthのミドルウェアを使ってみます。

//vi app/Http/Controllers/TaskController.php
    public function __construct()
    {
        $this->middleware('auth');
    }

そして、tasksを見てみると

004

確かに、indexは定義していないんですが、普通それより先に__constructが処理されるのでは?

仕方がないので、仮にindexを作ってみます。

//vi app/Http/Controllers/TaskController.php
    public function index()
    {
    }

で確認すると、loginにリダイレクトされたのですが。。。

404ぽい画面が表示されます。

003

ミドルウェアを見てみると、loginに飛ばすようになっています。

                return redirect()->guest('login');

先ほどの会員登録でも、同じような現象が起きていましたが、もしかして認証のルーティングが間違っているのでは?

とりあえず以下のルーティングを追加しました。

//vi app/Http/routes.php
Route::get('login', 'Auth\AuthController@getLogin');
Route::post('login', 'Auth\AuthController@postLogin');
Route::get('logout', 'Auth\AuthController@getLogout');

Route::get('register', 'Auth\AuthController@getRegister');
Route::post('register', 'Auth\AuthController@postRegister');

そして、再度tasksを開いてみると

009

無事、ログイン画面が表示出来るようになりました。

また、先程はうまくいかなかった会員登録も無事登録出来るようになりました。

mysql> select * from users;
+----+------+-------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
| id | name | email       | password                                                     | remember_token | created_at          | updated_at          |
+----+------+-------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
|  1 | foo  | foo@bar.com | $2y$10$r2FrK0r8Bsc0H/5GyAYwN.eESX//dfYmgJ7YtyqgL/6iqaJvzi1nO | NULL           | 2016-04-26 07:31:44 | 2016-04-26 07:31:44 |
+----+------+-------------+--------------------------------------------------------------+----------------+---------------------+---------------------+

あと、ミドルウェアの指定ですが以下のようにルーティングで指定することも出来るようです。

Route::get('profile', [
    'middleware' => 'auth',
    'uses' => 'UserController@showProfile'
]);

使い分ける感じですかね。

次回

チュートリアルをやっていくだけであれば、そうでもないんですが、いざ記事として書くとなると結構時間がかかってしまいます。
まだまだ、半分ぐらいなんですが。。。

あと、今更ながら気付いたのですが、見ているドキュメントは5.1で、インストールしたバージョンは5.2だったようです。
道理で、挙動がおかしかったわけです。

では、長くなってきたので、次回に続く。