20220322のlaravelに関する記事は6件です。

tail -f を使ってリアルタイムのログを確認する

tailコマンドでリアルタイムログを確認するまでの経緯 発生した問題 nginx.conf等諸々設定し、EC2インスタンスのIPv4DNSからアプリ画面へアクセス確認。 →ブラウザで「応答時間が長すぎます。」と警告が表示された。 問題の切り分け 発生した問題に対して以下の手順で調査を進めました。 1.sshは接続できるがhttpで接続できるか? 2.できないとして、どこまで来てるか? 3.EC2でアクセスがきているか確認する →netstat -ano | grep :80 (Linuxのポート状況を確認に関しては以下の記事を参考に。) →/var/log/nginx/access.logを確認 → きていない。 4.まずAWS側に問題があることが一つ考えられる →AWS側は修正するとして、EC2自体からローカルアクセスしたときにアクセスできるのか? →curl 127.0.0.1叩いてローカルアクセスしてみる →404で返ってくる → webサーバー(nginx)自体は正常に動いている。 5.アプリケーション側がおかしいかも? →/var/log/php-fpm/www-error.logのログを確認  →原因となるエラー出てた。 6.EC2コンソール画面からセキュリティルールのインバウンドルールを確認し、設定に不備があったので修正。その後、ローカルではなくブラウザからアクセス確認。 →最初に出た「応答時間が長すぎます。」は出なくなり、次は404エラーが発生。  →とりあえずサーバは正常に動いた。しかし、何故404エラーが発生するのか? 7.ターミナルからtail -fでログファイルを開いた状態でブラウザアクセス →404エラーの原因をリアルタイムで確認。 →DBの設定ができてないことが判明! [root@ip-??-??-??-?? php-fpm]# tail -f www-error.log ERROR: SQLSTATE[HY000] [2002] Connection refused ・・・・ 経緯はこんな感じです。 最後の箇所でブラウザからアクセスした時にリアルタイムでログを確認しました! まとめ ターミナルからtail -fでログファイルを開いた状態にし、ブラウザからアクセスしてリアルタイムでログを確認。 デバック中はこんな感じでログを流しながら調査する方が分かりやすく、今後多く使っていくことになりそうなので記事にしてみました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelバッチ(コマンド) のログをバッチ毎にCloudWatch Logsへ出力する

