Laravel Greatest Trick Revealed: Magic Methods What is a magic method ? How Laravel uses magic methods

2025-05-27

Laravel 最厉害的技巧揭秘:魔法方法

什么是魔法方法?

Laravel 如何使用魔法方法

照片由 Mervyn Chan 在 Unsplash 上拍摄
照片由 Mervyn Chan 在 Unsplash 上拍摄

Laravel 将 PHP 的利用提升到了一个全新的高度,为你的下一个项目提供了卓越的开发者体验 (DX)。因此,有些人称之为“魔法”。

今天,我将向你揭示 Laravel 技巧之一,魔术方法

什么是魔法方法?

重要的是要理解,魔法方法并非 Laravel 独有,而是适用于任何 PHP 应用程序。Laravel 恰好拥有一些最有趣的魔法方法用例。

魔术方法是在 PHP 中声明的任何类中可用的方法,它提供了在类中实现附加功能的方法。

这里有一个很好的定义:

魔法方法永远不会被程序员直接调用——实际上,PHP 会在“后台”调用它们。这就是为什么它们被称为“魔法”方法——因为它们永远不会被直接调用,并且允许程序员执行一些非常强大的操作。

总共有15种魔法方法:

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() {}
}
Enter fullscreen mode Exit fullscreen mode

如果你用 PHP 做过一些面向对象编程,你肯定会认出这个__construct方法,它也是一个魔法方法。所以你一直在使用魔法方法!

您还会注意到,所有魔术方法都以 为前缀,__这是惯例。

今天,我们不会深入探讨所有这些特性,而只会介绍 Laravel 代码库中使用的一些有趣的特性。如果您对其他特性感兴趣,欢迎查看下面的文档 👇

PHP:魔法方法 - 手册

Laravel 如何使用魔法方法

__get()

Laravel 中的模型非常特殊。它们不将属性数据存储为类的直接属性,而是存储在一个属性中protected $attributes,该属性是一个关联数组,包含模型所持有的所有数据。

让我们看看简单的 PHP 类和 Laravel 模型访问属性之间的区别。

<?php

/**
 * A normal user class in PHP (without Laravel) will be just a class with the said attributes
 */
class NormalUser
{
    public $firstName = 'Alice';
}

$normalUser = new NormalUser;

$normalUser->firstName; // Will return 'Alice'
Enter fullscreen mode Exit fullscreen mode
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * A user class in Laravel
 */
class LaravelUser extends Model
{
    /**
     * Note that we store all the attributes data in one and single array
     */
    protected $attributes = [
        'firstName' => 'Alice',
    ];
}

$laravelUser = new LaravelUser;

$laravelUser->firstName; // Will return 'Alice' as well
Enter fullscreen mode Exit fullscreen mode

我们可以看到,上面的 PHP 和 Laravel 类的功能完全相同。然而,在 Laravel 中,属性的存储方式与普通 PHP 不同,而是集中在一个名为 的属性中$attributes。我们仍然可以访问正确的数据,但是该如何操作呢?

这一切都归功于__get魔法方法。让我们尝试在一个示例中实现我们自己的简化版本。

<?php

class NormalUser
{
    /**
     * We declare the attributes that same way as in Laravel
     */
    protected $attributes = [
        'firstName' => 'Alice',
    ];

    /**
     * The function __get receive one parameter
     * which will be the name of the attribute you want to access
     * in this case $key = "firstName"
     */
    public function __get(string $key)
    {
        return $this->attributes[$key];
    }
}

$normalUser = new NormalUser;

$normalUser->firstName; // Will return 'Alice'
Enter fullscreen mode Exit fullscreen mode

我们做到了!🎉

需要注意的是,__get只有当类中找不到名称匹配的属性时,才会调用该魔法方法。它相当于 PHP 在类中找不到所需属性时调用的一种后备方法。因此,在下面的示例中,该魔法方法__get根本不会被调用。

<?php

