SHIRANGANA 忍者ブログ
エンジニア日記
Admin / Write
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

久しぶりのLaravel話です。

Laravelではエラーメッセージの表示などで馴染み深いflashセッションの機能がありますが
この機能は「次回のリクエストまでセッションを維持する」という言葉の通り動作します。

もし次回のリクエストでflashセッションを引き続き維持したい時は、
遷移やアクセスの可能性を十分に理解・整理したうえで、

Input::flash();



$request->session()->reflash();

をうまく使いましょう。

繰り返しますが、この言葉の通りなので使う時はちゃんとわかって使う事が大事だったりします。

はまりがちな罠 - 1

APIなどの非同期通信で消えてしまう。

はまりがちな罠 - 2

public以下に設置したcss, js, imgの404アクセス(laravelのハンドラで処理)で消えてしまう。

そんなことがあるのか的な罠(これが言いたかった)

barryvdh/laravel-debugbar を噛ませている場合、エラーハンドリング時は暗黙にreflashされるので
.envのdebug=falseで本番デプロイするまで上記の罠 - 2が表面化しない!!!

debug=trueの時に導入される便利ツールたちは便利ではありますが
こういう思いがけない事例もあるのだと感心しました。

flash自体は良い仕組みですので使いこなしていきたいところですが、
ちゃんとケースバイケースで用途を考えて実装する必要がありますね。
エラーハンドラ内で404であればreflashするとか、
APIのミドルウェアで特定の条件でのみreflashするとか、
安直に思いつく手はあれど、十分にケースを網羅した上で正解を決めていきたいところです。

この調査のためにまたソースをたくさん読んだので、良い勉強になりました。
PR

Laravel小ネタ。

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はソースがきれいなのですぐ分かります。
なるほどね、と思ったのでした。

LaraveのValidationは大変使いやすいです。
業務システムを作るとき、わりと使いやすいけど調べてもわかりにくいルールを 備忘録として書いておきます。

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」は大変便利な関数です。
Laravelのhelperにも入っています。

一般的な実装としては、「配列から与えられたキーの値を返す」というシンプルなもので、
たいていは3つの引数をとります。

第1引数 = 配列
第2引数 = キー
第3引数 = 値がなかった場合に返す値

Laravelはドット区切りの設定ファイルを取り扱うので、
'hoge.fuga' なんてキーを渡すと
$array['hoge']['fuga']
を探してくれるようなものを実装したりしていますね。

私も業務アプリを書くことが多かったので
多重の配列を取り扱うことが多く、階層を掘れるarray_getがほしい場面がたまにありました。
掘る階層の位置を動的に変える事に対応するため、引数に配列を許可し、
配列を許可した場合はその順番に階層を掘るという動きをさせるようにしました。 自作したソースは下記です。


<?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;
}
同時に、階層を調整して値を設定したい場面もありました。
setter版も作成しました。
<?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;
}
昔のソースを引っ張り出してきたので、出来は不明ですが・・・
複雑な配列構造の操作において、可読性にかなり貢献してくれた関数たちです。
<?php
/*
 
DIってなんだろう?

「依存性の注入」だそうで。
ソースコードの保全性向上とか、メンテナンスやらテストが綺麗にできるようになる
という事は聞いている。

単純な計算プログラムでも作って実感してみよう!

まずは、レガシーな書き方をしてみる。

Calcっていうクラスがあって、その中に各計算用のメソッドを用意して、
制御側でメソッドを呼び分けるという愚直なスタイル。

 */

// 計算するクラスだよ
class Calc
{
	// 足し算するとき呼んでね
	public function execAdd($left, $right)
	{
		return $left + $right;
	}
	// 引き算するとき呼んでね
	public function execDiv($left, $right)
	{
		return $left / $right;
	}
}

// では早速使ってみよう。「Add」か「Div」という文字を渡すと計算してくれるなかなかのやつ


$method = 'Add'; // 足し算をしよう
$left   = 10;    // 左辺は10
$right  = 5;     // 右辺は5としよう

// 計算機のお出ましだ
$calc = new Calc;

// 場合によって呼ぶメソッドを変えよう
if ($method === 'Add') {
	$answer = $calc->execAdd($left, $right);
} elseif ($method == 'Div') {
	$answer = $calc->execDiv($left, $right);
}


// とりあえず、$answerは15だった!




/*
 
まあ、こういう書き方は昔からありがちである。

なるほど、当然これだと処理が「Calcクラスの実装ありき」になってるので
Calcクラスのメソッド名とかが変更になれば、元の処理の流れも修正をしなきゃいけない。
計算ロジックを修正するときも、足し算割り算どちらにも関わらずこのクラスの書き換えが必要。

何をするにもCalc課長の重い腰を上げて頂く必要があるのだ。

=依存性が強いとはこういうことか!

じゃあ、DIっぽくしてみよう。
「依存性の注入」的なものである。

そもそも、Calcクラスの役割は「計算する」という概念なわけで、
「何々の計算をする」という具体的な所まで包括するといろいろ詰め込みすぎてしまう。
いろんな用途で編集されうるクラスになってしまうということだ。

なので、シンプルに分けていくことにする。
Calc課長しかいろいろできないのは困るので、
「計算する!」というGoサイン出しまでを受け持ってもらい、
「何々の計算をする!」という細かい話は、部下の「モジュール」に分けてそれぞれに依存していこう。

 */

