グローバル変数をダイナミックに管理

Laravelのバージョンが5になってから、グローバルスコープを持つ変数の設定は、.envconfig/のディレクトリの設定ファイルで、実用的しかも綺麗にまとまりました。変数の使用も、config('app.url')と、プログラムのでどこでも簡単に取得できます。

また、DBの設定などのデフォルト設定ファイル以外にも、独自の設定ファイルも作成できます。

例えば、

config/local.phpというファイルを作成して、


return [
        'convert' => '/usr/bin/convert', // 画像変換のプログラムのパス名
        'items_per_page' => 50,          // 1ページのアイテム数
        'max_upload_size' => 20,         // 最大アップロードの画像サイズ(MB)
        'company' => env('LOCAL_COMPANY', 'Lots of Bytes'), // 会社名
];

とすれば、config('local.company')と参照できます。

しかし、これらのグローバル変数設定の問題は、プログラムとして設定ファイルあるいは.envでハードコードしなければならないことです。

グローバル変数をDBに値を保存して、管理画面で以下のように値を編集できて、プログラムの各所で使用できるようにするには、どうしたらよいのでしょうか?

my-application

幸いにも、config()の関数は、取得だけでなく書き込みも可能なのです。


config(['global.admin_email' => 'abc@gmail.com']);

というように実行すれば、config('global.admin')abc@gmail.comを返すことができます。

global.とプリフィックスしたのは、単に他の変数と区別やグループ分けがしたいがためです。

ということは、DBからデータを読んでプログラムの実行時の最初に、configして入れしまえば良いわけです。

このための適切な場所は、プログラムの初期に実行される、app/Providers/AppServiceProvider.phpの中です。


namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use DB;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
	$setting = DB::table('setting')->pluck('value', 'key');

	config(['setting' => $setting]);        
    }
...
}

DBテーブルsettingの中身は、以下とすると、

+------------+---------------------+---------------------+------------------+-------------------+
| setting_id | created_at          | updated_at          | key              | value             |
+------------+---------------------+---------------------+------------------+-------------------+
|          1 | 2016-12-03 01:09:51 | 2016-12-03 01:09:51 | email_admin      | admin@gmail.com   | 
|          2 | 2016-12-03 01:09:51 | 2016-12-03 01:09:51 | email_from       | from@gmail.com    | 
|          3 | 2016-12-03 01:09:51 | 2016-12-03 01:09:51 | site_name        | ララジャパン       | 
+------------+---------------------+---------------------+------------------+-------------------+

例えば、config('setting')['site_name']の実行では、ララジャパンの値が返ってきます。

Laravel 5.3 タイムスタンプのDB項目名の指定

Laravel 5.3に更新して、Eloquentのモデルの設定において嬉しいこと発見しました。

LaravelのEloquentでは、指定のDBテーブルにおいて、作成日時と編集日時に、規定のcreated_atupdated_atの項目名が使用されているなら、いちいち、

use Users;
use Carbon\Carbon;

$user = new Users;
...
$user->created_at = $user->upddated_at = Carbon::now();

$user->save();

というようなことを、DBレコードの追加や編集の際に、書かなくとも自動で作成日時と編集日時に値を入れてくれます。

私のケースでは、既存のプロジェクトのDBにおいて、作成日時と編集日時には、date_createddate_modifiedと違う名前を使用していて、今まで「いちいち」コードで指定していました。

しかし、Laravel 5.3では、以下のようにモデルの定数(const)を指定することで、項目名を指定できるようになりました。


namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Company extends Model
{
	protected $table = 'company';
	protected $primaryKey = 'company_id';
	public $timestamps = true; //デフォルトではtrueなので、指定する必要はない

	const CREATED_AT = 'date_created';
	const UPDATED_AT = 'date_modified';
..

これは大変便利です。

もう1つ便利なことで最近見つけたことは、最初の例で使用したCarbonは、Laravelをインストールしたら一緒にインストールされるパッケージですが、使い勝手あります。

例えば、DBに記録した日時から現在までの「経過日数」を計算するには、

$days_past = (new Carbon($user->updated_at))->diff(Carbon::now())->days;

簡単でわかりやすいですね。これをphpでやろうとすると、文字列から秒数に変換してなどと大変です。

最後に、日時の使用で忘れてならないのは、必ずアプリの設定ファイルで、タイムゾーンを設定すること。

..
    'timezone' => env('APP_TIMEZONE', 'Asia/Tokyo'),
..

ログイン成功のイベント

ユーザー認証(11)Laravel 5.2 ログインの記録で、ログイン成功後の処理に関して説明しました。

しかし、前回のログインのRemember Meのポストのための調査で、この「Remember Me」がオンになっているときは、先のログイン成功後の処理は、最初のログインのときだけしか実行しないことを見つけました。

つまり、最悪のケース、5年間ログイン成功後の処理は実行されません。同じブラウザを使用してもIPアドレスが変わることがあるし、記録としても不十分となり不都合です。

さて、どう解決したらよいでしょうか?
続き “ログイン成功のイベント”

バリデーション (9) 配列のDistinctと暗黙の拡張

配列のDistinctバリデーション

前回の例題とした商品オプションに、オプション名に同じ値が入ることを禁じるバリデーションを追加します。

distinct

これには、配列プレースホルダー .* ともに laravel 5.2 で追加された distinct バリデーションが利用できます。よく考えられていますね。

$rules = [
  'option_id'     => 'required',
  'option_name.*' => 'required_with:option_id.*|distinct',
  'unit_price.*'  => 'required_with:option_id.*|integer|min:0',
  'inventory.*'   => 'required_with:option_id.*|integer|min:0',
];

 

空の入力に対するdistinct?

ところで、ここまで「オプション名」は必須入力であることを前提としてきましたが、商品オプションが1つだけなら「オプション名」は空のまま「販売単価」と「在庫数」だけ入力できるようにしたい、という要望が現場から上がってくるかもしれません。

もう少し仕様追求すると、商品オプションが2つ以上であっても、先頭(とは限らないかもしれません)の1つは空でよいかもしれません。

このために空の入力が1個まで可というバリデーションルールを追加するとロジックが複雑化します。required_with を外したとき distinct が空文字列もあわせて重複チェックしてくれれば話が簡単ですね。こんなふうに・・・

distinct_with_blank

3つの入力欄のうち2つを空にした場合のリクエストは次のようになります。

Array
(
    [option_name] => Array
        (
            [11] => 
            [12] => 
            [13] => オレンジ
        )

option_name のキー 11 と 12 は同じ値(空文字列)なのだから重複チェックにかかりそうなものです。

しかしこのバリデーションはそんな都合よく機能しません。

 

distinct バリデーションは、option_name 全体ではなく、配列の値それぞれに働きます。キー 11 の値は配列内で重複してるか? キー 12 の値は・・・と。

そして、バリデーションの原則は値が入力された項目にのみ働きます。このケースでは、そもそもキー 11 と 12 は調査されることなく、キー 13 の値が配列内で重複してるかだけが調べられ、重複なしと判定されるのです。

そう、リクエストが空でも働くのは、required 系バリデーションだけでした。

 

暗黙の拡張 implicitRules

ここまでくると次の展開が読めましたか?

この課題は解決には、distinctrequired と同様に 空リクエストに対してもバリデーションが働く 必要があり、これを実現するのが 暗黙の拡張 ルールです。

この暗黙の拡張を「暗黙の必須」と読んでしまうと理解を間違えます。空リクエストに対するバリデーション、それ以上でも以下でもありません。

暗黙の拡張ルールを作るには、Validator::extendImplicit() メソッドを使います。通常の拡張ルールとして登録されると同時に、暗黙の拡張ルールの名前を収めた配列 $implicitRules にルール名が追加されます。

Validator::extendImplicit('foo', function($attribute, $value, $parameters, $validator) {
    return $value == 'foo';
});

ということは、Validator を継承して作成した customValidaor クラスでは、直接 $implicitRules にルール名を追加することで暗黙の拡張を作ることができます。

以下は、空文字列を含んだ重複判定ができる distinct_with_blank バリデーションの作成例です。

class CustomValidator extends \Illuminate\Validation\Validator {

    public function __construct($translator, $data, $rules, $messages = [])
    {
        parent::__construct($translator, $data, $rules, $messages);

        // 暗黙の拡張に追加
        $this->implicitRules[] = 'DistinctWithBlank';
    }

    /**
     * 空文字列も含む重複判定 distinct_with_blank
     * @param  string $attribute
     * @param  string $value
     * @param  array  $parameters
     * @return true
     */
    public function validateDistinctWithBlank($attribute, $value, $parameters)
    {
        // 標準distinctを呼ぶだけ
        return parent::validateDistinct($attribute, $value, $parameters);
    }

これを用いて option_name.* から required_with を外すと、最初のルール定義は次のようになります。無事に、オプション名の空は1つだけ許可されるようになりました。

$rules = [
  'option_id'     => 'required',
  'option_name.*' => 'distinct_with_blank',
  'unit_price.*'  => 'required_with:option_id.*|integer|min:0',
  'inventory.*'   => 'required_with:option_id.*|integer|min:0',
];

 

この他にも、DB上の重複をチェックする unique に対し、空入力でも機能する unique_with_blank も状況によっては(DB定義がNULL OKではないなど)ニーズがあるかもしれませんね。

バリデーション (8) 配列をバリデーションする

laravel 5.2 で、バリデーションルールに配列を表す .* というプレースホルダーが使えるようになりました。

例えば、次のような商品オプションのバリデーションを考えます。

option_name

入力行の追加UIやドラッグ&ドロップによるソートはjQueryなどで実装することにします(laravel から離れるので解説は省きます)

この場合、項目数がいくつになるかわからないので、エレメントの属性名を name="option_name[{{$id}}]" などとし、配列を返すように作りますよね。
続き “バリデーション (8) 配列をバリデーションする”

Laravel 5.3 resourceでの名前付きrouteの変更

以前に、Route::resourceの便利さを紹介しました。

routesを使いこなす(1)resourceを使う

routesを使いこなす(2)resourceを使いこなす

また、名前付きrouteがもたらす便宜さも紹介しました。

routesを使いこなす(4)routeを名付ける

しかし、Laravel 5.3のバージョンアップで「ちょっと、それはないよ」みたいな問題が出てきました。
続き “Laravel 5.3 resourceでの名前付きrouteの変更”

バリデーション (7) エラーメッセージのリプレーサー

等号を含む日付の最大最小

日付の最大最小 after, before の比較はなぜか等号を含みません。英単語の意味を厳格にプログラム仕様に落とし込んだようですが、実際の使い勝手としては等号を含んでほしかったところです。

これを等号を含むように拡張してしまうと、前回の min, max を拡張したのと違って意味が変わってしまいます。
そこで、start, end を新たに作成して追加してみましょう。

'start_date' => 'date|start:today',      // 今日を含んでそれ以降
'end_date'   => 'date|start:start_date', // 開始日を含んでそれ以降

続き “バリデーション (7) エラーメッセージのリプレーサー”

バリデーション (6) 他の属性の値を参照

日付の最大最小 after, before では、引数に値(日付)を与えるだけでなく、比較対象の属性の名前を与えることができます。
むしろ具体的な日付を引数にすることのほうがまれでしょう。

'date_start' => 'date|before:today',
'date_end'   => 'date|after:date_start',

このような使い方は、数値型の最大最小 min, max でも使用したいこともありますよね?
続き “バリデーション (6) 他の属性の値を参照”

バリデーション (5) 最大値と最小値

バリデーション言語ファイルを編集するときにお気づきと思いますが、Laravel標準バリデーション MaxMin は(他に SizeBetween も)調査対象の属性の型でその挙動を変えます。

'max' => [
    'numeric' => ':attributeの値が:maxを超えています',
    'file'    => ':attributeのサイズが:max kBを超えています',
    'string'  => ':attributeの文字数が:maxを超えています',
    'array'   => ':attributeの個数が:maxを超えています',
],
'min' => [
    'numeric' => ':attributeの値が:minに足りません',
    'file'    => ':attributeのサイズが:min kBに足りません',
    'string'  => ':attributeの文字数が:minに足りません',
    'array'   => ':attributeの個数が:minに足りません',
],

ここで問題となるのは、文字列型が入力した文字数カウントとの比較になることです。文字列比較の大小によるバリデーションは存在してません。
続き “バリデーション (5) 最大値と最小値”

Top