November 30, 2014

Laravel SocialiteでFacebookとTwitterの認証をしてみた

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 5では、Laravel Socialiteを別途インストールすることで、Facebook,Twitter,Google,GitHub等と連携した認証(OAuth,OAuth2)を、簡単に実装できるようになります。
https://github.com/laravel/socialite

細かな書き方などは、以下がとても参考になりました。
https://laracasts.com/series/whats-new-in-laravel-5/episodes/9

以下、Facebookを用いた認証の簡単なサンプルです。

まず、予めFacebook側で適当にアプリを作成して、発行されたキーをプロジェクトルートの .env と config/services.php に設定しておきます。尚、.envはバージョン管理せずに、各環境でこっそりと作成するべきファイルです。

.env
FACEBOOK_CLIENT_ID=xxxxxxxxxx
FACEBOOK_CLIENT_SECRET=yyyyyyyyyy
config/services.php
// 以下を追記
'facebook' => [
    'client_id' => getenv('FACEBOOK_CLIENT_ID'),
    'client_secret' => getenv('FACEBOOK_CLIENT_SECRET'),
    'redirect' => url('facebook/callback'),
],
.envで設定した値はgetenv()で取得できます。CLIからの実行時でも取得できます。この機能は"PHP dotenv"を用いているので、同様のことは他のフレームワーク等でも簡単にできます。
https://github.com/vlucas/phpdotenv

Laravel Socialiteをインストールします。
$ composer require laravel/socialite:~2.0@dev
config/app.phpのprovidersにLaravel Socialiteのプロバイダを追記します。
'Laravel\Socialite\SocialiteServiceProvider',
routes.phpに、Facebook認証用のコントローラを追加します。以下、一例。
$router->controllers([
    'facebook' => 'FacebookController',
]);
app/Http/Controllers/FacebookController.php を作成します。
<?php namespace App\Http\Controllers;

use Laravel\Socialite\Contracts\Factory as Socialite;

class FacebookController extends Controller {

    /**
     * @var Socialite
     */
    protected $socialite;

    public function __construct(Socialite $socialite)
    {
        $this->socialite = $socialite;
    }

    public function getLogin()
    {
        return $this->socialite->driver('facebook')->redirect();
    }

    public function getCallback()
    {
        $user = $this->socialite->driver('facebook')->user();

        dd($user);
    }
}
以下の流れで、ユーザ情報が正しく表示されれば成功です。

* facebook/loginにアクセスしてFacebook側にリダイレクトされる。
* Facebook側で認証してfacebook/callbackにリダイレクトされる。
* ユーザ情報が表示される。

また、以下の形で、scopesの指定も出来ました。
return $this->socialite->driver('facebook')->scopes(['user_friends'])->redirect();
同じ流れで、Twitterの認証もうまくいきました。その他、GithubProviderやGoogleProviderも用意されているので、これらも同じような方法で上手くいくのかなと思います。
https://github.com/laravel/socialite/tree/master/src/Two

標準では用意されていないサービスでも、これらを参考にして簡単に自前できそうですね。

Laravel 5のイベントリスナのアノテーションを試してみた

この機能は廃止 https://github.com/laravel/framework/commit/4d9f92ef77b3610a05f71110f539daff06e9081d されたのかもしれません。

--

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 5では、ルーティングだけでなく、イベントリスナもアノテーションで定義できるようになります。

細かな書き方などは、以下がとても参考になりました。
http://mattstauffer.co/blog/laravel-5.0-event-annotations

以下、サンプルです。

適当な場所に適当なクラスを作成します。
<?php namespace App\EventListeners;

use Illuminate\Contracts\Logging\Log;

class Foo
{
    protected $log;

    /**
     * @param  Log $log
     */
    public function __construct(Log $log)
    {
        $this->log = $log;
    }

    /**
     * @Hears("bar.baz")
     *
     * @param  mixed $params
     */
    public function qux($params)
    {
        $this->log->debug(print_r($params, true));
    }
}
$this->log->debug() の箇所は、Laravel 5のContractsという仕組みを用いてみましたが、ファサードを用いて\Log::debug()でも同じです。

ポイントは @Hears("bar.baz") の箇所で、従来の書き方だと以下になります。
\Event::listen('bar.baz', 'App\EventListeners\Foo@qux');
次に app/Providers/EventServiceProvider.php を編集して protected $scan をオーバーライドします。値は配列で、アノテーションをスキャンしたいクラス名を書き並べます。
protected $scan = [
    '\App\EventListeners\Foo',
];
php artisan event:scan コマンドでスキャンします。
$ php artisan event:scan
Events scanned!
storage/framework/events.scanned.php がそれっぽい内容になっていれば正しくスキャンされています。
<?php 

