しかしながら、その機能の中で、今までどうしても不満に思っていた点がありました。
それは、Form Model Bindingをする時に
日付がCarbonである場合、テキスト出力される際にフォーマットが指定できず、
テキスト欄に強制的にY-m-d H:i:sと出てきてしまっていた点です。
なぜここだけこんなにも融通が利かないんだろう。
という印象を受けた人は一人ではないのではないかと思います。
私も、dateと datetimeメソッドをフォーマット指定が可能なように
拡張したクラスを作成しようか何度も迷っていました。
最近もまた、そんなことを考えていたところ!
Ver5.2から新機能が追加されてた。
FormModelBinding専用のアクセサが実装できるようです。
このように書くことができるようになります。(公式より抜粋)
https://laravelcollective.com/docs/5.2/html#form-model-accessors
namespace App; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * Get the user's first name. * * @param string $value * @return string */ public function getDateOfBirthAttribute($value) { return Carbon::parse($value)--->format('m/d/Y'); } /** * Get the user's first name for forms. * * @param string $value * @return string */ public function formDateOfBirthAttribute($value) { return Carbon::parse($value)->format('Y-m-d'); } }
formDateOfBirthAttribute が専用アクセサですね。
(getDateOfBirthAttributeはもともとあるEloquentのアクセサです)
まだ自由自在になりきったというところまではいかないものの、
普段使いで露骨に不足しない程度にはなってきたのではないかと思います。
ありがたいことです。
]]>Laravelで用意されている便利な配列操作オブジェクト「collection」
これを使用している中で、人によってはハマりそうなケースを発見しました。
// EloquentのCollectionをゲット。 $persons = App\Person::all(); // あるカラムだけのCollectionにして、重複を排除してみよう。 $nicknames = $persons->pluck('nickname')->unique();
( ゜Д゜)あれー
[Symfony\Component\Debug\Exception\FatalThrowableError] Fatal error: Call to a member function getKey() on integer
普通に重複を排除した値を取ってくれるなら問題ないんですが
エラーで落ちてしまいました。
Eloquentから得たCollectionは
「Illuminate\Database\Eloquent\Collection」
を使っている。
実はこれ、Illuminate\Support\Collection と似ているが、ちょっと違う。
このオブジェクトは所属アイテムがEloquentのModelオブジェクトである事を想定しているようだ!
よって、pluck()等でEloquentオブジェクト以外が所属している状態になった場合は
使うメソッドによっては、エラーになるぞ!
Modelのcollectionとして使用しないのなら、混同しないこと。
明示的にIlluminate\Support\Collection オブジェクトにして扱えばよろしい。
$nicknames = collect($persons->pluck('nickname'))->unique();
エラーが発生した場所のコードはこちら。
/** * Return only unique items from the collection. * * @param string|callable|null $key * @return static */ public function unique($key = null) { if (! is_null($key)) { return parent::unique($key); } return new static(array_values($this->getDictionary())); }
からの、
/** * Get a dictionary keyed by primary keys. * * @param \ArrayAccess|array $items * @return array */ public function getDictionary($items = null) { $items = is_null($items) ? $this->items : $items; $dictionary = []; foreach ($items as $value) { // ここ!!$itemにgetKey()が実装されている事が前提になっている。 // getKeyは、Eloquent Modelのプライマリキーのカラム名を返すメソッド。 $dictionary[$value->getKey()] = $value; } return $dictionary; }
Laravelはソースがきれいなのですぐ分かります。
なるほどね、と思ったのでした。
unique
よくある重複チェック。DB内を検索してくれます。
すべての検索条件が=(イコール)でチェックできるならこんな書き方ができます。
<?php // 必須ですよ|自分のid以外のcompany_idとshop_idが同じ商品の中でユニークじゃないとだめですよ $rules = [ 'item_code' => 'required|unique:items,item_code,'.$my_id.',id,company_id,'.$my_company_id.',shop_id,',$my_shop_id; ];
実際は下記の構成になっています。
unique:{テーブル名},{対象カラム名},{除外条件の値},{←のカラム名(id等)},{where カラム1},{←の値},{and カラム2},{←の値}, ...
required_if
一緒に渡されたパラメータの値を見て、特定の条件だったら必須とする。
<?php // 数字ですよ|typeがstudentの時だけ必須ですよ $rules = [ 'age' => 'numeric|required_if:type,student'; ];
before (after)
日付に適用できるフィルタ。以前、以後を表現できる。
< ?php // 必須ですよ|日付ですよ|今日より前である必要がありますよ $rules = [ 'birth_date' => 'required|date|before:'.date('Y-m-d'); ]; // 一緒に渡されたto_dateより前である必要がありますよ $rules = [ 'from_date' => 'date|before:to_date'; ];
ちなみに、たいていValidationのルール定義はFormRequest内のrules()メソッドで返すのが定石ですが
特殊な入力フローを使っていて、Controller等でValidationを行いたい時もあると思います。
そんな時はこんな感じでもかけます。
<?php // 手動でValidatorを作るよ $validator = Validator::make($params, [ // rules... ]); // Validateしてエラーがあったら if ($validator->fails()) { // エラーメッセージと入力された値を持って入力画面に戻るよ return redirect()->route('edit_page_route_name')->withErrors($validator)->withInput(); }
Laravelのすごいところは、たいていの事を網羅してくれているところだと思います。
Laravelの実装機能の範囲内で対応できない要件は、完全に特殊な入力をする必要があるか
そもそもの考え方や設計がおかしいかのどちらかなので、
書いていく中で「行き詰まる事」が「状況整理のタイミング」として
考えず書き進めるような事を避ける癖が付いていくのが嬉しいですね。
<?php /** * array_get の再帰版 * * 配列$arrayのキー$key(数値 or 文字 or 配列)の値を取得する。 * 配列で指定した場合、要素の順番に配列を掘っていく。 * 例外が発生した場合$defaultを返す。 * * @param array $array 探す配列 * @param mixed $key 探すキー。階層を掘る場合は配列で指定する * @param mixed $default 値がなかった場合に返すデフォルト値 * * $array['key1']['key2']['key3'] の値が欲しければ * * $key = array('key1', 'key2', 'key3'); * $value = aget($array, $key); と書ける。 */ function aget($array, $key, $default = null) { if (!is_array($key)) { return is_array($array) && array_key_exists($key, $array) ? $array[$key] : $default; } if (!$key) return $default; foreach ($key as $k) { if (is_array($array) && array_key_exists($k, $array)) { $array = $array[$k]; } else { return $default; } } return $array; }同時に、階層を調整して値を設定したい場面もありました。
<?php /** * aget のsetter版 * * 配列$arrayのキー$key(数値 or 文字 or 配列)に値$valueを代入する。 * 配列で指定した場合、要素の順番に配列を掘っていく。 * 値がnullであった場合、[] として扱う。 * * $array['key1']['key2']['key3'] = $value は * * $pos = array('key1', 'key2', 'key3'); * aset($array, $pos, $value); と書ける。 * * 値の設定に成功した場合、trueを返す。 */ function aset(&$array, $key, $value) { if (!is_array($array)) { return false; } if (!is_array($key)) { if ($key === null) { $array[] = $value; } else { $array[$key] = $value; } } else { if (!$key) return false; $ref = &$array; foreach ($key as $k) { if ($k === null) { $ref = &$ref[]; } else { $ref = &$ref[$k]; } } $ref = $value; unset($ref, $array); } return true; }昔のソースを引っ張り出してきたので、出来は不明ですが・・・