はじめに EC2上でLaravel APIサーバーが稼働しています。 このAPIサーバーでは$schedule->commandを使用して定期的にバッチ(コマンド)の実行もしています。 (バッチサーバーも兼ねているわけです。) APIとバッチがあいのりしている件はあまり良くないですが、今回はその改善には触れません。 今回の問題はログです。 storage/logs配下へログを出力していますが、 1つのログファイルにAPIとバッチのログがまじってしまっており、さらにバッチが10個程度存在していたため、 どれがどのログなのかを理解する・探すのも難しい状態になっていました。 そこで各ログファイルを分け、ついでにCloudWatchLogsへ連携してみました。 バッチごとにログを分ける logging.phpへチャンネルの設定を追加する バッチごとに使用するログチャンネルの設定を記載します。 日毎にローテーションしていたり、debugログすべて出力していたりするのでそこは調整してください。 config/logging.php ~省略~ 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['daily'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'permission' => 0777, ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 30, 'permission' => 0777 ], // バッチごとにログファイルを分けるためチャンネルを作成 'batch-a' => [ 'driver' => 'daily', 'path' => storage_path('logs/batch-a.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 30, 'permission' => 0777, 'ignore_exceptions' => false ], 'batch-b' => [ 'driver' => 'daily', 'path' => storage_path('logs/batch-b.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 30, 'permission' => 0777, 'ignore_exceptions' => false ], 'batch-c' => [ 'driver' => 'daily', 'path' => storage_path('logs/batch-c.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 30, 'permission' => 0777, 'ignore_exceptions' => false ], ] ~省略~ BatchA.php <?php namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; class BatchA extends Command { ~省略~ public function handle() { Log::setDefaultDriver('batch-a'); 処理 } } これでBatchAからのログ出力にはbatch-aチャンネルが利用されlogs/batch-a.logへログが出力されます。 ※BatchB、Cも同様です。 下記のようにログ出力するたびにチャンネルを指定する方法もあるようですが、 いちいちチャンネルを指定するのは面倒だったので上記のようにsetDefaultDriverを利用しています。 Log::channel('batch-a')->info('Something happened!'); また、下記のようにすればlogging.phpへ設定を行わなくてもログの設定ができるようですが、 この設定をsetDefaultDriverのように全体に反映する方法が見つからなかったため断念しました。 Log::build([ 'driver' => 'daily', 'path' => storage_path('logs/batch-a.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 30, 'permission' => 0777, 'ignore_exceptions' => false ])->info('Something happened!'); CloudWatchエージェントを使用してEC2からログを送る ここまででログの分離は完了したので、そのログをCloudWatch Logsへ連携していきます。 EC2のロールへ「CloudWatchLogsFullAccess」ポリシーを追加 対象となるインスタンスがCloudWatch Logsにアクセス可能とするために、EC2に割り当てるIAMロールを作成します。 こちらを参考に行いました。 EC2へssh接続しCloudWatchエージェントをインストール 以下のコマンドを実行しCloudWatchエージェントをインストールします。 $ wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb $ sudo dpkg -i -E ./amazon-cloudwatch-agent.deb $ sudo amazon-cloudwatch-agent-ctl -a start CloudWatchエージェントの定義ファイルを編集 ログファイルを転送する設定を書いていきます。 $ sudo vi /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/default バッチのログだけでなく、API側のログやnginxのログの転送設定も行いました。 { "agent": { "run_as_user": "root" }, "logs": { "logs_collected": { "files": { "collect_list": [ { "file_path": "/アプリのパス/storage/logs/laravel*log", "log_group_name": "api-laravel-log", "log_stream_name": "ec2-{instance_id}-laravel-log" }, { "file_path": "/アプリのパス/storage/logs/batch-a*log", "log_group_name": "batch-laravel-log", "log_stream_name": "ec2-{instance_id}-batch-a-log" }, { "file_path": "/アプリのパス/storage/logs/batch-b*log", "log_group_name": "batch-laravel-log", "log_stream_name": "ec2-{instance_id}-batch-b-log" }, { "file_path": "/アプリのパス/storage/logs/batch-c*log", "log_group_name": "batch-laravel-log", "log_stream_name": "ec2-{instance_id}-batch-c-log" }, { "file_path": "/var/log/nginx/api.log", "log_group_name": "api-nginx-log", "log_stream_name": "ec2-{instance_id}-nginx-log" }, { "file_path": "/var/log/nginx/api-error.log", "log_group_name": "api-nginx-error-log", "log_stream_name": "ec2-{instance_id}-nginx-error-log" }, { "file_path": "/var/log/php7.4-fpm.log", "log_group_name": "php-fpm-log", "log_stream_name": "ec2-{instance_id}-php-fpm-log" } ] } } } } CloudWatchエージェント再起動,ステータス確認 $ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -m ec2 -a stop $ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -m ec2 -a start ログがCloudWatchLogsに転送されているか確認 AWSマネージメントコンソールへサインインし、 CloudWatch Logs > ロググループ > batch-laravel-log を確認。 バッチごとにLog Streamが別れていることが確認できました! 最後に 定義ファイルをバッチごとに書かなくてはならないので、 1つの定義でいい感じに設定する方法を模索中。 知っている方いたら是非教えて下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelとは?

