上次幹這事已是一年前了,以前的作法特別的繁瑣、冗餘,具體就是建立一個自定義 Builder 類,繼承自 Query\Builder,而後覆蓋 Connection 裏面獲取 Builder 的方法,返回自定義的 Builder,還有其餘一系列很長的步驟。php
下面是以前的作法:laravel
(算了,仍是不說了,太蠢),總之,多看看源碼有好處git
就說最優雅的解決方法吧:github
laravel 中提供了 Macroable 的 trait,以前一直沒有想過能夠用上這個東西。app
最近纔想到能夠這麼作,源碼看這裏:https://github.com/laravel/framework/blob/5.6/src/Illuminate/Support/Traits/Macroable.phpcomposer
目前用的是 5.1 版本,應該 5.1 版本之後的都有這個特性。ide
固然,你也能夠在 Model 基類裏面添加自定義方法,可是這樣添加的方法 DB 類是用不了的。ui
可是使用 macro 定義到 Builder 的方法能夠同時在 DB 類和 Eloquent Model 中使用(具體不贅述,本身看源碼,就是利用 __call、__callStatic 這些魔術方法)。this
使用方法:調用 Macroable 的 macro 方法,綁定一個自定義方法到 Builder 類中,如:spa
\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 了,也就不會有語法提示了。
手速驚人的仍是忽略吧。