PHP后期靜態(tài)綁定的一個應(yīng)用

一個需求

做的某項目有一個“轉(zhuǎn)賬”的功能谈截,但是轉(zhuǎn)賬的類型有很多種,對應(yīng)每種轉(zhuǎn)賬需要的參數(shù)也不同涧偷,舉個例子一種轉(zhuǎn)賬是由系統(tǒng)轉(zhuǎn)賬給用戶簸喂,那么就只有接收方和金額兩個參數(shù),另一種轉(zhuǎn)賬是用戶之間的轉(zhuǎn)賬且支持留言燎潮,那么就有發(fā)送方接收方金額和留言四個參數(shù)娘赴。當(dāng)然最簡單的思路就是采用四個參數(shù),對于第一種轉(zhuǎn)賬將不用的兩個參數(shù)留空跟啤,這種方法的問題在于诽表,考慮到未來可能增加的新的轉(zhuǎn)賬類型唉锌,可能會引入新的參數(shù),那么代碼很可能需要推倒重來竿奏,有沒有更優(yōu)雅的解決方式呢袄简?

一個例子

其實Laravel里就有實現(xiàn)類似需求的例子,那就是查詢構(gòu)造器(Query Builder)泛啸,它的一個使用的例子如下:

$users = DB::table('users')
                     ->select(DB::raw('count(*) as user_count, status'))
                     ->where('status', '<>', 1)
                     ->groupBy('status')
                     ->get();

這個方法和我們的需求就很像了绿语,對于查詢這一功能,傳入哪些參數(shù)是未知的候址,例如某次具體的查詢吕粹,可能需要調(diào)用groupBy也可能調(diào)用orderBy,也可能兩者需要同時調(diào)用或者都不調(diào)用岗仑。一個思路就是針對每一個參數(shù)都寫一個方法匹耕,需要時則調(diào)用,不需要時則不調(diào)用荠雕。

解決方案

整體的解決思路是寫兩個類稳其,一個叫Transfer,一個叫Builder炸卑,每個參數(shù)對應(yīng)的方法寫在Builder里既鞠,由Transfer去調(diào)用Builder構(gòu)造我們需要的轉(zhuǎn)賬類型,完成相關(guān)操作盖文。這樣當(dāng)需求更新時(要增加新的參數(shù)時)嘱蛋,只要在Builder里添加相應(yīng)的方法即可,而不用改動現(xiàn)有代碼五续。下面先貼一下對應(yīng)的代碼再做詳細(xì)解釋浑槽。
Transfer類代碼:

class Transfer
{
    public function __call($method, $parameters)
    {
        $builder = new Builder();
        return call_user_func_array([$builder, $method], $parameters);
    }

    public static function __callStatic($method, $parameters)
    {
        $instance = new static;
        return call_user_func_array([$instance, $method], $parameters);
    }
}

Builder類實際上只涉及到具體的功能實現(xiàn),就貼部分代碼意思意思看看就行:

class Builder
{
    protected $from = 0; // 0 represents system
    protected $to = 0;
    protected $amount = 0;
    protected $comments = '';
    protected $related = [];

    public function from($user)
    {
        if ($user instanceof User) {
            $this->from = $user->getAuthIdentifier();
        } elseif (is_int($user)) {
            $this->from = $user;
        } else {
            throw new InvalidArgumentException(sprintf('%s excepts $user parameter to be \App\User or integer, %s given.', __METHOD__, gettype($user)));
        }
        return $this;
    }
    public function to($user){...}
    public function amount(int $amount){...}
    public function comments($comments){...}
    public function related(int $type, int $id, $extra = null){...}
    public function transfer(){...}
}

具體調(diào)用Transfer功能的代碼:

Transfer::from($sender)->to($receiver)->amount($amount)->comments($comments)->related($related_type, $related_id, $related_extra)->transfer();