$events->listen(array (
  0 => 'bar.baz',
), 'App\EventListeners\Foo@qux');
また、ルーティングのアノテーションと同様に app/Providers/EventServiceProvider.php の protected $scanWhenLocal をオーバーライドしてtrueを設定すると、環境値が"local"の場合に限って、リクエストの度に自動スキャンしてくれるようになりました。
protected $scanWhenLocal = true;
適当な場所でイベントをfire()してみます。
\Event::fire('bar.baz', [['xxx', 'yyy']]);
ログにパラメータが書き出されていれば成功です。
storage/logs/laravel-{yyyy-mm-dd}.log
[2014-11-30 00:10:52] local.DEBUG: Array
(
    [0] => xxx
    [1] => yyy
)
ルーティングのアノテーションと同様、デプロイ時のスキャン忘れとかには注意が必要なのかなと思いました。

November 25, 2014

Laravel 5のルーティングのアノテーションを試してみた

この機能は廃止 https://github.com/laravel/framework/commit/4d9f92ef77b3610a05f71110f539daff06e9081d されたのかもしれません。

--

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 4ではルーティングをroutes.phpで定義しますが、Laravel 5からはコントローラのアノテーションでも定義できるようになります。

細かな書き方などは、以下がとても参考になりました。
http://mattstauffer.co/blog/laravel-5.0-route-annotations

以下、簡単に試してみました。

コントローラを適当に作成します。
$ php artisan make:controller FooController --plain
メソッドとアノテーションを適当に書いてみます。
app/Http/Controllers/FooController.php
<?php namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class FooController extends Controller {

    /**
     * @Get("foo/{id}", as="foo.get")
     */
    public function getExample($id)
    {
        //
    }

    /**
     * @Post("foo/{id}", as="foo.post")
     */
    public function postExample($id)
    {
        //
    }
}
app/Providers/RouteServiceProvider.php を編集して protected $scan をオーバーライドします。値は配列で、アノテーションをスキャンしたいコントローラ名を書き並べます。
class RouteServiceProvider extends ServiceProvider {

    protected $scan = [
        '\App\Http\Controllers\FooController',
    ];
php artisan route:scan コマンドでスキャンします。
$ php artisan route:scan
Routes scanned!
php artisan route:list コマンドで確認してみます。
$ php artisan route:list
+--------+-------------------+----------+------------------------------------------------+------------+
| Domain | URI               | Name     | Action                                         | Middleware |
+--------+-------------------+----------+------------------------------------------------+------------+
|        | GET|HEAD foo/{id} | foo.get  | App\Http\Controllers\FooController@getExample  |            |
|        | POST foo/{id}     | foo.post | App\Http\Controllers\FooController@postExample |            |
+--------+-------------------+----------+------------------------------------------------+------------+
正しくスキャンされたようです。尚、スキャン内容は storage/framework/routes.scanned.php に保存されていました。

routes.php で同様の定義をするなら以下です。
$router->get('foo/{id}', ['as' => 'foo.get', 'uses' => 'FooController@getExample']);
$router->post('foo/{id}', ['as' => 'foo.post', 'uses' => 'FooController@postExample']);
また、app/Providers/RouteServiceProvider.php の protected $scanWhenLocal をオーバーライドしてtrueを設定すると、環境値が"local"の場合に限って、リクエストの度に自動スキャンしてくれるようになりました。
protected $scanWhenLocal = true;

routes.phpはどうしても肥大化しやすく、その点は解消しそうな気がします。参考記事を見ると @Where で正規表現によるマッチングにも対応しているようですね。更に、アノテーションはメソッドに対してだけでなく、コントローラに対しても書けるようで
@Resource
@Controller
等も記載されています。

ただ、routes.phpに書くと、before/afterフィルタ(Laravel 5ではMiddlewareという仕組みに置き換わります。)が一目瞭然で、その辺りで好き嫌いが別れそうな印象を受けました。また、アノテーション式だと、デプロイ時のスキャン忘れや、DB値を用いた動的なルーティングを定義する際などに注意が必要かもしれません。

最後に、Laravel 5ではルーティングのキャッシュが可能です。
$ php artisan route:cache
これは、routes.php式にもアノテーション式にも対応していました。キャッシュ内容は(少なくとも今現在は) config/cache.phpのdriver値に関わらず storage/framework/routes.php に保存されるようです。

November 24, 2014

Laravel 5のartisan schedule:runコマンドで定期実行するタスクをPHP側で管理する

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 5では、定期実行されるタスク(要はcron)を、PHP側で一元的に管理できるようになるみたいです。正確には、1 cron設定でNタスクを管理できる。といった感じでしょうか。

尚、この機能は、以下のライブラリに依存しているようです。
https://github.com/mtdowling/cron-expression

細かな書き方などは、以下がとても参考になりました。
http://laravel-news.com/2014/11/laravel-5-scheduler/
http://mattstauffer.co/blog/laravel-5.0-event-scheduling

以下、簡単なサンプルです。

適当なコマンドクラスを作ります。内容は、実行された時刻を標準出力するだけです。
$ php artisan make:console ScheduleExampleCommand --command=schedule:example
app/Console/Commands/ScheduleExampleCommand.php
<?php namespace App\Console\Commands;

use Illuminate\Console\Command;

class ScheduleExampleCommand extends Command {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'schedule:example';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command schedule:example';

