Laravel の unique ルールとソフトデリート
先月中旬から東京に出張行ってるんですが、通勤のストレス*1から部屋(くっそ狭いマンスリーマンション)で勉強する気力が沸かなかったのですが、1ヶ月弱経ちまして少しは回復したのでまずはブログでリハビリ。
フォーラムに投稿された質問
メールアドレスを他の人と被らないよう、バリデーションを「users,mail_address」としました。
[解決済み] バリデーションで「unique」を指定した際のソフトデリートとの連携について - laravel.jp
usersテーブル自体はソフトデリートを有効にしています。 この場合、削除したユーザのメールアドレスと、新しく登録するユーザのメールアドレスが同じ場合、 バリデーションに引っ掛かって登録することができません。
どうにかソフトデリートとユニークなメールアドレスを両立させる方法はないでしょうか。
Laravel の Validator からはモデルがソフトデリート(論理削除を簡単に実装できる機能)を使ってるかどうかわからないので unique
ルールを適用したら削除したモデルがひっかかって辛いという話です。
再現するとこんな感じです。
<?php // マイグレーションファイル(抜粋) <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; class CreateUsersTable extends Migration { public function up() { Schema::create('users', function(Blueprint $table) { $table->increments('id'); $table->string('email'); $table->softDeletes(); $table->timestamps(); }); } } // シードファイル(抜粋) class UserTableSeeder extends Seeder { public function run() { // Model の $unguarded を true にすると楽 // これ豆知識な User::unguard(); User::create([ 'email' => 'test@example.com' ]); } } Route::get('unique', function() { User::destory(1); // 削除した! $email = 'test@example.com'; $v = Validator::make(['email' => $email], ['email' => 'unique:users']); // 削除したけどバリデーションはエラー! var_dump($v->fails()); });
ソフトデリートの場合、削除されるとカラムの deleted_at
に日付を入れるだけなので unique
で発行される SQL ではダメなわけです。ちなみにこんなSQLが発行されます。
select count(*) as aggregate from "users" where "email" = ?
上記 SQL の結果が 0 の場合 unique
バリデーションは通過します。
解決方法
クエリーへWHERE節として追加される条件を追加することも可能です。
<?php 'email' => 'unique:users,email_address,NULL,id,account_id,1'v4.2:バリデーション
なのでこんな感じで書いて上げればOK.
<?php Validator::make(['email' => $email], ['email' => 'unique:users,email,NULL,id,deleted_at,NULL']);
こうするとこんなSQLが発行されます。
select count(*) as aggregate from "users" where "email" = ? and "deleted_at" is null
where 節が付加されているのがわかりますね。
*1:なんか抜け毛が増えた気がする
Laravel で簡易APIサーバーを作ってみた
風邪をひいて一日中臥せっていた。が、さすがにずっと眠れるわけもないので意味もなく blog を更新してみる試み。
Sinatraで簡易APIサーバーを作ってみた | Developers.IO の Laravel 版。
動作確認
では、実際に動かしてみよう。ターミナル(あるいはコマンド・プロンプト)で Laravel のプロジェクトにカレントディレクトリを移動させて以下のコマンドを実行する。
$ php artisan serve
こうするだけで PHP の Built-in web server が起動する。あとはブラウザから http://localhost:8000/show
にアクセスしてみよう。
{"id":1,"title":"today's dialy","content":"It's a sunny day."}
こんな感じで出力されていると思う。余談だが、return Response::json($article);
の部分を return json_encode($article);
しても同じ結果に見えるが Response::json
を使用すると Content-Type
を application/json
にしてくれる。json_encode
使うと text/html
になるので実際おすすめできない。
次は POST リクエスト。元エントリと同じく curl
を使用してリクエストを投げる。まずは body を未指定で POST する。
curl -X POST -I http://localhost:8000/edit
結果はこんな感じ
HTTP/1.0 400 Bad Request (以下略)
return App::abort(400);
がちゃんと機能しているのがわかる。
次は body を指定して POST する。
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"title":"MyFavorite","content":"Color is Black."}' http://localhost:8000/edit -w "\n%{http_code}\n"
結果は
略 {"title":"MyFavorite","content":"Color is Black."} 200
リクエストされたものがそのまま返却された。
言いたいこと
Laravel は簡単なことはちゃんと簡単に書ける。僕らは難しいことに頭をひねろう。
Typetalk Hack Fukuoka に行ってきたよ
ブログを書くまでが勉強会です。
Typetalk Hack Fukuoka on Zusaar
API を見ると、とりあえず叩いてみる習性があるので行ってきました。
感想としては、90 分でなにか作るのは結構厳しいなぁ…とか思ってたんですが発表を聞いている限りみんなすごかったので、こりゃあ頑張らなきゃなあとおもいました(小並感)。
発表で印象に残っているのは @kiwanami さんのS式をポストすると結果を返す変態botと cakephper さんのtwilio API とマッシュアップさせてtypetalk にボイスメッセージを届くようにする仕組み。
僕はなぜか、backlog-api-php のテスト書いてました。typetalk-api-php もリポジトリだけは作ったので近いうちにリリースしたい。
その後の懇親会は(いや、全体的に)@hayashi_77無双でした。とても愉快な方です。
楽しかったのでまたやって欲しいなぁ。
Laravel で View を文字列で取得する
簡単。
<?php $viewStr = View::make('home')->render();
でOK.セクション毎にも取れる。
<?php $viewStr = View::make('home')->renderSections()['content'];
Laravel 4.2 Beta をインストールする
これが一番簡単だと思います。
$ git clone -b develop https://github.com/laravel/laravel.git laravel-beta $ cd laravel-beta $ composer install
変更点等は @HiroKws のツイートをチェックするべし。
注意点ですが、trait を使用しているため PHP5.4 以上になっているのでそこだけ気をつけてください。
追記
今現在開発しているアプリケーションを 4.2 にあげたい人は composer.json
を修正しましょう。
{ "require": { "laravel/framework": "4.2.*" }, "minimum-stability": "dev" }
この2箇所を修正すればOKです。
Laravel で Controller から Controller を呼ぶ
@syossan27 やりたいことがピンときませんが、処理が終わったらもう一つのアクションにリダイレクトしてみてはいかがでしょうか?
— MATSUO Masaru (@localdisk) 2014, 5月 12
たまーに、こんなことしたいことがありますね。で、まぁリダイレクトすればいいよ、みたいな回答したんですが、よく考えたら普通にできるよなーと。
まず、呼び出される CalledController
.
<?php class CalledController extends \BaseController { protected $user; public function __construct(User $user) { $this->user = $user; } public function getUser() { return $this->user; } }
この getUser
を呼び出したいとします。
で、呼び出す側。CallController
はこんな実装になります。
<?php class CallController extends \BaseController { public function getIndex() { $called = app()->make('CalledController'); $user = $called->getUser(); var_dump($user); } }
app()->make('CalledController')
でコンテナ経由で CalledController
のインスタンスを取得しています。なので CalledController
に定義されているコンストラクタインジェクションが機能しててちゃんとインジェクションされた User
クラスが取得できるわけです。
もちろんコンストラクタで CalledController
をインジェクションすることもできます。
<?php class CallController extends \BaseController { protected $called; public function __construct(CalledController $called) { $this->called = $called; } public function getIndex() { $user = $this->called->getUser(); var_dump($user); } }
まぁ、Controller から別の Controller のメソッドを呼びたいというシチュエーションが発生した場合、リファクタリングのチャンスと思ってリファクタリングしたほうがいいんじゃないかなーと思います。色々あってそうも行かない時等にこのエントリを思い出していただければ幸い。
Typetalk Hack Fukuoka で Services_Backlog を作り直します
むかーし、Services_BacklogというBacklog の API の PHP Wrapper を作った*1んだけど、PEAR も時代遅れだし、対応しているAPIが少ないとか、なんか動かなくて困ってる人とかいて、心の中で謝罪を繰り返していたんですが、ちょうど上記のイベントが開催されるようなのでちゃんと作りなおそうと思います。余裕があれば typetalk の PHP API Wrapper も作りたいところ。
もう大体できてる。
手抜き部分*2やテスト書いたらリリースします。もちろん Composer 対応です。今回は PSR-4 使ってみた。
(2014/05/14 追記)
こっそりリリース localdisk/backlog-api-php - Packagist
あと、明日は Fukuoka.jvm #1 に行ってきます。ここのところ、Java で頭使う仕事全然してなくて*3 Java 力が落ちる一方なのでここらへんで喝を入れたい。
FuelPHP&CodeIgniter ユーザの集い #4 に行ってきたよ
東京の勉強会とか楽しそうだなーいいなーと言ってるばかりでは芸がないので遊びに行ってきました。
ついでに以前作りかけで放っておいた CodeIgniter 改善版をいい機会なので終わらせてしまおう、そして発表しようということで喋らせてもらう機会も作っていただきました。感謝。
当日発表したスライドはこちら。
スライドについて、少し解説。
CodeIgniter について
CodeIgniter は数ある PHP フレームワークの中でも(世界的には)群を抜く知名度を持ってるフレームワークです。
仕組みはとにかくシンプルで、それゆえに実行速度、開発速度、学習コストに優れています。
CodeIgniter の落日
そんな CodeIgniter ですが、身売りの問題が発生してリリースが滞っています。github を参照するに開発自体はまぁまぁ活発といえなくもないですが、この問題が解決しない限り 3.0 がリリースされることはないでしょう。
CodeIgniter の欠陥
CodeIgniter には get_instance
という非常に便利な関数があり、この関数は helper や Library 内のクラスから頻繁にコールされています。で、この get_instance
は何をしているかというと実行時にロードされるコントローラのインスタンスを返しています。この仕組みはテスト時に深刻な問題を引き起こしており、結果テストを行うのがめんどくさいことになってます。
だって、 helper ひとつテストするのにコントローラが必要とかわけがわからないよ。
問題を解決する
そんなわけで今作っているフレームワークで種々の問題の解決を試みています。新しいフレームワーク(CodeExploder という名前をつけました)では get_instance ではコントローラではなく Loader を返すようにして、Loader 自体は DI Container を採用しています(Laravel の illuminate/container を採用)。あとは名前空間や Composer 対応、機能が弱い DB 周りを illuminate/database に置き換えるとか日本語に弱いメール周りを swiftmailer/swiftmailer に置き換えるとかそんな感じ。
コンセプトとしては可能な限り後方互換性を維持しつつ、中身はモダン。GW でなにかしら出せればいいなぁと思います。
最後に
飽きるまで頑張ります。
Laravel 4.1.26 の対応方法
昨日、Laravel 4.1.26 がリリースされました。
4.1.26 は自動ログインに使用するクッキーのセキュリティ強化のためのアップデートなので、Laravel 標準のユーザー認証を使ってる人はすぐにアップデートすべし。
ただし、Sentry 使ってる人は範囲の対象外です。Sentry は Laravel の UserInterface
に依存してないので。
変更内容はこんな感じ
Laravelの"Remember me"の件、ソースを確認したところ
- tokenの作成方法が変わった ($remember が true の場合毎回作り直す)
- tokenをDBに保存(remember_tokenカラム)
という感じみたい。
— MATSUO Masaru (@localdisk) 2014, 4月 16
なので、自分たちで作成したプログラムにも変更が必要です。
UserInterface
UserInterface
に3つのメソッドが追加されました。なので実装する必要があります。実装しなければならないのは getRememberToken
, setRememberToken
, getRememberTokenName
メソッドです。
gist を作ったので参考にしてください。ぶっちゃけコピペでいいです。
remember_token カラム追加
認証対象のテーブルに remember_token
カラムを追加すればOK。
簡単ですね。
最後に
これ書いてる途中に気づいたんですけど、4.1.27 のアップデートがきてます。どうやら Validator
の bugfix のようです。