Miles' Blog

天涯何處無幹話,何必要講實務話

截至目前為止,應用程式該有基本功能都已經完備了,再來就是最後一哩路了--交付。

閱讀全文 »

在開始拆解前,首先我們先了解該如何使用它,官方提供的 Basic Usage 如下:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));

// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');

以下為方便說明,皆把 Monolog namespace 省略

通常好的函式庫,從範例就能快速了解函式庫的基本架構。這段程式碼可以發現主要的核心物件是 Logger,而另外一個依附在 Logger 上的物件 Handler\StreamHandlerHandler\StreamHandler 實作了 Handler\HandlerInterface,這也是 pushHandler 方法所限制的物件型別。

而後面兩行新增 log 的方法,翻一下原始碼,會發現是由 Logger 轉接到 Handler\StreamHandler 和其他被加入的 Handler 上。

通常兩個物件如果是抽象層依賴的話,有可能會是 Bridge Pattern 或是 Observer Pattern;而 Bridge Pattern 又通常會是建構時期決定依賴的物件為何,因此 LoggerHandler\HandlerInterface 的關係基本上比較像是 Observer Pattern。關係圖如下:

@startuml
Class Logger {
+ pushHandler(h: Handler\HandlerInterface)
+ popHandler(): Handler\HandlerInterface
}
Class Handler\StreamHandler
Class Handler\HandlerInterface
Logger -> Handler\HandlerInterface
Handler\HandlerInterface <|-- Handler\StreamHandler
@enduml

Logger 有些行為也正好是 Observer Pattern 的特色:

  • 沒有 Handler 一樣能 work
  • 可以動態追加或移除 Handler,Monolog 是使用 Array Stack 實作

使用 Observer Pattern 的好處很明確,實作儲存 log 的方法有千百種,加上寫 log 行為並不會太複雜(infoerror 等),自從 PHP 有 PSR-3 後,行為又更加明確了。這樣的情境下使用 Observer Pattern 更能看出套用 Pattern 的好處。

身為一個開發者,有時會需要知道維運的過程中發生了什麼意外錯誤訊息。現在 Slack 很紅,只要開個 channel 設定一下,就會生出一個 webhook url。然後只要透過 HTTP 協定,就能把訊息往 channel 送。

但有些人是 Atlassian 派,覺得 HipChat 才是王道;又有人說,我想用線上服務 Rollbar 來記錄;甚至還有人習慣收 Email 呢。

這麼多種服務,每次都要看文件串接,真的有點麻煩。

串接還是小事,我們再往下看:


身為一個 PHP 開發者,開發或測試階段會想知道某段程式發生了什麼事,最常用的招數相信大家都知道--echo 放在程式裡面就能看得到內容啦!但有時不是單純某一段程式,而是需要記錄很多位置的內容;又或是記錄的內容不是要印出來,而是要存在其他地方。

基於種種因素,echo 雖然是個 debug 最簡單也最常用的工具,但在這多變的需求下,就顯得太簡陋了。


又或是上述有提到串接很多種提醒機制,而有種需求是同個訊息內容,要分派到不同的提醒。比方說 DB 連線失敗要同時通知 Slack 給開發團隊、寄簡訊給 DBA、發 mail 給老闆。這樣 DB 連線豈不要寫三行程式才能做這三件事?

這還不打緊,要是哪天寄簡訊的 SDK 改版了,程式的所有角落都得掃過一次,不然 DBA 收不到簡訊,服務就完蛋了!


Monolog 正是解決上述問題的好夥伴。

  • 要串接常見的服務?可以!
  • 要用簡單的語法記錄多個位置?可以!
  • 同時通知多種服務?可以,而且只要使用 PSR-3 提供的介面就行了。

後面我們來一起看看 Monolog 是如何解決這些問題吧。

導入 Composer 的時候,我們有新增一個範例的單元測試。如果可以的話,下一步當然就是開始寫一些基本的單元測試,來保護系統元件。但,並不是每個專案都能這麼開心,如果真的無法寫單元測試的話該怎麼辦呢?沒關係,我們還能寫驗收測試!

閱讀全文 »

一個套件好不好用,除了它本身的功能要很厲害之外,我們也能藉由擴充功能的方法來讓套件更強大,那就再好也不過了。這正是開關原則的精神,而 Faker 正是符合此精神的套件。

就讓我們一起來證明它吧!

世間情產生器

世間情是一部八點檔連續劇,我們來試看看能不能做出一個世間情產生器,可以產生世間情的角色名字。

照 Faker 的設計,我們只需要寫一個 Provider,再加入 Generator 即可。

我們會有兩種做法,一種是全新的 Provider 與方法,另一種是繼承 Person 並覆寫原本的 name 方法。

全新 Provider

這個方法非常簡單,先寫 Provider,並定義好公開要讓 Generator 參考的方法(loveName):

use Faker\Provider\Base;