Laravelって何? Laravelとは、PHPのフレームワークで、2011年にリリースされたもの。 PHPとは? PHPとはWebページを動的に生成できるサーバー再度のスプリクト言語。 フレームワークとは? フレームワークとは、アプリケーションフレームワークともいい、アプリケーション開発の土台となるソフトウエアのこと。 アプリケーションの枠組み、骨組み、構造部となるため、フレームワーク内に必要な機能を追加しながら開発を進める。 Laravelの特徴 分業開発がしやすい Laravelは「MVC」というアーキテクチャを採用している。 「MVC」とは、データベースとの連携を担う「Model」と、ブラウザへの表示を司る「View」、指示や指令を行う「Controller」の3つに分別し、機能ごとに開発を進める開発方法。 それぞれの機能別に独立しているため、システムやアプリを分業体制で開発ができるのが特徴。 学習コストが低い シンプルなコードで記述できるため学習がしやすく、コーディングがしやすいのが特徴。 また、開発効率化を図ることができるのも特徴(簡単なコードなどは自動生成できる) プログラムの拡張性が高い 生成したコードのカスタマイズが容易。 composerでパッケージ管理ができる 必要なパッケージやライブラリの管理を行うことができるcomposerで管理されている。 LaravelはPHPの代表的なフレームワーク PHPで開発を行う時はLaravelを活用することで効率的に作業を進めることができる。 公式サイト Laravel
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XserverにLaravelをデプロイできない場合に注意する点

はじめに 今回は環境の異なるXserverにLaravelを使ったアプリをデプロイしたいけどエラーが起きる場合に注意する点をまとめました。 gitからクローンしてpublic配下に移動し、シンボリックリンクの設定を済ませた前提で書いていきます。 PHPのバージョンを確認 まずはlaravelアプリのcomposer.jsonファイルを確認して環境を確認しましょう。 "require": { "php": "^8.0.2", "guzzlehttp/guzzle": "^7.2", "laravel/breeze": "^1.8", "laravel/framework": "^9.2", "laravel/sanctum": "^2.14.1", "laravel/tinker": "^2.7", "laravel/ui": "^3.4" } このrequireに記載されているものを満たす必要があります。今回は8.0.2であるので8.0.12を選びましょう。同じバージョンがなくてもそれ以上なら問題なりです。 SSHのPHPバージョンも変更 //まずはバージョンを確認しましょう php -v //結果: PHP5.4.16 (cli) (built: Nov 1 2019 16:04:20) ・・・ //現在利用可能なバージョンを確認しましょう find /opt/php-*/bin -type f -name 'php' //結果: ・・・ /opt/php-7.4.3/bin/php /opt/php-7.4.4/bin/php /opt/php-7.4/bin/php /opt/php-8.0.0/bin/php /opt/php-8.0.1/bin/php /opt/php-8.0.10/bin/php /opt/php-8.0.12/bin/php /opt/php-8.0.6/bin/php /opt/php-8.0.7/bin/php /opt/php-8.0/bin/php このような感じに結果がでるのでその中から先ほどの8.0.2以上を満たす好みのバージョンをメモしてください。 //初心者の方は何も考えずにこのコマンドを打ってください mkdir $HOME/bin //先ほどメモしたバージョンを[php-8.0.7]の部分に入れ込んでコマンドを打ちこんであげてください ln -s /opt/php-8.0.7/bin/php $HOME/bin/php 通常の状態だとこの作業を毎回しなくては好みのバージョンのphpを使用することはできません。 しかし毎回打つのはどう考えても無駄です。そこでbash_profleというものに同じことを毎回するように書き込んでやるのです。 //ファイルを作成 touch bash_profile //vimで編集 vi ~/.bashrc viコマンドを打つと見慣れない画面に飛ばされますが、落ち着いてください。 この画面でキーボードのiを押して編集モードに切り替え export PATH=$HOME/bin:$PATH こちらを入力したらエスケープキー(ESC)を押して、続いて[:w]を入力してエンター、そして[:q!]を入力してエンターを押してもとの画面に戻りましょう。 //終わったら次にこちらのコマンドを打って反映させましょう source ~/.bash_profile //こちらも完了したらこのコマンドで再度バージョンを確認してあげてください php -v //結果がまだ5.4.16なら最初からやり直してください。どこかでタイプミスなどをしているはずです・・・ composer update phpを更新できたらcomposer updateを試しましょう。 composer update ここでエラーがでるようならエラー内容でググってみましょう。基本的にはphpのバージョンエラーなので上記の部分をしっかりしていれば問題なく通ります。 .htaccessファイルを確認 public_html配下に.htaccessファイルとphp.initファイルが存在していると思いますが、デプロイ時にpublicファイル配下にも入ってしまうことがあります。 そちらを二つともpublic_htmlの配下に移動させてあげないとエラーが起きてしまいます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのユニットテストで詰まった話

