- 投稿日:2020-02-17T22:55:12+09:00
Laravel migrationで文字列型カラムから数値型への変更でハマった解決策
マイグレーションで数値型→文字列型へのカラム変更が必要になり、変更時は問題なかったものの、rollback時に以下のエラーが発生。
Doctrine\DBAL\Driver\PDOException::("SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, CHANGE fax fax BIGI' at line 1")原因がわからず困っていたが、よく読むとcharsetが云々書いてあったので、↓で文字列型の時に残っているcahrsetを
null
にして解決!class AddStelToUsersTable extends Migration { /** * Run the migrations. */ public function up() { Schema::table('users', function (Blueprint $table) { $table->string('s_tel')->nullable()->after('tel'); $table->string('tel')->change(); $table->string('fax')->nullable()->change(); }); } /** * Reverse the migrations. */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('s_tel'); $table->bigInteger('tel')->charset(null)->change(); $table->bigInteger('fax')->charset(null)->nullable()->change(); }); } }
- 投稿日:2020-02-17T21:25:32+09:00
Laravel6.x系でPHPUnitのUnitテストを動かそうとしたらヘルパーが動かずエラった
DockerでLavarelを構築して遊んでいたらちょっと不思議な現象に出くわしたのでメモとして残しておきます。
動作環境
- PHP: 7.4
- Laravel: 6.11.0
エラー再現
以下コマンドでUnitテストを作成します。
php artisan make:test SampleTest --unitそうすると以下のようなファイルが出力されます。
<?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; class SampleTest extends TestCase { /** * A basic unit test example. * * @return void */ public function testExample() { $this->assertTrue(true); } }以下のようにテスト実行すると問題なくテストは完了します。
vendor/bin/phpunit tests/Unit/SampleTest.php次に実験としてヘルパーを記載します。
config
ヘルパーを使用していることに特に意味はありません。<?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; class SampleTest extends TestCase { /** * A basic unit test example. * * @return void */ public function testExample() { config('app.name'); $this->assertTrue(true); } }テストを実行すると以下のようなエラーメッセージが表示します。
1) Tests\Unit\SampleTest::testExample Illuminate\Contracts\Container\BindingResolutionException: Target class [config] does not exist.要するにヘルパーが動かないと。。。
解決策
use PHPUnit\Framework\TestCase;の代わりに
use Tests\TestCase;を
use
すれば問題なく動くようになりました。ソース全体は以下です。
<?php namespace Tests\Unit; use Tests\TestCase; class SampleTest extends TestCase { /** * A basic unit test example. * * @return void */ public function testExample() { config('app.name'); $this->assertTrue(true); } }詳細はこのあたりに記載されています。
- 投稿日:2020-02-17T18:43:02+09:00
Laravel 6系でバリデーションの日本語ファイルを簡単に生成する方法
はじめに
Laravel 6系でバリデーションメッセージの日本語化を行いました
GitHub上にも日本語されたファイルがありますが、こちらで紹介するのはコマンドで生成する方法になりますTL;DR
プロジェクト直下で以下のコマンドを実行すれば生成されます
$ php -r "copy('https://readouble.com/laravel/6.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');" $ php -f install-ja-lang.php $ php -r "unlink('install-ja-lang.php');"前提
- Mac
- PHP 7.3
- Laravelバージョン
$ php artisan -V Laravel Framework 6.15.1バリデーションメッセージの日本語化の流れ
以下のコマンドをプロジェクト配下で実行します
$ php -r "copy('https://readouble.com/laravel/6.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');" $ php -f install-ja-lang.php $ php -r "unlink('install-ja-lang.php');"resources/lang/jaに以下ファイルが生成されます
- auth.php
- pagination.php
- passwords.php
- validation.php
auth.php<?php return [ /* |-------------------------------------------------------------------------- | 認証言語行 |-------------------------------------------------------------------------- | | 以下の言語行は認証時にユーザーに対し表示する必要のある | 様々なメッセージです。アプリケーションの必要に合わせ | 自由にこれらの言語行を変更してください。 | */ 'failed' => 'ログイン情報が登録されていません。', 'throttle' => 'ログインに続けて失敗しています。:seconds秒後に再度お試しください。', ];pagination.php<?php return [ /* |-------------------------------------------------------------------------- | ペジネーション言語行 |-------------------------------------------------------------------------- | | 以下の言語行はペジネーターライブラリーによりシンプルなペジネーション | リンクを生成するために使用されます。アプリケーションに合うように、 | 自由に変更してください。 | */ 'previous' => '« 前', 'next' => '次 »', ];passwords.php<?php return [ /* |-------------------------------------------------------------------------- | パスワードリセット言語行 |-------------------------------------------------------------------------- | | 以下の言語行は既存のパスワードを無効にしたい場合に、無効なトークンや | 新しいパスワードが入力された場合のように、パスワードの更新に失敗した | 理由を示すデフォルトの文言です。 | */ 'reset' => 'パスワードをリセットしました。', 'sent' => 'パスワードリセットメールを送信しました。', 'token' => 'このパスワードリセットトークンは無効です。', 'user' => "メールアドレスに一致するユーザーは存在していません。", ];validation.php<?php return [ /* |-------------------------------------------------------------------------- | バリデーション言語行 |-------------------------------------------------------------------------- | | 以下の言語行はバリデタークラスにより使用されるデフォルトのエラー | メッセージです。サイズルールのようにいくつかのバリデーションを | 持っているものもあります。メッセージはご自由に調整してください。 | */ 'accepted' => ':attributeを承認してください。', 'active_url' => ':attributeが有効なURLではありません。', 'after' => ':attributeには、:dateより後の日付を指定してください。', 'after_or_equal' => ':attributeには、:date以降の日付を指定してください。', 'alpha' => ':attributeはアルファベットのみがご利用できます。', 'alpha_dash' => ':attributeはアルファベットとダッシュ(-)及び下線(_)がご利用できます。', 'alpha_num' => ':attributeはアルファベット数字がご利用できます。', 'array' => ':attributeは配列でなくてはなりません。', 'before' => ':attributeには、:dateより前の日付をご利用ください。', 'before_or_equal' => ':attributeには、:date以前の日付をご利用ください。', 'between' => [ 'numeric' => ':attributeは、:minから:maxの間で指定してください。', 'file' => ':attributeは、:min kBから、:max kBの間で指定してください。', 'string' => ':attributeは、:min文字から、:max文字の間で指定してください。', 'array' => ':attributeは、:min個から:max個の間で指定してください。', ], 'boolean' => ':attributeは、trueかfalseを指定してください。', 'confirmed' => ':attributeと、確認フィールドとが、一致していません。', 'date' => ':attributeには有効な日付を指定してください。', 'date_equals' => ':attributeには、:dateと同じ日付けを指定してください。', 'date_format' => ':attributeは:format形式で指定してください。', 'different' => ':attributeと:otherには、異なった内容を指定してください。', 'digits' => ':attributeは:digits桁で指定してください。', 'digits_between' => ':attributeは:min桁から:max桁の間で指定してください。', 'dimensions' => ':attributeの図形サイズが正しくありません。', 'distinct' => ':attributeには異なった値を指定してください。', 'email' => ':attributeには、有効なメールアドレスを指定してください。', 'ends_with' => ':attributeには、:valuesのどれかで終わる値を指定してください。', 'exists' => '選択された:attributeは正しくありません。', 'file' => ':attributeにはファイルを指定してください。', 'filled' => ':attributeに値を指定してください。', 'gt' => [ 'numeric' => ':attributeには、:valueより大きな値を指定してください。', 'file' => ':attributeには、:value kBより大きなファイルを指定してください。', 'string' => ':attributeは、:value文字より長く指定してください。', 'array' => ':attributeには、:value個より多くのアイテムを指定してください。', ], 'gte' => [ 'numeric' => ':attributeには、:value以上の値を指定してください。', 'file' => ':attributeには、:value kB以上のファイルを指定してください。', 'string' => ':attributeは、:value文字以上で指定してください。', 'array' => ':attributeには、:value個以上のアイテムを指定してください。', ], 'image' => ':attributeには画像ファイルを指定してください。', 'in' => '選択された:attributeは正しくありません。', 'in_array' => ':attributeには:otherの値を指定してください。', 'integer' => ':attributeは整数で指定してください。', 'ip' => ':attributeには、有効なIPアドレスを指定してください。', 'ipv4' => ':attributeには、有効なIPv4アドレスを指定してください。', 'ipv6' => ':attributeには、有効なIPv6アドレスを指定してください。', 'json' => ':attributeには、有効なJSON文字列を指定してください。', 'lt' => [ 'numeric' => ':attributeには、:valueより小さな値を指定してください。', 'file' => ':attributeには、:value kBより小さなファイルを指定してください。', 'string' => ':attributeは、:value文字より短く指定してください。', 'array' => ':attributeには、:value個より少ないアイテムを指定してください。', ], 'lte' => [ 'numeric' => ':attributeには、:value以下の値を指定してください。', 'file' => ':attributeには、:value kB以下のファイルを指定してください。', 'string' => ':attributeは、:value文字以下で指定してください。', 'array' => ':attributeには、:value個以下のアイテムを指定してください。', ], 'max' => [ 'numeric' => ':attributeには、:max以下の数字を指定してください。', 'file' => ':attributeには、:max kB以下のファイルを指定してください。', 'string' => ':attributeは、:max文字以下で指定してください。', 'array' => ':attributeは:max個以下指定してください。', ], 'mimes' => ':attributeには:valuesタイプのファイルを指定してください。', 'mimetypes' => ':attributeには:valuesタイプのファイルを指定してください。', 'min' => [ 'numeric' => ':attributeには、:min以上の数字を指定してください。', 'file' => ':attributeには、:min kB以上のファイルを指定してください。', 'string' => ':attributeは、:min文字以上で指定してください。', 'array' => ':attributeは:min個以上指定してください。', ], 'not_in' => '選択された:attributeは正しくありません。', 'not_regex' => ':attributeの形式が正しくありません。', 'numeric' => ':attributeには、数字を指定してください。', 'present' => ':attributeが存在していません。', 'regex' => ':attributeに正しい形式を指定してください。', 'required' => ':attributeは必ず指定してください。', 'required_if' => ':otherが:valueの場合、:attributeも指定してください。', 'required_unless' => ':otherが:valuesでない場合、:attributeを指定してください。', 'required_with' => ':valuesを指定する場合は、:attributeも指定してください。', 'required_with_all' => ':valuesを指定する場合は、:attributeも指定してください。', 'required_without' => ':valuesを指定しない場合は、:attributeを指定してください。', 'required_without_all' => ':valuesのどれも指定しない場合は、:attributeを指定してください。', 'same' => ':attributeと:otherには同じ値を指定してください。', 'size' => [ 'numeric' => ':attributeは:sizeを指定してください。', 'file' => ':attributeのファイルは、:sizeキロバイトでなくてはなりません。', 'string' => ':attributeは:size文字で指定してください。', 'array' => ':attributeは:size個指定してください。', ], 'starts_with' => ':attributeには、:valuesのどれかで始まる値を指定してください。', 'string' => ':attributeは文字列を指定してください。', 'timezone' => ':attributeには、有効なゾーンを指定してください。', 'unique' => ':attributeの値は既に存在しています。', 'uploaded' => ':attributeのアップロードに失敗しました。', 'url' => ':attributeに正しい形式を指定してください。', 'uuid' => ':attributeに有効なUUIDを指定してください。', /* |-------------------------------------------------------------------------- | Custom バリデーション言語行 |-------------------------------------------------------------------------- | | "属性.ルール"の規約でキーを指定することでカスタムバリデーション | メッセージを定義できます。指定した属性ルールに対する特定の | カスタム言語行を手早く指定できます。 | */ 'custom' => [ '属性名' => [ 'ルール名' => 'カスタムメッセージ', ], ], /* |-------------------------------------------------------------------------- | カスタムバリデーション属性名 |-------------------------------------------------------------------------- | | 以下の言語行は、例えば"email"の代わりに「メールアドレス」のように、 | 読み手にフレンドリーな表現でプレースホルダーを置き換えるために指定する | 言語行です。これはメッセージをよりきれいに表示するために役に立ちます。 | */ 'attributes' => [], ];おわりに
バリデーションメッセージの日本語化をする際は参考にしてみてください
参考
- 投稿日:2020-02-17T18:37:47+09:00
Laravel バリデーションのメモ
Validator::extend('kana', function ($attribute, $value, $parameters) { if (mb_strlen($value) > 100) { return false; } if (preg_match('/[^ぁ-んー]/u', $value) !== 0) { return false; } return true; });Validator::extend('url', function ($attribute, $value, $parameters) { return(preg_match("/^(https?|ftp)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)$/", $value)); });Validator::extend('alpha_number', function ($attribute, $value, $parameters) { return(preg_match("/^[\w-]*$/", $value)); });Validator::extend('env_dep', function ($attribute, $value, $parameters) { $pass = false; foreach (preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY) as $s) { $pass = (strlen($s) == strlen(mb_convert_encoding(mb_convert_encoding($s, 'SJIS', 'UTF-8'), 'UTF-8', 'SJIS'))) ? true : false; } return $pass; });半角英数字Validator::extend('half_width_alpha_numeric', function ($attribute, $value, $parameters, $validator) { if (is_null($value)) return true; return(preg_match("/^[a-zA-Z0-9]+$/u", $value)); });半角英数字、半角ハイフンValidator::extend('half_width_alpha_numeric_hyphen', function ($attribute, $value, $parameters, $validator) { if (is_null($value)) return true; return(preg_match("/^[a-zA-Z0-9\-]+$/u", $value)); });全角(ひらがな、カタカナ、漢字)Validator::extend('kanji_katakana_hiragana', function ($attribute, $value, $parameters, $validator) { if (is_null($value)) return true; return(preg_match("/^[ぁ-んァ-ヶ一-龥々]+$/u", $value)); }); ```php:全角ひらがな Validator::extend('hiragana', function ($attribute, $value, $parameters, $validator) { if (is_null($value)) return true; return(preg_match("/^[ぁ-ん]+$/u", $value)); });全角(ひらがな、カタカナ、漢字、数字、英字、ハイフン全角、中黒、全角シングルクオート、@全角)Validator::extend('full_width', function ($attribute, $value, $parameters, $validator) { if (is_null($value)) return true; return(preg_match("/^[ぁ-んァ-ヶ一-龥々0-9a-zA-Zー・’@]+$/u", $value)); });全角(ひらがな、カタカナ、漢字、数字、英字、全角ハイフン、中黒)、半角英数字、半角ハイフンValidator::extend('not_half_width_kana_symbol', function ($attribute, $value, $parameters, $validator) { if (is_null($value)) return true; return(preg_match("/^[ぁ-んァ-ヶ一-龥々0-9a-zA-Zー・a-zA-Z0-9\-]+$/u", $value)); });半角英数記号Validator::extend('half_width_alpha_numeric_symbol', function ($attribute, $value, $parameters, $validator) { if (is_null($value)) return true; return(preg_match("/^[!-~]+$/", $value)); });電話番号(任意入力)// 入力値にNULLまたは半角数字でない値が混在している場合、バリデーションエラー Validator::extend('optional_phone_no', function ($attribute, $value, $parameters, $validator) { // null check $null_count = 0; foreach ($value as $v) { if (is_null($v)) $null_count++; } // 全てNULLだった場合、バリデーションを行わない if ($null_count === 3) return true; // validate check $validate_ok_count = 0; foreach ($value as $v) { if (!is_null($v)) { if (preg_match("/^[0-9]+$/u", $v)) { $validate_ok_count++; } } } // 全て半角数字だった場合、バリデーションOK if ($validate_ok_count === 3) return true; return false; });配列の要素を文字列結合し、その文字列が指定された文字列長以下であることValidator::extend('max_implode_array', function ($attribute, $value, $parameters, $validator) { $max_len = (int) $parameters[0]; if (mb_strlen(implode('', $value)) > $max_len) { return false; } return true; });電話番号(必須)Validator::extend('phone_no', function ($attribute, $value, $parameters, $validator) { // null check $null_count = 0; foreach ($value as $v) { if (is_null($v)) { $null_count++; } } // NULLがあった場合、バリデーションエラー if ($null_count > 0) { return false; } // 半角数字でバリデーション $validate_ok_count = 0; foreach ($value as $v) { if (!is_null($v)) { if (preg_match("/^[0-9]+$/u", $v)) { $validate_ok_count++; } } } // 全て半角数字だった場合、バリデーションOK if ($validate_ok_count === 3) { return true; } return false; });
半角英数字記号・全角英数字かなカナ記号の一覧
- 投稿日:2020-02-17T10:37:25+09:00
Laravel で Fat Controller を防ぐもうひとつの Tip
この記事について
先日書いたこちらの記事の補遺です。
Laravel で Fat Controller を防ぐ 5 つの Tips - Qiita
Fat Controller になる主な要因として、Model 層が貧弱すぎる、ということが考えられます。本来 Model に書くべき処理を Controller に書いてしまっており、結果的に可読性が悪くなったり、ひとつのユースケースに対する変更が他のユースケースに影響を及ぼしたり、Controller 間でのコピペコードを量産したり、ということが起きます。
と書いておきながら、「本来 Model に書くべき処理を Controller に書いてしまって」いる例を書きそびれたので、それについて補足します。
はじめに
「Model に書くべき処理」とは
本記事では「Model に書くべき処理」を以下のように定義します。
- Model の状態を変更する処理
- Model の状態による条件分岐
- クエリビルディング
1. Model の状態を更新する処理, 2. Model の状態による条件分岐
1, 2はセットで解説します。
いちばん簡単なのは、以下のようなオブジェクトの生成においてひとつずつプロパティをセットする書き方です。プロパティ分行数が増えますので、これをまとめるのは効果があります。
$user = new User(); $user->name = $request->name; $user->email = $request->email; // .... $user->save();よりは、バリデーションルールを適切に設定した上で以下のようにまとめるのがいいでしょう。
User::create($request->all());データの変換が必要なら、FormRequest 側に関数をつくって、それにやらせるといいと思います。
もっとドメインに近い例だと、たとえば、TODO アプリケーションでタスクの生成をする処理で、「タスクの初期状態は 'todo' である」というルールがあったとき、
$task = Task::create(['status' => 'todo'] + $request->all());みたいな処理は、以下のように Model に専用のメソッドをつくって、そこに持たせることができます。
// Controller $task = Task::createTodo($request->all()); // Model public static function createTodo(array $attributes) { return parent::create([ 'status' => 'todo', ] + $attributes); }行数自体は減りませんが、Controller のコードを見ると「TODO をつくる」役割に特化した名前があるので、識別しやすくなると思います。
また、同じく TODO アプリケーションで、「完了しているタスクのみ削除可能である」というドメインルールがあったとき、
if ($task->is_complete) { $task->delete(); }みたいに書くよりは、
// Controller $task->delete(); // Model public function delete() { if ($this->is_complete) { parent::delete(); } throw new \DomainException('...'); }とすると、行数が少なくなります(これには副次的な作用があって、削除可能な条件をさらにメソッド化することによって、条件が変わったときに対処しやすくなります)。条件を満たさない場合にどう振る舞うか、というのはアプリケーションの要件によると思いますが、422 Unprocessable Entity なり、404 Not Found なりを Controller で返すようなフローが必要になるかもしれません。
いまはひとつのプロパティだけなので恩恵は薄いですが、条件が複雑になってくると Model 内に閉じ込めておくほうがコードの重複(と、それによる更新漏れ)を防ぎやすくなると思います。
個人的には、ある処理が行われる条件を満たしているか、という判定はバリデーションで行うのでもいいかな、と思いますが、実行される処理と実行可能条件が近くにあるほうがいいとも思うので、上の例では Model 側に入れています(これが、Controller に対する入力パラメータの状態によって、複数のモデルにまたがる処理全体の実行可否を左右するような場合には、バリデーション側で実装するほうがいい気がします)。
3. クエリビルディング
単純な WHERE , ORDER BY あたりを組み合わせたものはそのままでもいいですが、複雑な条件句があるようなやつはメソッド化して Model に入れたほうがいいと思います。ちょっと例が書きづらいんですが、たとえば、サブタスクを含めた複数の条件に一致するクエリだと、下記のようにずらずらとクエリビルディングが続くことになるでしょう。
$tasks = Task ::whereHas('sub_tasks', function ($builder) { $builder->where('...') // 他にもずらずらと条件がある ; }) ->where('...') // 他にもずらずらと条件がある ;これを、
// Controller $tasks = Task::inSomeState()->get(); // Model public function scopeInSomeState(Builder $builder) { return $builder ->whereHas('sub_tasks', ...) ->where(...) ; }みたいにスコープにします。
あるいは普通のメソッドにして、それを呼んでもいいでしょう。
// Controller $tasks = Task::getInSomeState(); // Model public static function getInSomeState() { return static::whereHas(...)->where(...)->get(); }個人的にはスコープ化しておくほうが好みではありますが、IDE での補完やジャンプの問題で(PHPDoc に書くことで認識はされますが、定義元にジャンプできるわけではない)避けたい、という方もいると思います。そこらへんはお好みで。
おわりに
「Model に書くべき処理」は他にもあるかと思いますが、どの場合でも他の Tips と同じく「重複することによって不具合の原因になる」ことが問題なので、どこに書くべきかというよりも、意味のある処理のまとまりに名前をつけ、局所化しておく、ということが大事かな、と思います。
他にも「Model に書くべき処理」のパターンがあれば、コメント欄にて教えていただけると助かります