    public function fire()
    {
        echo sprintf('%s fired at %s', $this->name, date('Y-m-d H:i:s'));
    }
}
app/Console/Kernel.php の protected $commands に、作成したコマンドクラスを登録します。
<?php namespace App\Console;

use Exception;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel {

    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        'App\Console\Commands\ScheduleExampleCommand',
    ];
同様に、app/Console/Kernel.php のschedule()メソッドでスケジューリングします。
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('schedule:example')
            ->cron('*/1 * * * * *')
            ->sendOutputTo(storage_path('logs/schedule.example.log'));
    }
cron()メソッドに、お馴染みの書式で実行間隔を指定しています。今回は、1分毎です。5分毎とかならcron()ではなくeveryFiveMinutes()とかでOKです。

参考: https://github.com/laravel/framework/blob/master/src/Illuminate/Console/Scheduling/Event.php

また、sendOutputTo()メソッドで、標準出力の保存先を指定しています。実行結果メールの送信先を指定するemailOutputTo()とかもあるみたいです。

php artisan schedule:run コマンドで、正しく実行されるか確認してみます。
$ php artisan schedule:run
Running scheduled command: /usr/bin/php5 artisan schedule:example > /home/vagrant/Code/example/storage/logs/schedule.example.log 2>&1 &
storage/logs/schedule.example.log が正しく出力されていれば成功です。どうも、今現在は追記式ではないようですね。実行の度に同一のログファイル名を出力先に指定すれば、毎回、新たなログファイルが生成されてしまいます。この辺は、そのうち追記式もできるようになる気がする。(根拠は無い。)

最後に php artisan schedule:run コマンドをcron設定してみます。Homesteadで確認しています。
$ sudo vim /etc/cron.d/schedule-example

# 以下を記述
*/1 * * * * vagrant /usr/bin/php /home/vagrant/Code/example/artisan schedule:run
storage/logs/schedule.example.log が毎分正しく更新されれば成功です。
schedule:example fired at 2014-11-23 16:43:21
cron設定は数が増えてくると、いつどのようなタスクが実行されているか把握するのが面倒な時がありますが、この方法なら一目瞭然に管理できそうですね。

November 23, 2014

Laravel Elixirを試してみた

Laravel 5ではGulpが標準採用され、Laravel Elixirというタスクツールが併せて実装されるようです。

http://gulpjs.com/
https://github.com/laravel/elixir

ドキュメントを見てみると
* Sass,Less,CoffeeScript等のコンパイル
* CSS,JavaScriptの結合やminify
* PHPUnit,PHPSpec等の実行
* イベントやルーティングのスキャン(キャッシュ化)
といった、様々なタスクがサポートされています。

そんなLaravel Elixirを、Homesteadで試してみました。尚、Homesteadには最初からGulpがインストールされています。
http://laravel.com/docs/4.2/homestead

* Laravel 5もLaravel Elixirも現在開発中なので、今後もまだまだ変わる可能性はあるでしょう。

1. Homesteadのインストールと起動

http://madroom-project.blogspot.jp/2014/11/laravel-homesteadhomestead.html

2. Laravelプロジェクトの作成とLaravel Elixirに必要なパッケージのインストール

# Homesteadにログイン
$ homestead ssh

# Laravelプロジェクトの作成
# Laravel 5は現在開発中なので"dev-develop"を付けてインストールします
$ composer create-project laravel/laravel elixir-example dev-develop

# Laravel Elixirに必要なパッケージのインストール
$ cd elixir-example/
$ npm install
以下のコマンドは全てHomesteadの中です。

3. sassのコンパイルを試してみる