class Love extends Base
{
protected static $name = array(
'杜瑞峰',
'郭佳佳',
'羅元浩',
'柯展弘',
'趙怡琇',
// ...
);

public function loveName()
{
return static::randomElement(self::$name);
}
}

接著在 Generator 產生後,再使用 addProvider 方法加入即可:

$generator = Faker\Factory::create('zh_TW');
$generator->addProvider(new Love($generator));

最後即可用剛剛公開的方法 loveName,來產生世間情角色的名字:

echo $generator->loveName . PHP_EOL;

繼承原有的 Provider

全新的方法可以全都自己定義,因此可以定的非常簡單,像上面只有取得名字。而原有的 Person 有姓有名,我們必須要想辦法讓這些方法都能回傳世間情角色的名字,相對就有點麻煩。

以下讓我們一步一步達成它吧!首先先繼承台灣版的 Persion

class Love extends \Faker\Provider\zh_TW\Person
{
}

接著,我們有以下目標的行為要覆寫:

echo $generator->name;              // 產生一個角色全名
echo $generator->firstName; // 產生一個名
echo $generator->firstNameMale; // 產生一個男性角色名
echo $generator->firstNameFemale; // 產生一個女性角色名
echo $generator->lastName; // 產生一個姓

先從 lastNamefirstNamename 的關係開始。我們 name 想要角色的全名,但 lastNamefirstName 的卻是要它們拆開的,這代表我們可能需要一個二維陣列來存放這些資料:

protected static $name = array(
array('杜', '瑞峰'),
array('郭', '佳佳'),
array('羅', '元浩'),
array('柯', '展弘'),
array('趙', '怡琇'),
array('朱', '卉喬'),
array('謝', '萱萱'),
array('謝', '曉婷'),
array('江', '曉婷'),
array('謝', '子奇'),
array('杜', '仁德'),
array('方', '思瑤'),
array('李', '雅欣'),
...
);

接著 name 方法就非常簡單,把陣列組合起來就行了:

public function name($gender = null)
{
$nameArray = static::randomElement(static::$name);

return $nameArray[0] . $nameArray[1];
}

lastNamefirstName 則是把陣列的值各別回傳:

public function firstName($gender = null)
{
$nameArray = static::randomElement(static::$name);

return $nameArray[1];
}

public function lastName()
{
$nameArray = static::randomElement(static::$name);

return $nameArray[0];
}

再來下一個課題就是要把男女分開了,直接從 $name 的資料拆分成兩種資料:

protected static $maleName = array(
array('杜', '瑞峰'),
array('羅', '元浩'),
array('柯', '展弘'),
array('謝', '子奇'),
array('杜', '仁德'),
);

protected static $femaleName = array(
array('郭', '佳佳'),
array('趙', '怡琇'),
array('朱', '卉喬'),
array('謝', '萱萱'),
array('謝', '曉婷'),
array('江', '曉婷'),
array('方', '思瑤'),
array('李', '雅欣'),
);

namefirstName 需要做點調整,先用最蠢的方法完成:

public function name($gender = null)
{
if ($gender === static::GENDER_MALE) {
$nameArray = static::randomElement(static::$maleName);
} elseif ($gender === static::GENDER_FEMALE) {
$nameArray = static::randomElement(static::$femaleName);
} else {
$nameArray = static::randomElement(array_merge(static::$maleName, static::$femaleName));
}

return $nameArray[0] . $nameArray[1];
}

public function firstName($gender = null)
{
if ($gender === static::GENDER_MALE) {
$nameArray = static::randomElement(static::$maleName);
} elseif ($gender === static::GENDER_FEMALE) {
$nameArray = static::randomElement(static::$femaleName);
} else {
$nameArray = static::randomElement(array_merge(static::$maleName, static::$femaleName));
}

return $nameArray[1];
}

再來有相同的程式碼是一個明顯的壞味道,我們來重構,把它抽出方法再呼叫它:

protected function loveName($gender)
{
if ($gender === static::GENDER_MALE) {
return static::randomElement(static::$maleName);
} elseif ($gender === static::GENDER_FEMALE) {
return static::randomElement(static::$femaleName);
} else {
return static::randomElement(array_merge(static::$maleName, static::$femaleName));
}
}

public function name($gender = null)
{
$nameArray = $this->loveName($gender);

return $nameArray[0] . $nameArray[1];
}

public function firstName($gender = null)
{
$nameArray = $this->loveName($gender);

return $nameArray[1];
}

而男女性的呼叫相信大家應該都了解了:

public static function firstNameMale()
{
$nameArray = static::randomElement(static::$maleName);

return $nameArray[1];
}

public static function firstNameFemale()
{
$nameArray = static::randomElement(static::$femaleName);

return $nameArray[1];
}

再來假設我們原本在用 Faker,只要有這個 class,把它放到 Generator 後,就可以產生世間情的角色名字了!

Faker 的介紹到此結束,它是一個值得參考設計的好套件,大家有空也可以翻翻。

參考資料

0%