【问题标题】:Extending Laravel/Lumen Query Builder to automagically add SQL comments扩展 Laravel/Lumen Query Builder 以自动添加 SQL 注释
【发布时间】:2021-06-06 12:01:17
【问题描述】:

我使用一个大型 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 cmets。所以当我在我的控制器中使用它时:

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

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

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

我需要帮助是为了

  • 修改这些类的正确方法
  • 自动添加sql注释

【问题讨论】:

  • 你想要Mysql的解决方案?
  • @Vinay 当然可以。但只是好奇,为什么我们不能有一个适用于任何数据库的通用解决方案?

标签: php mysql laravel amazon-rds lumen-5.8


【解决方案1】:

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

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

对于 Postgres 也是如此,它将是 Illuminate\Database\Query\Grammars\PostgresGrammar

根据数据库配置 ConnectionFactory[src/Illuminate/Database/Connectors/ConnectionFactory.php-&gt;createConnection()]

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

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

因此,我建议不要使用 laravel 宏,通过它您可以向使用 Macroable trait 的现有类添加新函数

可以在下面找到一个 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();

【讨论】:

  • 感谢您的回答。我会试试这个。顺便说一句,你测试过这项工作吗?
  • 可以让这个注释方法自动运行而不显式调用它吗?
  • 是的,它会起作用,因为register() 中的所有代码都是直接从src/Illuminate/Database/Query/Grammars/Grammar.phpsrc/Illuminate/Database/Query/Builder.php 提取的,我们只需修改名称例如T_compileSelect 而不是原来的compileSelect 以避免碰撞
  • 我在测试之前接受了它,它确实有效。谢谢!另外我想知道如何在不让开发人员到处调用这个方法的情况下隐式调用这个方法?
  • 这样看起来不可能,也许我们可以继承核心 Eloquents Model 类,然后从它继承你的 Admin 模型,这样你就可以在执行时操纵幕后的代码 ->get( )、->插入()、->更新()。通过__callStatic() 魔术方法,但我想再次不值得努力
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-01
  • 2018-04-02
  • 1970-01-01
  • 1970-01-01
  • 2021-02-26
  • 1970-01-01
相关资源
最近更新 更多