gulpfile.jsを適当に編集してみます。デフォルトではbowerと絡めてBootstrapを使用するための記述がありますが、今回は使わないので削除しています。
elixir(function(mix) {
    mix.sass('app.scss');
});
上記の'app.scss'は resources/assets/sass/app.scss を指しています。こちらも適当に編集してみます。
body {
  background: #000;
}
gulpコマンドでコンパイルしてみます。
$ gulp
[20:31:25] Using gulpfile ~/Code/elixir-example/gulpfile.js
[20:31:25] Starting 'default'...
[20:31:25] Starting 'sass'...
[20:31:27] Finished 'default' after 1.91 s
[20:31:27] gulp-notify: [Laravel Elixir]
[20:31:27] Finished 'sass' after 1.98 s
[20:31:27] gulp-notify: [Error in notifier] Error in plugin 'gulp-notify'
not found: notify-send
("gulp-notify"云々のエラーはなんだろう)

public/css/app.css としてコンパイルされました。
body {
  background: #000; }
gulp watch コマンドで、ファイル変更の度に自動でコンパイルすることもできます。
$ gulp watch

4. コンパイルするファイルにバージョン名をつける

gulpfile.jsを次のようにしてコンパイルしてみます。
elixir(function(mix) {
    mix.sass('app.scss')
        .version('css/app.css');
});
すると public/build/css/app-d8890e6e.css のように、ハッシュ付きのcssファイルが生成されました。

ビューファイルで以下のように書けば
<link rel="stylesheet" href="{{ elixir("css/app.css") }}">
public/build/rev-manifest.json を参照して
<link rel="stylesheet" href="/build/css/app-d8890e6e.css">
のようにHTMLを出力してくれます。

5. minifyする

$ gulp --production
でminifyしてくれました。

6. PHPUnitを実行する

gulpfile.jsを以下のようにすると、PHPUnitを実行して、テストが失敗すれば処理を停止、テストが成功すれば処理を継続。となりました。
elixir(function(mix) {
    mix.phpUnit()
        .sass('app.scss')
        .version('css/app.css');
}); 

7. 複数ファイルを結合してみる

scssファイルを適当に2ファイル用意してみます。
resources/assets/sass/foo.scss
.foo {
  background: #000;
}
resources/assets/sass/bar.scss
.bar {
  background: #fff;
}
gulpfile.jsを次のようにします。
elixir(function(mix) {
    mix.sass(['**/*.scss'])
        .styles('css/**/*.css')
        .version('css/all.css');
});
コンパイルすると、結合されたファイルが生成されました。
public/build/css/all-993ce219.css
.bar {
  background: #fff; }

.foo {
  background: #000; }
LessやCoffeeScriptのコンパイル、JavaScriptの結合やminify、PHPSpecの実行等も、似たような感じで実行できるはずです。(たぶん。)

以上のように、Laravel 5では、Laravel Elixirを用いて様々なタスクを簡単に実行できるようになるみたいですね。

gulpはタスクが非同期で実行されるが故に、実行順序で少しハマったことがあるのですが、その辺も吸収してくれると助かるなぁと思いました。

November 17, 2014

Laravel Homesteadをhomesteadコマンドで起動してみる

Laravelが標準的に提供している仮想環境"Homestead"がCLI化され、より簡単に起動できるようになったみたいなので試してみました。
http://laravel.com/docs/4.2/homestead

当記事は
* Mac OSX Yosemite
* VirtualBox 4.3.18
* vagrant 1.6.5
で確認しています。

1. composer global require コマンドでインストールします。
$ composer global require "laravel/homestead=~2.0"
2. ~/.composer/vendor/bin/homestead にパスを通します。以下、一例。
$ ln -s ~/.composer/vendor/bin/homestead /usr/local/bin/
3. homestead init コマンドを実行して初期化します。ホームディレクトリに".homestead"ディレクトリと、必要なファイルが生成されます。
$ homestead init
Creating Homestead.yaml file... ✔
Homestead.yaml file created at: /Users/xxx/.homestead/Homestead.yaml
4. homestead edit コマンドでHomestead.yamlがエディタで開かれました。
$ homestead edit
5. 以下、Homestead.yamlの設定項目の抜粋です。必要に応じて編集します。今回は、デフォルト設定のまま進めました。
authorize ... 使用する公開鍵
keys ... 使用する秘密鍵
folders (map/toを対にして複数指定できると思います。)
  map ... ホスト側のディレクトリ
  to ... ゲスト(Homestead)側のディレクトリ
sites (map/toを対にして複数指定できると思います。)
  map ... ベースURL
  to ... ドキュメント(WEB)ルート
6. デフォルト設定に対して必要なファイルを用意します。
$ mkdir -p ~/Code/Laravel/public
$ vim ~/Code/Laravel/public/index.php
~/Code/Laravel/public/index.php
<?php

phpinfo();
7. hostsも忘れずに編集しておきます。
$ sudo vim /etc/hosts
192.168.10.10 homestead.app # 追記
8. homestead up コマンドでHomesteadを起動します。
$ homestead up
9. http://homestead.app/ に正しくアクセスできれば成功です。