‘Laravel’ タグのアーカイブ

murave

Laravelのテスト用便利トレイトの動作を変えてみる

Laravel 5.5 です。

Laravelにはunit test等の際にuseするとよろしくやってくれるトレイトがあります。
昨日の記事に出てきたRefreshDatabaseやDatabaseMigrations、他にはDatabaseTransactions、WithoutMiddleware、WithoutEventsがあります。

本当に便利なのですが、すこし動作を調整したいこともあります。

例えばDatabaseMigrationsでマスターの流し込みまでやりたいとか。

フレームワーク内を調べるとsetUpTraits()でトレイトがuseされたかを判定し、特定のメソッドを呼ぶようになっていることがわかります。

protected function setUpTraits()
{
$uses = array_flip(class_uses_recursive(static::class));

if (isset($uses[RefreshDatabase::class])) {
$this->refreshDatabase();
}

if (isset($uses[DatabaseMigrations::class])) {
$this->runDatabaseMigrations();
}

if (isset($uses[DatabaseTransactions::class])) {
$this->beginDatabaseTransaction();
}

if (isset($uses[WithoutMiddleware::class])) {
$this->disableMiddlewareForAllTests();
}

if (isset($uses[WithoutEvents::class])) {
$this->disableEventsForAllTests();
}

return $uses;
}
このメソッドをapp/tests/TestCase.phpやDuskTestCase.phpでオーバーライドして自分が作ったトレイトを実行するようにもできます。

例えば私の場合、データベースダンプをリストアするSnapshotLoadというトレイトを作成して追加しています。

protected function setUpTraits()
{
$uses = array_flip(class_uses_recursive(static::class));
if (isset($uses[SnapshotLoad::class])) {
// DatabaseTransactionsの前に実行する必要がある。
$this->runSnapshotLoad();
}
return parent::setUpTraits();
}
閑話休題、前回記事の最後のテストサンプルでのDatabaseMigrationsの動作を変更してみましょう。呼び出されるrunDatabaseMigrationsメソッドを別名に変更し、新たに定義します。

<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use App\User;

class LoginTest extends DuskTestCase
{
use DatabaseMigrations
{
runDatabaseMigrations as runDatabaseMigrationsOrg; //名前変える
}
//DatabaseMigrationsの動作カスタマイズ
public function runDatabaseMigrations()
{
$this->runDatabaseMigrationsOrg(); //元々の処理を実行
\Artisan::call('db:seed', ['--class' => 'MasterSeeder']);
}

public function testLogin()
{
$user = factory(User::class)->create();

$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'secret')
->press('ログイン')
->assertPathIs('/home');
});
}
}
元々のDatabaseMigrationsの処理後にマスター設定用のMasterSeederが実行されます。

murave

Laravel Dusk メモ

Laravel 5.5 & Laradock(https://github.com/laradock/laradock)環境です。
Laradockについては少し古いかもしれません(活発に更新されているのですぐに古くなる)。

Laravel Dusk はブラウザテストをお手軽に!というパッケージです。

https://readouble.com/laravel/5.5/ja/dusk.html

Laradockでのセットアップと使い始めてすぐのログインテストで何回かつまづいたのでメモを残しておきます。

Laradock問題

まず、Laradockのドキュメント通りにセットアップしても正常動作しませんでした(2017/12/13現在)

LaradockでLaravel Duskを動かすメモ(https://qiita.com/uenoryo/items/bdded2dc6f8e5f2075cc)

こちらを参考に動かすことができました。ただし、seleniumコンテナへフォントを入れなければ失敗時などのscreenshotで日本語が表示されないため laradock/selenium/Dockerfile を以下のように編集しました。

FROM selenium/standalone-chrome

MAINTAINER Edmund Luong <edmundvmluong@gmail.com>

RUN sudo apt-get update && sudo apt-get install fonts-ipafont -y

EXPOSE 4444

そしてビルド

$ docker-compose up -d --build selenium
これで豆腐たち(□□□□的な文字化け)とはおさらばです。

Session問題

午前中、Laravelへのブラウザでのログインテストが動かないと悩んでた原因はセッションドライバにarrayを設定してたというオチだったアカウントがこちら。単体テスト用の設定流用してたのが原因(調整漏れ)。
— murave (@murave) 2017年12月13日

.env.dusk.local でDusk用の設定が行えるのですが単体テスト用のものをコピーして修正していたためセッションにarrayドライバを設定してたのでした。

https://readouble.com/laravel/5.5/ja/session.html

array – セッションはPHPの配列として保存されるだけで、リクエスト間で継続しません。

ですよね〜。

他環境への影響を抑えるためにCookieドライバを使用することにしました。

SESSION_DRIVER=cookie
Transaction問題テスト開始時にデータベースクリアしてマイグレーションしてマスター用のSeederでマスター準備してFakerでユーザー作って、みたいにやるじゃないですか。

Laravel 5.5 にはRefreshDatabaseという便利トレイトがあります。

https://readouble.com/laravel/5.5/ja/database-testing.html

前のテストがその後のテストデータに影響しないように、各テストの後にデータベースをリセットできると便利です。インメモリデータベースを使っていても、トラディショナルなデータベースを使用していても、RefreshDatabaseトレイトにより、マイグレーションに最適なアプローチが取れます。テストクラスてこのトレイトを使うだけで、全てが処理されます。

そう、全てが処理されるのです。影響が残らないようにTransaction中に処理してRollbackまでしてくれる。便利。

だが思い出そう。ブラウザテストだということを。

ページ遷移したらデータ消えるわけで、うっかりRefreshDatabaseトレイトを使うと想定通りに動作しません。

そんなわけで、リセットしてマイグレーションまでだけしてくれるDatabaseMigrationsトレイトのほうを使いましょう。

例

<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use App\User;

class LoginTest extends DuskTestCase
{
use DatabaseMigrations; //実行時DB内容は消える

public function testLogin()
{
\Artisan::call('db:seed', ['--class' => 'MasterSeeder']);
$user = factory(User::class)->create();

$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'secret')
->press('ログイン')
->assertPathIs('/home');
});
}
}
この例ではDatabaseMigrationsで再構築されたまっさらな状態のデータベースにマスター系投入用のMasterSeederを実行した後にテスト用のユーザーを作成してログイン処理のテストを行っています。