// 「何々の計算をする」にあたる、計算モジュールの実装フォーマット(interface)だよ。
// これを土台に、足し算や引き算を作って、CalcDIに「注入」するよ。
interface CalcModule
{
	// 計算モジュールには、とにかく「計算結果をくれ」というメソッドだけは必ず実装してね。
	// CalcDI課長が必ず呼ぶよ。
	public function getResult($left, $right);
}

// 計算モジュール「足し算」だよ
class CalcAdd implements CalcModule
{
	public function getResult($left, $right)
	{
		return $left + $right;
	}
}

// 計算モジュール「割り算」だよ
class CalcDiv implements CalcModule
{
	public function getResult($left, $right)
	{
		return $left / $right;
	}
}


// 計算をするクラスがDIっぽくなったよ。
// CalcさんからCalcDIさんに進化した!(実際はスーパー社員からハンコ押し係に格下げ)
// 計算部分は「モジュール」として別のクラスに分けていくよ。
class CalcDI
{
	// とにかく、「計算する!」はこれだけだ!
	// 計算を実行するときは常にこれを呼ぶだけでいいよ。
	// その代わり、$moduleに「何々の計算をする」にあたるモジュールをセットしてね。
        // あとCalcModuleの枠に従ってない部下は受け付けないからね!
	public function exec(CalcModule $module, $left, $right)
	{
		// 計算結果を返すね
		return $module->getResult($left, $right);
	}
}


// では、CalcDI版で足し算をしよう。
$method = 'Add'; // 足し算をしよう
$left   = 10;    // 左辺は10
$right  = 5;     // 右辺は5としよう

// 場合によって「何々の計算をする」部分だけを取り分けよう。
if ($method === 'Add') {
	$module = new CalcAdd;
} elseif ($method == 'Div') {
	$module = new CalcDiv;
}

// さあ計算だ
$calc   = new CalcDI;

// 「計算する!」をひたすら叫ぶだけのCalcDIさん
$answer = $calc->exec($module, $left, $right); 

// $answerは15だった!


/*
 
これでもうCalcDI課長は全体の処理で口火を切る「計算をする!」を叫ぶだけの係になり、
ごちゃごちゃ触れられる機会が激減した事が分かった。

計算の種類を増やしたり、計算の内容を変更するときは
それぞれのモジュールを調達したり編集するだけでよくなった。

ただ、まだモジュールの生成に分岐が入っていて実装側はだるいままである。
依頼者は、課長に仕事を依頼する前に望む処理ができる社員を自分で引っ張ってこなければならないのである。

ではここで、やりたいことを伝えれば勝手にモジュールを取ってきてくれる
Factory主任にお出ましいただこう

Factory主任「望みを言え!!さすれば適切な社員をアサインしてやる!」

 */



// ----------------------


// Factory主任だよ。計算モジュールをよしなに取ってくるすごいやつだよ
class CalcModuleFactory
{
	// 計算したい種類を教えてくれ!
	public function make($method)
	{
		// その種類なら、計算モジュールの名前はこれのはずだ!
		$moduleName = $this->getModuleName($method);

		// 一応計算モジュールがあるかどうか事前に調べておこう!
		$this->_checkModule($moduleName);

		// 計算モジュールが居たぞ受け取れ!!
		return new $moduleName;
	}

	public function getModuleName($method)
	{
		return 'Calc'.$method;
	}

	protected function _checkModule($moduleName)
	{
		if (!class_exists($moduleName)) {
			throw new Exception('計算の種類:'.$moduleName.'は未実装のようだ!');
		}
	}
}

// ではFactory主任に助けてもらって足し算を。
$method = 'Add';
$left   = 10; // 左辺
$right  = 5;  // 右辺


// 分岐もモジュール探しも全部Factory主任にお任せだ
$factory = new CalcModuleFactory;
$module  = $factory->make($method);

// ささっと計算!
$calc    = new CalcDI;
$answer  = $calc->exec($module, $left, $right);

// $answerは15だった!


/*

すばらしい。
これでもう、計算の種類を追加するときはモジュールを作るだけで良くなった。
他に触るべき処理もなくなってしまった。

CalcDI課長はモジュールと左辺と右辺をもらって「計算する!」を叫ぶだけであり、
渡すモジュールはFactory主任が勝手に連れてきてくれるし、
具体的な計算処理であるモジュールたちはinterfaceの秩序のもと足並みを揃えてお行儀良くしながら、
自分の処理に集中しているからである。

結局メインの処理は「目的(やりたい事)」と「材料(左辺と右辺)」を用意するだけで、
もう何も弄らなくて良くなってしまった。

やりたい事が増えても、モジュールを追加(スペシャリスト社員を雇用)するだけでいいのである。

以上で、DIの良さを実感し、なんとなくわかったつもりになれた。

なお、LaravelはDIの概念を掲げてソースを構築しているので、とても勉強になる。
自分も綺麗な実装ができるように日々精進する次第である。


以上でDIってなんだろう?は終了です。





p.s.
これだけ整頓されているとワンライナー化してドヤ顔することもできるぞ!
(ワンライナーは独りよがりにならない範囲で計画的に!)
*/

$answer = (new CalcDI)->exec((new CalcModuleFactory)->make($method), $left, $right);
1  2 

忍者ブログ [PR]