Laravel に関する投稿を表示しています

authorization と authentication の話。そして Laravel の Gate の紹介をちらっと。

要するに、認証と認可は別物だよって話。

 

ざっくりとまとめると。

認証 Authentication
あなたは誰ですか
私は X というユーザです、パスワードはこれです
確認しました X で間違いないです

 

認可 Authorization
X です、管理画面を開きたいです
あなたは管理画面への"鍵"をもっていません、拒否します

 

もうちょい例。
世帯主じゃなくても、家の鍵を持っていれば家に入れる。=認可
世帯主かどうかは公的文書(住民票とか)で確認する。=認証

 

で、じゃあ Web ではどうやんねん、というと、
(例によってよく使うものだけ紹介)

CakePHP ならこのあたりにまとまってる。

認証 - 3.6

 

Laravel だと分かれてる。使う仕組みが違うので別項目になってるって。

Authentication - Laravel - The PHP Framework For Web Artisans
Authorization - Laravel - The PHP Framework For Web Artisans


Laravel の Gate の説明をちゃんと見てみたの初めてだけど Gate かっちょいいな、権限管理とか、できるできないみたいな話がわりかし簡単にかけそう。

できるできないはこういう風に AuthServiceProvider->boot に書いたり。

Gate::define('update-post' function ($user $post) {
    return $user->id == $post->user_id;
});

こういう形で クラス名 @ メソッド名 として外だししたり。

Gate::define('update-post' 'PostPolicy@update');
class PostPolicy
{
    public function update(User $user Post $post)
    {
        return $user->id == $post->user_id;
    }
}

でもって、定義した Gate は、コントローラーだったりモデルだったりで、こうやって使えるらしい。

if (Gate::forUser($user)->allows('update-post' $post)) {
    // The user can update the post...
}

あとは、ポリシーを定義することができて(↑にある PostPolicy@update という書き方で使えるものとは異なる)
Eloquant に対して view/create/update/delete がそれぞれ権限ありますか?というのをお手軽に内部でつないでくれるらしい。

たぶん実際のユースケース的にはいろいろ分割しやすいこの書き方がいいんじゃなかろうか。

AuthServiceProvider はこういう感じで

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Post::class => PostPolicy::class,
    ];
...

ポリシークラスのほうはこんな感じ。

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    public function update(User $user Post $post)
    {
        return $user->id === $post->user_id;
    }
}

さっきの update とまったく同じなんだけど、こうすることで AuthServiceProvider が勝手に Post を監視してくれて PostPolicy に書かれている通り update 時のチェックを実行して true なら update ができる、というようなことをやってくれるんだそうだ。

便利~~~~。

 

例えば wordpress 的なものをイメージして、簡単にやってみると。

  • 投稿
    • 管理者でログインしていれば投稿の作成、編集、閲覧、公開設定にすることが可能
    • 編集者でログインしていれば投稿の作成、編集、閲覧、が可能。公開設定にはできない
    • ログインしていなければ投稿の閲覧が可能
  • コメント
    • 管理者でログインしていればコメントの作成、削除、公開されたコメントの閲覧、管理者向けコメントの閲覧が可能
    • それ以外は作成と、公開されたコメントの閲覧のみ可能

みたいなのも Gate をつかったらぱっぱっぱ~~~ってかけそうだって思った。
投稿ポリシーとコメントポリシーを、それぞれ書いてある通りに定義すればいいだけだし、仕様を形にするのが簡単そうだ。

んん~~~、使いどころがあれば使いたいなあ。

Carbon を使って 開始日と終了日を指定した // 単位の日付データ列を生成する

日単位のデータがわっとあって、週とか月とかの単位で集計してーなー!みたいな、そういう事をやりたいときってあると思うんですよ。
そのための日付の範囲を一覧にしてくれるやつ、というイメージで。作ったらあとは SQL とかそれに準ずる何かにポイポイしたらいいと思う。