需要的儲備知識:

  1. PHP魔術(shù)方法__call()和__callStatic()
  2. PHP面向?qū)ο蠡A(chǔ)知識

下面我們來走一遍調(diào)用Transfer的流程來看看返帕。首先調(diào)用了Transfer類中的靜態(tài)方法from桐玻,然而Transfer中并不存在這個靜態(tài)方法,則會自動調(diào)用__callStatic()這個魔術(shù)方法荆萤。這個方法首先實例化了一個static對象镊靴。注意這里的static是一個類名,new出來的$instance是屬于static這個類的一個實例化對象链韭,有點拗口(:з」∠)然后返回時調(diào)用了call_user_func_array這個方法偏竟,這個方法具體可以參考php的手冊,實際上它完成了類似$instance->method($parameters)這樣的操作敞峭,放到我們當(dāng)前的情境下實際執(zhí)行了$transfer_instance->from($user)這樣的操作踊谋。

然后發(fā)覺Transfer中并不存在這個動態(tài)方法,于是又會自動調(diào)用__call()這個魔術(shù)方法旋讹。這個方法首先創(chuàng)建了一個Builder類的實例殖蚕,之后調(diào)用call_user_func_array這個方法轿衔,實際上相當(dāng)于執(zhí)行了$builder->from($user)方法,然后終于得Builder類里找到了這個from()方法睦疫,注意它的返回值是$this害驹。(補充閱讀:self vs. this in PHP

然后當(dāng)前這個Builder這個對象繼續(xù)調(diào)用to方法,發(fā)覺又不存在又去調(diào)用__call()這個魔術(shù)方法蛤育,之后的過程同上宛官,反復(fù)調(diào)用Builder中的方法把所有需要的參數(shù)都處理過以后最后調(diào)用了transfer()方法最終完成轉(zhuǎn)賬操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓦糕,一起剝皮案震驚了整個濱河市底洗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咕娄,老刑警劉巖亥揖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谭胚,居然都是意外死亡,警方通過查閱死者的電腦和手機未玻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門灾而,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扳剿,你說我怎么就攤上這事旁趟。” “怎么了庇绽?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵锡搜,是天一觀的道長。 經(jīng)常有香客問我瞧掺,道長耕餐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任辟狈,我火速辦了婚禮肠缔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哼转。我一直安慰自己明未,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布壹蔓。 她就那樣靜靜地躺著趟妥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佣蓉。 梳的紋絲不亂的頭發(fā)上披摄,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天亲雪,我揣著相機與錄音,去河邊找鬼行疏。 笑死匆光,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酿联。 我是一名探鬼主播终息,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贞让!你這毒婦竟也來了周崭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤喳张,失蹤者是張志新(化名)和其女友劉穎续镇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體销部,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡摸航,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了舅桩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酱虎。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖擂涛,靈堂內(nèi)的尸體忽然破棺而出读串,到底是詐尸還是另有隱情,我是刑警寧澤撒妈,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布恢暖,位于F島的核電站,受9級特大地震影響狰右,放射性物質(zhì)發(fā)生泄漏杰捂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一棋蚌、第九天 我趴在偏房一處隱蔽的房頂上張望琼娘。 院中可真熱鬧,春花似錦附鸽、人聲如沸脱拼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熄浓。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赌蔑,已是汗流浹背俯在。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留娃惯,地道東北人跷乐。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像趾浅,于是被迫代替她去往敵國和親愕提。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理皿哨,服務(wù)發(fā)現(xiàn)浅侨,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 3,660評論 0 7
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,094評論 25 707
  • ¥開啟¥ 【iAPP實現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程证膨,因...
    小菜c閱讀 6,402評論 0 17
  • Welcome 目前網(wǎng)絡(luò)上充斥著大量的陳舊信息如输,讓PHP新手誤入歧途,傳播著錯誤的實踐和糟糕的代碼央勒,這必須得到糾正...
    layjoy閱讀 21,672評論 7 118