接触Laravel一段时间,对它的一些操作的原理比较感兴趣,比如Cache::get('a','b')

这短短的一条语句背后,Laravel进行了哪些工作,让它变成现实呢?今天,我特地研究了一下它的工作原理。理解了这些原理之后,我们也可以实现自己的Facade

要想操作Cacheget方法,必须先有Cache这个类。那么这个类从哪来呢?打开项目app/config/app.php可以看到有一个aliase数组,这里定义了一些框架自带类的别名。我们可以看到,调用Cache类,其实是调用了Illuminate\Support\Facades\Cache这个类。

给类定义别名,可以使用class_alias这个函数: class_alias('Illuminate\Support\Facades\Cache','Cache',true);

来看看Illuminate\Support\Facades\Cache这个类是如何定义的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
namespace Illuminate\Support\Facades;

/**
 * @see \Illuminate\Cache\CacheManager
 * @see \Illuminate\Cache\Repository
 */
class Cache extends Facade {

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'cache'; }

}

有没有发现这个类里根本没有get这个方法?这到底是怎么回事?这就引出了今天的主题Facde

可以看到,Cache类继承了Facade类。Facade其实是一个静态代理(static proxy),它并不提供具体的操作方法,而是实例化一个真正具有该方法的对象,并调用这个方法。我们不妨打开Facade类的定义文件看看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
//摘录部分关键代码
namespace Illuminate\Support\Facades;
abstract class Facade {
     protected static $app;
     /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }
    /**
     * Get the registered name of the component.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    protected static function getFacadeAccessor()
    {
        throw new \RuntimeException("Facade does not implement getFacadeAccessor method.");
    }
    /**
     * Resolve the facade root instance from the container.
     *
     * @param  string  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) return $name;

        if (isset(static::$resolvedInstance[$name]))
        {
            return static::$resolvedInstance[$name];
        }

        return static::$resolvedInstance[$name] = static::$app[$name];
    }
    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        switch (count($args))
        {
            case 0:
                return $instance->$method();

            case 1:
                return $instance->$method($args[0]);

            case 2:
                return $instance->$method($args[0], $args[1]);

            case 3:
                return $instance->$method($args[0], $args[1], $args[2]);

            case 4:
                return $instance->$method($args[0], $args[1], $args[2], $args[3]);

            default:
                return call_user_func_array(array($instance, $method), $args);
        }
    }
}

既然Cache类没有get这个方法,Facade必然得实现__callStatic方法。__callStatic方法很简单——获取对象、调用对象的相应方法。而Cache类实现的getFacadeAccessor方法只不过是告诉Facade应该去获取哪个对象。

所以看到这里,我们可以试着写一个自己的Facade

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
class Facade {

    public static function __callStatic($method,$args){
        $instance=static::getInstance(static::getFacadeAccessor());
        return $instance->$method($args);
    }

    public static function getInstance($name){
        $class=$name.'Real';    //这里只是为了演示,随便定了一个真正调用类的名字
        return new $class;
    }
}


class CacheReal {
    public function test(){
        echo 'hello world';
    }
}

class Cache extends Facade {

    public static function getFacadeAccessor(){
        return 'Cache'; //返回真正执行操作的类的信息
    }
}

function __autoload($class){
    class_alias('Cache',$class,true);   //为了简单演示,所有请求的类定义为Cache类的别名
}

AAA::test();   //屏幕打印出Hello world

看到这里,你应该明白Facade到底是怎么回事了吧?那么使用Facade有什么好处呢?在Laravel里,Facade其实是和IOC联合发挥威力的,我们这里单独讨论Facade。

以我目前粗浅的理解,能感受到的好处有:

  1. 语句更简短。不需要非得new一个对象出来再执行。
  2. 解除调用对象的强依赖关系。当我们执行Cache::set()的方法时,并不需要关心具体是哪个后端的类真正做了set这个操作。如果将来想把缓存从文件改到redis里,只需要在配置文件里修改一下,业务代码完全不受影响。

参考内容:

  1. 官方文档