Laravel の気持ちだったので Carbon だけど Chronos でも同じようにいけると思う。 startOfMonth とかで、自身の日付変わるところは結構驚いた、さすが Carbon やりますねぇ!(

<?php
require_once('vendor/autoload.php');

use Carbon\Carbon;

// 週の最初を日曜日、最後を土曜日に設定
Carbon::setWeekStartsAt(Carbon::SUNDAY); 
Carbon::setWeekEndsAt(Carbon::SATURDAY);

// $type = 1, 2, 3 // day, week, month
function dateRangeIterator($startDate, $endDate, $type=2) {
	$startDate = Carbon::parse($startDate)->startOfDay();
	$endDate = Carbon::parse($endDate)->endOfDay();

	while ($endDate > $startDate) {
		// "この" 始まりと終わりを取得する
		if ($type === 1) {
			$thisStartDay = $startDate->copy();
			$thisEndDay = $thisStartDay->copy()->endOfDay();
		} elseif ($type === 2) {
			$thisStartDay = $startDate->copy();
			$thisEndDay = $thisStartDay->copy()->endOfWeek();
		} elseif ($type === 3) {
			$thisStartDay = $startDate->copy();
			$thisEndDay = $thisStartDay->copy()->endOfMonth();
		}

		// 終わりが指定値を超えないように調整
		if ($thisEndDay > $endDate) {
			$thisEndDay = $endDate;
		}

		// ジェネレーター生成
		yield [$thisStartDay, $thisEndDay];

		// 次の日/週/月にいく
		if ($type === 1) {
			$startDate->startOfDay();
			$startDate->addDays(1);
		} elseif ($type === 2) {
			$startDate->startOfWeek();
			$startDate->addWeeks(1);
		} elseif ($type === 3) {
			$startDate->startOfMonth();
			$startDate->addMonths(1);
		}
	}
}


// 月単位 2/2 ~ 3/2 まで
foreach(dateRangeIterator('2018-02-02', '2018-03-02', 3) as $x) {
	echo "$x[0] - $x[1]\n";
}

echo "------\n";

// 週単位 2/3 ~ 2/18 まで
foreach(dateRangeIterator('2018-02-03', '2018-02-18') as $x) {
	echo "$x[0] - $x[1]\n";
}

echo "------\n";

// 月単位 2/2 ~ 10/12 まで
foreach(dateRangeIterator('2018-02-02', '2018-10-12', 3) as $x) {
	echo "$x[0] - $x[1]\n";
}

/**
$ php hoge.php
2018-02-02 00:00:00 - 2018-02-28 23:59:59
2018-03-01 00:00:00 - 2018-03-02 23:59:59
------
2018-02-03 00:00:00 - 2018-02-03 23:59:59
2018-02-04 00:00:00 - 2018-02-10 23:59:59
2018-02-11 00:00:00 - 2018-02-17 23:59:59
2018-02-18 00:00:00 - 2018-02-18 23:59:59
------
2018-02-02 00:00:00 - 2018-02-28 23:59:59
2018-03-01 00:00:00 - 2018-03-31 23:59:59
2018-04-01 00:00:00 - 2018-04-30 23:59:59
2018-05-01 00:00:00 - 2018-05-31 23:59:59
2018-06-01 00:00:00 - 2018-06-30 23:59:59
2018-07-01 00:00:00 - 2018-07-31 23:59:59
2018-08-01 00:00:00 - 2018-08-31 23:59:59
2018-09-01 00:00:00 - 2018-09-30 23:59:59
2018-10-01 00:00:00 - 2018-10-12 23:59:59
*/

時間とか分とか、単位をもっと細かくしたいんだよね~~ってなっても、同様にして細かい単位のものを実装したらいいと思う。

いじよ。

Laravel でマイグレーションなどの artisan コマンドを実行するときのエラーを詳し表示した

artisan コマンドでエラーが出た場合、そのまま実行していると例外クラスとメッセージが出る程度で、どこでエラーになっとんねん!がわかりません。

実は自分で作った artisan コマンド含めてすべての artisan コマンドは verbose なオプションをサポートしています。そのためオプションを指定するだけで、例外を詳しく見ることができます。

$ php artisan migrate

  [Illuminate\Database\QueryException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists (SQL: alter table `my_tables` drop `created_at`, drop `updated_at`)

  [Doctrine\DBAL\Driver\PDOException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists
  
  [PDOException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists 


$ php artisan migrate -v
                                                                                       
  [Illuminate\Database\QueryException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists (SQL: alter table `my_tables` drop `created_at`, drop `updated_at`)

Exception trace:
 () at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:647
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35


  [Doctrine\DBAL\Driver\PDOException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists

Exception trace:
 () at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:107
 Doctrine\DBAL\Driver\PDOStatement->execute() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:449
 Illuminate\Database\Connection->Illuminate\Database\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:640
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35


  [PDOException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists

Exception trace:
 () at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:105
 PDOStatement->execute() at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:105
 Doctrine\DBAL\Driver\PDOStatement->execute() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:449
 Illuminate\Database\Connection->Illuminate\Database\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:640
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35

ちなみにサポートするオプションと、表示レベルは以下のようになっています。


// /vendor/laravel/framework/src/Illuminate/Console/Command.php
// ※抜粋

use Symfony\Component\Console\Output\OutputInterface;

protected $verbosity = OutputInterface::VERBOSITY_NORMAL;

protected $verbosityMap = [
    'v' => OutputInterface::VERBOSITY_VERBOSE,
    'vv' => OutputInterface::VERBOSITY_VERY_VERBOSE,
    'vvv' => OutputInterface::VERBOSITY_DEBUG,
    'quiet' => OutputInterface::VERBOSITY_QUIET,
    'normal' => OutputInterface::VERBOSITY_NORMAL,
];
オプション レベル 生数値
v OutputInterface::VERBOSITY_VERBOSE 64
vv OutputInterface::VERBOSITY_VERY_VERBOSE 128
vvv OutputInterface::VERBOSITY_DEBUG 256
quiet OutputInterface::VERBOSITY_QUIET 16
normal OutputInterface::VERBOSITY_NORMAL 32

まあ、これってソースのとこにもあるんですけど、ベースになってる Symfony2 のものなんですけどね。

Verbosity Levels (current)

Symfony2 で定義されている値を使って artisan コマンドも同じように verbose な指定をサポートしているんですねー。もし自分で作るコマンドも verbose な指定をサポートするなら、指定されている定数を目安に出力すると良さそう。

Laravel5 で Log ファサードを使ったときに一緒に標準出力にも出した

artisan コマンドを作ったとき、何かと様子を出力するために Log ファサードを使ってログファイルに出している。

 

ちなみに。そのとき Monolog のハンドラを使って、レベル別にファイルを分けたりなどしている。こんな具合にするとログレベルごとにファイルが分かれる、めっちゃ便利。

// bootstrap/app.php
$app->configureMonologUsing(function ($monolog) {
    $logConfigure = config('log');
    foreach ($logConfigure['file'] as $logLevel => $logFile) {
        $path = storage_path('logs/' . $logFile . '.log');
        $handler = new \Monolog\Handler\RotatingFileHandler(
            $path,
            $logConfigure['rotate'],
            $logLevel,
            false,
            $logConfigure['permission']
        );
        $handler->setFormatter(new Monolog\Formatter\JsonFormatter());
        $monolog->pushHandler($handler);
    }
});


// config/log
return [
    'permission' => 0777,
    'rotate' => 30,
    'file' => [
        \Monolog\Logger::DEBUG     => 'debug',
        \Monolog\Logger::INFO      => 'debug',
        \Monolog\Logger::NOTICE    => 'debug',
        \Monolog\Logger::WARNING   => 'warning',
        \Monolog\Logger::ERROR     => 'error',
        \Monolog\Logger::CRITICAL  => 'error',
        \Monolog\Logger::ALERT     => 'error',
        \Monolog\Logger::EMERGENCY => 'error',
    ],
];

自分で用意する artisan コマンド、ようはバッチでは様子の出力などをファイルに出すために Log::info(); とかして、いろいろな情報をログに出ていく。この ID のデータをやるよー、とか、おわったよー、とか、色々。

しかし開発中はそれって厄介で、パッとコマンド実行してログ見て。いや、横で tail -f とかしておけば良いのだろうけど、それはそれでちょっと面倒だなあ。というか artisan コマンドって verbose なオプションをサポートしているので、それをうまく使ってログに出すと同時に標準出力にも出せないだろうか。

と思って出来たのがコレ。

namespace App\Console;

use Illuminate\Console\Command;
use Monolog\Handler\StreamHandler;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

trait LogWithStdout
{
    /**
     * execute メソッドに割り込み Monolog の設定をする
     */
    public function execute(InputInterface $input, OutputInterface $output)
    {
        // verboseなオプションが付いたらログを画面に出す
        if ($output->getVerbosity() >= $this->verbosityMap['v']) {
            $monolog = \Log::getMonolog();
            $handler = new StreamHandler("php://stdout", 'info');
            $monolog->pushHandler($handler);
        }

        // 元の処理の呼び出し
        return parent::execute($input, $output);
    }
}
namespace App\Console\Commands;

use App\Console\LogWithStdout;
use Illuminate\Console\Command;

class FetchTrafficSummary extends Command
{
    use LogWithStdout;

    public function handle()
    {
        Log::info('start!');

        ...

        Log::info('complete!');
    }
}

本当はサービスプロバイダにしてイベントにしてあげたほうがキレイなのだが、 Laravel の実装上、それは出来ないっぽい…。うぬ。。

具体的にはコマンドの発火時は取れるのだが verbose オプションがあるかどうかが取れない。 OutputInterface がイベントに入ってこないのだ。ソースも追いかけてみたがイベント引数に入ってくるのは Application インスタンスだけ。 OutputInterface を持ったものは Application から Command を呼ぶときにしか作られないらしい。

 
 
とまあ、作ってみたものの Artisan コマンドでも $this->info などして標準出力に出せるので、そちらをオーバーラップしたほうがキレイなんじゃないかなーと思った。こういうイメージ。

public function info($string, $verbosity = null)
{
    parent::info($string, $verbosity);
    \Log::info($string);
}

どちらかといえばこちらのほうが、メソッド別にログファサードのメソッドを呼べるのでパッと見でわかりやすそうだ。