laravel query builder/ Eloquent builder 添加自定义方法
上次干这事已经是一年前了,之前的做法特别的繁琐、冗余,具体就是创建一个自定义 Builder 类,继承自 Query\Builder,然后覆盖 Connection 里面获取 Builder 的方法,返回自定义的 Builder,还有其他一系列很长的步骤。
下面是之前的做法:
(算了,还是不说了,太蠢),总之,多看看源码有好处
就说最优雅的解决方法吧:
laravel 中提供了 Macroable 的 trait,之前一直没有想过可以用上这个东西。
最近才想到可以这么做,源码看这里:https://github.com/laravel/framework/blob/5.6/src/Illuminate/Support/Traits/Macroable.php
目前用的是 5.1 版本,应该 5.1 版本以后的都有这个特性。
当然,你也可以在 Model 基类里面添加自定义方法,但是这样添加的方法 DB 类是用不了的。
但是使用 macro 定义到 Builder 的方法可以同时在 DB 类和 Eloquent Model 中使用(具体不赘述,自己看源码,就是利用 __call、__callStatic 这些魔术方法)。
使用方法:调用 Macroable 的 macro 方法,绑定一个自定义方法到 Builder 类中,如:
\Illuminate\Database\Query\Builder\Builder::macro('active', function () { return $this->where('status', 1); });
调用方法是(使用 DB 类):
DB::table(xxx)->active()->get();
或者(使用 Eloquent Model):
\App\Model\User::active()->first();
至于我们应该把上面那几行放哪里?
个人觉得一个比较好的地方是 Providers 中,在 app/Providers 下面新建一个 Provider,把 macro 调用放到 Provider 的 register 方法中。如:
<?php namespace App\Providers; use Illuminate\Database\Query\Builder; use Illuminate\Support\ServiceProvider; /** @mixin \Illuminate\Database\Eloquent\Builder */ class DatabaseServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { Builder::macro('active', function () { return $this->where('status', 1); }); } }
当然,加了 Providers 之后还要在 config/app.php 中配置这个 Provider。
就这样。
还有个问题是,这样添加的方法 ide 无法识别,我们这时候就可以使用 @method 了,如:
@method $this active()
可以使用命令把这些 @method 写入到 Builder 头部(我看 laravel-ide-helper 这么干我才这么干的):
<?php namespace App\Console\Utils; use App\Console\Commands\BaseCommand; /** @see \Illuminate\Database\Query\Builder */ class Ide extends BaseCommand { public $signature = 'ide'; public $description = '生成 PHP doc到 Query Builder'; public function handle() { $file = base_path() . '/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php'; if (!is_file($file)) { $this->info("文件不存在({$file}), 先 composer install"); return; } $content = file_get_contents($file); $pos = strpos($content, 'class Builder'); list($before, $after) = $this->splitFileByPos($content, $pos); $before = $this->removeOldDoc($before); $new_file_content = $before . $this->docProvider() . $after; $f = fopen($file, 'w+'); fwrite($f, $new_file_content); fclose($f); } // 移除旧的 php doc private function removeOldDoc($before) { if (preg_match('/(\/\*.*?\*\/)/ism', $before, $match)) { $before = preg_replace('/(\/\*.*?\*\/)/ism', '', $before); } return $before; } // 生成新的 php doc private function docProvider() { return <<<DOC /** * @method \$this like(\$field, \$keyword = '') * @method \$this recent() * @method \$this active() * @method \$this notDeleted() */\n DOC; } // 通过一个点把文件分割成两部分 private function splitFileByPos($content_string, $pos) { $before = substr($content_string, 0, $pos); $after = substr($content_string, $pos); return [$before, $after]; } }
这样的好处是,也没什么好处,就是你 push 上去的时候,一般 vendor 都 ignore 了,在其他电脑 clone 下来,就没有那些 @method 了,也就不会有语法提示了。
手速惊人的还是忽略吧。