- 投稿日:2020-06-30T23:59:23+09:00
artisanコマンドの自作で引数付きのSeederを作成する
あらすじ
Laravelにはseederがありテスト時などにサンプルデータを気軽に作ることができます。
ただartisanコマンドを用いて呼び出すのですが、この時にshellscriptのように作成するデータ数などを引数として与えられたらなあ...と思っていました。
そんなところartisanのコマンド自作で行けそうといアドバイスをいただいたので、コマンド作成の練習もかねて作成してみることにしました。通常のSeeder
今回は例として、Laravelのプロジェクトに最初からついてくるUserテーブルのSeederをもとに作っていきます。似た構造のレコードを複数追加するのでFactoryを記述して(されていて)、
Userfactory.php<?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use App\User; use Illuminate\Support\Str; use Faker\Generator as Faker; $factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; });これをSeederから呼び出します。
factoryの第二引数を変えることで生成するレコードの数を変えられるので、ここにartisanコマンドで受け取った引数を渡すことにします。UsersTableSeeder.php<?php use Illuminate\Database\Seeder; class UsersTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { factory(User::class, 10)->create(); } }artisanコマンドで引数を使用する
引数の扱いは、
https://qiita.com/nenokido2000/items/abbf70c87c9ad86a2b89
などを参考にしました。まずはターミナルから雛形を生成します。
$ php artisan make:command BulkUserSeed作成されたファイルを変更して、
・呼び出すためのコマンド
・引数
・呼ばれた際の処理
を記述していきます。
雛形そのままで動く部分は省略してあります。BulkUserSeed.php<?php namespace App\Console\Commands; use App\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\Schema; class BulkUserSeed extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'command:BulkUserSeed {number=10 : number of users}'; // 〜〜(中略)〜〜 public function handle() { print("Start seeding.\n"); $numberOfUsers = (int)$this->argument("number"); factory(User::class, $numberOfUsers)->create(); print("Seeding complete.\n"); } }また、オプションとしての引数も与えられるので、今回は試しにmigrate:refreshのようにテーブルをまず全消去するかを選べるようにしたいと思います。
BulkUserSeed.php// ... ... protected $signature = 'command:BulkUserSeed {number=10 : number of users} {--refresh : drop all existing users}'; // ... ... public function handle() { $refresh = $this->option("refresh"); if ($refresh){ print("Are you sure you want to delete all users? [Press y] \n"); $input = trim(fgets(STDIN)); if ($input === 'y') { print("User table truncated.\n"); User::truncate(); } else { print("Seeding canceled.\n"); return; } print("Start seeding.\n"); $numberOfUsers = (int)$this->argument("number"); factory(User::class, $numberOfUsers)->create(); print("Seeding complete.\n"); } }ログなどはお好みで。
これで> php artisan command:BulkUserSeed > php artisan command:BulkUserSeed 100 > php artisan command:BulkUserSeed 100 --refreshなどのコマンドが選択できるようになりました。
おまけ 連番でデータを作成する
実は元々作っていたプロジェクトでは(と言っても練習用の仮想の物ですが)、日にちごとに担当者を決めて管理するという機能を付けようとしていました。そこで用いるテーブルには以下のようなものがあります。
例えば1月1日から12月31日までの仮データを一気に作りたい!と思った時は、先ほどと同じ方法ではうまくいかず、dateの値を加算しながら与える必要があります。
1日ずつずれた日付を与えるにはfor文を回しながら
・基準となる日にちを決め、+i日加算した日付を与える
・ループごとに変数を1日ずつインクリメントする
などで達成できます。下のコードでは後者を用いています。なお、+1 monthなど月単位で加算する場合、月末の日付のズレなどに気を使う必要があるようです。
(参考: https://www.p-nt.com/technicblog/archives/11 など)BulkAssignmentSeed.php<?php namespace App\Console\Commands; use App\Assignment; use Illuminate\Console\Command; class BulkAssignmentSeed extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'command:BulkAssignmentSeed {number=10 : number of assignments} {first_date=1970-01-01 : the date of first row} {--refresh : drop all existing assignments}'; /** * The console command description. * * @var string */ protected $description = 'Seeding arbitrary number of assignments.'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { $refresh = $this->option("refresh"); if ($refresh){ print("Are you sure you want to delete all Assignments? [Press y] \n"); $input = trim(fgets(STDIN)); if ($input === 'y') { print("Assignment table truncated.\n"); Assignment::truncate(); } else { print("Seeding canceled.\n"); return; } } print("Start seeding.\n"); $numberOfAssignments = (int)$this->argument("number"); $base = $this->argument("first_date"); for($i = 0; $i < $numberOfAssignments; $i++){ // Log::debug($baseDate); $date = date("Y-m-d", strtotime($base . "+($i) day")); factory(Assignment::class)->create([ 'date' => $date, ]); } print("Seeding complete.\n"); } }AssignmentFactory.php<?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use App\Assignment; use Faker\Generator as Faker; $factory->define(Assignment::class, function (Faker $faker) { return [ 'staff' => $faker->name, ]; });終わりに
コマンド拡張の楽しさを知ったので、これからも色々やってみたいと思います。
- 投稿日:2020-06-30T23:25:15+09:00
【Laravel】Route/web.phpで構文ミスするとartisanコマンドが動かなくなる問題
はじめに
プロジェクトファイルをgitからクローンしてartisanコマンドを入力したら以下のエラーがでた。
PHP Fatal error: Uncaught Error: Class 'CommonHelper' not found in /home/vagrant/{アプリ名}/app/Logging/CustomJsonFormatter.php:13 Stack trace: #0 /home/vagrant/{アプリ名}/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php(40): App\Logging\CustomJsonFormatter->format() #1 /home/vagrant/{アプリ名}/vendor/monolog/monolog/src/Monolog/Logger.php(323): Monolog\Handler\AbstractProcessingHandler->handle() #2 /home/vagrant/{アプリ名}/vendor/monolog/monolog/src/Monolog/Logger.php(541): Monolog\Logger->addRecord() #3 /home/vagrant/{アプリ名}/vendor/laravel/framework/src/Illuminate/Log/Logger.php(176): Monolog\Logger->error() #4 /home/vagrant/{アプリ名}/vendor/laravel/framework/src/Illuminate/Log/Logger.php(87): Illuminate\Log\Logger->writeLog() #5 /home/vagrant/{アプリ名}/vendor/laravel/framework/src/Illuminate/Log/LogManager.php(547): Illuminate\Log\Logger->error() #6 /home/vagrant/{アプリ名}/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php(121): Illuminate\Log\LogManager->error() #7 /home/vagrant/{アプリ名}/app/Exceptions/ in /home/vagrant/{アプリ名}/app/Logging/CustomJsonFormatter.php on line 13解決するためにした事
1,composer dump-autoload
クラスがない系のエラーは大体composer dump-autoloadで直ると思っていたら同じようなエラー内容が表示された。
> Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi PHP Fatal error: Uncaught Error: Class 'CommonHelper' not found in /home/vagrant/{アプリ名}/app/Logging/CustomJsonFormatter.php:13 以下同文どうやらクラスの読み込みが問題ではなく、そもそもartisanコマンド自体が受け入れられていないようだ。
2,routes/web.phpの構文ミス
あれこれ悩んだ結果、結局routesのファイルで構文ミスをしていただけで、内容は括弧が一行分足りていなかった。
これとCommonHelperのクラスがみつからなくなる因果関係が未だわかっていないが、おそらくLaravelのアーキテクチャに関わるデザインパターンのファイルを間違えるとそもそもクラスの読み込みまで到達してくれないのではないか、その為Stack traceで全く関係のないlog周りの内容が表示されているのだと思います。おわりに
同様なエラーが発生された方がいましたら、Laravelのアーキテクチャに関わるデザインパターンのファイルを調べてみてください。
- 投稿日:2020-06-30T22:53:16+09:00
【Laravel】laravel new のあとにやること
環境
- macOS
- PHP7.4
- Laravel7.x
- SQLite3
やること
新しくアプリケーションを作成する時、laravel new [アプリ名]を実行した後にすることをまとめました
あくまで筆者の場合なので、参考程度に読んでみてください
- git周辺
- アプリ名、タイムゾーン、言語の設定
- データベースの設定
- メールの設定
- Auth機能の導入
- 日本語化
git周辺
はじめに、BitbucketやGitHubのリポジトリを作っておきます
以下のコマンドを実行して、リポジトリの初期化、BitbucketやGitHubへの接続を行います
ちなみに筆者は執筆現在Bitbucketを使っています# LaravelAppディレクトリに移動 % git init % git add -A % git commit -m "Initialize repository" % git remote add origin [BitbucketやGitHubのリポジトリ名] % git push -u origin masterアプリ名、タイムゾーン、言語の設定
- .envファイルのAPP_NAME
- config/app.phpのname、timezone、local
を設定します
.envAPP_NAME=アプリ名config/app.php'name' => env('APP_NAME', 'アプリ名'), ... 'timezone' => 'Asia/Tokyo', ... 'locale' => 'ja',データベースの設定
今回はSQLiteを使います
- databaseフォルダにdatabase.sqliteを作成
- config/database.phpのdefaultを設定
- .envファイルのDB_変数を設定
% touch database/database.sqlite
config/database.php'default' => env('DB_CONNECTION', 'sqlite'),.envDB_CONNECTION=sqlite.envにデフォルトで設定されている他のDB_変数は削除します
メールの設定
今回はGメールを使うので、あらかじめGメール側でアプリパスワード(16桁)を生成しておきます
アプリパスワードは基本メモを取ったり他人と共有しませんが、環境変数に設定するので覚えておきましょう
- config/mail.phpのdefault
- .envファイルのMAIL_変数
を設定します
config/mail.php'default' => env('MAIL_MAILER', 'smtp'),envMAIL_MAILER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USERNAME=Gmailアドレス MAIL_PASSWORD=アプリパスワード(16桁) MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=Gmailアドレス MAIL_FROM_NAME="${APP_NAME}"Auth機能の導入
以下のコマンドを実行します
※Laravel5以下はコマンドが異なります// laravel/uiをインストール % composer require laravel/ui // Auth関連ファイル(基本的なスキャフォールド)を生成 % php artisan ui vue --auth // フロントエンドに必要なパッケージをインストール&必要なファイルをコンパイル・ビルド % npm install && npm run dev日本語化
Auth機能を導入したときに自動生成されるファイルがあります
フォームやバリデーションやメールで日本語で表示されるように変更します
日本語化は以下のURLのブログを見たほうがいいです(丸投げ)https://blog.capilano-fw.com/?p=289#i-3
おわり
現在ポートフォリオ作成中で同時に記録として記事を書いていこうと思います
- 投稿日:2020-06-30T17:31:55+09:00
Laravel 1対多のリレーション先の項目の集計値(最小値, 最大値, 平均値, 合計値 など)で親テーブルを並べ替えたい
テーブルの例
たとえば、商品基本情報と詳細が1対多の関係で、データベースを構成したとします。
一覧表示する時に、価格順で並び替えたい時はどんな感じにクエリビルダーを使えばいいか、いろいろ試してこれかなぁというのを投降します。【商品情報テーブル (products)】
id 商品ID
name 商品名【商品詳細テーブル (detail)】
id 商品詳細ID
productID 商品詳細ID
size サイズ
price 価格
visible 表示・非表示コードの例
$aggregates = DB::table('detail') ->select('productID', DB::raw('MIN(price) as min_price'), DB::raw('MAX(price) as max_price') ) ->where('visible', 1) ->groupBy('itemID'); $db = DB::table('products') ->joinSub($aggregates, 'aggregates', function ($join) { $join->on('products.id', '=', 'aggregates.itemID'); }) ->orderBy('min_price01', 'desc') ->get();これなら、並び替えできます。ここ参考にしました。
https://readouble.com/laravel/6.x/ja/queries.html
最初は、joinでリレーションを作って、groupByでまとめ、価格の最大・最小をselectに入れて、を並び替えてみようとしたのが、できなかったので、こうなりました。もっと上手な書き方があれば、こんな方法はどうだい?ってコメントいただけると、ありがたいです。
よろしくお願いいたします。
- 投稿日:2020-06-30T16:56:31+09:00
【Laravel】任意のExceptionをthrowする。
- 投稿日:2020-06-30T12:20:09+09:00
LaravelでCSVする方法
はじめに
FuelPHPには
Format:forge($array)->toCsv()
という感じで配列からかんたんにCSV用の文字列を生み出すことができたのですが、Laravelにはありません。
http://fuelphp.jp/docs/1.9/classes/format.htmlそれくらい
foreach
でぐるぐる回せやってのはあるのでしょうけど、めんどくさいじゃないですか。
そこでいい方法がないかなって探してみました。
soapbox/laravel-formatter
を入れるまあ、だいたいこういうものはpackagist漁ればいいのがありそうなもんなんですよ。
Laravel用じゃなくてもいいのですが、Laravel用だと5.5以降ならServiceProviderにいちいち登録しなくても使えるのがいいですよね!というわけで今回は
soapbox/laravel-formatter
というのを使ってみます。
https://packagist.org/packages/soapbox/laravel-formatterこいつを
composer require
しましょう。$ composer require soapbox/laravel-formatter
これでLaravel内で
Formatter
として使えるようになっているので、あとは以下の感じで行けるはずです。
基本的には連想配列のキーがそのままラベル行として使われるので、DBから引っ張ってくるときにカラム名をas使って日本語とかにすると使いやすいと思います。use SoapBox\Formatter\Formatter; class HogeService { public function makeCsvData() { $list = $this->hogeRepository->listForDownload(); $formatter = Formatter::make($list->toArray(), Formatter::ARR); return $formatter->toCsv(); } }
- 投稿日:2020-06-30T10:23:32+09:00
Laravel ログの格納場所を変更する
目的
- Laravelのログの内容によって出力先を変更する第一歩として、デフォルトのログ出力状態で格納場所を任意の場所に変更する方法をまとめる
実施環境
- ハードウェア環境
項目 情報 OS macOS Catalina(10.15.5) ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) プロセッサ 2 GHz クアッドコアIntel Core i5 メモリ 32 GB 3733 MHz LPDDR4 グラフィックス Intel Iris Plus Graphics 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.3 Homwbrewを用いて導入 Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする 前提条件
- 下記、または下記に準ずる方法でMacのネイティブ環境(DockerやAWSを使わない)にLaravelの環境構築が完了していること。
読後感
- Laravelのログファイルの場所を
アプリ名ディレクトリ/storage/logs/
直下からアプリ名ディレクトリ/storage/my_logs
に変更できる。詳細
.envファイルの編集
アプリ名ディレクトリで下記コマンドを実行して.envファイルを開く。
$ vi .envLOG_CHANNELの記載を下記の様に修正する。
アプリ名ディレクトリ/.envLOG_CHANNEL=single修正後の.envファイルの例を下記に記載する。
アプリ名ディレクトリ/.envAPP_NAME=Laravel APP_ENV=local APP_KEY=base64:1IFTNHZwVQ6AwDC+U7XfApuoSf5s02NK49auu7okT/E= APP_DEBUG=true APP_URL=http://localhost LOG_CHANNEL=single DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel_auth_login DB_USERNAME=root DB_PASSWORD=**** BROADCAST_DRIVER=log CACHE_DRIVER=file QUEUE_CONNECTION=sync SESSION_DRIVER=file SESSION_LIFETIME=120 REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 MAIL_MAILER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"ログの設定
アプリ名ディレクトリで下記コマンドを実行してログの設定ファイルを開く
$ vi config/logging.php開いたファイルを下記の様に修正する。
アプリ名ディレクトリ/config/logging.php<?php use Monolog\Handler\NullHandler; use Monolog\Handler\StreamHandler; use Monolog\Handler\SyslogUdpHandler; return [ /* |-------------------------------------------------------------------------- | Default Log Channel |-------------------------------------------------------------------------- | | This option defines the default log channel that gets used when writing | messages to the logs. The name specified in this option should match | one of the channels defined in the "channels" configuration array. | */ 'default' => env('LOG_CHANNEL', 'stack'), /* |-------------------------------------------------------------------------- | Log Channels |-------------------------------------------------------------------------- | | Here you may configure the log channels for your application. Out of | the box, Laravel uses the Monolog PHP logging library. This gives | you a variety of powerful log handlers / formatters to utilize. | | Available Drivers: "single", "daily", "slack", "syslog", | "errorlog", "monolog", | "custom", "stack" | */ 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('my_logs/laravel.log'), 'level' => 'debug', ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', 'days' => 14, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => 'critical', ], 'papertrail' => [ 'driver' => 'monolog', 'level' => 'debug', 'handler' => SyslogUdpHandler::class, 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), ], ], 'stderr' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', ], ], 'syslog' => [ 'driver' => 'syslog', 'level' => 'debug', ], 'errorlog' => [ 'driver' => 'errorlog', 'level' => 'debug', ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], ], ];メモ