<?php
// @see http://php.net/manual/en/function.func-get-args.php
public function with($relations)
{
$eagerLoad = $this->parseWithRelations(
is_string($relations) ? func_get_args() : $relations
);
$this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);
return $this;
}
這一段不長,它的的意思基本上就是:
把收到的所有「關聯」用 parseWithRelations()
處理過之後,
再把結果跟目前的 $this->eagerLoad
合併起來。
<?php
is_string($relations) ? func_get_args() : $relations;
至於上面這行的用意在:
雖然 with() 也接受 string 的參數,但 parseWithRelations()
收的是陣列,
所以它會用 func_get_args()
重新把傳入的參數以陣列的形式讀出來,
這樣子就能確保傳遞給 parseWithRelations()
的變數只有可能是陣列了。
<?php
protected function parseWithRelations(array $relations)
{
$results = [];
foreach ($relations as $name => $constraints) {
if (is_numeric($name)) {
$name = $constraints;
// @see http://php.net/manual/en/function.list.php
// 請特別注意 list() 在 PHP5 及 PHP7 有行為的差異
list($name, $constraints) = Str::contains($name, ':')
? $this->createSelectWithConstraint($name)
: [$name, function () {
//
}];
}
$results = $this->addNestedWiths($name, $results);
$results[$name] = $constraints;
}
return $results;
}
這一段程式碼主要透過 foreach 解析傳入的所有關聯。
其中 if-else 中間的內容完全是在做參數的處理,
作者會在這段把「裝著關聯的陣列」,
都轉換成「key 是關聯名稱;value 是描述附加條件的 callable」的陣列。
最後在真的把關聯及條件塞進 $result 之前,
透過 addNestedWiths() 處理「巢狀預載入」。
最後再回傳給 with() 的 $eagerLoad,
讓它能與已經設定好的條件($this->eagerLoad)合併。
<?php
foreach ($relations as $name => $constraints) {
/*...*/
}
透過 foreach 我們可以發現到:
作者認為每一陣列元素的 key 是 $name;value 是 $constraints。
這種寫法可以讓我們直接聯想到文件中提到的「預載入條件限制」。
但因為也支援 with('relationOne', 'relationTwo')
這種「只傳關聯名稱」的用法,
(這兩個參數是以一般陣列的方式傳入 parseWithRelations())
所以緊接著的 if-else 會檢查 $name 是不是數字,
是的話 $name = $constraints
;
接著再給 $constraint 指派一個空的 Callable。
比如說:
<?php
// 首先是
with('author', 'comments:id');
// 接著會
parseWithRelations([0 => 'author', 1 => 'comments:id']);
// 在 foreach 中的 is_numeric($name) 符合時
$relation = [
'author' => 'author',
'comments:id' => 'comments:id'
];
// 然後變成
$relation = [
'author' => function () { /*...*/ },
'comments' => $this->createSelectWithConstraint($name)
];
<?php
protected function createSelectWithConstraint($name)
{
return [explode(':', $name)[0], function ($query) use ($name) {
$query->select(explode(',', explode(':', $name)[1]));
}];
}
這是用在處理下面這種只撈特定欄位的行為的。
它這邊的語法比較混雜,
但總之它是回傳一個「包含關聯名稱及附加條件的callback」的陣列回去。
(並透過 list() 把結果分別指派給 $name 及 $constraints)
<?php
protected function addNestedWiths($name, $results)
{
$progress = [];
foreach (explode('.', $name) as $segment) {
$progress[] = $segment;
if (! isset($results[$last = implode('.', $progress)])) {
$results[$last] = function () {
//
};
}
}
return $results;
}
有趣的是,在它把關聯名稱用「.」拆成一個一個片段之後,
它會自己去依序設定要載入每一層關聯(同時已經設定過的就會略過)。
比如說如果我是這樣寫的:
<?php
with([
'author.organization',
'author.organization.members.article'
]);
那麼當它處理到第二個關聯(author.organization.members.article)的時候,
它會依序檢查下列的關聯有沒有被載入了:
1.author
2.author.organization
3.author.organization.members
4.author.organization.members.article
所以其實當你有一個超巢的關聯(4)要載入,那麼你也不需要設定多餘的關聯了(1,2,3)。
因為筆者只寫過 5.2 版的 Laravel
所以底下的內容是筆者在看完這段程式碼之後的一些想法,並不代表實際狀況,
如果有什麼想法請留言在底下~
但如果是透過只寫一筆巢狀關聯來作載入的話,每一層關聯都會拉回所有欄位的樣子,
這樣似乎不是很好r。
但看起來目前的 createSelectWithConstraint()
,
處理不了在巢狀預載入中各自設定載入欄位的狀況。addNestedWiths()
似乎也沒處理的意思。
所以底下這種寫法應該是不成立的:
<?php
with('author:id.organization:id.members:id.article:id')
但如果手動設定的話應該還是辦得到的:
<?php
with(
'author:id',
'author.organization:id',
'author.organization.members:id',
'author.organization.members.article:id'
);
這樣就可以降低每一層實際從資料庫拉出的欄位了吧?
不過要小心不要沒拉到能讓 Laravel 繼續往下層抓資料的欄位就是了~
收工~