在業務中,關聯是咱們最經常使用到的場景。在開發時咱們始終都在強調對數據庫設計選擇可解耦,簡潔化,最小化。在這種開發環境下,每每都會將傳統的一個大表拆分紅多個小表,這時候關聯就顯得很重要。php
MySQL 爲咱們提供了像 inner join
、left join
、 right join
這些關聯方式,知足了絕大部分需求。可是在實際開發中,咱們仍是會去選擇一些程序上的關聯關係,讓代碼去處理關聯,這些關聯從簡單的一對一,一對多,再到複雜的多態關聯、中間表關聯,等等,下面主要從源碼的角度去講解一下 Laravel 中的多態關聯。laravel
從 Laravel 官方文檔的中文翻譯中,咱們能夠找到關於多態關聯的內容。git
一對一多態關聯與簡單的一對一關聯相似;不過,目標模型可以在一個關聯上從屬於多個模型。例如,博客 Post 和 User 可能共享一個關聯到 Image 模型的關係。使用一對一多態關聯容許使用一個惟一圖片列表同時用於博客文章和用戶帳戶。
官網的文檔可能不是那麼的直觀,這裏推薦一個 文章 能夠幫助你加深理解,這裏就不展開了。github
單從文檔來講,若是你的設計或者你以前的設計符合官方的要求以及要求。 *_type 的值必須爲被關聯的模型的類名數據庫
不少時候,咱們的設計中 type 都不必定會那樣設計,基本都是以數字爲主,雖然 Laravel 爲咱們提供了自定義 type 的解決辦法 ,可是也不能很好的解決關於數字做爲 type 的問題,我還搜索到了一個同樣的問題。那麼咱們就來解決一下,如今有三張表。segmentfault
字段 | 類型 | 介紹 |
---|---|---|
id | int | 主鍵 |
product_type | tinyint(1) | 關聯的產品類型 1 表示 Tool、2 表示 Food |
product_id | int | 關聯的產品的ID |
字段 | 類型 | 介紹 |
---|---|---|
id | int | 主鍵ID |
name | varchar(20) | 名字 |
字段 | 類型 | 介紹 |
---|---|---|
id | int | 主鍵ID |
name | varchar(20) | 名字 |
如今咱們有了這三張表,購物車表中根據 product_type 的不一樣值去關聯不一樣的模型,這裏就要用到 多態關聯
,如今若是咱們直接按照官方的文檔來編寫咱們的 Model ,那麼,應該是下面這樣的。數據庫設計
class ShoppingCart extends Model { const TABLE = 'shopping_cart'; protected $table = self::TABLE; public function product() { return $this->morphTo(); } }
class Tool extends Model { const TABLE = 'tool'; protected $table = self::TABLE; public function product() { return $this->morphOne(ShoppingCart::class, 'product'); } }
class Food extends Model { const TABLE = 'food'; protected $table = self::TABLE; public function product() { return $this->morphOne(ShoppingCart::class, 'product'); } }
根據官方的文檔:模型關聯 |《Laravel 5.8 中文文檔》| Laravel China 社區。咱們的代碼應該能夠運行,可是可能不符合預期。工具
不出意外的看到了錯誤信息 「類名必須是有效的對象或者字符」,源碼中也是一個 new $class,到 IDE 中打開並斷點調試。oop
此時 $class 爲 1 ,根據調用棧一路往上找,發現了一個有價值的方法。this
能夠看到,這個 $type
是從這裏 $this->dictionary取出來的,按住 Ctrl
+ 點擊
後到了屬性定義的位置,而後再按住 Ctrl
+ 點擊
,選擇上面的篩選賦值操做,能夠看到只有一處有賦值的操做,點擊轉到。
轉到賦值的位置後,打一個斷點。
看到這裏調用棧,源碼 過多,就不展開講解。下面講重點。
看到這個屬性,$model->{$this->morphType}
,先打印它的值$this->morphType
,結果是 product_type
,而後外層還有 點擊進入按鈕,咱們進入到了模型實例中的 __get
魔術方法。
在官方手冊中,關於 __get
的定義爲:
讀取不可訪問屬性的值時, __get() 會被調用。
首先,對於 Model
而言,是沒有 product_type
屬性的,因此觸發了它,方法內部調用了 getAttribute 。
看到 getAttribute
方法內部,第 321 行,使用了一個屬性 $this->attribute
,執行表達式能夠看到,這就是咱們的數據結果。而根據 array_key_exists
的判斷能夠肯定這個 if 是成立的,由於 後面的是 ||
運算,即便後面是 false ,這個表達式也是成立,可是咱們這裏仍是但願來看一下這個方法。
這個方法只作了一件事,就是判斷一個 getter 方法是否存在,這裏的 Str::studly() 的做用是把 字符串從下劃線命名規則轉爲大駝峯。也就是說,在這裏會檢查訪問器
,固然,如今咱們是沒有這個方法的,繼續往下。
果真,在 349 ~ 351 行,有着這樣的一個邏輯,那麼咱們回過來看一下 Laravel 文檔中關於 修改器 & 訪問器 的介紹。
簡而言之就是,當在訪問這個字段的值時,咱們能夠本身根據獲取器的規則定一個名爲 getProductTypeAttribute
的訪問器方法,在這個方法中,咱們能夠修改其返回值,做爲最終的結果返回給訪問者。這樣看來,咱們就能夠在訪問器中修改咱們本來的 product_type
的 1
爲對應的須要實例化的類名稱,便可,如今開始定義一下。
public function getProductTypeAttribute($val) { $map = [ 1 => Tool::class, 2 => Food::class, ]; return $map[$val] ?? Tool::class; }
根據文檔咱們能夠得知,在對一個已存在的字段添加訪問器時,訪問器方法能夠接受一個參數,其值爲本來值,在這個方法中,咱們編寫了一個 $map
,其 key 爲 product_type 字段的原值$val
,若是這個字段原值 ($val
) ,對應的 key 不存在,就返回默認爲 App\Models\Tool
模型類,如今這樣就夠了嗎?咱們能夠來試試。
果真,代碼能夠工做了 ,再也不報錯,並且,在 relations
屬性中咱們還能夠看到 product 分別是兩個不一樣的模型,接下來咱們 toArray
看一下結果。
果真,結果已經達到了咱們的預期,可是咱們卻發現 product_type 字段值變成了字符串,而不是原來的數字 一、2,該怎麼辦?兩個辦法。
下面來展現一下第二種方法,從上面的截圖中能夠了解到,查詢結果給咱們返回的是一個Eloquent 集合,如今咱們使用其中的 transform,方法來轉換原集合。
$list = $cart->with(['product'])->get(); $list->transform(function (ShoppingCart $item) { $item->product_type_origin = $item->getOriginal('product_type'); return $item; }); dump($list->toArray());
經過模型的 getOriginal
方法拿到了原有的值。
到這裏,問題已經解決了,那麼咱們能夠自定義 product
、 product_type
、 product_id
這三個的名字嗎?這一點在 Laravel 文檔中鮮有提到,在這裏答案是能夠的。
咱們經過 ShoppingCart
模型的 product
方法,這裏咱們調用 morphTo 方法沒有傳遞 任何的值。
public function product() { return $this->morphTo(); }
接下來咱們進入進入 morphTo
方法,一探究竟。
首先映入眼簾的是一段註釋,這段註釋的 大概意思就是,若是沒有指定 $name
那麼就從調用棧中取第一條的 function名字做爲 $name
也就是最終掛載的模型上的字段名字 ,方法實現以下
protected function guessBelongsToRelation() { [$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); return $caller['function']; }
接着往下看到 $type
和 $id
protected function getMorphs($name, $type, $id) { return [$type ?: $name.'_type', $id ?: $name.'_id']; }
能夠看到,當咱們沒有本身給定 $type
和 $id
時,那麼默認值即爲 $name
分別加上 _type
、_id
後綴。
發現通過上面一番操做後,使用 whereHasMorph
方法進行篩選時,type
的值變成了 getAttribute
的值。這時候只須要在被關聯的模型
、「Food
」和「Tool
」 中重寫 getMorphClass
,返回值分別爲其 type 映射前的值 一、2 便可。
// Tool public function getMorphClass() { return 1; }
至此,文章內容結束了。本文主要涉及 Laravel 中關於 多態關聯
、獲取器
兩個知識點的瞭解。
文中所使用的調試工具爲 PHPStorm 和 Xdebug 。
文中若有紕漏,請不吝賜教,如文中內容涉及到你的利益,請與我聯繫。