localdisk

PHP とか Java とか Web とか好きなことを書きます。

Laravel の unique ルールとソフトデリート

先月中旬から東京に出張行ってるんですが、通勤のストレス*1から部屋(くっそ狭いマンスリーマンション)で勉強する気力が沸かなかったのですが、1ヶ月弱経ちまして少しは回復したのでまずはブログでリハビリ。

フォーラムに投稿された質問

メールアドレスを他の人と被らないよう、バリデーションを「users,mail_address」としました。
usersテーブル自体はソフトデリートを有効にしています。 この場合、削除したユーザのメールアドレスと、新しく登録するユーザのメールアドレスが同じ場合、 バリデーションに引っ掛かって登録することができません。
どうにかソフトデリートとユニークなメールアドレスを両立させる方法はないでしょうか。

[解決済み] バリデーションで「unique」を指定した際のソフトデリートとの連携について - laravel.jp

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 について

Laravelは表現力に富むエレガントな記述が使用できるWebアプリケーションのフレームワークです。

Laravel-イントロダクション

多分トレーズ閣下くらいエレガント。

ソース

Gist を作ってある。

説明は割愛。見ればなんとなくわかると思う。元エントリである Sinatra と比べてみてほしい。

動作確認

では、実際に動かしてみよう。ターミナル(あるいはコマンド・プロンプト)で 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-Typeapplication/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式をポストすると結果を返す変態botcakephper さんのtwilio APIマッシュアップさせてtypetalk にボイスメッセージを届くようにする仕組み。

僕はなぜか、backlog-api-php のテスト書いてました。typetalk-api-phpリポジトリだけは作ったので近いうちにリリースしたい。

その後の懇親会は(いや、全体的に)@hayashi_77無双でした。とても愉快な方です。

楽しかったのでまたやって欲しいなぁ。

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 を呼ぶ


たまーに、こんなことしたいことがありますね。で、まぁリダイレクトすればいいよ、みたいな回答したんですが、よく考えたら普通にできるよなーと。

まず、呼び出される 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 のメソッドを呼びたいというシチュエーションが発生した場合、リファクタリングのチャンスと思ってリファクタリングしたほうがいいんじゃないかなーと思います。色々あってそうも行かない時等にこのエントリを思い出していただければ幸い。

Guzzle を使って XML-RPC を利用する

作る前はめんどくさいかなーと思ってたら簡単だった。

22 行目の xmlrpc_encode_request は、XML-RPC のめんどくさい XML を作ってくれる関数。もう一つは 25 行目の auth 部分。こう書けば HTTP HEADER をちゃんと付加してくれる。ほんとは user/password のほかに認証形式が必要なんだけどデフォルトが Basic 認証なのでそのまま。

さよなら PEAR XML-RPC. 君にはお世話になった。

Typetalk Hack Fukuoka で Services_Backlog を作り直します

むかーし、Services_BacklogというBacklogAPIPHP 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 力が落ちる一方なのでここらへんで喝を入れたい。

*1:devworks っていうのは僕の旧アカウントです。

*2:xmlrpc_decode が入ってない人対応とか。

*3:同じような Web アプリケーションばっかり作ってた。

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 に依存してないので。

変更内容はこんな感じ

なので、自分たちで作成したプログラムにも変更が必要です。

UserInterface

UserInterface に3つのメソッドが追加されました。なので実装する必要があります。実装しなければならないのは getRememberToken, setRememberToken, getRememberTokenName メソッドです。

gist を作ったので参考にしてください。ぶっちゃけコピペでいいです。


remember_token カラム追加

認証対象のテーブルに remember_token カラムを追加すればOK。

簡単ですね。

最後に

これ書いてる途中に気づいたんですけど、4.1.27 のアップデートがきてます。どうやら Validator の bugfix のようです。