murave

Laravel 5.5 でデータベースdumpをお手軽にとったりもどしたり

muraveです。LTSを使いたい人なのでLaravelのバージョンが5.1から5.5にジャンプアップしました。
その際、いままで使っていたデータベースバックアップのプラグインが使えなくなりました。

データベースdumpをお手軽にとったりもどしたりしたかっただけだったんだけど、重厚なバックアップ用パッケージが見つかった。これはこれで入れとくかぁ。
— murave (@murave) 2017年11月24日

ということで

https://github.com/spatie/laravel-backup

を導入。良い感じです。もしかして?と、この方のリポジトリを探してみたらありました。

https://github.com/spatie/laravel-db-snapshots

This package provides Artisan commands to quickly dump and load databases in a Laravel application.

そうそう、この記事の対象データベースはPostgreSQLです。

PostgreSQLの場合にレストア時にエラーでデータベースを飛ばしたので、その対処方法のメモだったりします。MySQL、SQLiteにも対応らしいですが試してません。導入や使い方はドキュメント見てくださいね。

ドキュメント通りにスナップショットをロード(restore)しようとしたらエラーでコケたのでした。

# artisan snapshot:load my-first-dump

[Illuminate\Database\QueryException]
SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "\"
LINE 464: \.
^ (SQL: --

### 以下略

で、コケるとデータベース飛びます(掃除がリストアの前に別途行われるようです)。

バックアップ・リストアのテストしててバックアップオプションを変えてからデータのバックアップが取れないと思ってたら、リストアのテストの失敗でデータが消滅してたせいだった。
— murave (@murave) 2017年11月27日

こんあとき、ボクのように上書きで再度スナップショットを取ると空のデータベースのdumpで上書きという悲劇が起こるので気をつけましょう。

dumpファイルの内容を調べるとCOPY from stdinで.から読むようになってるところでコケてます。

「ははーん。pg_dump呼び出しに–insertsつければいいんだな」

pg_dumpに–insertsオプションをつけるとデータをCOPYではなくINSERTコマンドでdumpします。

「どうやってつけたらいいんだ?」

コードを追ったらdatabase.phpでdumpのオプション付けられるようになってました。
insertsオプションにも対応してます。サンプル載せときます。

'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
//PDO絡みで環境によって型がばらつく可能性があるので割り切って全て文字列で取得。
'options' => [
PDO::ATTR_STRINGIFY_FETCHES => true,
],
'dump' => [
'useInserts',
],
],
‘dump’ => [‘useInserts’] の所です。無事snapshot:loadできるようになりました。

# artisan snapshot:create for-test-dump
# artisan --env=testing snapshot:load for-test-dump
これはデフォルトの環境でスナップショットを取ってtesting環境でロード(restore)してます。

PDO::ATTR_STRINGIFY_FETCHESについての余談

前記database.phpのサンプルのコメントにありますが、開発環境ではintで取れてたのに本番のサーバーにデプロイしたらstringで返ってくるなどデータ取得時に動作環境によって型がばらつくことがあります。

厳密比較の動作が異なって判定が変わったり、unit testがコケたりと困ったことになるので全て文字列で取得するオプションを設定するようにしています。

Model経由で値を扱う場合は

