- 投稿日: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-17T19:19:31+09:00
php + ajax でDBからデータを取得・出力するためのちょっとした話
概要
案件で、2000ぐらいのファイルをそれぞれダウンロードするサイトを制作しました。
クライアントからDB仕様指示はないので、静的htmlで制作することも可でしたが、量が多すぎるのでDBを使用にすることに至りました。
DBを使った案件は初めてで(WPを除く)、PHPもWPのテーマ作成ぐらいしか実戦経験がなかったので、キツかった。。。
振り返りを兼ねての自分メモで。
DBはMySQLでphpMyAdminを使用しています。仕様
目的:
製品サイト内にて、製品の資料ファイルをダウンロードする。サイト自体は1ページです。
サイトを上下に2分割した際に、上に製品カテゴリのナビがあり、選択したカテゴリのダウンロードリンクを搭載した下にテーブルが表示される。
上で選択したアイテムをパラメータとして渡すことで、下のテーブルをajaxで動的に出力します。Ajax
「エイジャックス」派です。
基本的な書き方はこんな感じでしょうか。ajax.js$(function() { $('.btn').on('click', function() { $.ajax({ url: ('path/to/file.php'), // ajax通信をするファイル type: 'GET', // GET or POST data: { f: $(this).value(); }, // パラメータとして渡す値の指定 dataType: 'html', // 今回は直接htmlを出力したかったのでhtml。受け取ったデータで更に処理したい場合はjson cache: true, // 通信のキャッシュを残すか // ajax通信成功 success: function(data) { // 引数でデータを受け取り $('#content').html(data); }, // ajax通信失敗 error: function() { console.log('fail'); } }); }); });コードの意図としては、ボタンをクリックした際にajax通信を行う。押したボタン
button
のvalue
を取得しパラメータf
としてpath/to/file.php
に渡します。
一連の流れが成功すれば、#content
にhtml
を出力します。ボタンを押すたびにhtmlを動的に書き換えることができます。
data
の取得方法がよくわからず苦戦しました。他にフォームであれば、inputの値
などを指定します。ちなみにナウい書き方だとこんな感じ
ajax_new.js$(function() { $('.btn').on('click', function() { $.ajax({ // options 略 }).then( function(data) { // success console.log(data) }, function() { // fail console.log('error'); } ); }); });PHP
ajax通信時の設定を行う。
connect.php// DB settings $host = 'localhost'; $dbname = 'dbname'; $dbusr = 'dbusr'; $dbpass = 'dbpass'; //↑ settingsからDBに接続する try { // 成功 $dbh = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8mb4", $dbuser, $dbpass); } catch (PDOException $e) { // 失敗 var_dump('データベース接続失敗' . $e->getMessage()); exit; } // SQL実行準備 $stmt = $dbh->prepare("SELECT * FROM table"); // SQL実行 $stmt->execute(); // 受け取りデータを配列に格納 $dataList = array(); // あらかじめ作成した配列にwhuileで格納する // FETCH_ASSOCで連想配列化する while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ $productList[] = array( 'id' => $row['id']; 'name' => $row['name']; ); } // XSS対策 function hsc($str){ return htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); } // htmlをそのままechoするので以下指定。Content-typeは適宜変更 header('Content-type:text/plain; charset=utf8'); // urlパラメータ「p」のGETが実行されているか if (isset($_GET['p'])) { // パラメータを受け取ったら変数化 $format = $_GET['p']; // ここでhtml出力します echo '通信成功です!'; }ひとまず完成して動いたので、営業にテストアップ報告を行ったのですが
まだまだわからないことが多い。。。
- 投稿日: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-17T18:19:45+09:00
【2020年度版】WordPressに入れるべきPlugin(プラグイン)12選
WordPressでWebサイトを構築する際、必ず何らかのPlugin(プラグイン)を導入すると思います。
しかし、以前は便利なPlugin(プラグイン)だったとしても、更新が長期に渡ってされなくなったり、機能面で物足りなくなったりして、入れるべきPlugin(プラグイン)も年々変わります。
そこで、2020年2月段階で入れるべきPlugin(プラグイン)を改めて洗い出してみました。
設定系
設定系のPlugin(プラグイン)は、WordPressの構築時や運用時に必要な各種設定ができるPlugin(プラグイン)です。
WP Multibyte Patch
項目名 更新日や有効ダウンロード数 最終更新 3か月前 有効インストール数 1,000,000+ WordPress バージョン 4.5 またはそれ以上 検証済み最新バージョン 5.3.2 WordPress 日本語版パッケージのためのマルチバイト機能の拡張を行うPlugin(プラグイン)です。
Jetpack by WordPress.com
項目名 更新日や有効ダウンロード数 最終更新 3日前 有効インストール数 5,000,000+ WordPress バージョン 5.2 またはそれ以上 検証済み最新バージョン 5.3.2 PHP バージョン 5.6 またはそれ以上 Jetpack by WordPress.comは、WordPressを提供しているAutomattic社の公式プラグインです。
Google XML Sitemaps
項目名 更新日や有効ダウンロード数 最終更新 3か月前 有効インストール数 2,000,000+ WordPress バージョン 3.3 またはそれ以上 検証済み最新バージョン 5.3.2 Google XML Sitemapsは、Google Search ConsoleにWebサイトのxmlサイトマップ登録ができるPlugin(プラグイン)です。
記事作成・表示
記事を作成したり管理する、記事の巡回を促すために便利なPlugin(プラグイン)。
Duplicate Post
項目名 更新日や有効ダウンロード数 最終更新 2か月前 有効インストール数 3,000,000+ WordPress バージョン 3.6 またはそれ以上 検証済み最新バージョン 5.3.2 PHP バージョン 5.2.4 またはそれ以上 Duplicate Postは、記事の複製機能が使えるPlugin(プラグイン)です。
WordPress Popular Posts
項目名 更新日や有効ダウンロード数 最終更新 2か月前 有効インストール数 300,000+ WordPress バージョン 4.7 またはそれ以上 検証済み最新バージョン 5.3.2 PHP バージョン 5.4 またはそれ以上 WordPress Popular Postsは、閲覧されている人気記事のランキングなどを任意の場所に表示することができるPlugin(プラグイン)です。
Broken Link Checker
項目名 更新日や有効ダウンロード数 最終更新 1週間前 有効インストール数 700,000+ WordPress バージョン 3.2 またはそれ以上 検証済み最新バージョン 5.3.2 このPlugin(プラグイン)は、リンク切れが起こった瞬間にダッシュボードやメールですぐに通知してくれます。
バックアップ
Webサイトのバックアップを取る事は、Webサイトをきちんと運用するための必須項目です。
BackWPup – WordPress Backup Plugin
BackWPup – WordPress Backup Plugin
項目名 更新日や有効ダウンロード数 最終更新 1か月前 有効インストール数 600,000+ WordPress バージョン 3.9 またはそれ以上 検証済み最新バージョン 5.3.2 PHP バージョン 5.6 またはそれ以上 WordPressのバックアップ用Plugin(プラグイン)としては定番。
UpdraftPlus WordPress Backup Plugin
UpdraftPlus WordPress Backup Plugin
項目名 更新日や有効ダウンロード数 最終更新 2か月前 有効インストール数 2,000,000+ WordPress バージョン 3.2 またはそれ以上 検証済み最新バージョン 5.3.2 UpdraftPlus WordPress Backup Pluginは、BackWPup – WordPress Backup Pluginよりも後発のPlugin(プラグイン)ですが、今やBackWPup – WordPress Backup Pluginよりも有効インストール数が多くなったバックアップ用のPlugin(プラグイン)。
フォーム作成
フォーム作成と言えば圧倒的に利用されている数が多いのはContact Form 7ですが、拡張性やカスタマイズを考えるとMW WP Formを私はオススメしたい。
MW WP Form
項目名 更新日や有効ダウンロード数 最終更新 4か月前 有効インストール数 100,000+ WordPress バージョン 4.0 またはそれ以上 検証済み最新バージョン 5.2.5 入力、確認、完了という日本人が好むフォーム形式に対応したPlugin(プラグイン)。
Contact Form 7
項目名 更新日や有効ダウンロード数 最終更新 3か月前 有効インストール数 5,000,000+ WordPress バージョン 4.9 またはそれ以上 検証済み最新バージョン 5.3.2 フォーム作成Plugin(プラグイン)の名作。
高速化
Webサイト表示の高速化は、SEO対策としても重要です。
EWWW Image Optimizer
項目名 更新日や有効ダウンロード数 最終更新 1週間前 有効インストール数 700,000+ WordPress バージョン 5.0 またはそれ以上 検証済み最新バージョン 5.3.2 PHP バージョン 5.6 またはそれ以上 EWWW Image Optimizerは、画像最適化のPlugin(プラグイン)です。
W3 Total Cache
項目名 更新日や有効ダウンロード数 最終更新 3日前 有効インストール数 1,000,000+ WordPress バージョン 3.2 またはそれ以上 検証済み最新バージョン 5.3.2 W3 Total Cacheは、高速化の為にサーバ側、ブラウザ側でさまざまなキャッシュの設定をする事が出来るPlugin(プラグイン)です。
必要なものだけを導入する
Plugin(プラグイン)は大変便利なものですが、WordPressのコアと同じくセキュリティの脆弱性に対応するためにはアップデートを常に行う必要があります。
また、Plugin(プラグイン)を入れすぎるとWordPressが重たくなるケースもありますので、Plugin(プラグイン)は必要最小限のものだけを入れるようにしましょう。
- 投稿日:2020-02-17T15:43:32+09:00
Cisco Meraki DashBoard API使用時に"API rate limit exceeded for organization" エラーが出た時の対処法
PHPを用いてmerakiのネットワークにアクセスしているデバイスや、ユーザーの情報をWeb上に出力して、把握するためのアプリを作っているときに出たエラー
"API rate limit exceeded for organization"
の対処法です。
原因
複数のmeraki dashboard APIと、PHPのforeach文などを組み合わせてデータの取得を行ったりするとこのエラーが出ます。原因は、アクセスが多すぎることでした。
merakiの公式ドキュメントを読むと、組織ごとに1秒間に5回までのアクセスしか認められていないと書かれています。
https://documentation.meraki.com/zGeneral_Administration/Other_Topics/The_Cisco_Meraki_Dashboard_API解決策
PHPのusleep関数を用いて、APIの呼び出しごとに usleep(200000)を実行(API実行ごとに0.2秒処理を停止させる)
1秒間に5回以上APIの呼び出しが行われないようになりました。
- 投稿日:2020-02-17T14:14:33+09:00
【CakePHP】CakePHP3でクリーンアーキテクチャをやりたい【Componentを駆使して失敗した話】
問題提起
CakePHPでもUseCase層やService層を作りたい
CakePHP3ではデフォルトで下記のようなディレクトリ構成となっています。
src ├── Controller │ └── Component ├── Model │ ├── Behavior │ ├── Entity │ └── Table ├── Template ├── View │ ...基本的には、ControllerからComponentを、ComponentからEntityを操作することになるかと思いますが、
愚直に実装を進めていくとComponentが肥大化していってしまいます。そのためUseCase層やService層が欲しくなってきます。
ControllerやModelと同階層に実装しようとすると下記のようになります。src ├── Controller │ └── Component ├── Model │ ├── Behavior │ ├── Entity │ └── Table ├── UseCase ├── Service ├── Template ├── View │ ...一般的な構造ですが、CakePHPでこのように実装した場合、下記のような問題が発生します。
- 既存のComponentの呼び出し方法が変わる
$this->MyComponent = new MyComponent(new ComponentRegistry());
- UseCase等の呼び出し方法が長くなる
(new HogehogeUseCase())->action();
$this->hogehogeUseCase->action();
といった、cakePHPっぽい書き方はできないでしょうか?とりあえずの解決策
UseCaseディレクトリ等をController\Componentディレクトリの下に作成するとスムーズに動かすことができました。
ディレクトリ構成
src ├── Controller │ └── Component │ ├── UseCase │ │ └── HogehogeUseCase.php │ └── Service │ └── FugafugaService.php ├── Model │ ├── Behavior │ ├── Entity │ └── Table ├── Template ├── View │ ...呼び出し方
class HomeController extends AppController { public $components = [ 'HogehogeUseCase' => ['className' => 'UseCase/HogehogeUseCase'], 'FugafugaService' => ['className' => 'Service/FugafugaService'], ]; public function index() { return $this->HogehogeUseCase->action(); } }問題点
Controllerの下に収められてしまうことの違和感(問題度:小)
シンボリックリンクを張れば別階層に置けますが…
インターフェースの置き場所どうする?(問題度:小)
こんな感じになるでしょうか…
src ├── Controller │ └── Component │ ├── Interface │ │ ├── UseCase │ │ │ └── HogeUseCaseInterface.php │ │ └── Service │ │ └── FugaServiceInterface.php │ ├── UseCase │ │ └── HogehogeUseCase.php │ └── Service │ └── FugafugaService.php ├── Model │ ├── Behavior │ ├── Entity │ └── Table ├── Template ├── View │ ...テストがしづらい(問題度:大)
コンポーネントのテストが絶望的に面倒くさいです。
ComponentがComponentを使用できるようにしている関係上、コンポーネント同士が密結合な状態となっております。このため例えばモックとの相性が悪いです。
テスト対象のComponentが複数のComponentに依存している場合、
複数のComponentをモッキングすることになりますが、
これがどうにもきれいな形に収めることができませんでした。問題点対処
そもそもComponentを使用しないように方針転換
$component変数(Service Locater)とテストの相性がそもそも悪いのではないか?
→テストしやすい形を考え、DIの形にする必要があると判断
→構成を1から見直し、CakePHP的な構造を捨て、Componentディレクトリを極力使わないよう方針転換。改変後の構成
Controllerと同階層にUseCaseやServiceディレクトリを作成し、
Controllerがnewで呼び出し。
(さらに言えばControllerとUseCaseの間にFormクラスを使用するとよい。今回は割愛。 ex:モデルのないフォーム)src ├── Controller │ └── ExampleController.php ├── Model │ ├── Behavior │ ├── Entity │ └── Table ├── Service │ └── FugafugaService.php ├── Template ├── UseCase │ └── HogehogeUseCase.php ├── View │ ...ExampleController.phpclass ExampleController extends AppController { public function index() { $service = new FugafugaService(); return (new HogehogeUseCase($service))->action($this->request->getData()); } }HogehogeUseCase.phpclass HogehogeUseCase { /** @var FugafugaService $service */ private $service; public function __construct(FugafugaService $service) { $this->service = $service; } public function action(array $request) { return $this->service->get($request); } }結果
この例では特に処理が無いため実感が湧きづらいですが、
UseCase層以下のテストがしやすくなりました。
またついでに、UseCaseやServiceがController下にあるという状態も解消されました。さいごに
伝えたかった事
Componentの肥大化を抑え、またテストのしやすい形にできたのでとりあえず満足しています。
同じ道を通る人も多いかと思いましたので、同じ轍を踏む前にこの失敗をご参考にしていただければ幸いです。意見募集
このディレクトリ構成はCakePHPの構造を破壊したような感じがしてどうにも煮え切らない気分ではあります。
- こうやった方がいいんじゃない?
- こうやったらスッキリしたよ。
といった意見がありましたら、アドバイスいただければ幸いです。
参考
- 投稿日:2020-02-17T12:22:28+09:00
phpのjson_encodeでjsonの形式を空配列と空のオブジェクトで識別するオプション
phpでjsonの形式にエンコードする際に連想配列を用いていることが多いと思いますが、
空の配列の場合はどうなるでしょう。普通にデフォルトで指定するとこうなります
$a = []; var_dump(json_encode($a));出力結果string(2) "[]"あれ?jsonって
{}
になるんじゃないの?って思った方もいると思います。
そこのjsonの形式を変更させるオプションがJSON_FORCE_OBJECT
です。これを使うと空配列
[]
ではなく{}
と変換されます。JSON_FORCE_OBJECTを指定した場合とデフォルトの場合
$a = []; var_dump(json_encode($a)); var_dump(json_encode($a, JSON_FORCE_OBJECT));出力結果string(2) "[]" string(2) "{}"
JSON_FORCE_OBJECT
パラメータはphp5.3.0からoptionsパラメータに追加されました。
- 投稿日:2020-02-17T10:38:57+09:00
プログラムの可読性を上げるための条件分岐を減らす方法7個
受託開発エンジニアでプロジェクトリーダーという立場になるにあたって品質保証について考えるようになりました。
品質と一口に言っても「内部品質」と「外部品質」の2点がありますが、エンジニアである以上、内部品質の向上が外部品質を上げる最適解だと思います。
※内部品質、外部品質についてはこちらを参照。
そして内部品質を上げるためにはアーキテクチャを考えることが大切ですが、既存プロジェクトの場合はアーキテクチャが
存在しないレベルでぐっちゃぐちゃ十分に検討されていないケースも多いです。そこで、一番手っ取り早くプログラムの可読性を上げる方法は、新規に書くコードをできるだけシンプルに実装することです。今回はその中でも個人的に使っている条件分岐を少なくする or 見やすくするテクニックについてまとめてみました。
なお、今回の例は「こういうやり方がある」という説明のために利用しているだけで、必ずしもベストな例ではないことをご了承ください。
条件分岐を減らす方法7個
今回の分岐を減らす方法ですが、基準としては
- 1メソッドあたりの循環的複雑度を下げる
- ネストを1段階浅くできる
のいずれかを満たしているという条件で考えています。
ガード節
まず、ネストを浅くする最もメジャーな方法としてガード節を思い浮かべた方も多いかと思います。
ガード節とは処理の対象外と条件を、関数の先頭でreturn/continueする方法のことで、その後の処理条件を限定することができるメリットがあります。
まず、悪い例から見ていくと、
sample.phppublic function hoge($a, $b) { $result = 0; if (isset($a)) { $result = 1; } else { if (isset($b)) { $result = 2; } } return $result; }あまりいい例ではありませんが、このようにifを重ねることによってネストが深くなるケースがあります。これをガード節を書き加えることによって以下のようになります。
sample.phppublic function hoge($a, $b) { if (isset($a)) return 1; if (isset($b)) return 2; return 0; }最初に
$a, $b
に値が入っていないかを判定することによって、不要なelseを書く必要がなくなります。
条件分岐を減らしたいと考えたときに真っ先に検討する方法の1つです。bool判定
続いて true/false 判定をする際にも余計な条件分岐が書かれているケースも少なくありません。
具体的な悪いコードは以下の通りです。sample.phppublic function hoge($a) { if ($a === '') { return true; } return false; }基本的にtrue/falseを取得したい場合はその条件を変数を返すだけで事足ります。
sample.phppublic function hoge($a) { return $a === ''; }配列利用
続いて配列を使って条件分岐を減らす方法もあります。
enumやconfigで定義するほどでもないけど、DBから値を取得して画面でテキストを表示したいといったときに便利です。では、具体的なコードを見ていきましょう。
sample.phppublic function hoge($x) { $a = 0; switch ($x) { case 0: $a = '駅名1'; break; case 1: $a = '駅名2'; break; case 2: $a = '駅名3'; break; } return $a; }決して悪い書き方ではありませんが、配列を使って以下のように書き換えることができます。
sample.phppublic function hoge($x) { $array = [ 0 => '駅名1', 1 => '駅名2', 2 => '駅名3', ]; return $array[$x]; }こちらの方がシンプルでいいですね!
仮にswitch文に「defaultに入ったときは0を返す」といった条件があったときもnull合体演算子を使ってシンプルに書くことができます。
こちらはあくまでもPHPの例なので他の言語では上手く動作しない可能性があります
@Ask79273103 さん、ありがとうございます。sample.phppublic function hoge($x) { $array = [ 0 => '駅名1', 1 => '駅名2', 2 => '駅名3', ]; return $array[$x] ?? '駅名0'; // $xが'駅名1', '駅名2', '駅名3'でないときは$array[$x]がnullのため'駅名0'が返される }メソッド分割
続いて根本的な解決ではありませんがメソッドを分割して、1つのメソッドあたりの循環複雑度を減らす方法があります。
例えば以下のようなコードがあったとします。public function hoge($array) { $result = []; foreach ($array as $item) { if ($item) { array_push($item); } } return $result; }このif文を別メソッドに切り分けることでコードの可読性を上げることができます。
sample.phppublic function hoge($array) { $result = []; foreach ($array as $item) { $this->fuga($result, $item); } return $result; } private function fuga(&$result, $item) { if ($item) { array_push($item); } }正直この例ではあまりメリットが分かりづらいですが、例えば「ある特定の条件結果がtrue/falseによって処理を分ける」といったケースでは可読性が上がるケースがあります。
sample.php// 変更前 $result = ''; if ($condition) { if (...) { foreach (...) { // TODO } } } else { foreach ($array as $item) { // TODO } } // 変更後 $result = $condition ? $this->hoge() : $this->fuga();条件分岐を減らすテクニックというよりは、きちんとモジュール化しましょうという話なので少し毛色は違いますが立派なテクニックと言えるかと思います。
三項演算子の活用
人によって意見が分かれますが三項演算子を使うのもネストを浅くする1つの選択肢になります。
具体的に以下のコードを見ていきましょう。sample.php$result = 0; if ($condition) { $result = 1; } else { $result = 2; }三項演算子を使えば以下のように書くことが可能です。
sample.php$result = $condition ? 1 : 2;ただし三項演算子は読み手によってはわかりにくいと言われることも少なくありません。
したがって、三項演算子を使う場合にも複雑な処理を書かないことは頭に入れておく必要があるかと思います。型定義の実装
また、型定義が行える言語であるにも関わらず型を定義していないという場合もあるかもしれません。
例えば、PHPは7系から型定義ができますし、TypeScriptなどでもanyをできるだけ使わないようにすることで分岐を減らすことができます。sample.phppublic function hoge($id) { if (!is_int($id)) { throw new \Exception('id must be type int'); } $sql = "SELECT * FROM user WHERE user_id = {$id}"; ... }型定義は以下のように実装することができます。
sample.phppublic function hoge(int $id) { $sql = "SELECT * FROM user WHERE user_id = {$id}"; ... }型定義によってどれだけエラー発生が減らせるか?については以下のスライドも参考になります。
PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計言語特有の演算子活用
最後に言語特有の演算子を活用したり言語特性を生かすことによって条件分岐を減らすことができます。
例えばPHPであれば、null合体演算子を活用して以下のようにコードを変更することができます。sample.php// 変更前 public function hoge($a) { $result = 0; if (!is_null($a)) { $result = $a; } return $result; } // 変更後 public function hoge($a) { return $a ?? 0; }他にもJavaScriptでは以下のように書くことができます。
sample.js// 変更前 const hoge = value => { let result = 0; if (!!value) { result = value; } return result; } // 変更後 const hoge = value => { return value || 0; }追記
@aliyome @shogogg さんにコメントをいただきましたが、正しくnull/undefined判定を行うためには以下が正しいコードになります。sample.js// 変更後 const hoge = value => { return value ?? 0; }JavaScriptは0やfalseなどもfalsyであり、それらも含めて判定を行いたい場合には上記の論理OR演算子
||
を活用してください。このように言語特性を生かすことによってネストを浅くする方法がないか?は一度探してみてもいいのではないかと思います。
他にもこんなテクニックあるよ!という方は、ぜひ追記したいのでコメントをお願いします!
- 投稿日: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 に書くべき処理」のパターンがあれば、コメント欄にて教えていただけると助かります