Laravelでトランザクションをネストせずに新しいトランザクションを切る

毎度同じことをやろうと思った時にどうやればいいか忘れてしまうので備忘も兼ねて

# はじめに
Laravelでは以下のコードのようにするとトランザクションをネストしてSAVEPOINTを作成するため、やんごとなき事情で新しいトランザクションを切りたいなーと思ってもうまくいきません。
（ID発番が連番ではない場合に別テーブルでID発番を管理しトランザクションを分けることでロックする時間を短くするとか）

```php
    DB::beginTransaction();
    {
        DB::beginTransaction();
        Animal::create(['name' => 'ぺんぎんさん']);
        DB::commit();
    }
    DB::rollBack(); // ぺんぎんさんの挿入もロールバックされる！
```

SAVEPOINTってなんだ！って方は以下の記事が非常にわかりやすかったです。
●トランザクションのネストの使い方まとめた（初心者向け）
https://qiita.com/_natsu_no_yuki_/items/e1db2a132cbff740896d

# 実装方法

トランザクションを乱立させることはそう多くないと思うので、簡単にできる別コネクションを作成する方法を記載します。

## 準備
`config/database.php`にデフォルトで利用している接続情報をまるっとコピーします。

```php:database.php
        // 既存の接続情報
        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

        // ★mysqlの項目を丸コピした接続情報
        'mysql_2' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],
```

## 実装

新しいトランザクションを切りたい場合は、DBファサードの`connection`で追加した接続情報のキーを設定します。
Eloquentを利用する場合は、その中でModelのインスタンスを作成し、`setConnection`で追加した接続情報のキーを設定します。

- 手動トランザクションの場合

```php
    // デフォルトのコネクションでトランザクションの開始
    DB::beginTransaction();
    {
        // 追加した接続情報をセットして、トランザクションの開始
        $mysql2 = \DB::connection('mysql_2');
        $mysql2->beginTransaction();

        // Modelのインスタンスを作成し、追加した接続情報をセットする
        $animal = new Animal();
        $animal->setConnection('mysql_2');
        $animal->create(['name' => 'ぺんぎんさん']);

        // mysql_2のコネクションをコミットする
        $mysql2->commit();
    }
    // デフォルトのコネクションをロールバックする
    DB::rollBack(); // ぺんぎんさんのデータもコミットされている！
```

- トランザクションメソッドの場合

```php
    DB::transaction(function () {
        DB::connection('mysql_2')->transaction(function () {
            $animal = new Animal();
            $animal->setConnection('mysql_2');
            $animal->create(['name' => 'ぺんぎんさん']);
        });
        throw new Exception(); // ぺんぎんさんのデータもコミットされている！
    });
```

# おわりに

なんだかんだネストではなく、別トランザクションを切りたくなることはあるので、`newTransaction`とかできると嬉しいんだろうなというお気持ちでした。