背景

前几篇的关于服务提供者、Facade以及Contract中,我们经常会看到,在类文件中有一个共同的方法construct() ,并且是以__为头,这个呐,就是魔术方法

一、什么是魔术方法呐

首先要了解到魔术方法并不是laravel独有的,而且任务php应用中都可以使用
魔术方法是在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() {}
}

这里附上PHP文档中魔术方法的介绍地址

二、 laravel如何使用魔术方法

__construct

正如一开始所说,我们经常在开发使用到的__construct方法,叫构造方法或者构造器,主要用于当一个对象被实例化的时候会被首先调用,在PHP框架种依赖注入以及中间件一般就是这种方法完成的。当然父类的构造方法是可以被子类继承和重写的

<?php
class A {

    public function __construct() {
        echo "This is A construct\n";
    }
}

class B extends A{

    // 调用父类构造方法,再调用自己的构造方法
    public function __construct() {
        parent::__construct();
        echo "This is B construct\n";
    }
}

class C extends A{

    // 重写构造方法,之调用自己的构造方法
    public function __construct() {
        echo "This is C construct";
    }
}

new A();// This is A construct
new B();// This is A construct This is B construct
new C();// This is c construct

由此可以看出,构造方法是可以帮助我们实现数据或属性初始化的任务,方便在实例化类以后调用

__set

__get

当试图设置的属性没有在类中声明的时候,会使用魔术方法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'; //属性firstName不存在,所以就调用了set方法,修改数组attributes的值

$normalUser->attributes['firstName']; //  'Bob' //调用前面通过set设置好的属性值

__call

__callStatic

这也是经常用到的,例子如下:

class Car{
    public function __call($method,$params=[]){
        echo "car call\n";
    }

    public function color(){
        echo "color red\n";
    }

    protected function isRed(){
        echo "yes is Red\n";
    }

    public function checkColor(){
        $this->color();
        $this->isRed();
    }
}
$car = new Car();
$car->color(); //color red
$car->isRed();//car call

$car->checkColor(); //color red    yes is Red

下面是__callStatic

class Car{
    public static function __callStatic($method,$params=[]){
        echo "car callStatic\n";
    }

    public function color(){
        echo "color red\n";
    }

    protected function isRed(){
        echo "yes is Red\n";
    }

    public function checkColor(){
        Car::color();
        Car::isRed();
    }
}

Car::color(); //color red
Car::isRed();//car callStatic
(new Car())->checkColor();//color red      yes is Read

你会发现在外部以静态方式调用*Car::color()*时,有Notice错误提示,但是内部调用是没有的,那么假如两个魔术方法都存在时,是什么样子的呐???

class Car{
    public static function __callStatic($method,$params=[]){
        echo "car callStatic $method\n";
    }

    public  function __call($method,$params=[]){
        echo "car call $method\n";
    }

    public function checkColor(){
        Car::color();
        Car::isRed();
    }
}

$car = new Car();
Car::color();//car callStatic color
Car::isRed();//car callStatic isRed

$car->color();//car call color
$car->isRed();//car call isRed

(new Car())->checkColor(); //car call color    car call isRed

从结果可以看出,外部静态调用colorisRed时,上下文是静态方式,所以执行callStatic方法。但是在调用当前类对象内部的chaeckColor时,因为是在当前类中调用,所以即使是以静态方式调用,最终执行的还是call方法

这里就有一个小小的总结:

  • __call()方法关注方法能否被访问到,而不仅仅是关注是否存在
  • __callStatic()方法关注的是方法能否被静态的访问到,而不是关注方法是否存在,是否是静态方法
  • 具体执行__call,__callStatic,是根据调用的上下文。如果处于静态上下文内,则调用__callStatic。如果处于对象的上线文内,则调用__call
写在最后

上述分享了几个常用的魔术方法,其他的可以参考文档,自我扩充学习哟
当时要是好奇魔术方法的背后实现逻辑,也可以学习一下源代码点击学习