Laravelのバリデーション拡張を作った
LaravelでWebアプリを書いていて、ややバリデーション周りの機能が薄いと感じたので拡張パッケージを書きました。
Laravel標準のバリデーションについては公式のドキュメントを見ればいいかと思います。この場合Validator::make
メソッドに入力データ、バリデーションルール、デフォルトから変える必要があるならエラー時のメッセージ、をそれぞれ配列で渡してvalidator
オブジェクトを作成し、その後fails
メソッドで検証が通るかどうかを確認します。例えば以下のようなコードになります。
<?php $rules = array( 'username' => 'required|alpha', 'password' => 'required|alpha|min:8', ); $validator = Validator::make(Input::all(), $rules); if ($validator->fails()) { return Redirect::back()->withErrors($validator); }
シンプルなのはいいのですが、実際のアプリケーションを書くとなると更に以下のようなことがしたくなります。
- バリデーションルールの定義箇所をメインロジックから個別のクラスに外出ししたい。
- バリデーションの前後で値の変換処理を行いたい。例えばバリデーションの前に値をtrimしたり年月日で個別のフィールドに入力された値を結合して日付形式にするなど。
このへんの仕組みが標準では用意されていなかったので、前述の拡張を書きました。
使い方
バリデータクラスをこんな感じに定義して。。。
<?php // app/validators/BlogValidator.php class BlogValidator extends BaseValidator { protected function configure() { $this ->rule('title', 'required', 'Title is required.') ->rule('title', 'max:100', 'Title must not be greater than 100 characters.') ->rule('body', 'pass') ; } }
以下のように使います。
<?php $validator = BlogValidator::make(Input::all()); if ($validator->fails()) { return Redirect::back()->withInput(Input::all())->withErrors($validator); } $data = $validator->onlyValidData();
バリデーション定義がメインロジックから切りだされてスッキリ。
また$validator->onlyValidData
メソッドはバリデートが行われた項目の値のみを配列で戻すメソッドなので、DB更新時などはこの値をEloquentモデルのマスアサインメントでまるっと設定してやればいいかと思います。
フィルタとカスタムバリデーションルール
バリデーション前後に何らかの処理を入れたいときはbeforeFilter
とafterFilter
にクロージャを登録します。
<?php class BlogValidator extends BaseValidator { protected function configure() { $this->beforeFilter(function($validator){ // your code }); $this->afterFilter(function($validator){ // Modify title after validation. $title = $validator->title; $title .= " created by kohki"; $validator->title = $title; }); } }
独自のバリデーションルールを定義したいときはvalidateXXX
というメソッドを作ればOKです。メソッドの規約は標準のカスタムバリデーションルールの定義方法を同じですので、公式ドキュメントを参考にしてください。
<?php class BlogValidator extends BaseValidator { protected function configure() { $this ->rule('title', 'required', 'Title is required.') ->rule('title', 'max:100', 'Title must not be greater than 100 characters.') ->rule('body', 'foo', 'Body must be foo only!') ; } protected function validateFoo($attribute, $value, $parameters) { return $value == 'foo'; } }
インストール方法
composerでインストールします。composer.json
に以下を記述して
"require": { "kohkimakimoto/laravel-validator-extension": "0.*" }
composer updateします。
$ composer update
ServiceProviderとBaseValidatorのエイリアスをapp/config/app.php
に登録します。
'providers' => array(
...
'Kohkimakimoto\ValidatorExtension\ValidatorExtensionServiceProvider',
}
'aliases' => array( ... 'BaseValidator' => 'Kohkimakimoto\ValidatorExtension\Validator', ),
また、私は今のところapps/validators
ディレクトリを切ってそこに個々のバリデータクラスを作成しているので、オートロードされるようにLaravel(app/start/global.php
)とcomposer(composer.json
)にオートロード設定を追加します。
ClassLoader::addDirectories(array( ... app_path().'/validators', ));
"autoload": { "classmap": [ ... "app/validators" ] }
Laravelかわいいよ、Laravelヽ(´ー`)ノ
pecoでカレントディレクトリごとによく使うコマンドを呼び出せるようにする
の続き。ちょいちょいスクリプトをカスタマイズしていたので。
ところで最近の開発はコードを書いているときだいたい開発中アプリケーションのトップディレクトリにいて、そこでいつもいくつか決まったコマンドを実行する、ということがよくあります。grunt
とかcomposer install
とかphp vendor/bin/phpunit
とか。
私はPHPをよく書くのでcomposer install
などは手に馴染んでいて入力するのに困ったりしないのですが、たまにRubyのプロジェクトをいじるときBundlerの使い方を忘れていてbundle install --path=vendor/bundle --binstubs=vendor/bin
とかを毎回、Gistにメモっておいたスニペットから引っ張ってきて入力してたりしました。
アプリケーションごとのスニペットファイル
そこでpecoをランチャーのようにして使うで書いたスクリプトをちょっと拡張して、カレントディレクトリに.snippets
ファイルをおいておくと、そこからもスニペットを取得するようにしました。
# snippets function peco-snippets() { local line local snippet local cwd local local_snippet if [ ! -e "~/.snippets" ]; then echo "~/.snippets is not found." >&2 return 1 fi # Get snippets in the current directory if it exists. cwd=`pwd` if [ -e "$cwd/.snippets" ]; then local_snippet="$cwd/.snippets" else local_snippet="" fi line=$(cat $local_snippet ~/.snippets | grep -v "^\s*#" | grep -v '^\s*$' | peco --query "$LBUFFER") if [ -z "$line" ]; then return 1 fi snippet=$(echo "$line" | sed "s/^[ |\*]*\[[^]]*\] *//g") if [ -z "$snippet" ]; then return 1 fi BUFFER="$snippet" zle clear-screen } zle -N peco-snippets bindkey '^x^x' peco-snippets
忘れやすいコマンドなどは.snippets
ファイルを以下のように記述しておけば、
EArray (https://github.com/kohkimakimoto/EArray) * [phpunit test] php vendor/bin/phpunit * [fix code] php vendor/bin/php-cs-fixer fix src * [composer] composer update
cmd-x+cmd-x
のショートカットでpecoの選択インターフェースが起動して、アプリごとに必要なコマンドが選択肢に表示されるので、あとはそこから選べばよくなりました。
pecoをランチャーのようにして使う
前回に引き続き、pecoが大変気に入ったので、その後もいろいろネットで情報さがしたりしてました。それで
peco/percolでCUIなスニペットツールを作ってみる
の記事を見て同じこと導入してみました。いやはや便利。元記事に感謝。ついでに多少カスタマイズして、スニペットの先頭にラベルをつけてみました。
手順
zshの場合は以下のような関数を.zshrc
などに記述しておく
function peco-snippets() { local line local snippet if [ ! -e ~/.snippets ]; then echo "~/.snippets is not found." >&2 return 1 fi line=$(grep -v "^#" ~/.snippets | peco --query "$LBUFFER") if [ -z "$line" ]; then return 1 fi snippet=$(echo "$line" | sed "s/^\[[^]]*\] *//g") if [ -z "$snippet" ]; then return 1 fi BUFFER=$snippet zle clear-screen } zle -N peco-snippets bindkey '^x^x' peco-snippets
bashの場合は以下のような関数を.bashrc
などに記述
function peco-snippets() { local line local snippet if [ ! -e ~/.snippets ]; then echo "~/.snippets is not found." >&2 return 1 fi line=$(grep -v "^#" ~/.snippets | peco --query "$READLINE_LINE") if [ -z "$line" ]; then return 1 fi snippet=$(echo "$line" | sed "s/^\[[^]]*\] *//g") if [ -z "$snippet" ]; then return 1 fi READLINE_LINE="$snippet" READLINE_POINT=${#READLINE_LINE} clear } bind -x '"\C-x\C-x":peco-snippets'
あとはスニペットファイル.snippet
に[***]
の形式でラベルをつけたコマンドを記述しておく。
# SSH [ローカル仮想環境へSSH接続:local-server01:connect] ssh kohkimakimoto@192.168.56.21 -p 22 # Vagrant [ローカル仮想環境Vagrant状態確認:status] cd /Users/kohkimakimoto/Documents/vagrant/hk && vagrant status && cd - [ローカル仮想環境Vagrant起動:up] cd /Users/kohkimakimoto/Documents/vagrant/hk && vagrant up && cd - [ローカル仮想環境Vagrant停止:halt] cd /Users/kohkimakimoto/Documents/vagrant/hk && vagrant halt && cd - # etc [Snippetsファイルを開く:open] subl ~/.snippets [Shell拡張設定ファイルを開く:open] subl ~/.shell_extention
2014/06/30編集
.snippetsを読みに行く前にファイルの存在チェックをいれました。
2014/07/01編集
コマンド内に[]があるとラベル削除がうまくいかないところを直しました。
pecoでコマンドラインからファイルやディレクトリを開いたりしてみる
すこし乗り遅れた感じですが、最近話題のpecoをさわってみました。 pecoがなんなのかは以下のページなどを参照してください。
このツールはGoで書かれていて、しかも各種プラットフォーム向けにバイナリファイルを配布しているので、パスの通ったディレクトリにそのバイナリファイルをおくだけで動作するという手軽さがいいです。導入が楽なのは個人的にすごく重要なので。
機能をざっくりいうと、コマンド標準出力の行に対して選択機能のインターフェースを差し込めるというモノです。 文字にすると簡素なものですが、使ってみるとものすごく応用性の高いツールです。これホントすげー。でもこのすごさが言葉で説明できん。ツール自体のシンプルさとそれ故の応用性の高さが見事で、なんというか美しいのです。たぶんあれだ、確かUnixの哲学に「パイプやリダイレクトでつなげられるように、プログラムは最小の単位で設計すべし」みたいなものがあったハズで、それをドンピシャで体現しているんじゃないかと。pecoはpercolという同様のツールをもとに作られているとのことですが、この仕組みを考えた人は相当頭いいなーと、思わせてくれる逸品です。
さて、調べてみたらみんな自分の目的にあわせて、いろいろラッパーの関数やエイリアスを書いたりしてるみたいなので、私もちょこっと書いてみました。なおMacの環境用です。
function peco-cd() { local var local dir if [ ! -t 0 ]; then var=$(cat -) dir=$(echo -n $var | peco) else return 1 fi if [ -d "$dir" ]; then cd "$dir" else echo "'$dir' was not directory." >&2 return 1 fi }
これはcd
で移動したいディレクトリをfind
コマンドなどで検索して、検索結果からpecoで選択、移動する、というのをやります。以下のように使っています。
find . | peco-cd
同様にfind
コマンドで探したファイルを開くような関数も書きました。
function peco-open() { local var local file local command="open" if [ ! -t 0 ]; then var=$(cat -) file=$(echo -n $var | peco) else return 1 fi if [ -n "$1" ]; then command="$1" fi if [ -e "$file" ]; then eval "$command $file" else echo "Could not open '$file'." >&2 return 1 fi }
これは
find . | peco-open
のように使います。またpeco-open
の第二引数にコマンドを指定すれば、そのコマンドで選択したファイルを開きます。
たとえば、テキストファイルをコマンドラインで検索して、SublimeTextで開くには以下のようにやります。
find . | peco-open subl
これでもはや、Finderでちまちまディレクトリをおりていく必要はなくなったのでした。(^O^)
Bashスクリプトで実行ファイルのディレクトリを取得する
よくやるのが以下の記述だったのだけれど
SCRIPT_DIR=$(cd $(dirname $0); pwd)
これだと、このスクリプトをシンボリックリンクから呼び出したときに、 実ファイルのパスでなくリンク先のディレクトリが取得されてしまっていた。 シンボリックリンクから呼び出されたときも、実ファイルのパスを返すようにするには、
SCRIPT_DIR=$(cd $(dirname $(readlink $0 || echo $0));pwd)
のようにすればよさげ。
追記)id:ngyukiさんにご指摘をいただいたので修正。
スクリプトがシンボリックリンクなディレクトリにあることを考慮して readlink -f とか cd -P とか pwd -P とかにするとなお良いのかな
おお、なるほど。やってみたら確かにシンボリックリンクなディレクトリにあるとうまくいかなかった。なので修正した以下の書き方がベターです。
SCRIPT_DIR=$(cd $(dirname $(readlink -f $0 || echo $0));pwd -P)
これならうまくいきました。
追記)2014-06-25
上記の記事はシンボリックリンクが相対パスだとやっぱりうまくいかなかったり、readlink -f
がmacでは使えなかったりでいろいろ至らない。
その後もいろいろ調べたら、rbenvのスクリプト内にあったabs_dirname
がいい感じでした。
ちょこっと手直ししたのが以下になります。
abs_dirname() { local cwd="$(pwd)" local path="$1" while [ -n "$path" ]; do cd "${path%/*}" local name="${path##*/}" path="$(readlink "$name" || true)" done pwd -P cd "$cwd" } script_dir="$(abs_dirname "$0")"
枠だけのUIButtonをハイライトで背景色を変えるための拡張
中身透明で枠だけのボタンを作って、ハイライト時に背景色をボーダー色と同じに変えるUIButton拡張クラス(iOS7のロック画面パスコード入力ページの数値ボタンのようなヤツね)の作り方をメモしておきます。
UIBorderOnlyButton.h
#import <UIKit/UIKit.h> @interface UIBorderOnlyButton : UIButton @property (strong, nonatomic, readonly) UIColor *originalBackgroundColor; @end
UIBorderOnlyButton.m
#import "UIBorderOnlyButton.h" @implementation UIBorderOnlyButton - (void)setHighlighted:(BOOL)highlighted { [super setHighlighted:highlighted]; if (highlighted) { super.backgroundColor = [UIColor colorWithCGColor:self.layer.borderColor]; } else { super.backgroundColor = self.originalBackgroundColor; } } - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; _originalBackgroundColor = backgroundColor; } @end
setHighlighted
をオーバーライドして同時に背景色を変え、元々の背景色をoriginalBackgroundColor
プロパティに退避しておく
しくみになっている。
あとは、IBビルダーで配置したButtonのclassをUIBorderOnlyButton
に変えて、ViewControllerなどのコード上で以下のようにボーダーを設定してやればOKです。
self.hogeButton.layer.borderColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0].CGColor; self.hogeButton.layer.borderWidth = 1.0;
参考サイト:
第77回PHP勉強会でAltaxをとりあげていだだきました
4/28にEngineYardさんで行われた第77回PHP勉強会で、私がオープンソースで作っているAltaxというPHPデプロイツールをとりあげていただきました。とっても嬉しかったのでブログに書いちゃいます。PHP勉強会についての詳細はリンク先を参照ください。
セッションの内容が動画でアップされてあったので先日拝見させていただきました。 登壇者の木村さんには丁寧な資料を作っていただき、デモまでして頂いて感謝の限りです。
自分が作っているプログラムが衆人の前で実行される場面を見るというのは、なんだか緊張と気恥ずかしさみたいなものがあって、動画を再生しているPCの前でニヤニヤ、もじもじしてしまいました。俺きめぇ。
さて、セッション内であがっていた話題などについて、いくつか言及させていただきたいと思います。
利用実績について
Altaxの利用実績があるのかという質問に対して、登壇者の木村さんも「......う〜ん(笑」と苦笑いしておりましたが、私も聞いたことがありません(笑。ごめんなさい。 あ、でも「作者は使っているハズ」という件について、はYESと答えられます。 私自身は自分が仕事で開発、運用しているWebアプリケーションのデプロイに使っています。 使い方はデプロイ用の作業サーバがあってそこでAltaxを実行してます。処理としては
- ローカルにリポジトリからWebアプリのソース一式を取得
- composer installなどのWebアプリのビルド処理を実行
- アプリケーションサーバ群にWebアプリをrsyncして配布
- 再起動が必要なデーモンの再起動
という一連の作業を自動化しています。
Githubで公開しているライブラリについて
セッション中、私がGithubで公開している他のPHPライブラリについても取り上げていただきました。 いくつか概要と私個人の利用内容について記載します。
kohkimakimoto/BackgroundProcess
これはPHPのWebアプリから非同期にコマンドを実行させるためのライブラリで、ここ にも書いたように、CSVファイルなどから一括で大量データをDBに投入するコマンドとWebインターフェースの連携に実際に使っています。 実はエラー処理まわりが不十分でその辺を修正してアップデートさせたいと思っているのですが、手がまわっていません。
MySQL用のDBマイグレーションツールです。PHPファイル一つで動くシンプルなツールで、後継のライブラリにkohkimakimoto/lib-migrationがあります。こちらはcomposerからのインストールに対応しています。 両方ともPHPクラス内に記述した素のSQLでスキーマ変更を行います。 これらのツールはこれまで素のSQLファイルと手作業でDB変更を行ってきていた古い既存システムがあって、そこにDBマイグレーションを導入する際に作ったという経緯があります。そのため今までのSQLをそのまま使いたかったのと、スキーマ定義をあまり抽象化した記述で行いたくなかったので、こういう設計のツールになりました。詳しくはここに書いてあります。
システムメンテの改善と小さなツール
Altaxもそうなのですが、これらのツールは私が実際に仕事で関わっている「3、4年まえからメンテし続けているちょっと古いシステム」の運用を改善するために作ったものです。このようなシステムは「まるっとすべてのリプレースを要するほど非効率な状態にあるわけでもないが、モダンなフレームワークなどにはある便利機能がなくて不満」という状況にあります。そこを少しずつ埋めるために開発しました。
おわりに
Altaxにはなぜか海外の開発者が多くGithubのスターをつけてくれていて、たまーにプルリクエストをくれるのも海外の方が多いです。うまく書けている自信は全然ないのですが英語でドキュメントを書いていたのがよかったのかもしれない、と個人的には思っています。 Altaxの初期バージョンはOSのSSHコマンドをPHPプログラムから実行するただのラッパーでした。改めてこれまでの開発を見直すと、なかなか成長したなあと思え、ちょっと感慨深いです。
今回、PHP勉強会で取り上げられたことはまったく予期していなかったので本当に驚いて、そしてこのように自分の作っているソフトウェアに好意的な反応をいただけるのはとても嬉しいことだな、と思いました。