分解:PHP 魔術(shù)方法在 Laravel 中的應(yīng)用

Laravel

原文鏈接:https://learnku.com/laravel/t/40926

討論請(qǐng)前往專(zhuān)業(yè)的 Laravel 開(kāi)發(fā)者論壇:https://learnku.com/Laravel

Laravel將PHP應(yīng)用到了一個(gè)全新的水平,為您打造下一個(gè)項(xiàng)目提供了出色的開(kāi)發(fā)體驗(yàn)(DX)枷畏。因此银萍,一些人將其稱(chēng)為“魔術(shù)”茴扁。

今天,我將向您展示Laravel的一個(gè)技巧悦穿,魔術(shù)方法吸申。

什么是魔術(shù)方法?

重要的是丛楚,要了解魔術(shù)方法并不是Laravel獨(dú)有的,而是可以在任何PHP應(yīng)用中使用憔辫。Laravel恰好有一些最有趣的魔術(shù)方法用例趣些。

魔術(shù)方法是在PHP中聲明的任何類(lèi)中都可以使用的方法,它提供了在類(lèi)中實(shí)現(xiàn)附加功能的方法贰您。

這里有一個(gè)很好的定義:

魔術(shù)方法永遠(yuǎn)不會(huì)被程序員調(diào)用——實(shí)際上坏平,PHP將在后臺(tái)調(diào)用該方法。這就是為什么它們被稱(chēng)為“魔術(shù)”方法——因?yàn)樗鼈儚膩?lái)沒(méi)有被直接調(diào)用锦亦,它們?cè)试S程序員做一些非常強(qiáng)大的是事情舶替。

總共有15中魔術(shù)方法:

class MyClass
{
    public function __construct() {}

    public function __destruct() {}

    public function __call() {}

    public function __callStatic() {}

    public function __get() {}

    public function __set() {}

    public function __isset() {}

    public function __unset() {}

    public function __sleep() {}

    public function __wakeup() {}

    public function __toString() {}

    public function __invoke() {}

    public function __set_state() {}

    public function __clone() {}

    public function __debuginfo() {}
}

如果您用PHP做過(guò)一些面向?qū)ο蟮木幊蹋敲茨欢ㄖ?__construct 方法孽亲,這是一個(gè)魔術(shù)方法坎穿。所以您一直在使用魔術(shù)方法。

您還注意到返劲,所有的魔術(shù)的方法都是以“__”為前綴的玲昧。

今天,我們不會(huì)深入研究這些方法篮绿,而只會(huì)深入了解整個(gè)Laravel代碼庫(kù)中使用的那些有趣的方法孵延。如果其他人對(duì)此感興趣,請(qǐng)隨時(shí)查看下面的文檔??

PHP: Méthodes magiques - Manual

Laravel是如何使用的魔術(shù)方法

__get()

Laravel中的模型非常特別亲配。它們不將屬性數(shù)據(jù)存儲(chǔ)為類(lèi)的直接屬性尘应,而是存儲(chǔ)在protected $attributes 屬性中惶凝,該屬性是模型所保存的所有數(shù)據(jù)的相關(guān)數(shù)組。

讓我們看看簡(jiǎn)單的PHP類(lèi)和Laravel模型訪問(wèn)屬性的區(qū)別犬钢。

<?php

/**
 * PHP中的普通用戶(hù)類(lèi)(非Laravel)將只是一個(gè)具有上述屬性的類(lèi)
 */
class NormalUser
{
    public $firstName = 'Alice';
}

$normalUser = new NormalUser;

$normalUser->firstName; // 將返回'Alice'

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Laravel中的一個(gè)user類(lèi)
 */
class LaravelUser extends Model
{
    /**
     * 注意苍鲜,我們將所有屬性數(shù)據(jù)存儲(chǔ)在一個(gè)單獨(dú)的數(shù)組中
     */
    protected $attributes = [
        'firstName' => 'Alice',
    ];
}

$laravelUser = new LaravelUser;

$laravelUser->firstName; // 依然返回'Alice'

我們可以看到,上面的PHP和Laravel類(lèi)的行為完全相同玷犹。

然而混滔,在Laravel的例子中,屬性不像普通PHP那樣存儲(chǔ)歹颓,而是集中在一個(gè)名為$attributes的屬性中坯屿。我們?nèi)匀辉O(shè)法訪問(wèn)正確的數(shù)據(jù),但是如何訪問(wèn)呢?

這一切都是可能的巍扛,這是因?yàn)?code>_get魔術(shù)方法领跛。讓我們自己嘗試實(shí)現(xiàn)一個(gè)簡(jiǎn)單的例子。

<?php

class NormalUser
{
    /**
     * 像在Laravel中那樣聲明屬性
     */
    protected $attributes = [
        'firstName' => 'Alice',
    ];