はじめに タイトルにある通り未解決ですが、 Laravelでユニットテストをコーディングしていた時に起こった事を書きます。 テストクラスにはメソッドが2つあり、1つずつテストを実行した時は問題なくそれぞれPassします。 しかし、通しで2つ同時に実行するとこけちゃいます。 環境 $php --version PHP 8.0.16 (cli) (built: Feb 18 2022 20:51:37) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.16, Copyright (c) Zend Technologies $php artisan --version Laravel Framework 8.36.2 $phpunit --version PHPUnit 9.5.10 by Sebastian Bergmann and contributors. $mysql --version mysql Ver 14.14 Distrib 5.6.38, for osx10.9 (x86_64) using EditLine wrapper テスト準備 テスト用のDBでテストするための準備を簡単に書きます。 .env.testing APP_ENV=testing DB_CONNECTION=testing DB_HOST=localhost DB_PORT=3306 DB_DATABASE=test DB_USERNAME=root DB_PASSWORD=root phpunut.xml <php> <server name="APP_ENV" value="testing"/> <server name="BCRYPT_ROUNDS" value="4"/> <server name="CACHE_DRIVER" value="array"/> <!-- <server name="DB_CONNECTION" value="sqlite"/> --> <!-- <server name="DB_DATABASE" value=":memory:"/> --> <server name="MAIL_MAILER" value="array"/> <server name="QUEUE_CONNECTION" value="sync"/> <server name="SESSION_DRIVER" value="array"/> <server name="TELESCOPE_ENABLED" value="false"/> </php> migration_create_clients.php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTblClient extends Migration { /** * Run the migrations. * * @return void */ public function up() { // テストに関係ないテーブル定義は簡略しています。 Schema::create('clients', function (Blueprint $table) { $table->increments('id'); $table->string('client_id'); $table->string('type'); $table->string('name'); $table->integer('test_flag'); $table->timestamp('reg_date'); $table->timestamp('upd_date'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('clients'); } } ClientSeeder.php <?php namespace Database\Seeders; use App\Models\Client; use Illuminate\Database\Seeder; class ClientSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { Client::insert([ [ // 'id' => 1, 'client_id' => 'hoge', 'type' => 'super', 'name' => 'ほげ', 'test_flag' => 0, ], [ // 'id' => 2, 'client_id' => 'hogehoge', 'type' => 'super', 'name' => 'ほげほげ', 'test_flag' => 0, ], [ // 'id' => 3, 'client_id' => 'fuga', 'type' => 'super', 'name' => 'ふが', 'test_flag' => 0, ], [ // 'id' => 4, 'client_id' => 'fugafuga', 'type' => 'super', 'name' => 'ふがふが', 'test_flag' => 0, ], [ // 'id' => 5, 'client_id' => 'hogefuga', 'type' => 'super', 'name' => 'ほげふが', 'test_flag' => 1, ], ]); } } DatabaseSeeder.php <?php namespace Database\Seeders; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call([ ClientSeeder::class, ]); } } ClientTest.php <?php namespace Tests\Unit; use Exception; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ClientTest extends TestCase { use RefreshDatabase; public function setUp(): void { parent::setUp(); $this->artisan('db:seed'); $this->client = $this->app->make('App\略\Client'); } public function test_fetchNonTestClientType() { $type = 'super'; $search = 'h'; $res = $this->client->fetchNonTestClientType($type, $search); $this->assertEquals([ [ 'id' => 1, 'client_id' => 'hoge', 'name' => 'ほげ', ], [ 'id' => 2, 'client_id' => 'hogehoge', 'name' => 'ほげほげ', ], ], $res); $search = 'ふが'; $res = $this->client->fetchNonTestClientType($type, $search); $this->assertEquals([ [ 'id' => 3, 'client_id' => 'fuga', 'name' => 'ふが', ], [ 'id' => 4, 'client_id' => 'fugafuga', 'name' => 'ふがふが', ], ], $res); $search = ''; $res = $this->client->fetchNonTestClientType($type, $search); $this->assertEquals([ [ 'id' => 1, 'client_id' => 'hoge', 'name' => 'ほげ', ], [ 'id' => 2, 'client_id' => 'hogehoge', 'name' => 'ほげほげ', ], [ 'id' => 3, 'client_id' => 'fuga', 'name' => 'ふが', ], [ 'id' => 4, 'client_id' => 'fugafuga', 'name' => 'ふがふが', ], ], $res); $search = 'dahodajdadaoi'; $res = $this->client->fetchNonTestClientType($type, $search); $this->assertEquals([], $res); $type = 'ultra'; $search = 'h'; $res = $this->client->fetchNonTestClientType($type, $search); $this->assertEquals([], $res); } public function test_setTestFlag() { $res = $this->client->setTestFlag(1); $this->assertTrue($res); $res = $this->client->setTestFlag(5); $this->assertTrue($res); $this->expectException(Exception::class); $this->client->setTestFlag(10); } } Client.php <?php namespace App\略\Client; use Exception; use App\Models\Client; use Throwable; class Client { public function __construct(private $client = new Client){} /** * * @param string $type * @param string $search * @return array */ public function fetchNonTestClientType(string $type, string $search): array { try { $res = $this->clientModel->select('id', 'client_id', 'name') ->where('type', $type) ->where(function($query) use ($search){ $query->where('client_id', 'like', "%$search%") ->orWhere('name', 'like', "%$search%"); }) ->where('test_flag', '<>', 1) ->get() ->toArray(); } catch (Throwable $e) { throw new Exception($e, 500); } return $res; } /** * * @param integer $id * @return boolean */ public function setTestFlag(int $id): bool { try { $client = $this->client->findOrFail($id); $client->test_flag = 1; $res = $client->save(); } catch (Throwable $e) { throw new Exception($e, 500); } return $res; } 長々とファイルを書きましたが簡単に説明すると fetchNonTestClientType()はClientから条件にあったレコードを取得してtoArray()して返す。 setTestFlag()は渡された引数のidのtest_flagを1書き換えてsave()して実行結果を返す。 という事を行なっています。 test_fetchNonTestClientType()は$typeと$searchを投げて検索結果が想定通りのものか確認するテスト test_setTestFlag()は指定した$idのレコードが上書きに成功したか、$idが見つからずExceptionを発行したかどうかを確認するテスト となっています。 テスト実行 やっとテスト実行の準備ができました。 まずはテストを個別に実行します。 $ php artisan test tests/Unit/ClientTest.php --filter test_fetchNonTestClientType PASS Tests\Unit\ClientTest ✓ fetch non test client type Tests: 1 passed Time: 0.91s $ php artisan test tests/Unit/ClientTest.php --filter test_setTestFlag PASS Tests\Unit\ClientTest ✓ set test flag Tests: 1 passed Time: 0.89s うんうん、いい感じ。 それでは本命のClientTest.phpをまとめて実行してみます。 $ php artisan test tests/Unit/ClientTest.php FAIL Tests\Unit\ClientTest ✓ fetch test client type ⨯ set test flag --- • Tests\Unit\ClientTest > set test flag Exception Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Models\Client] 1 in /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:420 Stack trace: #0 /var/www/vendor/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php(23): Illuminate\Database\Eloquent\Builder->findOrFail(1) #1 /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1890): Illuminate\Database\Eloquent\Model->forwardCallTo(Object(Illuminate\Database\Eloquent\Builder), 'findOrFail', Array) #2 /var/www/app/略/Client.php(361): Illuminate\Database\Eloquent\Model->__call('findOrFail', Array) #3 /var/www/tests/Unit/ClientTest.php(91): App\略\Client->setTestFlag(1) #4 /var/www/vendor/phpunit/phpunit/src/Framework/TestCase.php(1527): Tests\Unit\ClientTest->test_setTestFlag() #5 /var/www/vendor/phpunit/phpunit/src/Framework/TestCase.php(1133): PHPUnit\Framework\TestCase->runTest() #6 /var/www/vendor/phpunit/phpunit/src/Framework/TestResult.php(722): PHPUnit\Framework\TestCase->runBare() #7 /var/www/vendor/phpunit/phpunit/src/Framework/TestCase.php(885): PHPUnit\Framework\TestResult->run(Object(Tests\Unit\ClientTest)) #8 /var/www/vendor/phpunit/phpunit/src/Framework/TestSuite.php(678): PHPUnit\Framework\TestCase->run(Object(PHPUnit\Framework\TestResult)) #9 /var/www/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(670): PHPUnit\Framework\TestSuite->run(Object(PHPUnit\Framework\TestResult)) #10 /var/www/vendor/phpunit/phpunit/src/TextUI/Command.php(143): PHPUnit\TextUI\TestRunner->run(Object(PHPUnit\Framework\TestSuite), Array, Array, true) #11 /var/www/vendor/phpunit/phpunit/src/TextUI/Command.php(96): PHPUnit\TextUI\Command->run(Array, true) #12 /var/www/vendor/phpunit/phpunit/phpunit(92): PHPUnit\TextUI\Command::main() #13 {main} at app/略/Client/Client.php:365 361▕ $client = $this->client->findOrFail($id); 362▕ $client->test_flag = 1; 363▕ $res = $client->save(); 364▕ } catch (Throwable $e) { ➜ 365▕ throw new Exception($e, 500); 366▕ } 367▕ return $res; 368▕ } 369▕ } 1 tests/Unit/ClientTest.php:91 App\略\Client::setTestFlag() Tests: 1 failed, 1 passed Time: 1.04s エラーを見る限りtest_setTestFlagで$this->expectException(Exception::class);を宣言?する前にExceptionが発行されてしまっている様です。個別のテストでうまくいってるのになんだこれと思って色々試してみました。 test_fetchNonTestClientType()とtest_setTestFlag()の順序を変えてみた。 primaryKeyを指定しながらseedしてみた。(seederのコメントアウトを解除) test_setTestFlag()の先頭で$this->expectException(Exception::class);を追加  // 1の結果 $ php artisan test tests/Unit/ClientTest.php FAIL Tests\Unit\ClientTest ✓ set test flag ⨯ fetch test client type --- • Tests\Unit\ClientTest > fetch test client type Failed asserting that two arrays are equal. at tests/Unit/ClientTest.php:40 36▕ $search = 'h'; 37▕ $res = $this->client->fetchNonTestClientType($type, $search); 38▕ $this->assertEquals([ 39▕ [ ➜ 40▕ 'id' => 1, 41▕ 'client_id' => 'hoge', 42▕ 'name' => 'ほげ', 43▕ ], 44▕ [ --- Expected +++ Actual @@ @@ Array ( 0 => Array ( - 'id' => 1 + 'id' => 6 'client_id' => 'hoge' 'name' => 'ほげ' ) 1 => Array ( - 'id' => 2 + 'id' => 7 'client_id' => 'hogehoge' 'name' => 'ほげほげ' ) ) Tests: 1 failed, 1 passed Time: 1.01s // 2の結果 $ php artisan test tests/Unit/ClientTest.php PASS Tests\Unit\ClientTest ✓ fetch fetch test client type ✓ set test flag Tests: 2 passed Time: 1.00s // 3の結果 $ php artisan test tests/Unit/ClientTest.php PASS Tests\Unit\ClientTest ✓ fetch fetch test client type ✓ set test flag Tests: 2 passed Time: 1.00s それぞれの結果を見ると、auto incrementしているidが違っていることによりエラーになっていたことがわかりました。 これらを見るに今のテストの設定だと1つのテストメソッド毎にseedされている?という疑問が浮上したのでテストクラスに1つ実験用メソッドを追加しました。 ClientTest.php public function test_hoge() { $type = 'sf'; $search = 'h'; $res = $this->client->fetchNonTestClientType($type, $search); $this->assertEquals([ [ 'id' => 1, 'client_id' => 'hoge', 'name' => 'ほげ', ], [ 'id' => 2, 'client_id' => 'hogehoge', 'name' => 'ほげほげ', ], ], $res); } php artisan test tests/Unit/ClientTest.php FAIL Tests\Unit\ClientTest ✓ fetch test client type ✓ set test flag  // 先頭でExceptionを許容 ⨯ hoge --- • Tests\Unit\ClientTest > hoge Failed asserting that two arrays are equal. at tests/Unit/ClientTest.php:109 105▕ $search = 'h'; 106▕ $res = $this->client->fetchNonTestClientType($type, $search); 107▕ $this->assertEquals([ 108▕ [ ➜ 109▕ 'id' => 1, 110▕ 'client_id' => 'hoge', 111▕ 'name' => 'ほげ', 112▕ ], 113▕ [ --- Expected +++ Actual @@ @@ Array ( 0 => Array ( - 'id' => 1 + 'id' => 11 'client_id' => 'hoge' 'name' => 'ほげ' ) 1 => Array ( - 'id' => 2 + 'id' => 12 'client_id' => 'hogehoge' 'name' => 'ほげほげ' ) ) Tests: 1 failed, 2 passed Time: 1.13s 予想通りテストメソッド毎にseedされているようです さいごに setUp()とRefreshDatabaseが全てのテストメソッド実行前ではなくテスト実行前にだけ実行されると思い込んでいたことが原因でこういった事象が起こりました。今回の解決案としては seederを書く時はautoincrements指定されているカラムもきちんと書いてやる seedするのはartisanコマンドで事前にやってもらい、RefreshDatabaseを指定しないことで採番が変わらない様にする。 今回は1を採用することにしました。 余談 ほんとは未解決ってタイトルにしようと思ってましたが、前日寝ようとしてる時にふと思いついて解決してしまいました。(よくあるよね)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Web開発初心者が1週間で入門チュートリアルを実施した記録(8)

以下の続き。 前回までで、タスクの編集機能をつくった。 入門Laravelチュートリアル (8) ToDoアプリの認証機能を作る 認証機能の実装。 ・会員登録機能 ・ログイン機能 ・ログインしたユーザーは自分のフォルダのタスクだけを閲覧できる機能 ユーザーとフォルダの紐付け。 単純にFoldersテーブルにユーザーIDカラムを追加するだけだと既存データのNot Null制約が邪魔してうまくいかないので、以下コマンドでテーブルを作り直す。 php artisan migrate:fresh このコマンドでは、テーブルを全て削除してマイグレーションを実行し直す。 すると問題なくマイグレーションできる。なお、Usersテーブルは、既存の以下ファイルにて作成済み。 ・2014_10_12_000000_create_users_table.php ユーザーとフォルダは1対多(フォルダとタスクも1対多)の関係なので、ユーザーモデルにhasManyを付与。 public function folders() { return $this->hasMany('App\Folder'); } シーダーを用いたデータの準備と、ホームページを作成後、いよいよ登録機能に入る。 登録機能 ・Laravel には認証機能が最初から搭載されている。 ・認証機能を受け持つコントローラーは app/Http/Controllers/Auth ディレクトリにすでに用意されている。  /app/Http/Controllers/Auth/ConfirmPasswordController.php  /app/Http/Controllers/Auth/ForgotPasswordController.php  /app/Http/Controllers/Auth/LoginController.php  /app/Http/Controllers/Auth/RegisterController.php  /app/Http/Controllers/Auth/ResetPasswordController.php  /app/Http/Controllers/Auth/VerificationController.php ・ルーティングについても認証用の設定を吐き出すメソッドが用意されているので、基本的にはテンプレートを作成するだけでアプリケーションに認証機能を追加することができる。 では、実際に実装していく。 ①ルーティング設定 Auth::routes(); ⑧テンプレートの作成(register) resources/views/auth/register.blade.php メールアドレスとユーザー名、PW、PW(確認用)があるだけのシンプルな画面だが、ポイントは2点。 まず1点目。 <form action="{{ route('register') }}" method="POST"> 上記route関数の向き先って・・・?以下コマンドのName列を参照。 php artisan route:list +--------+----------+------------------------------------+------------------+------------------------------------------------------------------------+--------------------------------------------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+------------------------------------+------------------+------------------------------------------------------------------------+--------------------------------------------------+ | | POST | register | | App\Http\Controllers\Auth\RegisterController@register | web | | | | | | | App\Http\Middleware\RedirectIfAuthenticated | | | GET|HEAD | register | register | App\Http\Controllers\Auth\RegisterController@showRegistrationForm | web | | | | | | | App\Http\Middleware\RedirectIfAuthenticated | +--------+----------+------------------------------------+------------------+------------------------------------------------------------------------+--------------------------------------------------+ 以下のメソッドを見にいく。 RegisterController@showRegistrationForm 続いて2点目。 パスワード一致確認の書き方について。 Laravel には confirmed ルールというバリデーションルールが実装されている。このルールはある項目(仮に abc とします)とその項目名 + _confirmation という名前の項目(abc_confirmation)の入力値が一致することを検証する。 <input type="password" class="form-control" id="password-confirm" name="password_confirmation"> name="password_confirmation"とすることで、name="password"の項目と入力値が一致するか確認する。 テンプレートの記載が完了したところで、コントローラーの記述を見ていく。 さきほどもでてきたApp\Http\Controllers\Auth\RegisterControllerについて、登録完了後のリダイレクト先を以下のように修正。 protected $redirectTo = '/'; このままでも登録処理自体は問題なくできる。このようにLaravelの認証機能はそのままでも十分使える上に、カスタマイズもしやすい。 続いて、コントローラーのvalidatorメソッドに設定内容を書き込んでいく。 これまでバリデーションはFormRequestクラスを使って実装していたが、このようにコントローラーに記載しても実現可能。 protected function validator(array $data) { return Validator::make($data, [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', ], [], [ 'name' => 'ユーザー名', 'email' => 'メールアドレス', 'password' => 'パスワード', ]); } makeメソッドの第一引数は検証するデータ、第二引数がルール定義、第三引数がメッセージ定義、第四引数が項目名定義。第3引数のメッセージ定義は、validation.php で定義するので空の配列を渡し、第四引数で日本語の項目名を定義。 validation.phpは以下の通り編集。 'confirmed' => ':attribute が確認欄と一致していません。', 'email' => ':attribute には有効な形式のメールアドレスを入力してください。', 'min' => [ // 略 'string' => ':attribute は:min文字以上で入力してください。', // 略 ], 'string' => ':attribute には文字を入力してください。', 'unique' => '入力いただいた :attribute はすでに使用されています。', ログイン機能 ルーティング設定は登録機能と同じものでOKなので、テンプレートの修正をおこなっていく。 ⑧テンプレートの修正 resources/views/auth/login.blade.phpを修正していく。 メールアドレスとPWをいれて送信するだけの画面。パスワード変更はLaravelの機能で対応可能。 password.requestはApp\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestFormをみている。 会員登録のときと同じように、ログインに成功したあとの遷移先を指定するため、app/Http/Controllers/Auth/LoginController.php を編集。 ログインメッセージはauth.phpで定義されているので、jpの下にコピーしてきて編集。 これでログイン機能の実装も完了!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む