Laravel QueryBuilderのupdate()でupdated_atを自動更新する方法

Laravel Auto Update updated_at for query builder PHP
Laravel Auto Update updated_at for query builder

ついに、クエリビルダによるupdate実行で、updated_atを自動更新させる方法を見つけました。

多くの人がLaravelの save() や update() の挙動に躓き、ブログのネタにされ、
Eloquentではなく、QueryBuilderを用いたupdate()に関しては
updated_atを自動更新することを諦めている方が多くいらっしゃるのではないかと思います。

そんな方々に朗報です。
ついに突破口を見つけました。

Override QueryBuilder

早速、クエリビルダのupdateをoverrideしていきます。

<?php
namespace App\Override;

use Carbon\Carbon;

class QueryBuilder extends \Illuminate\Database\Query\Builder
{
    public function update($values)
    {
        $t = '';
        foreach ($values as $key => $value) {
            if (0 < $pos = strpos($key, '.')) {
                $t = substr($key, 0, $pos) . '.';
                break;
            }
        }
        $values[$t . 'updated_at'] = Carbon::now();
        parent::update($values);
    }
}

標準のupdateを流す前に、$valuesupdated_at に日時を挿入してあげることで、
今回の目的を達成できます。

また、上記ソースでは、更新テーブルと別テーブルを結合した状態で実行されるケースにも対応できるように、カラムの前にテーブル名を付与する為の処理も実装しています。

Override Connection

先程作成したクラスを呼び出すクラスを定義します。

<?php
namespace App\Override;

class Connection extends \Illuminate\Database\MySqlConnection
{
    public function query()
    {
        return new QueryBuilder( // App\Override\QueryBuilder が使用される
            $this,
            $this->getQueryGrammar(),
            $this->getPostProcessor()
        );
    }
}

このクラスでは、MySQLのコネクションを生成する際に、
クエリビルダのインスタンス化を行っています。

この時に、先ほど作成したカスタム版クエリビルダを使用するように促しているわけです。

Override CustomDatabaseServiceProvider

先程の定義した Connection クラスを呼び出す為、
サービスプロバイダーもオーバーライドします。

<?php
namespace App\Providers;

use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\DatabaseServiceProvider;
use App\Override\Connection;

class CustomDatabaseServiceProvider extends DatabaseServiceProvider
{
    /**
     * Register the primary database bindings.
     *
     * @return void
     */
    protected function registerConnectionServices()
    {
        // The connection factory is used to create the actual connection instances on
        // the database. We will inject the factory into the manager so that it may
        // make the connections while they are actually needed and not of before.
        $this->app->singleton('db.factory', function ($app) {
            return new ConnectionFactory($app);
        });

        // The database manager is used to resolve various connections, since multiple
        // connections might be managed. It also implements the connection resolver
        // interface which may be used by other components requiring connections.
        $this->app->singleton('db', function ($app) {
            $dbm = new DatabaseManager($app, $app['db.factory']);
            $dbm->extend('mysql', function ($config, $name) use ($app) {
                $connection = $app['db.factory']->make($config, $name);

                $new_connection = new Connection(
                    $connection->getPdo(),
                    $connection->getDatabaseName(),
                    $connection->getTablePrefix(),
                    $config
                );

                return $new_connection;
            });

            return $dbm;
        });

        $this->app->bind('db.connection', function ($app) {
            return $app['db']->connection();
        });
    }
}

もうゴールは目前です。
あとは、このサービスプロバイダーを app.php に登録するだけです。

Call CustomDatabaseServiceProvider

既存の DatabaseServiceProvider をコメントアウトして
先程作成した CustomDatabaseServiceProvider を定義します。

    'providers' => [
        ...
        // Illuminate\Database\DatabaseServiceProvider::class,
        App\Providers\CustomDatabaseServiceProvider::class,
        ...

これで準備OKです。

標準機能のオーバーライドで更に便利に

今回、参考にさせて戴いたのは こちら のサイトです。
正直、まだ細かいところまで理解が及んでいないのですが、
標準機能のオーバーライド方法が分かったのは大きいです。

他にも痒いところに手が届くようなカスタム
もしくはライブラリが作れるようになると
もっと開発が楽しくなりそうです。

ではまた。