    /**
     *  __get 函數(shù)接收一個(gè)參數(shù)
     * 它將會(huì)是你想要訪問(wèn)的屬性名
     * 在這個(gè)例子中是 $key = "firstName"
     */
    public function __get(string $key)
    {
        return $this->attributes[$key];
    }
}

$normalUser = new NormalUser;

$normalUser->firstName; // 將會(huì)返回 'Alice'

我們做到了撤奸! ??

我們需要注意吠昭,只有在類(lèi)中找不到具有匹配名稱(chēng)的屬性時(shí),才會(huì)調(diào)用魔術(shù)方法_get寂呛。這是一種后備方法怎诫,當(dāng)PHP在類(lèi)中找不到所訪問(wèn)的屬性時(shí)調(diào)用。因此贷痪,在下面的示例中,根本不會(huì)調(diào)用魔術(shù)方法_get蹦误。

<?php

class NormalUser
{
    public $firstName = 'Bob';

    protected $attributes = [
        'firstName' => 'Alice',
    ];

    public function __get($key)
    {
        return $this->attributes[$key];
    }
}

$normalUser = new NormalUser;

/**
 * 由于類(lèi)中存在該屬性劫拢,將會(huì)返回 Bob
 * 所以該例子中沒(méi)有調(diào)用到魔術(shù)方法__get
 */
$normalUser->firstName;

有更多的事情在幕后發(fā)生。如果你想更多地了解 Laravel 的模型是如何確切地使用 __get 的强胰,你可以查看下面的源代碼舱沧。

laravel/framework

__set()

當(dāng)試圖設(shè)置的屬性沒(méi)有在類(lèi)中聲明時(shí),使用魔術(shù)方法_set偶洋。讓我們?cè)俅慰纯磏ormal PHP類(lèi)和Laravel中model模型的區(qū)別熟吏。

<?php

class NormalUser
{
    public $firstName = 'Alice';
}

$normalUser = new NormalUser;

$normalUser->firstName = 'Bob';

$normalUser->firstName; // Will return 'Bob'

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class LaravelUser extends Model
{
    protected $attributes = [
        'firstName' => 'Alice',
    ];
}

$laravelUser = new LaravelUser;

$laravelUser->firstName = 'Bob';

$laravelUser->firstName; // Will return 'Bob' as well

如我們所見(jiàn),在此示例中玄窝,我們?nèi)匀粐L試影響Bob的值牵寺,該值在類(lèi)中實(shí)際上不存在但位于屬性$ attributes中。讓我們嘗試使用魔術(shù)方法__ set

<?php

class NormalUser
{
    public $attributes = [
        'firstName' => 'Alice',
    ];

    /**
     * The magic method __set receives the $name you want to affect the value on
     * and the value
     */
    public function __set($key, $value)
    {
        $this->attributes[$key] = $value;
    }
}

$normalUser = new NormalUser;

$normalUser->firstName = 'Bob';

/**
 * As we don't have the __get magic method define in this example for simplicity sake,
 * we will access the $attributes directly
 */
$normalUser->attributes['firstName']; // Will return 'Bob'

現(xiàn)在我們開(kāi)始恩脂!我們?cè)贚aravel中成功實(shí)施了__ get__ set魔術(shù)方法的基本用法帽氓!他們只需幾行代碼就能完成!

請(qǐng)記住俩块,這些魔術(shù)方法盡可能簡(jiǎn)單黎休,而不必涉及太多細(xì)節(jié)浓领,因?yàn)槌四切┻€有更多而不僅僅是用例,如果您對(duì)它的工作原理感到好奇势腮,我邀請(qǐng)您親自做一些探索联贩! (如果您有任何疑問(wèn),也可以隨時(shí)在Twitter上與我聯(lián)系)

同樣捎拯,如果您想進(jìn)一步挖掘泪幌,請(qǐng)?jiān)诖颂庢溄拥皆创a

laravel/framework

讓我們繼續(xù)最后一個(gè)也是最有趣的一個(gè)事! ??

__call() & __callStatic()

當(dāng)調(diào)用的方法在類(lèi)中找不到時(shí)玄渗,__call()會(huì)被調(diào)用座菠。 在laravel中,該魔術(shù)方法方法使宏在 php 中成為可能藤树。

我不會(huì)深入討論宏的所有細(xì)節(jié)浴滴,但如果您感興趣,這里有一篇很好的文章解釋了如何在 Laravel 應(yīng)用程序中使用它們??

The Magic of Laravel Macros

讓我們?cè)囍纯慈绾尉帉?xiě)一個(gè)簡(jiǎn)單的宏示例岁钓。

<?php

class NormalUser
{
    public $firstName = 'Alice';

    public $lastName = 'Bob';
}

$normalUser = new NormalUser;

$normalUser->fullName(); // 由于沒(méi)有聲明 "fullName" 方法升略,這將會(huì)拋出錯(cuò)誤

使用 __call ,可以定義一個(gè)包含閉包函數(shù)的數(shù)組屡限,在我們開(kāi)發(fā)時(shí)可以程序化地添加到應(yīng)用里品嚣。

