向 Laravel 查询构建器添加自定义函数

Posted

技术标签:

【中文标题】向 Laravel 查询构建器添加自定义函数【英文标题】:Add custom function to Laravel query builder 【发布时间】:2022-01-01 05:48:14 【问题描述】:

我正在尝试将 USE INDEX() 添加到 Laravel 的查询构建器中。我尝试按照与link 类似的步骤进行操作,并且有点成功,但我无法管理最后一点,我不确定我的临时代码是否创建了一个巨大的后门。

目标:我练习的目标是将索引添加到查询构建器,如下所示:

DB::table('users')->where('id',1)->**useIndex**('users')->get()->first();

这里有一个选项useIndex 指定我将用于此查询的索引。

我已经做了什么:在App/Override中创建了一个名为Connection的类

   <?php
    
    namespace App\Override;
    class Connection extends \Illuminate\Database\mysqlConnection 
        //@Override
        public function query() 
            return new QueryBuilder(
                $this,
                $this->getQueryGrammar(),
                $this->getPostProcessor()
            );
        
    

在 App/Providers 中创建了一个名为 CustomDatabaseServiceProvider 的服务提供者。在这里,我只是操纵了registerConnectionServices 函数。我进一步评论了Illuminate\Database\DatabaseServiceProvider::class, 并将 App\Providers\CustomDatabaseServiceProvider::class, 添加到配置目录中的app.php

<?php

namespace App\Providers;

use App\Override\Connection;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Database\Schema;
use Illuminate\Contracts\Queue\EntityResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\QueueEntityResolver;
use Illuminate\Support\ServiceProvider;

class CustomDatabaseServiceProvider extends ServiceProvider

    /**
     * The array of resolved Faker instances.
     *
     * @var array
     */
    protected static $fakers = [];

    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    
        Model::setConnectionResolver($this->app['db']);

        Model::setEventDispatcher($this->app['events']);
    

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    
        Model::clearBootedModels();

        $this->registerConnectionServices();

        $this->registerEloquentFactory();

        $this->registerQueueableEntityResolver();
    

    /**
     * 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']);
            //Extend to include the custom connection (MySql in this example)
            $dbm->extend('mysql', function ($config, $name) use ($app) 
                //Create default connection from factory
                $connection = $app['db.factory']->make($config, $name);
                //Instantiate our connection with the default connection data
                $new_connection = new Connection(
                    $connection->getPdo(),
                    $connection->getDatabaseName(),
                    $connection->getTablePrefix(),
                    $config
                );
                //Set the appropriate grammar object
//                $new_connection->setQueryGrammar(new Grammar());
//                $new_connection->setSchemaGrammar(new Schema\());
                return $new_connection;
            );
            return $dbm;
        );

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

    /**
     * Register the Eloquent factory instance in the container.
     *
     * @return void
     */
    protected function registerEloquentFactory()
    
        $this->app->singleton(FakerGenerator::class, function ($app, $parameters) 
            $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');

            if (!isset(static::$fakers[$locale])) 
                static::$fakers[$locale] = FakerFactory::create($locale);
            

            static::$fakers[$locale]->unique(true);

            return static::$fakers[$locale];
        );

        $this->app->singleton(EloquentFactory::class, function ($app) 
            return EloquentFactory::construct(
                $app->make(FakerGenerator::class), $this->app->databasePath('factories')
            );
        );
    

    /**
     * Register the queueable entity resolver implementation.
     *
     * @return void
     */
    protected function registerQueueableEntityResolver()
    
        $this->app->singleton(EntityResolver::class, function () 
            return new QueueEntityResolver;
        );
    

最后在 App/Override 中创建了一个名为QueryBuilder 的类。这是有问题的课程:

<?php

namespace App\Override;

use Illuminate\Support\Facades\Cache;

class QueryBuilder extends \Illuminate\Database\Query\Builder

    private $Index = [];

    public function useIndex($index = null)
    
        $this->Index = $index;
        return $this;
    

    //@Override
    public function get($columns = ['*'])
    
        if ($this->Index) 
            //Get the raw query string with the PDO bindings
            $sql_str = str_replace('from `' . $this->from . '`', 'from `' . $this->from . '` USE INDEX (`' . $this->Index . '`) ', $this->toSql());
            $sql_str = vsprintf($sql_str, $this->getBindings());
            return parent::get($sql_str);
         else 
            //Return default
            return parent::get($columns);
        
    

这里的问题是:

    输出不包含 USE INDEX 使用 str_replace 操作查询是否安全?

【问题讨论】:

这有帮助吗? ***.com/a/69092348/1255289 @miken32,谢谢,我以前见过这个 (github.com/vpominchuk/laravel-mysql-use-index-scope) 但是,我需要在 DB:: 中使用 useIndex 而不是模型 哈哈,那家伙直接抄袭了我的答案。抱歉,我没有注意到您正在寻找不是 Eloquent 的查询生成器。 str_replace 应该可以安全使用,因为您只是替换了表名。如果有办法在将索引插入查询之前检查索引是否存在,我会这样做,或者为了安全起见清理索引名称 - 除了[a-z0-9_] 之外的所有内容都应该删除。 您有一个对parent::get() 的调用,它传递了一个列数组,另一个调用了一个查询字符串。我想这是你的问题。 【参考方案1】:

查询构建器是可宏的,因此在您的服务提供商中您可能可以这样做:

Illuminate\Database\Query\Builder::macro(
    'tableWithIndex',
    function ($table, $index) 
        $table = $this->grammar->wrapTable($table);
        $index = $this->grammar->wrap($index);
        return $this->fromRaw("$table USE INDEX ($index)");
    
);

那么你可以使用这个:

DB::tableWithIndex('users', 'users');

在宏 $this 中将引用查询构建器实例

请注意,我将它们合二为一,因为您可能有多个 from 调用相同的查询,并且试图弄清楚去哪里会很麻烦

【讨论】:

您是否建议只在服务提供商处完成这项工作? 基本上是的。我没有测试过,如果你想指定更复杂的索引,你可能需要修改它,但我不明白为什么这不起作用 我可以知道我应该把这段代码放在我的 CustomDatabaseServiceProvider 的什么位置吗? 通常你会将宏放在boot 方法中。文档中唯一实际提到可宏化的是 here,它只提到了集合,但同样适用于所有可宏化的 Laravel 类(其中有很多) 不错的答案。我冒昧地添加了return 声明,并将其垂直展开,这样在不水平滚动的情况下更易于阅读。

以上是关于向 Laravel 查询构建器添加自定义函数的主要内容,如果未能解决你的问题,请参考以下文章

laravel 雄辩的查询构建器更新自定义时间戳字段而没有任何逻辑

php 向WPB页面构建器添加新的自定义元素

使用 Laravel 使自定义类在任何地方可用

向 Laravel 表单添加自定义验证错误

如何在 Laravel 5.6 中向资源控制器添加自定义方法

向 Laravel 忘记密码表单添加额外问题并自定义其错误消息