扩展 Laravel/Lumen 查询生成器以自动添加 SQL 评论

Extending Laravel/Lumen Query Builder to automagically add SQL comments

我使用一个大型 RDS 数据库实例,它在几个不同的项目(准确地说不是微服务)之间共享,这个数据库的性能至关重要。因此,每当支持团队提出与我们的服务性能相关的票时,我都会监控查询。因此,为了让我跟踪每个查询的来源,即哪个应用程序、文件和行号,我想为所有查询自动添加 SQL 评论。因此,当我在查询生成器对象上调用 toSql() 时,它必须向我显示评论

-- lumen-api:app/Http/Controllers/APIController.php:85
select * from users;
env(app_name) .  ':'. __FILE__  . ':' . __LINE__.

我尝试扩展查询构建器和语法 类 并将它们绑定到服务容器,但我认为我做错了什么。请看一下我是如何扩展这些 类.

的实现的

<?php

// app/Classes/Database/Query/Grammars/QueryGrammar.php

namespace App\Classes\Database\Query\Grammars;

use App\Classes\Database\Query\QueryBuilder;
use Illuminate\Database\Query\Grammars\Grammar;

class QueryGrammar extends Grammar
{
    /**
     * @param QueryBuilder $query
     * @param $comment
     * @return string
     */
    public function compileComment(QueryBuilder $query, $comment)
    {
        $this->selectComponents[] = 'comment';
        return '-- ' . $comment . PHP_EOL;
    }
}
<?php

// app/Classes/Database/Query/QueryBuilder.php

<?php

namespace App\Classes\Database\Query;

use Illuminate\Database\Query\Builder;

class QueryBuilder extends Builder {

    /**
     * @param $comment
     * @return $this
     */
    public function comment($comment): QueryBuilder
    {
        $this->comment = $comment;
        return $this;
    }
}
<?php

//app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;

use App\Classes\Database\Query\Grammars\QueryGrammar;
use App\Classes\Database\Query\QueryBuilder;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Support\ServiceProvider;


class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        /**
         * Extending Query Builder to support SQL comments
         */
        $this->app->bind(Grammar::class, function () {
            return new QueryGrammar();
        });

        $this->app->bind(Builder::class, function () {
            return new QueryBuilder(/* how to send params?*/);
        });

    }
}

我知道此实现不适用于自动添加 sql 评论。所以当我在我的控制器中使用它时:

return Admin::where('login_email','bhargav.nanekalva@mpokket.com')->comment(__FILE__ . __LINE__)->toSql();

Laravel 抛出以下错误:(这意味着绑定没有发生)

(1/1) BadMethodCallException
Call to undefined method Illuminate\Database\Eloquent\Builder::comment()

我需要帮助的是

直接覆盖语法 class 可能是可能的,但它在内部将其工作委托给数据库特定语法 classes

例如,如果您在 config/database.php 中配置了 Mysql,那么 Grammer class 会将工作委托给 Illuminate\Database\Query\Grammars\MySqlGrammar

与 Postgres 类似,它将是 Illuminate\Database\Query\Grammars\PostgresGrammar

基于数据库配置 连接工厂[src/Illuminate/Database/Connectors/ConnectionFactory.php->createConnection()]

为给定的数据库加载正确的连接管理器

我不确定是否可以重写这个 classes,因为 PSR-4 加载是因为命名空间与文件在目录树中的物理位置紧密相关

因此,我建议使用 laravel 宏,您可以将新函数添加到使用 Macroable 特征

的现有 classes

可以在下面找到一个 POC 示例,为了进一步推进,我们鼓励您在 Grammer.php 和 Builder.php

中挖掘更新、插入、删除等代码
<?php
namespace App\Providers;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
use DB;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\Grammar;

class AppServiceProvider extends ServiceProvider
{

    public function register()
    {
        Grammar::macro("T_compileSelect", function (Builder $query) {
            if ($query->unions && $query->aggregate) {
                return $this->compileUnionAggregate($query);
            }

            $original = $query->columns;

            if (is_null($query->columns)) {
                $query->columns = ['*'];
            }

            $sql = trim($this->concatenate(
                $this->compileComponents($query))
            );

            if ($query->unions) {
                $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
            }

            $query->columns = $original;

            return $sql . ' -- ' . (!empty($query->comment)?$query->comment:'');
        });

        Builder::macro("T_toSql", function () {
            $str = $this->grammar->T_compileSelect($this);
            return $str;
        });

        Builder::macro("T_runSelect", function () {
            $str =  $this->connection->select(
                $this->T_toSql(), $this->getBindings(), ! $this->useWritePdo
            );
            return $str;
        });

        Builder::macro("addComment", function ($comment, $columns = ['*']) {
            $this->comment = $comment;
            $res = collect($this->onceWithColumns(Arr::wrap($columns), function () {
                return  $this->processor->processSelect($this, $this->T_runSelect());
            }));
            return $res;
        });

    }

    public function boot()
    {
    }
}

用法:

            Admin::where('login_email','bhargav.nanekalva@mpokket.com')
            ->addComment(__FILE__ . __LINE__)
            ->get();