class NormalUser
{
    public $firstName = 'Bob';

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

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

$normalUser = new NormalUser;

/**
 * Will return 'Bob' as the attribute exists in the class
 * so the magic method __get doesn't get call in this case
 */
$normalUser->firstName;
Enter fullscreen mode Exit fullscreen mode

幕后还有很多事情发生。如果你想深入了解 Laravel 模型的具体用法,__get可以查看下面的源代码。

Laravel/框架

__set()

__set当要设置的属性未在类中声明时,将使用魔术方法。让我们再次看看普通 PHP 类和 Laravel 中的模型之间的区别。

<?php

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

$normalUser = new NormalUser;

$normalUser->firstName = 'Bob';

$normalUser->firstName; // Will return 'Bob'
Enter fullscreen mode Exit fullscreen mode
<?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
Enter fullscreen mode Exit fullscreen mode

如我们所见,在这个例子中,我们仍然尝试影响Bob一个实际上并不存在于类中,但存在于属性内部的属性值$attributes。让我们尝试用魔法方法来实现这种精确的行为__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'
Enter fullscreen mode Exit fullscreen mode

好了!我们成功实现了Laravel 中__get__set魔法方法的基本用法!而且只需要几行代码!

请记住,这些魔法方法尽可能简单,无需赘述太多细节,因为它的意义远不止这些用例。如果您好奇它究竟是如何运作的,我邀请您亲自深入研究代码进行探索!(如果您有任何疑问,也欢迎在 Twitter 上联系我)

再次强调,如果你想了解更多,这里是源代码的链接

Laravel/框架

让我们继续讨论最后一个也是最有趣的案例!🙌

__call()&__callStatic()

__call当调用某个方法但在特定类中找不到时执行。在 Laravel 中,正是这个魔法方法使得 PHP 中的宏成为可能。

我不会详细介绍宏,但如果你感兴趣的话,这里有一篇很好的文章解释了如何在 Laravel 应用程序中使用它们👇

Laravel 宏的魔力

让我们尝试看看如何在纯 PHP 中重现一个简单的宏示例。

<?php

class NormalUser
{
    public $firstName = 'Alice';

    public $lastName = 'Bob';
}

$normalUser = new NormalUser;

$normalUser->fullName(); // This will throw an error as no method "fullName" has been declared.
Enter fullscreen mode Exit fullscreen mode

现在__call,我们可以定义一个数组,其中将包含闭包函数,我们可以在应用程序中以编程方式添加这些闭包函数。

<?php

class NormalUser
{
    public $firstName = 'Alice';

    public $lastName = 'Bob';

    /**
     * We initialise our macros as an empty array that we will fill
     */
    public static $macros = [];

    /**
     * We define this method to add new macro as we go
     * the first argument will be the name of the macro we want to define
     * the second will be a Closure function that will be executed when calling the macro
     */
    public static function addMacro($name, $macro) {
        static::$macros[$name] = $macro;
    }

    /**
     * "__call" receives two parameters,
     * $name which will be the name of the function called in our case "fullName"
     * $arguments which will be all the arguments passed in the function in our case it'll just be an empty array as we can't pass any arguments in our function
     */
    public function __call(string $name, array $arguments) {
        /**
         * We retrieve the macro with the right name
         */
        $macro = static::$macros[$name];
        /**
         * Then we execute the macro with the arguments
         * Note: we need to bind the Closure with "$this" before calling it to allow the macro method to act in the same context
         */
        return call_user_func_array($macro->bindTo($this, static::class), $arguments);
    }
}

$normalUser = new NormalUser;

$normalUser->fullName(); // This will still break as we didn't define the macro "fullName" yet and the method "fullName" doesn't exist either

/**
 * We add the macro function "fullName"
 */
NormalUser::addMacro('fullName', function () {
    return $this->firstName.' '.$this->lastName;
});

$normalUser->fullName(); // Now, returns "Alice Bob"
Enter fullscreen mode Exit fullscreen mode

宏比这稍微复杂一些,但是我们再次设法使用__call魔术方法创建了宏的简单工作版本。

__callStatic__call工作原理与静态方法完全相同。

如果你想自己深入挖掘一下,这里是Macroabletrait 源代码

Laravel/框架

最终拍摄

女士们,先生们,虽然第一次使用 Laravel 时确实会感觉很神奇,但通过查看源代码本身,您可以了解幕后的“魔法”是如何运作的。

就像现实生活中的魔术一样,任何事情的发生都有其背后的原因,代码更是如此。总有一行代码在某个地方执行着它,只是你需要找到它。

我鼓励您深入研究 Laravel 以发挥其魔力,并且不要忘记在 Twitter 上与我分享您的知识!

这篇博文最初发布在我的个人博客上,请拥抱一下🤗

特别感谢:SamuelJean-BaptisteJessica

文章来源:https://dev.to/excessivecoding/laravel-greatest-trick-revealed-magic-methods-31om
PREV
Git 初学者完整指南
NEXT
我必须构建自己的 Markdown 编辑器,因为对我来说没有哪个工具足够快。