/**
* キャスト設定(文字列以外は要設定)
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'updated_by' => 'integer',
'options' => 'array', //jsonb
];
のように$castsに書いておけばLaravelさんがキャストしてくれます。

okada

dockerからAmazon EC2への奮闘記

今までdockerを使ったことがなかった私、勉強がてらLaradockを使ってLaravelで開発を行っていました
そこからAmazon EC2へ移行するまでの過程で色々行き詰まり、
なんとかLaradockからEC2へ環境移行することができましたので、
今後のメモのためにログを残しておこうと思います

今回書く内容はLaradockの環境設定〜Amazon EC2へ設置するまでの簡単な流れで行き詰ったものを晒すだけのほぼ日記の様なものです(笑)

Docker for Mac 〜 Laradockを導入するまで

$ git clone --depth=1 -b v5.5.1 https://github.com/LaraDock/laradock.git
$ cd laradock
$ cp env-example .env
$ vi .env
…とこのようにまずはドキュメントに従って導入を行っていました
しかし

問題1:Laradockを導入したが起動しない

$ docker-compose up -d nginx mysql
ERROR: Couldn't connect to Docker daemon - you might need to run `docker-machine start default`.
$ docker-machine start default
Host does not exist: "default"
defaultを設定しないといけない?

$ docker-machine create --driver virtualbox default
をすればよい、とのことだったので実行してみたけれどエラー発生
そもそもDocker for Macはvirtualbox使わなくても良くなった、との記述を見かけた気が…

解決:boot2dockerとDocker for macが混在していた

Laradockの問題ではなく、dockerの問題でした
どうやらDocker for Macを入れる前にboot2dockerをhomebrewで入れていたようで、
PC内にdockerが2つある状態でした
ということでboot2dokerを削除

brew uninstall docker boot2docker docker-compose docker-machine
念のためDocker for Macもアンインストールして入れ直し、もう一度動かしてみた所無事にLaradockを動かすことができました!
まぁこんなことしでかすなんて自分くらいしかいないと思うので参考にはならないかもしれませんが、思い出の一つとして…

問題2:MySQLが動かない

次のようなエラーが発生してMySQLが起動しない
docker-compose psを実行してみた所、laradock_mysql_1のstateはExitでした

$ docker-compose up --build -d nginx mysql
$ docker-compose exec mysql bash
ERROR: No container found for mysql_1
ログを確認

$ docker logs laradock_mysql_1
2017-08-01T08:01:50.339823Z 0 [ERROR] [FATAL] InnoDB: Table flags are 0 in the data dictionary but the flags in file ./ibdata1 are 0x4800!
解決:MySQLのデータファイルを削除このエラーに関しては.envファイルを確認し、MySQLのデータファイルを削除した所、起動に成功しました

$ vi .env ⇐ laradock/.envを確認
10 ### Data Path:
11 # For all storage systems.
12
13 DATA_SAVE_PATH=~/.laradock/data
$ cd ~/.laradock/data
$ mv data data.old
Laradockが動かない…動かない…と入れ直しを繰り返した時にゴミファイルとして残ってしまっていた模様

ローカルからAmazon EC2へ

システムが出来上がり、次はシステムをローカルからEC2へ移行作業へ…
まずLaradock環境内のバージョンを確認
EC2側にもPHP7.1、MySQL5.7、nginxをインストール

$ docker-compose exec workspace bash
# php -v
PHP 7.1.4-1+deb.sury.org~xenial+1 (cli) (built: Apr 11 2017 22:12:32) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.1.4-1+deb.sury.org~xenial+1, Copyright (c) 1999-2017, by Zend Technologies
# php artisan --version
Laravel Framework 5.4.30
# exit
exit

$ docker-compose exec mysql bash
# mysql --version
mysql Ver 14.14 Distrib 5.7.19, for Linux (x86_64) using EditLine wrapper
# exit
exit
EC2側環境作成PHP、MySQL、nginxインストール後…
以下のファイルを編集

$ vi /etc/php-fpm.d/www.conf
24 user = nginx
25 ; RPM: Keep a group allowed to write in log dir.
26 group = nginx

48 listen.owner = nginx
49 listen.group = nginx
50 listen.mode = 0660
$ vi /etc/nginx/nginx.conf
40 server {

44 root /var/www/html/public;

73 location ~ \.php$ {
74 root /var/www/html/public;
75 fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
76 fastcgi_index index.php;
77 fastcgi_param SCRIPT_FILENAME /var/www/html/public$fastcgi_script_name;
78 include fastcgi_params;
79 }
それぞれのサービスを起動 & 自動起動するように設定

$ sudo service nginx start ⇐ サービス起動
$ sudo service php-fpm start
$ sudo service mysqld start
$ sudo chkconfig nginx on ⇐ 自動起動
$ sudo chkconfig php-fpm on
$ sudo chkconfig mysqld on
MySQL5.7ではrootユーザには初期パスワードなるものが存在し、
最初はそのパスワードでログインする

[ec2-user@ip-172-31-46-167 ~]$ sudo cat /var/log/mysqld.log
2017-08-03T06:59:34.488345Z 1 [Note] A temporary password is generated for root@localhost: Jt?eJUpbO6kl ⇐コレ
rootのパスワードは変更しておくべし

$ mysql -u root -p
> ALTER USER 'root'@'localhost' IDENTIFIED BY 'パスワード';
Laravelのドキュメントに従いComposerをインストール後、Laravel5.4を導入

$ composer global require "laravel/installer"
$ cd /var/www/
$ laravel new html
ブラウザでLaravelのTOP画面が表示されることを確認

Laradockからお引っ越し

ローカルのLaravelより、gitを使ってアーカイブファイル作成!
Laradockには既に.gitignoreファイルが準備されており、logファイル等など必要ないファイルは省かれる様になっていました。便利!

git archive HEAD --output=~/WorkSpace/project/release/archive.zip
このファイルを/var/www/htmlに上書き(.envの取扱い注意!)し、
ファイルの権限を変更 chmod o+w -R storage
そして以下のコードを実行

$ php artisan config:cache
$ php artisan route:cache
.env.exampleを.envに書き換え、EC2上の環境に合わせた設定に書き換えます
そしてブラウザで確認!

The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.

こんなエラーが表示されましたよ…
以下を実行

$ php artisan key:generate
$ php artisan config:clear
.envファイルの APP_KEY= にキーがセットされるわけですね
無事TOPが表示されました!

でもページが遷移しませんよ?

/loginへ遷移できません。404エラーです。
原因はnginxの設定。5.4のドキュメント通りにnginx.confを編集。

$ vi /etc/nginx/nginx.conf
49 location / {
50 try_files $uri /index.php?$query_string;
51 }
これで他のページへも遷移できるようになりました!
以上がEC2への引っ越し奮闘記になります。

murave

Laravel 5.1 でログ出力、faultline-php追加編

Laravel の次のLTS、5.5もそろそろ来そうな昨今ですが、また5.1のログ出力の話です。

昨日のBlogに書いたようにプロジェクトのログ出力に faultline-php (https://github.com/faultline/faultline-php) を組み込みました。Monolog integration のありがたさよ。

今日は faultline 関係の .env 対応を進めているのですが、コードがややこしくなる前の素朴なのをサンプルとして出しておこうかな〜と。

組み込み前の状態についてはこちらをご参照ください。

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        //登録されたloggerのhandlerを置き換える
        $monolog = \Log::getMonolog();
        $monolog->popHandler(); //既存のhandlerを削除
        $monolog->pushHandler(
                    (new \Monolog\Handler\RotatingFileHandler(
                        $this->app->storagePath() . '/logs/' 
                            . $this->app->environment() . '_'
                            . (defined('ARTISAN_BINARY') ? ARTISAN_BINARY . '_' : '')
                            . posix_getpwuid(posix_geteuid())['name'] 
                            . '.log',
                        \Config::get('app.log_max_files', 5),
                        (\Config::get('app.debug') || $this->app->environment('testing')) 
                            ? \Monolog\Logger::DEBUG 
                            : \Monolog\Logger::INFO //出力レベル制御のため追加
                    ))
                    ->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true))
                );
        //Faultline\MonologHandlerを追加。Notifierはregister()で行っているバインドから取得。
        $monolog->pushHandler(new \Faultline\MonologHandler(App('faultline.notifier')));
        $monolog->pushProcessor(
                    new \Monolog\Processor\IntrospectionProcessor(
                        \Monolog\Logger::NOTICE,    //DEBUG,INFOは発生場所の情報を付加しない
                        [
                            'Illuminate\\',
                            //他、呼び出し元としてはスキップしたい対象を設定,
                        ]
                    )
                );

        //.envのAPP_QUERY_LOGGINGがtrueの場合クエリを独自ログquery_{実行アカウント}.logに出力する。
        if(env('APP_QUERY_LOGGING', false)) {
            \DB::listen(function($query, $bindings, $time){
                $queryLogger = (new \Monolog\Logger(
                    $this->app->environment()
                ))->pushHandler(
                    new \Monolog\Handler\StreamHandler (
                        $this->app->storagePath() . '/logs/query_' . posix_getpwuid(posix_geteuid())['name'] . '.log')
                );

                $queryLogger->info('QUERY:[' . $query . '] BINDINGS:'
                        . json_encode($bindings), ['TIME' => $time]);
            });
        }
    }

    public function register()
    {
        //faultline.notifier バインド
        $this->app->bind('faultline.notifier', function($app) {
            return new \Faultline\Notifier([
                'project' => "project_name_{$app->env}",
                'apiKey' => 'XXxxXXXXxxxXXXxxxxXXXxxxXXX',
                'endpoint' => 'https://xXXXXxxxX.execute-api.ap-northeast-1.amazonaws.com/v0',
                'notifications' => [
                    [
                        'type'=> 'slack',
                        'endpoint'=> 'https://hooks.slack.com/services/XXxxxxXXXxxxXXX/XXxxXXxXXXxxxxXXXxxxXXX',
                        'channel'=> '#faultline',
                        'username'=> 'faultline-notify',
                        'notifyInterval'=> 5,
                        'threshold'=> 10,
                        'timezone'=> 'Asia/Tokyo'
                    ],
                ]
            ]);
        });
    }
}
murave

Laravel 5.1 でログ出力、それから (ログメールドライバー動作不良対処、クエリ出力の調整)

おひさしぶりです。運用していたら結構変わったので、またこのネタです。

一応以前の記事へのリンクおいときます。

『Laravel 5.1 で実行ユーザー毎にログ出力』

『Laravel 5.1 でログ出力、その後 (出力レベル設定、クエリ出力)』

が、以前の記事の方法だとログメールドライバーが動作しないという問題が発生したので参考にしちゃだめ。

というわけで、以下の要件と不具合対処のために改修した内容を紹介します。

  • ログファイルの権限絡みのエラー回避、何処から起動された処理かわかりやすくする等の目的で実行ユーザー名ベースでログファイルを作成。
  • 動作環境(local, testing, production等のenvironment)でもログファイルを分ける。
  • バッチ処理やartisanのコマンド実行の場合はARTISAN_BINARYをログファイル名に挿入してWebアクセスとはログファイルを分ける。
  • デバッグフラグ(app.debug)を落とした状態ではINFO以上のログのみ出力。
  • .envでAPP_QUERY_LOGGINGにtrueを設定したら発行されたクエリをinfoログで専用ログファイルに出力(デバッグフラグでのコントロールを廃止し個別にコントロール可能とし、ファイルも分けた)
  • 以前の実装ではログメールドライバー(メール送信内容をログ出力するドライバ)が正常動作しなかったためLoggerインスタンスを差し替える方法から既存Loggerインスタンスのhandlerを差し替える方法に実装を変更。

class AppServiceProvider extends ServiceProvider
{
public function boot()
{
//登録されたloggerのhandlerを置き換える
$monolog = \Log::getMonolog();
$monolog->popHandler(); //既存のhandlerを削除
$monolog->pushHandler(
(new \Monolog\Handler\RotatingFileHandler(
$this->app->storagePath() . '/logs/'
. $this->app->environment() . '_'
. (defined('ARTISAN_BINARY') ? ARTISAN_BINARY . '_' : '')
. posix_getpwuid(posix_geteuid())['name']
. '.log',
\Config::get('app.log_max_files', 5),
(\Config::get('app.debug') || $this->app->environment('testing'))
? \Monolog\Logger::DEBUG
: \Monolog\Logger::INFO //出力レベル制御のため追加
))
->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true))
);
$monolog->pushProcessor(
new \Monolog\Processor\IntrospectionProcessor(
\Monolog\Logger::NOTICE, //DEBUG,INFOは発生場所の情報を付加しない
[
'Illuminate\\',
//他、呼び出し元としてはスキップしたい対象を設定,
]
)
);

//.envのAPP_QUERY_LOGGINGがtrueの場合クエリを独自ログquery_{実行アカウント}.logに出力する。
if(env('APP_QUERY_LOGGING', false)) {
\DB::listen(function($query, $bindings, $time){
$queryLogger = (new \Monolog\Logger(
$this->app->environment()
))->pushHandler(
new \Monolog\Handler\StreamHandler (
$this->app->storagePath() . '/logs/query_' . posix_getpwuid(posix_geteuid())['name'] . '.log')
);

$queryLogger->info('QUERY:[' . $query . '] BINDINGS:'
. json_encode($bindings), ['TIME' => $time]);
});
}
// 以下略
クエリ出力については他のログに混ざると見にくくなる、デバッグフラグに連動させていると本番環境での調査で使えない、クエリ出力のログファイルは日付でローテーションさせるより同じファイル名のログファイル一本に出力されるほうが扱いやすい(消して良いものだし)、ということで今の形におさまりました。

ログファイル名に関して、例えばnginxユーザーがphpunitを回したときなど testing_artisan_nginx-2017-01-31.log みたいになるので「なげぇな〜」とは思いますが、場合によってログファイルが別れているのは便利ですよ。

murave

Laravel 5.1 でログ出力、その後 (出力レベル設定、クエリ出力)

2017/2/1
不具合があったので『Laravel 5.1 でログ出力、それから (ログメールドライバー動作不良対処、クエリ出力の調整)』のほうを見て下さい

少し前に書いた『Laravel 5.1 で実行ユーザー毎にログ出力』という記事の続き的なものです。
環境も変わらず Laravel Framework version 5.1.35 (LTS) です。

前回のあらすじ
「ログ出力時に実行ユーザー名ベースでログファイルを作るようにすればログファイルの権限絡みのエラーを回避できるし何処から起動された処理かわかりやすくなって良くない?」と思ったボク。AppServiceProviderのboot()でloggerを差し替えつつも「ここでやっちゃっていいのかしら」という戸惑いを感じたりもして…


で、戸惑ってましたが実行準備の一貫としてAppServiceProviderでやってて問題なさそうかなと思ってます。ツッコミもこなかったし。

今回は実際に開発で使っていて

・デバッグフラグ(app.debug)を落とした状態ではINFO以上のログのみ出力
・デバッグフラグ(app.debug)を立てた状態では発行されたクエリをDEBUGログで出力

という機能追加をしたので、その紹介です。

class AppServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->instance('log', new \Illuminate\Log\Writer(
(new \Monolog\Logger(
$this->app->environment()
))->pushProcessor(
new \Monolog\Processor\IntrospectionProcessor(
\Monolog\Logger::DEBUG,
[
'Monolog\\',
'Illuminate\\',
//他、呼び出し元としてはスキップしたい対象を設定,
]
)
)->pushHandler(
new \Monolog\Handler\RotatingFileHandler(
$this->app->storagePath() . '/logs/' . posix_getpwuid(posix_geteuid())['name'] . '.log',
\Config::get('app.log_max_files', 5),
\Config::get('app.debug') ? \Monolog\Logger::DEBUG : \Monolog\Logger::INFO //出力レベル制御のため追加
)
)
)
);

//app.debugがtrueの場合クエリをログに出力する。
if(\Config::get('app.debug')) {
\DB::listen(function($query, $bindings, $time){
\Log::debug('QUERY:[' . $query . '] BINDINGS:'
. json_encode($bindings), ['TIME' => $time]);
});
}
}
// 以下略

出力レベルはロギングハンドラ側に設定があったんですね。前回は未指定だったので全レベルが出力される状態でした。
app.debugでコントロールすることにして3項演算子でapp.debugが有効な場合はDEBUG、無効な場合はINFOを設定することにしました。

もう一つのクエリ出力についてはloggerとは別の話ですが、設定した場所も近いですしついでに紹介。
実行されたクエリを見ないと不安になるので確認方法は必須ですが、デバッグ状態で常に出力されるのは過剰な感じもしています。見えないよりはいいかな。

murave

Laravel 5.1 で実行ユーザー毎にログ出力

2017/2/1
不具合があったので『Laravel 5.1 でログ出力、それから (ログメールドライバー動作不良対処、クエリ出力の調整)』のほうを見て下さい

Laravel Framework version 5.1.35 (LTS) についての記事です。

muraveです。あんまりにもあんまりな記事(「PHP カンファレンス福岡 2016 で思ったこと」)を上げて反省しましたのでPHPお役立ち記事を書きたいと思います。

CLIコマンドを作ることができるフレームワーク、CakePHPなどもですが、ログ出力時にWebとCLIで実行したユーザーが異なり、権限がらみのエラーが発生したりしがちです。

そもそもログを調べる時、WebとCLIは別ファイルになってた方が良くないです?

ということでログファイルを分ける方法を考えていたのですが、実行ユーザー名でログファイルを作ればいいと気がつきました。まぁ、nginxやapacheユーザーでCLIを扱うと混ざっちゃいますが。

よ~し、おっちゃん、実行ユーザー名からファイル名を決めるインスタンスを作ってloggerを差し替えちゃぞ~

ついでにextraに呼び出し元情報を追加し(参考:「Laravelのログにファイル名やメソッド名を出力する」)、日毎のファイル作成(RotatingFileHandlerベース)にしましょう。

class AppServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->instance('log', new \Illuminate\Log\Writer(
(new \Monolog\Logger(
$this->app->environment()
))->pushProcessor(
new \Monolog\Processor\IntrospectionProcessor(
\Monolog\Logger::DEBUG,
[
'Monolog\\',
'Illuminate\\',
//他、呼び出し元としてはスキップしたい対象を設定,
]
)
)->pushHandler(
new \Monolog\Handler\RotatingFileHandler(
$this->app->storagePath() . '/logs/' . posix_getpwuid(posix_geteuid())['name'] . '.log',
\Config::get('app.log_max_files', 5)
)
)
)
);
}
// 以下略

bootstrapのapp.php内で設定しようとしたらenv関係でエラーが出たので上記のようにAppServiceProviderのboot()で差し替えていますが、もっと適切な場所があるのでは?というのが気になるところ。

もっとスマートな方法があったら助言いただけるとうれしいなぁ。


2016/07/20 『Laravel 5.1 でログ出力、その後 (出力レベル設定、クエリ出力)』というその後の機能拡張の話を書きました。

murave

『Laravel リファレンス』発売、電子とキャンプ、そして紙

muraveです。最近はCakePHP(2系)を扱っていてLaravelはすっかりご無沙汰です。

2016/1/8 に株式会社インプレスより発売された『Laravel リファレンス[Ver.5.1 LTS 対応] Web職人好みの新世代PHPフレームワーク』(以降『Laravelリファレンス』)の見本誌を頂きました。ありがとうございます!

電子書籍のはなし

『Laravelリファレンス』、電子書籍版も同日に発売されました。すばらしい!

見本誌をいただけるというお話はあったのですが辛抱たまらず「田舎で最速で読むには電子書籍だべやばってん荒川」(嘘方言)とKindle版の予約をクリッククリックしておりました。

おかげで発売日の0時から読み始めることができましたが残念な点もかなり。

電子書籍版なら検索できたりして便利にちがいないという目論見があったのですが、Kindle版はあまり便利ではありません。

LaravelリファレンスのKindle版、検索できないしハイライトもできない。メモもたぶんできない。あかん。
— murave (@murave) January 7, 2016

まぁ、販売ページにちゃんと

※この商品は固定レイアウトで作成されており、タブレットなど大きいディスプレイを備えた端末で読むことに適しています。また、文字列のハイライトや検索、辞書の参照、引用などの機能が使用できません。
購入前にお使いの端末で無料サンプルをお試しください。

と書いてありまして確認しなかった自分がわるいんですが、リファレンスという名前から辞書的な本(CakePHP辞典みたいな)を想像していたので「検索できると超便利そう」という期待がたかまっておりガッカリ度は高かったです。

一通り読み終わった今は目次からたどれればそんなに困らないかなぁとは思ってます(辞書的なものではなく丁寧な解説書でしたので)。

Laravelリファレンス、一通り目を通した(後半とばしとばしだけど)。これはコードを書くときに手許に置いておきたいという意味でのリファレンスなんだな
— murave (@murave) January 10, 2016

ところで、電子版にもいろいろあります。

まぁ、読むだけならいいんだけどリファレンスだしなぁ。インプレスのPDF版はどうなんだろう。
— murave (@murave) January 7, 2016

「開発しているときに常に手元にある」という要件ではKindle版にもメリットがありますが検索できないのはやはりつらい。インプレスのサイトから購入できるPDF版については検索が可能ではないかと思われますので電子版の購入を検討されている方はこちらいいのではないかと思います(PDF版は持っていないので予想ですが)。

Laravelとキャンプと電子書籍

Laravelの書籍というとわたし的にはキャンプです。「『Laravelエキスパート養成読本』献本いただいたのでキャンプへGO!」という記事のネタ写真を撮りに会社を休んでキャンプにいったんですが、実はこれが自分がそろえた装備で行く初めてのキャンプでした。その後ハマって去年は5回くらいキャンプにいった模様です。

2016年初キャンプもLaravelですよ!ということで 成人の日の連休に同じキャンプ場に行きました。結果

キャンプ場でLaravelリファレンスの電子書籍版を読んでいるがNexus7のテカテカ画面ではきびしい
— murave (@murave) January 10, 2016
テカテカでつらい

Nexus7さんのテカりっぷり

デバイスの問題が大きいですが(キャンプのときは大体IPhoneで何か本を読んでたりする)、電子書籍は外で読むのにはあまり向いてない感。

IPhoneだと固定レイアウトなので字が小さすぎて見えませんでした。

電子とキャンプについては以上です。


『Laravelリファレンス』感想

おまたせしました。本題です。

今、自分がPHPのフレームワークを選定するならばLaravel、もしくはLumenになるとおもいます(書籍でも少しふれられていますがLumenはLaravelのコンポーネントを使用した軽量(高速化)フレームワークです)。

どこに惹かれているのかを考えると、もちろん高機能だというのが前提としてはあるのですが、全部入りのフレームワークだけどコンポーネント間の依存が比較的緩やかで利用範囲を選べたり、規約もあるけどガッチガチではない、そんな押し付けがましくないところかなと。

とはいえ、なぜフレームワークを使うのかといえば乗っかって楽したいわけで、そのためにはフレームワークのルールにしたがうのがベターです。『Laravelリファレンス』はLaravelでの開発における大枠、さらには志向を学習するには最適だと思います。

必要な前提知識(デザインパターンやDIについてなど)も多いので初心者バッチコイ!とは言えませんがLaravelを採用するか検討するような方、扱う方、全てにオススメできます。

チャプターは以下のような構成です。

Chap.01 Laravelの概要
Chap.02 Laravelの基本
Chap.03 データベース
Chap.04 フレームワークの機能
Chap.05 フレームワークの拡張
Chap.06 テスト
Chap.07 実践的なアプリケーション構築
Chap.08 Laravelの実践

扱っている内容についてより詳しくはインプレス様のサイト(http://book.impress.co.jp/books/1114101107)等をご参照頂きたいのですが、要点がギュギュッっと詰め込まれています。

コードを書くとき、実際によく見そうなのはChap.02「Laravelの基本」Chap.03「データベース」Chap.04「フレームワークの機能」あたりでしょうか。実際に活用されている筆者の方々ですので実践的なチップスが挿入されているのも魅力です(メール関係など)。

わたし的イチオシはChap.05「フレームワークの拡張」(+Chap.06「テスト」)、次にChap.07「実践的なアプリケーション構築」です。

Chap.05「フレームワークの拡張」はLaravelの特徴的な仕組みについてのよくまとまった解説となっており、なんといっても読んでいてワクワクします。そして続けてChap.06「テスト」を読むとLaravelというフレームワークが志向しているものが見える感覚があります。

Chap.07「実践的なアプリケーション構築」はセキュリティ対策、CLI、拡張パッケージとかゆいところに手がとどくという感じの章です。とくにセキュリティ対策はフレームワークを採用する大きな理由ですので把握しておきたいものです。

CLIのコマンドもだいたい必要になりますよね。

拡張パッケージ、便利便利! 便利なんですがセキュリティ対策をぶっ壊されたりすることがあるので注意(余談)。

Chap.08「Laravelの実践」はサンプルアプリケーションを作成しての総復習ですが、Laravelのファサードを避けるとともにリポジトリパターンを採用するという味付けが加えられているのがイカします。

『Laravel リファレンス』いいよ!

そんな話をしていたら「5.xはものすごい勢いで変わっているので書籍を買うをあきらめた」というお話も。実際、5.1はLTSなんですが新機能が追加されたりで結構変わっていっているようです。だけど「変わらないところ」をつかむことができる本だと感じていますのでぜひ手にとってみてはいかがかと思います。

あわせて読みたい

「Laravelリファレンス発売記念!販売促進!! Advent Calendar 2015」
には関連する話とか濃ゆい話とか満載です。ぜひ。

紙書籍はいい

紙はいい

いただいた見本誌

紙書籍で読みなおして「やっぱり紙はいい」と思いました。紙質もよい。あと、付箋貼れる。

紙すごい(蛇足)

キャンプ、寝袋2枚重ねで寝る予定だったのに一つ持っていくのを忘れて寒さに震えてた時に新聞紙をかぶったらすごくあたたかくなったので紙は神。

でも電子もね(フォロー)

置き場所の問題で自分で購入する場合は電子書籍優先です。思いついた時にすぐに読めるのもよいところです(スマートフォンで読めればですが)。

以上。


murave

『Laravelエキスパート養成読本』献本いただいたのでキャンプへGO!

muraveです。Laravel(4系)には平素よりお世話になっております。

待望のLaravel本『Laravelエキスパート養成読本』が4月21日に技術評論社から発売されます。

著者のお一人、Laravel答えるマンことlocaldiskこと松尾大さんから献本いただきました。ありがとうございます!

先週の木曜日午後に届きまして、翌日、とても晴れていたので、休みを貰ってキャンプに行きました!(謎)

キャンプに行っての利点は落ち着いて読めること、ダメな点は実際に試せないことでした(おい)。

『Laravelエキスパート養成読本』お買い得!

『Laravelエキスパート養成読本』お買い得品!

この写真が撮りたかっただけ的。

な〜んてことをやっていたらレビュー祭りに乗り遅れました。すみません。

レビューに入ります。


PHPによる他のWebアプリケーションフレームワーク経験者がLaravelを始めたり、興味をもったりした時に最適の一冊だと思います。すなわち、半年前くらいの私にピッタリ!

あの頃、この本があれば…

そんな私みたいな人に特にオススメな読み方をご提案したいと思います。

まずChapter1「Laravelをはじめよう」を読みます。個人的に一押しの章です。川瀬さんの毒と愛を感じつつニヤニヤしつつLaravelの成り立ちを味わいましょう。様々なLaravelのなぜ?について腹におちます。

次にChapter3「IoCコンテナ、ファサード、サービスプロバイダ、Eloquent」を読みます。特にIoCコンテナとファサードについて。「この処理、結局どこでやってんだよー。ぐわー」という他のフレームワーク経験者で特に発生しがちな惨事を避けられるかもしれません。まぁ、私、いまだにここら辺りふんわりなんですが。

あとは好きなように、心がおもむくままに読みましょう。4系、5系どちらに興味があるかによっても変わると思います。

ということで、結局、特にいそがないのであれば順番に読んでいけば間違いないです。よく考えられてますね。


他、読んでいて私が勉強になるなぁと思ったのはLaravel最前線で活躍されている各著者の方々のTipsです。さり気なく提示されているのを見て「こうすればよかったのか!」と思ったところが多々ありました。

また、私のようにある日いきなりLaravel4なプロジェクトにぶちこまれたような人間は、信じられない位の基本が抜けていたりするので基礎を一通り俯瞰できる本は本当に助かります。

恥をさらしますと、Chapter2の「artisanコマンドでログを確認する」を読んで、うわ、これ便利じゃん!とか思いました。日毎にログを分けるようにしてまして、最新のログを毎回確認して tail -f してたんですが php artisan tail で何も考えずに表示できちゃうわけじゃないですか。ねぇ?

LaravelのREPLである php artisan tinker にもずいぶんと長いあいだ気づかなかったんですよね、私。

php artisan tinker に今まで気付かなかった…
— murave (@murave) February 13, 2015

いまならこの本があります。私はまだ触ってないんですがLaravel5かなり良さそうですし、これきっかけでLaravelに触ってみるのもいいんじゃないでしょうか? お試し用の環境も用意してくださるそうですよ。


キャンプ場独り占めで最高でしたよ。

laravel_camp_02