Faker(4)--Provider 與 Generator 之間的愛恨情仇
如果有認真看前兩天的文章,應該會發現一個很奇怪的事:
- Day 7 提到:
Factory產生Generator物件時,會使用addProvider()把Provider加入Generator - Day 8 提到:
Provider\Base建構子的依賴是Generator
這造成了一個循環引用的關係
@startuml |
通常這樣的關係是不利於維護的,容易修改一個地方,而讓很多地方同時受影響。不過我們還是先一起來看看它到底在做什麼吧!
關於 Generator 使用 Provider 的部分,Day 7 已經有說明,不再贅述。
Provider\Base 如何使用 Generator
打開 IDE 搜尋一下,會發現 Provider\Base 有 3 個方法會用到 Generator:
optional()unique()valid()
與其他產生假資料的方法不同的是:這三個方法會回傳 Generator 物件或是另外三種 Generator 物件:
先來了解這三個方法在做什麼:
optional()
optional() 方法需要給一個權重值(百分比)與一個預設值(default),它會依權重值來隨機決定要回傳 DefaultGenerator 還是正常的 Generator。
先來看個簡單的範例:
$generator = Faker\Factory::create('en_US'); |
輸出效果:
預設值 |
其中權重 0% 指的是一定會回傳預設值,而 100% 則會回傳 Generator,因此有產生隨機的名字,中間的機率則各是 50%。
DefaultGenerator 的設計是:先把預設值存下來之後,在由 __get(),__call() 回傳回去。這樣使用回傳的 Generator 時,我們也不會發現它是 Generator 還是 DefaultGenerator,蠻有趣的。
unique()
連續產生一百組假資料,有可能會出現一樣的資料。有時會不允許資料裡有重覆的值,比方說資料庫欄位被設定成 UNIQUE,如果 insert 遇到重覆的值就會發生錯誤。
unique() 能解決這個問題,它會回傳 UniqueGenerator。同一個 UniqueGenerator 產生的假資料,可以確保每次都會不一樣。
它的設計是 Generator 任務不變,而 UniqueGenerator 的任務是記錄已產生過的資料,如有重覆,會再重新跟 Generator 要資料。
從 UniqueGenerator 呼叫 Generator 的方式如下:
public function __get($attribute) |
一樣舉 name 屬性與 name() 方法為例,從上面 UniqueGenerator 的程式可以得知,最後呼叫 Generator 的結果如下:
$generator = Faker\Factory::create('en_US'); |
這裡先提示一下:$generator->name 與 $generator->name() 的結果是一樣的。Day 7 介紹 Generator 曾有翻過原始碼,知道 __get() 最終跟 __call() 一樣,是呼叫 format()
因此這個結果可以得知:雖然包裝有做處理,但 UniqueGenerator 的介面接到 Generator 的介面是一模一樣的。這正是標準的 Proxy Pattern。
@startuml |
如果昨天有翻程式碼的話應該也會發現另一件事:Provider 提供的方法如果有參數,幾乎都會有預設值,即使是看起來沒用處的
[a, b, c],目前也只有發現,還不清楚目的。
valid()
如果需要產出名字為 M 開頭的假資料的話,那麼使用 valid() 會是最好的選擇!
與 UniqueGenerator 在驗證假資料不重覆很類似,而 valid() 的驗證方法使用 Closure 自定義方法,做法可以更為廣泛。
也因為很類似,所以設計也很像 UniqueGenerator,一樣透過 __get() 與 __call() 轉接到 Generator,所以也是使用 Proxy Pattern。
Provider\Base 本身並沒有使用到 Generator 的功能,只是單純的把它包裝後再回傳,這樣比較不容易有改 A 壞 B 的情況發生。
事實上,用最頻繁的是其他的 Provider。我們明天再來看看這些 Provider 的細節吧!
參考資料
- Proxy 模式 - 良葛格學習筆記