<?php

class NormalUser
{
    public $firstName = 'Alice';

    public $lastName = 'Bob';

    /**
     * 將我們的宏初始化為一個(gè)空數(shù)組,后面再賦值
     */
    public static $macros = [];

    /**
     * 定義一個(gè)添加新宏的方法
     * 第一個(gè)參數(shù)是我們想要定義的宏的名字
     * 第二個(gè)參數(shù)是調(diào)用宏時(shí)將會(huì)被執(zhí)行的閉包函數(shù)
     */
    public static function addMacro($name, $macro) {
        static::$macros[$name] = $macro;
    }

    /**
     * "__call" 接收兩個(gè)參數(shù),
     * $name 是被調(diào)用的函數(shù)名稱(chēng)钧大,比如 “fullName”
     * $arguments 是傳遞給函數(shù)的所有參數(shù)翰撑,這里我們使用了一個(gè)空數(shù)組,因?yàn)槲覀兊暮瘮?shù)不用傳參
     */
    public function __call(string $name, array $arguments) {
        /**
         * 通過(guò)名稱(chēng)獲取到宏
         */
        $macro = static::$macros[$name];
        /**
         * 然后通過(guò)參數(shù)執(zhí)行宏
         * 備注:調(diào)用之前啊央,我們需要將閉包綁定到 “$this”眶诈,從而使宏方法在同樣的上下文中執(zhí)行
         */
        return call_user_func_array($macro->bindTo($this, static::class), $arguments);
    }
}

$normalUser = new NormalUser;

$normalUser->fullName(); // 這里會(huì)中斷,因?yàn)槲覀兗葲](méi)有定義 “fullName” 宏瓜饥,也沒(méi)有 “fullName” 方法存在逝撬。

/**
 * 添加 “fullName” 宏方法
 */
NormalUser::addMacro('fullName', function () {
    return $this->firstName.' '.$this->lastName;
});

$normalUser->fullName(); // 現(xiàn)在,返回 “Alice Bob”

宏要比那個(gè)復(fù)雜一些乓土,但是我們?cè)O(shè)法使用 __call 魔術(shù)方法來(lái)創(chuàng)建一個(gè)宏的簡(jiǎn)單工作版本宪潮。

除了對(duì)于靜態(tài)方法, __callStatic__call 是完全一樣的趣苏。

如果你打算自己再深入研究狡相,這里有宏的特性源代碼。

laravel/framework

總結(jié)

所以說(shuō)碼農(nóng)們拦键,當(dāng)你第一次用 Laravel 會(huì)感覺(jué)它神奇是對(duì)的谣光,但是通過(guò)深入查看源代碼,你會(huì)理解魔法是如何在場(chǎng)景背后施展的芬为。

就像現(xiàn)實(shí)生活中的魔法萄金,沒(méi)有道理的事情是不會(huì)發(fā)生的蟀悦,在代碼中就更加是了。程序運(yùn)行的背后總是有著一行代碼在執(zhí)行氧敢,只不過(guò)需要你去發(fā)現(xiàn)它日戈。

原文鏈接:https://learnku.com/laravel/t/40926

討論請(qǐng)前往專(zhuān)業(yè)的 Laravel 開(kāi)發(fā)者論壇:https://learnku.com/Laravel

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市孙乖,隨后出現(xiàn)的幾起案子浙炼,更是在濱河造成了極大的恐慌,老刑警劉巖唯袄,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弯屈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡恋拷,警方通過(guò)查閱死者的電腦和手機(jī)资厉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蔬顾,“玉大人宴偿,你說(shuō)我怎么就攤上這事【骰恚” “怎么了窄刘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)舷胜。 經(jīng)常有香客問(wèn)我娩践,道長(zhǎng),這世上最難降的妖魔是什么烹骨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任欺矫,我火速辦了婚禮,結(jié)果婚禮上展氓,老公的妹妹穿的比我還像新娘。我一直安慰自己脸爱,他們只是感情好遇汞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著簿废,像睡著了一般空入。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上族檬,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天歪赢,我揣著相機(jī)與錄音,去河邊找鬼单料。 笑死埋凯,一個(gè)胖子當(dāng)著我的面吹牛点楼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播白对,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼掠廓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了甩恼?” 一聲冷哼從身側(cè)響起蟀瞧,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎条摸,沒(méi)想到半個(gè)月后悦污,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钉蒲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年切端,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片子巾。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帆赢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出线梗,到底是詐尸還是另有隱情椰于,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布仪搔,位于F島的核電站瘾婿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏烤咧。R本人自食惡果不足惜偏陪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望煮嫌。 院中可真熱鬧笛谦,春花似錦、人聲如沸昌阿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)懦冰。三九已至灶轰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刷钢,已是汗流浹背笋颤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留内地,地道東北人伴澄。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓赋除,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親秉版。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贤重,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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