Builder
就是查詢到SQL
轉換的紐帶!sql
這章很難,勸你放棄閱讀 🤷♀️🤷♂️bash
`Eloquent Builder` 是咱們在使用 `Laravel`
模型進行查詢的時候調用的對象,轉換 `SQL` 最終是調用了
`Query Builder` 對象的服務。
因此咱們將介紹兩個 `Builder` 對象。
複製代碼
Query Builder
指Illuminate\Database\Query\Builder
閉包
Eloquent Builder
指Illuminate\Database\Eloquent\Builder
app
這兩個對象的關係就像老闆和打工仔,上層Eloquent Builder
指揮下層 Query Builder
幹活。ui
User::find(1)
當咱們執行這條查詢的時候,會觸發 Model
的方法this
這裏無論是否靜態調用都不要緊,最終會轉到 __call
spa
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
複製代碼
再轉發到code
public function __call($method, $parameters)
{
// "若是是這兩個方法的話會優先調用 Model自身定義的"
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
// "這裏的 $this->newQuery() 就是 Eloquent Builder 對象!"
// "轉發調用,實際執行了 $this->newQuery()->{$method}"
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
複製代碼
首先出場的是
Eloquent Builder
cdn
咱們來看 $this->newQuery()
獲取的是什麼!對象
public function newQuery()
{
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
}
複製代碼
繼續分析 newQueryWithoutScopes()
public function newQueryWithoutScopes()
{
return $this->newModelQuery()
->with($this->with)
->withCount($this->withCount);
}
複製代碼
public function newModelQuery()
{
return $this->newEloquentBuilder(
$this->newBaseQueryBuilder()
)->setModel($this);
}
複製代碼
public function newEloquentBuilder($query)
{
return new Builder($query);
}
// "Builder 的構造方法聲明"
public function __construct(QueryBuilder $query)
{
$this->query = $query;
}
複製代碼
返回一個 Query Builder
對象
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new QueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
);
}
複製代碼
其實通過上面一系列的操做最主要的目的就是將 Query Builder
賦值給 Eloquent Builder
可見 Eloquent Builder
並無構建 SQL
語句的能力
可是這層封裝使得 Eloquent Builder
擁有了這能力。
因此真正的構建服務仍是來自 Query Builder
對象。
通過上面的分析咱們回到最開始的調用處
public function __call($method, $parameters)
{
// "若是是這兩個方法的話會優先調用 Model自身定義的"
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
// "這裏的 $this->newQuery() 就是 Eloquent Builder 對象!"
// "轉發調用,實際執行了 $this->newQuery()->{$method}"
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
複製代碼
因此咱們對模型層的大部分調用都是調用 (Eloquent Builder)>{$method}
那麼就從開篇的例子開始分析這個 Eloquent Builder
到底有什麼方法!
Find(1)
方法解析public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
return $this->whereKey($id)->first($columns);
}
複製代碼
咱們傳入的是一個 Int
,直接分析 $this->whereKey($id)->first($columns)
public function whereKey($id)
{
if (is_array($id) || $id instanceof Arrayable) {
$this->query->whereIn($this->model->getQualifiedKeyName(), $id);
return $this;
}
// "從這裏開始分析"
// "$this->model->getQualifiedKeyName() 就是獲取主鍵的名字是什麼,就不贅述"
return $this->where($this->model->getQualifiedKeyName(), '=', $id);
}
複製代碼
️🏁繼續看,接下來就是重點了!關於查詢構建器是如何構建 SQL
的。
咱們在腦海裏面先想一下,查詢構建器是幹啥的?!
回憶下是否是好久沒有寫原生 SQL
了?還記得 SELECT * FROM users WHERE id = 1;
嗎,在 Laravel
中查詢構建器功能就是將咱們的 User::find(1)
轉化成上面的 SQL
好了,咱們回來繼續分析如何完成這個轉化!
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure) {
$column($query = $this->model->newModelQuery());
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
$this->query->where(...func_get_args());
}
return $this;
}
複製代碼
執行這裏的代碼,這裏調用了 打工仔 Query Builder
$this->query->where(...func_get_args());
複製代碼
展開打工仔的 where
, 接收的參數就是上面完完整整的轉發了一次。
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if (is_array($column)) {
return $this->addArrayOfWheres($column, $boolean);
}
[$value, $operator] = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
);
if ($column instanceof Closure) {
return $this->whereNested($column, $boolean);
}
if ($this->invalidOperator($operator)) {
[$value, $operator] = [$operator, '='];
}
if ($value instanceof Closure) {
return $this->whereSub($column, $operator, $value, $boolean);
}
if (is_null($value)) {
return $this->whereNull($column, $boolean, $operator !== '=');
}
if (Str::contains($column, '->') && is_bool($value)) {
$value = new Expression($value ? 'true' : 'false');
}
$type = 'Basic';
$this->wheres[] = compact(
'type', 'column', 'operator', 'value', 'boolean'
);
if (! $value instanceof Expression) {
$this->addBinding($value, 'where');
}
return $this;
}
複製代碼
上面這麼一大堆的代碼實在是懶得講了~看圖吧,
反正就是對 Builder
這幾個圈起來的屬性賦值
仔細看看,反正沒什麼難的,就是先把數據丟到這些成員裏存起來。
上面咱們存好了數據,那麼後面咱們就要想辦法從這些屬性中構建處 SQL
了,別急,咱們如今開始。
回到剛纔開始的地方
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
// "剛纔執行了這句"
return $this->whereKey($id)->first($columns);
}
複製代碼
Illuminate\Database\Concerns\BuildsQueries
現身這裏的 first()
方法是 use
這 BuildsQueries
這個特質類
public function first($columns = ['*'])
{
return $this->take(1)->get($columns)->first();
}
複製代碼
追進去這裏要注意 $this
這裏指向的是 Eloquent Builder
對象 ,
源碼裏面是沒有 take
這個方法,這又是經過 __call
方法來調用
最終執行代碼就是 $this->query->take(1)->get($columns)->first()
這裏關於爲何這樣執行的能夠查閱 Eloquent Builder
魔術方法。
接着來
public function take($value)
{
// "就是賦值操做,給對象的 $this->limit = $value;"
return $this->limit($value);
}
複製代碼
繼續看,準備好秋名山最後幾個關卡來了
// "這個 `get`方法是老闆 `Eloquent Builder` 中定義的"
public function get($columns = ['*'])
{
$builder = $this->applyScopes();
if (count($models = $builder->getModels($columns)) > 0) {
$models = $builder->eagerLoadRelations($models);
}
return $builder->getModel()->newCollection($models);
}
複製代碼
接着重點是 $builder->getModels($columns)
獲取數據的操做
public function getModels($columns = ['*'])
{
return $this->model->hydrate(
$this->query->get($columns)->all()
)->all();
}
複製代碼
咱們不理會其餘,只看 $this->query->get($columns)->all()
這裏就是調用打工仔 Query Builder
的 get()
public function get($columns = ['*'])
{
// "onceWithColumns 這個方法沒什麼好分析,接收兩個參數,返回第二個參數(閉包)"
return collect($this->onceWithColumns($columns, function () {
return $this->processor->processSelect($this, $this->runSelect());
}));
}
複製代碼
繼續看閉包裏面 $this->processor->processSelect($this, $this->runSelect());
public function processSelect(Builder $query, $results)
{
// "這裏沒幹啥,就是把 $results 返回"
return $results;
}
複製代碼
那麼最重點的來了,看名字就是運行 SQL
$this->runSelect()
複製代碼
這裏的
$this->connection->select()
是驅動層提供對接MySQL
的調用,咱們不用關心啦~咱們看到這裏的select
有三個參數,第一個就是咱們苦苦尋找的SQL
,第二個是PDO
參數綁定的數據。
protected function runSelect()
{
return $this->connection->select(
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
);
}
複製代碼
tips 咱們平時在使用
(new User)->getQuery()->toSql();
能夠看到預編譯的SQL
public function toSql()
{
return $this->grammar->compileSelect($this);
}
...
// "方便閱讀合併了部分源碼"
public function compileSelect(Builder $query)
{
if ($query->unions && $query->aggregate) {
$column = $this->columnize($aggregate['columns']);
if ($query->distinct && $column !== '*') {
$column = 'distinct '.$column;
}
$sql = 'select '.$aggregate['function'].'('.$column.') as aggregate';
$query->aggregate = null;
$sql = $sql.' from ('.$this->compileSelect($query).') as '.$this->wrapTable('temp_table');
}
$original = $query->columns;
if (is_null($query->columns)) {
$query->columns = ['*'];
}
$sql = trim($this->concatenate(
$this->compileComponents($query))
);
$query->columns = $original;
return $sql;
}
複製代碼
這一坨坨代實在講起來沒有味道,就是各類判斷,而後抽取屬性拼接成字符串。
這裏面有興趣能夠自行研究,這篇僅僅介紹執行邏輯。
老闆
Eloquent Builder
和打工仔Query Builder
的職責!
Builder
的原理,先存入屬性,在執行toSql()
其餘功能等待讀者開發!