Laravel TBL（ちょっと便利になるライブラリ）の紹介

# はじめに
GitHubにあげるまでもない。作った！とドヤるほどでもない。
誰が作ったかしらんけど、しれっとある便利機能って、みなさんのプロジェクトでもありますよね。
今回は弊社で作った、Laravelで開発するにあたりちょっと楽になるプログラムを掲載したいと思います。

# クエリーログ
EloqentORMは非常に便利ですし、QueryBuilderも直感的に使えます。しかし、「実際はどんなSQLになってるんだろう」「意図した結果取れないけど生SQL見たいな」ってこともあると思います。
Laravelには [laravel-debugbar](URL "https://github.com/barryvdh/laravel-debugbar")もありますが、たとえばPHPUnitでモデルのテストを書いているときに、いちいち

```php
$builder = $this->where('status', $status)
    ->when($role, function ($query) use ($role) {
        return $query->where('role_id', $role);
    })
    ->orWhere(function ($query) {
        $query->where('votes', '>', 100)
            ->where('title', '<>', 'Admin');
    })
    ->orderBy('name', 'desc');

var_dump($builder->toSql());
```

みたいなことしたくないです。


### QueryLogger

クエリーをログに書き出すマンです。コピペでどうぞ。
あとはこのServiceProviderをLaravelなら`config/app.php`の`providers`に追加すればいいし、Lumenなら`bootstrap/app.php`に`$app->register(App\Providers\QueryLogServiceProvider::class);`と書けば、自動でクエリーログがどしどし出力されます。

```php
<?php
declare(strict_types=1);

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Class QueryLogServiceProvider
 * @package App\Providers
 */
class QueryLogServiceProvider extends ServiceProvider
{
    /**
     * @var array ログ出力する環境
     */
    private $loggingEnvironments = [
        'local',
        'testing',
    ];

    /**
     * queryログ出力設定
     */
    public function boot()
    {
        DB::listen(function ($query) {
            if (in_array(env('APP_ENV'), $this->loggingEnvironments, true)) {
                $queryLog = 'QueryLog[' . $query->time . 'ms] ' . $query->sql;
                if (!empty($query->bindings)) {
                    $queryLog .= ' (bindings:' . implode(', ', $query->bindings) . ')';
                }
                Log::info($queryLog);
            }
        });
    }
}
```

# シーダーでCSVを使いたい
Laravelでデータベースのテストを書くとき、[モデルファクトリ](URL "https://laravel.com/docs/5.7/database-testing#writing-factories")は非常に便利な機能です。
しかし、ときにはExcelなどでテストデータをつくり、それを使ってテストをしたいときもあるでしょう。その際、シーダーをPHPUnit内でコールしてデータをINSERTすると楽ちんです。


### BulkCsvSeeder
CSV形式のテストデータをよしなにバルクインサートしてくれるマンです。これを基底クラスとしてseederを書きます。コピペでどうぞ。

```php
<?php
declare(strict_types=1);

namespace Database\seeds;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use SplFileObject;

/**
 * Class CsvBulkSeeder
 */
abstract class CsvBulkSeeder extends Seeder
{
    /**
     * @const string 変換前文字コード
     */
    private const CHARACTER_CODE_SHIFT_JIS = 'SJIS';

    /**
     * @const string 変換後文字コード
     */
    private const CHARACTER_CODE_UTF = 'UTF-8';

    /**
     * @const int カラム名が入っている行数
     */
    private const COLUMN_NAME_ROW_NUMBER = 1;

    /**
     * @const string CSVファイルが置いてあるディレクトリ
     */
    private const CSV_PATH = '/csvFiles/';

    /**
     * @var string 対象のCSVファイル
     */
    protected $csvFileName = '';

    /**
     * @var string INSERTするテーブル名
     */
    protected $tableName = '';

    /**
     * @var array nullに変換するカラム番号群
     */
    protected $nullConvertNumber = [];

    /**
     * @var null|string コネクション名
     */
    protected $connectionName = null;

    /**
     * CSVをバルクインサートする
     * @param bool $isShiftJis
     */
    protected function seed(bool $isShiftJis = false)
    {
        $filePath = database_path() . self::CSV_PATH . $this->csvFileName;
        $splFileObject = new SplFileObject($filePath);
        $splFileObject->setFlags(SplFileObject::READ_CSV);
        $rowCounter = 0;
        $columnNames = [];
        $insertRecords = [];

        foreach ($splFileObject as $line) {
            $rowCounter++;
            if (empty(array_filter($line))) {
                break;
            }
            if ($rowCounter === self::COLUMN_NAME_ROW_NUMBER) {
                $columnNames = $line;
                continue;
            }
            foreach ($this->nullConvertNumber as $columnNumber) {
                if (
                    $line[$columnNumber] === 'NULL' || $line[$columnNumber] === 'null' || $line[$columnNumber] === ''
                ) {
                    $line[$columnNumber] = null;
                }
            }
            if ($isShiftJis) {
                mb_convert_variables(self::CHARACTER_CODE_UTF, self::CHARACTER_CODE_SHIFT_JIS, $line);
            }
            $insertRecords[] = array_combine($columnNames, $line);
        }
        if (!empty($insertRecords)) {
            if (!is_null($this->connectionName)) {
                DB::connection($this->connectionName)->table($this->tableName)->insert($insertRecords);
                return;
            }
            DB::table($this->tableName)->insert($insertRecords);
        }
    }

    /**
     * 実行処理のabstract
     */
    abstract public function run();
}
```

実際のseederはこんなかんじ。

```php
<?php
declare(strict_types=1);

namespace Database\seeds;

/**
 * Class SampleSeeder
 */
class SampleSeeder extends CsvBulkSeeder
{
    /**
     * @var string
     */
    protected $csvFileName = 'sample.csv';

    /**
     * @var string
     */
    protected $tableName = 'samples';

    /**
     * @var array
     */
    protected $nullConvertNumber = [3, 4];

    /**
     * シード実行
     */
    public function run()
    {
        $this->seed();
    }
}
```

あとはPHPUnit内で`Artisan::call('db:seed', ['--class' => 'SampleSeeder']);`
とかくだけで、INSERTされます。

# まとめ
みなさんのプロジェクトでもTBL（ちょっと便利になるライブラリ）はありますか？
ないなら、ちょっと作ってみる事をおすすめします。意外とフレームワークの中身の勉強になったりすることが多いです。

素敵なLaravelライフを！