GVKun编程网logo

Laravel Facade 的详细解读(laravel facades)

20

这篇文章主要围绕LaravelFacade的详细解读和laravelfacades展开,旨在为您提供一份详细的参考资料。我们将全面介绍LaravelFacade的详细解读的优缺点,解答laravelf

这篇文章主要围绕Laravel Facade 的详细解读laravel facades展开,旨在为您提供一份详细的参考资料。我们将全面介绍Laravel Facade 的详细解读的优缺点,解答laravel facades的相关问题,同时也会为您带来Facade 在 Laravel 中的工作机制、Facade---Laravel学习笔记、laravel Facade、Laravel Facade 解读的实用方法。

本文目录一览:

Laravel Facade 的详细解读(laravel facades)

Laravel Facade 的详细解读(laravel facades)

下面由Laravel教程栏目给大家介绍Laravel Facade 的详细解读,希望对需要的朋友有所帮助!

大家好,今天带来的内容是 Laravel 的 Facade 机制实现原理。

Facade的简单使用

数据库的使用:

$users = DB::connection('foo')->select(...);

IOC容器

众所周知,IOC容器是 Laravel 框架的最最重要的部分。它提供了两个功能,IOC和容器。

  • IOC(Inversion of Control),也叫控制反转。说白了,就是控制对象的生成,使开发者不用关心对象的如何生成,只需要关心它的使用即可。
  • 而通过IOC机制生成的对象实例需要一个存放的位置,以便之后继续使用,便是它的容器功能。

这次不准备讲解IOC容器的具体实现,之后会有文章详细解读它。关于IOC容器,读者只需要记住两点即可:

  1. 根据配置生成对象实例;
  2. 保存对象实例,方便随时取用;

简化后 Facade 类

<?PHP
namespace facades;

abstract class Facade
{
    protected static $app;

    /** 
     * Set the application instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public static function setFacadeApplication($app)
    {   
        static::$app = $app;
    } 

    /** 
     * Get the registered name of the component.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    protected static function getFacadeAccessor()
    {
        throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
    }

    /** 
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {   
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    } 

    /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        return static::$app->instances[$name];
    }

    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }   

        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([$instance, $method], $args);
        } 
    }
}

代码说明:

  1. $app中存放的就是一个IOC容器实例,它是在框架初始化时,通过 setFacadeApplication() 这个静态方法设置的
  2. 它实现了 __callStatic 魔术方法
  3. getFacadeAccessor() 方法需要子类去继承,返回一个string的标识,通过这个标识,IOC容器便能返回它所绑定类(框架初始化或其它时刻绑定)的对象
  4. 通过 $instance 调用具体的方法

创建自己的Facade:

TEST1 的具体逻辑:

<?PHP
class Test1{
	public function hello()
	{
		print(hello world);
	}}

TEST1 类的Facade:

<?PHP
namespace facades;/**
 * Class Test1
 * @package facades
 *
 * @method static setoverRecommendInfo [设置播放完毕时的回调函数]
 * @method static setHandlerPlayer [明确指定下一首时的执行类]
 */class Test1Facade extends Facade{
    protected static function getFacadeAccessor()
    {   
        return 'test1';
    }   }

使用:

use facades\Test1Facade;Test1Facade::hello();  // 这是 Facade 调用

解释:

  1. facades\Test1Facade 调用静态方法 hello() 时,由于没有定义此方法,会调用 __callStatic;
  2. 在 __callStatic 中,首先是获取对应的实例,即 return static::$app->instances[$name];。这其中的 $name,即为 facades\Test1 里的 test1
  3. $app, 即为 IOC 容器,类的实例化过程,就交由它来处理。

Facade 在 Laravel 中的工作机制

Facade 在 Laravel 中的工作机制

我们知道 Facade 门面模式是一个中介类,对子系统或者类对象进行封装和代理,为子系统的一组接口提供一个统一的高层接口。它对外暴露子系统的功能,但是不暴露子系统对象,用户要使用子系统的某个功能 (方法) 时,先调用门店,由于门店代理了子系统,所以委托子系统去处理相应的功能请求,从而达到将用户和子系统解耦的目的,用户只需要和门店打交道,不需要知道所有的子系统及其内部构造。

我们接下来通过在 Laravel 中最常用的 DB-Facade,来看看 Facade 在 Laravel 中是如何工作的。

1,代码如下

<?php

use Exception;
use Illuminate\Support\Facades\DB;

Class A {
    function a() {
        try {
            DB::beginTransaction();
            //do something
            DB::commit();
        } catch (Exception $e) {
            DB::rollback();
            // handle exception
        }
    }
}

这里,我们看到 use 了一个 DB 的 Facades,为什么写了 DB 两个字母,就会自动 use DB 的 Facade 呢?这就涉及到 Laravel 的自动加载和依赖注入机制,这里略过。

我们来看看这个类文件的代码:

2, DB Facades

<?php

namespace Illuminate\Support\Facades;

/**
 * @method static \Illuminate\Database\ConnectionInterface connection(string $name = null)
 * @method static string getDefaultConnection()
 * @method static void setDefaultConnection(string $name)
 * @method static \Illuminate\Database\Query\Builder table(string $table)
 * @method static \Illuminate\Database\Query\Expression raw($value)
 * @method static mixed selectOne(string $query, array $bindings = [])
 * @method static array select(string $query, array $bindings = [])
 * @method static bool insert(string $query, array $bindings = [])
 * @method static int update(string $query, array $bindings = [])
 * @method static int delete(string $query, array $bindings = [])
 * @method static bool statement(string $query, array $bindings = [])
 * @method static int affectingStatement(string $query, array $bindings = [])
 * @method static bool unprepared(string $query)
 * @method static array prepareBindings(array $bindings)
 * @method static mixed transaction(\Closure $callback, int $attempts = 1)
 * @method static void beginTransaction()
 * @method static void commit()
 * @method static void rollBack()
 * @method static int transactionLevel()
 * @method static array pretend(\Closure $callback)
 *
 * @see \Illuminate\Database\DatabaseManager
 * @see \Illuminate\Database\Connection
 */
class DB extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return ''db'';
    }
}

发现它只有一个 getFacadeAccessor 静态方法,返回一个 ''db'' 的字符串,其他方法都没有。

那它到底是怎么执行的其他方法呢,比如这里的 beginTranscation () 方法?我们知道,当调用一个不存在的静态方法时,PHP 会自动调用__callStatic () 魔术方法,而这里没有,那我们只能从它继承的 Facade 父类中看看。

3,Facade 父类 / 基类

<?php

namespace Illuminate\Support\Facades;

use Closure;
use Mockery;
use RuntimeException;
use Mockery\MockInterface;

abstract class Facade
{
    /**
     * The application instance being facaded.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected static $app;

    /**
     * The resolved object instances.
     *
     * @var array
     */
    protected static $resolvedInstance;

    /**
     * Run a Closure when the facade has been resolved.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function resolved(Closure $callback)
    {
        static::$app->afterResolving(static::getFacadeAccessor(), function ($service) use ($callback) {
            $callback($service);
        });
    }

    /**
     * Convert the facade into a Mockery spy.
     *
     * @return \Mockery\MockInterface
     */
    public static function spy()
    {
        if (! static::isMock()) {
            $class = static::getMockableClass();

            return tap($class ? Mockery::spy($class) : Mockery::spy(), function ($spy) {
                static::swap($spy);
            });
        }
    }

    /**
     * Initiate a mock expectation on the facade.
     *
     * @return \Mockery\Expectation
     */
    public static function shouldReceive()
    {
        $name = static::getFacadeAccessor();

        $mock = static::isMock()
                    ? static::$resolvedInstance[$name]
                    : static::createFreshMockInstance();

        return $mock->shouldReceive(...func_get_args());
    }

    /**
     * Create a fresh mock instance for the given class.
     *
     * @return \Mockery\Expectation
     */
    protected static function createFreshMockInstance()
    {
        return tap(static::createMock(), function ($mock) {
            static::swap($mock);

            $mock->shouldAllowMockingProtectedMethods();
        });
    }

    /**
     * Create a fresh mock instance for the given class.
     *
     * @return \Mockery\MockInterface
     */
    protected static function createMock()
    {
        $class = static::getMockableClass();

        return $class ? Mockery::mock($class) : Mockery::mock();
    }

    /**
     * Determines whether a mock is set as the instance of the facade.
     *
     * @return bool
     */
    protected static function isMock()
    {
        $name = static::getFacadeAccessor();

        return isset(static::$resolvedInstance[$name]) &&
               static::$resolvedInstance[$name] instanceof MockInterface;
    }

    /**
     * Get the mockable class for the bound instance.
     *
     * @return string|null
     */
    protected static function getMockableClass()
    {
        if ($root = static::getFacadeRoot()) {
            return get_class($root);
        }
    }

    /**
     * Hotswap the underlying instance behind the facade.
     *
     * @param  mixed  $instance
     * @return void
     */
    public static function swap($instance)
    {
        static::$resolvedInstance[static::getFacadeAccessor()] = $instance;

        if (isset(static::$app)) {
            static::$app->instance(static::getFacadeAccessor(), $instance);
        }
    }

    /**
     * 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|object  $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];
    }

    /**
     * Clear a resolved facade instance.
     *
     * @param  string  $name
     * @return void
     */
    public static function clearResolvedInstance($name)
    {
        unset(static::$resolvedInstance[$name]);
    }

    /**
     * Clear all of the resolved instances.
     *
     * @return void
     */
    public static function clearResolvedInstances()
    {
        static::$resolvedInstance = [];
    }

    /**
     * Get the application instance behind the facade.
     *
     * @return \Illuminate\Contracts\Foundation\Application
     */
    public static function getFacadeApplication()
    {
        return static::$app;
    }

    /**
     * Set the application instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public static function setFacadeApplication($app)
    {
        static::$app = $app;
    }

    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     *
     * @throws \RuntimeException
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException(''A facade root has not been set.'');
        }

        return $instance->$method(...$args);
    }
}

这里的基类是给所有 Facade 子类继承的,很明显,是不可能有子类的静态方法的。所以只能看__callStatic () 方法,也就是最后这个。

    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException(''A facade root has not been set.'');
        }

        return $instance->$method(...$args);
    }

它的第一步,是获取在 Facade 后面的 根对象 (root object)。

   /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

这里的 static::getFacadeAccessor () 方法是不是很眼熟,对,它就是 DB-Facade 类里面唯一的一个方法,返回字符串 ''db''。OK,我们看它的主方法 resolveFacadeInstance,代码如下。它会从容器中解析 facade 根实例。

   /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $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];
    }

如果是第一次解析,它就会从 static::$app 这个数组中,去获取 key 为 字符串 ''db'' 的值。

那我们看看 static::$app 是什么。

    /**
     * The application instance being facaded.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected static $app;

是正在被门店代理的应用实例,来自于 \Illuminate\Contracts\Foundation\Application.

这是一个契约,有契约就会有实现契约的类,全局搜一下整个框架,发现,唯一的实现,是在 namespace Illuminate\Foundation\Application 下 (vendor/laravel/framework/src/Illuminate/Foundation/Application.php),我们来看看

<?php

namespace Illuminate\Foundation;

.
.
use Symfony\Component\HttpKernel\HttpKernelInterface;
.
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    /**
     * Create a new Illuminate application instance.
     *
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();

        $this->registerBaseServiceProviders();

        $this->registerCoreContainerAliases();
    }

}

代码太多,就截取了最关键的部分。可以看到这个类实现了上面的 ApplicationContract。

看到构造方法,其实很熟悉了,是在将各种基本的绑定,基本的服务提供者,还有核心容器别名,注入到 服务容器中,也就是 $app 中。

也就是说,我们要在服务容器 $app 中,找一个名为 ''db'' 的 facade 根实例。

2,$app

我们知道,在 Laravel 的入口文件 index.php 文件中,第一步是注册自动加载器,第二步实例化出服务容器 $app,第三步是将内核启动,处理请求。

其中在处理请求中,有一个步骤是 bootstrap (),为 HTTP 请求运行起应用。

    /**
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

这里会让服务容器 $app 将 很多需要启动的组件给启动起来,如下:

    /**
     * The bootstrap classes for the application.
     *
     * @var array
     */
    protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];

而 $this->app->bootstrapWith () 是怎么处理这些 bootstrap 类呢?

这里的 bootstrapWith () 方法,点进去,它是在  vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php 文件中,

namespace Illuminate\Contracts\Foundation;

use Closure;
use Illuminate\Contracts\Container\Container;

interface Application extends Container
{
   /**
     * Run the given array of bootstrap classes.
     *
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers);

}
    

又是一个接口,那谁实现的呢?就是上面提到的 vendor/laravel/framework/src/Illuminate/Foundation/Application.php,代码如下

   /**
     * Run the given array of bootstrap classes.
     *
     * @param  string[]  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this[''events'']->dispatch(''bootstrapping: ''.$bootstrapper, [$this]);

            $this->make($bootstrapper)->bootstrap($this);

            $this[''events'']->dispatch(''bootstrapped: ''.$bootstrapper, [$this]);
        }
    }

这里传入一个数组,然后实例化他们,获取到对象后,分别调用了各自的 bootstrap 方法。

我们看两个:RegisterFacades 和 RegisterProviders。

2.1 RegisterFacades 的 bootstrap ()

文件: vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php

<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\Facades\Facade;
use Illuminate\Foundation\PackageManifest;
use Illuminate\Contracts\Foundation\Application;

class RegisterFacades
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance(array_merge(
            $app->make(''config'')->get(''app.aliases'', []),
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }
}

它关键的一步,是将 config/app.php 文件中的 aliases 这些 类的别名 和 具体的 Facades,加上 PackageManifest 包列表的别名,一起打包, 将这些映射关系 调用 register () 方法,进行注册。注意以下的注释部分,这些别名是被 懒加载 处理的,也就是说,等到程序要用的时候才会真正被加载进来,所以不会影响性能。

/*
    |--------------------------------------------------------------------------
    | Class Aliases
    |--------------------------------------------------------------------------
    |
    | This array of class aliases will be registered when this application
    | is started. However, feel free to register as many as you wish as
    | the aliases are "lazy" loaded so they don''t hinder performance.
    |
    */

    ''aliases'' => [

        ''App''          => Illuminate\Support\Facades\App::class,
        .
        ''DB''           => Illuminate\Support\Facades\DB::class,

    ],

这是 RegisterFacades 部分。我们再看 RegisterProviders。

2.2 RegisterProviders

目录: vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php

<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Contracts\Foundation\Application;

class RegisterProviders
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
}

这里的 registerConfiguredProviders () 方法点进去,又是一个接口:

目录: vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php

<?php

namespace Illuminate\Contracts\Foundation;

use Illuminate\Contracts\Container\Container;

interface Application extends Container
{   
   /**
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders();
}

具体的实现,还是在 vendor/laravel/framework/src/Illuminate/Foundation/Application.php

<?php

namespace Illuminate\Foundation;


use Illuminate\Support\ServiceProvider;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;
use Symfony\Component\HttpKernel\HttpKernelInterface;
.
use Illuminate\Contracts\Http\Kernel as HttpKernelContract;
.
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
   /**
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->config[''app.providers''])
                        ->partition(function ($provider) {
                            return Str::startsWith($provider, ''Illuminate\\'');
                        });

        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }
}

它会将 config/app.php 配置文件的 providers, 以 ''Illuminate\\'' 开头的类 当做第一部分,其他的为第二部分。然后将 packageManifest 插入到中间,最后,providers 就是这个三个元素的数组,如下:

object(Illuminate\Support\Collection)#32 (1) {
  ["items":protected]=>
  array(3) {
    [0]=>
    object(Illuminate\Support\Collection)#22 (1) {
      ["items":protected]=>
      array(22) {
        [0]=>
        string(35) "Illuminate\Auth\AuthServiceProvider"
        [1]=>
        string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
        [2]=>
        string(33) "Illuminate\Bus\BusServiceProvider"
        [3]=>
        string(37) "Illuminate\Cache\CacheServiceProvider"
        [4]=>
        string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
        [5]=>
        string(39) "Illuminate\Cookie\CookieServiceProvider"
        [6]=>
        string(43) "Illuminate\Database\DatabaseServiceProvider"
        [7]=>
        string(47) "Illuminate\Encryption\EncryptionServiceProvider"
        [8]=>
        string(47) "Illuminate\Filesystem\FilesystemServiceProvider"
        [9]=>
        string(57) "Illuminate\Foundation\Providers\FoundationServiceProvider"
        [10]=>
        string(38) "Illuminate\Hashing\HashServiceProvider"
        [11]=>
        string(35) "Illuminate\Mail\MailServiceProvider"
        [12]=>
        string(52) "Illuminate\Notifications\NotificationServiceProvider"
        [13]=>
        string(47) "Illuminate\Pagination\PaginationServiceProvider"
        [14]=>
        string(43) "Illuminate\Pipeline\PipelineServiceProvider"
        [15]=>
        string(37) "Illuminate\Queue\QueueServiceProvider"
        [16]=>
        string(37) "Illuminate\Redis\RedisServiceProvider"
        [17]=>
        string(54) "Illuminate\Auth\Passwords\PasswordResetServiceProvider"
        [18]=>
        string(41) "Illuminate\Session\SessionServiceProvider"
        [19]=>
        string(49) "Illuminate\Translation\TranslationServiceProvider"
        [20]=>
        string(47) "Illuminate\Validation\ValidationServiceProvider"
        [21]=>
        string(35) "Illuminate\View\ViewServiceProvider"
      }
    }
    [1]=>
    array(9) {
      [0]=>
      string(43) "Fideloper\Proxy\TrustedProxyServiceProvider"
      [1]=>
      string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
      [2]=>
      string(52) "Illuminate\Notifications\NexmoChannelServiceProvider"
      [3]=>
      string(52) "Illuminate\Notifications\SlackChannelServiceProvider"
      [4]=>
      string(36) "Laravel\Tinker\TinkerServiceProvider"
      [5]=>
      string(38) "Maatwebsite\Excel\ExcelServiceProvider"
      [6]=>
      string(30) "Carbon\Laravel\ServiceProvider"
      [7]=>
      string(45) "SimpleSoftwareIO\QrCode\QrCodeServiceProvider"
      [8]=>
      string(43) "Spatie\Permission\PermissionServiceProvider"
    }
    [2]=>
    object(Illuminate\Support\Collection)#30 (1) {
      ["items":protected]=>
      array(8) {
        [22]=>
        string(32) "App\Providers\AppServiceProvider"
        [23]=>
        string(33) "App\Providers\AuthServiceProvider"
        [24]=>
        string(34) "App\Providers\EventServiceProvider"
        [25]=>
        string(34) "App\Providers\RouteServiceProvider"
        [26]=>
        string(39) "App\Providers\ApiRequestServiceProvider"
        [27]=>
        string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
        [28]=>
        string(38) "Maatwebsite\Excel\ExcelServiceProvider"
        [29]=>
        string(33) "App\Providers\TestServiceProvider"
      }
    }
  }
}

然后用 vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php 这个服务提供者仓库(ProviderRepository),将这些服务提供者,逐个 load 加载注册 到 $app 中。

<?php

namespace Illuminate\Foundation;

use Exception;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class ProviderRepository
{
    /**
     * Register the application service providers.
     *
     * @param  array  $providers
     * @return void
     */
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();

        // First we will load the service manifest, which contains information on all
        // service providers registered with the application and which services it
        // provides. This is used to know which services are "deferred" loaders.
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }

        // Next, we will register events to load the providers for each of the events
        // that it has requested. This allows the service provider to defer itself
        // while still getting automatically loaded when a certain event occurs.
        foreach ($manifest[''when''] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }

        // We will go ahead and register all of the eagerly loaded providers with the
        // application so their services can be registered with the application as
        // a provided service. Then we will set the deferred service list on it.
        foreach ($manifest[''eager''] as $provider) {
            $this->app->register($provider);
        }

        $this->app->addDeferredServices($manifest[''deferred'']);
    }
}

执行顺序如下:

2.2.1 如果存在服务提供者缓存清单,则直接读取「服务提供者」集合;

2.2.2 否则,将从 config/app.php 配置中的服务提供者编译到缓存清单中;

2.2.2.1 这个处理由 compileManifest() 方法完成,之后 $manifest 是一个数组,含 providers, eager, when, deferred 四个字段,如下

array(4) {
  ["when"]=>
  array(14) {
    ["Illuminate\Broadcasting\BroadcastServiceProvider"]=>
    array(0) {
    }
    ["Illuminate\Bus\BusServiceProvider"]=>
    array(0) {
    }
    ["Illuminate\Cache\CacheServiceProvider"]=>
    array(0) {
    }
    .
  }
  ["providers"]=>
  array(39) {
    [0]=>
    string(35) "Illuminate\Auth\AuthServiceProvider"
    [1]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    [2]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    [3]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    [4]=>
    string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
    [5]=>
    string(39) "Illuminate\Cookie\CookieServiceProvider"
    [6]=>
    string(43) "Illuminate\Database\DatabaseServiceProvider"
     .
    [31]=>
    string(32) "App\Providers\AppServiceProvider"
    [32]=>
    string(33) "App\Providers\AuthServiceProvider"
    [33]=>
    string(34) "App\Providers\EventServiceProvider"
    [34]=>
    string(34) "App\Providers\RouteServiceProvider"
    [35]=>
    string(39) "App\Providers\ApiRequestServiceProvider"
    [36]=>
    string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
    [37]=>
    string(38) "Maatwebsite\Excel\ExcelServiceProvider"
    [38]=>
    string(33) "App\Providers\TestServiceProvider"
  }
  ["eager"]=>
  array(25) {
    [0]=>
    string(35) "Illuminate\Auth\AuthServiceProvider"
    [1]=>
    string(39) "Illuminate\Cookie\CookieServiceProvider"
    [2]=>
    string(43) "Illuminate\Database\DatabaseServiceProvider"
    [3]=>
    string(47) "Illuminate\Encryption\EncryptionServiceProvider"
    [4]=>
    string(47) "Illuminate\Filesystem\FilesystemServiceProvider"
    [5]=>
    string(57) "Illuminate\Foundation\Providers\FoundationServiceProvider"
    .
    [17]=>
    string(32) "App\Providers\AppServiceProvider"
    [18]=>
    string(33) "App\Providers\AuthServiceProvider"
    [19]=>
    string(34) "App\Providers\EventServiceProvider"
    [20]=>
    string(34) "App\Providers\RouteServiceProvider"
    [21]=>
    string(39) "App\Providers\ApiRequestServiceProvider"
    [22]=>
    string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
    [23]=>
    string(38) "Maatwebsite\Excel\ExcelServiceProvider"
    [24]=>
    string(33) "App\Providers\TestServiceProvider"
  }
  ["deferred"]=>
  array(103) {
    ["Illuminate\Broadcasting\BroadcastManager"]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    ["Illuminate\Contracts\Broadcasting\Factory"]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    ["Illuminate\Contracts\Broadcasting\Broadcaster"]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    ["Illuminate\Bus\Dispatcher"]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    ["Illuminate\Contracts\Bus\Dispatcher"]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    ["Illuminate\Contracts\Bus\QueueingDispatcher"]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    ["cache"]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    ["cache.store"]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    ["memcached.connector"]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    ["command.cache.clear"]=>
    string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
    .
    string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
    ["migrator"]=>
    .
  }
}

我们看到 config/app.php 中的 providers 都被加定义在 eager 字段下,也就是饥渴加载。

2.2.2.2 编译缓存清单时将处理 贪婪 / 饥渴加载(eager)和 延迟加载(deferred)的服务提供者;

2.2.3 对于贪婪加载的提供者直接执行服务容器的 register 方法完成服务注册;

2.2.4 将延迟加载提供者加入到服务容器中,按需注册和引导启动。

这边关注需要被饥渴加载的服务提供则,它调用了 register () 方法,同样是 vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php 这个 契约接口,仍然是由 Illuminate\Foundation\Application 来实现的,具体如下:

/**
     * Register a service provider with the application.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  bool   $force
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $force = false)
    {
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // If the given "provider" is a string, we will resolve it, passing in the
        // application instance automatically for the developer. This is simply
        // a more convenient way of specifying your service provider classes.
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        if (method_exists($provider, ''register'')) {
            $provider->register();
        }

        // If there are bindings / singletons set as properties on the provider we
        // will spin through them and register them with the application, which
        // serves as a convenience layer while registering a lot of bindings.
        if (property_exists($provider, ''bindings'')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }

        if (property_exists($provider, ''singletons'')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }

        $this->markAsRegistered($provider);

        // If the application has already booted, we will call this boot method on
        // the provider class so it has an opportunity to do its boot logic and
        // will be ready for any usage by this developer''s application logic.
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

关键是这一步,如果 provider 的控制器中有 register 方法,则执行该方法。

if (method_exists($provider, ''register'')) {
    $provider->register();
}

我们在上面的 eager 字段中包含了 Illuminate\Database\DatabaseServiceProvider 这个服务提供者,让我们来看看它的注册方法。

<?php

namespace Illuminate\Database;

.
.

class DatabaseServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
        Model::setConnectionResolver($this->app[''db'']);

        Model::setEventDispatcher($this->app[''events'']);
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        Model::clearBootedModels();

        $this->registerConnectionServices();

        $this->registerEloquentFactory();

        $this->registerQueueableEntityResolver();
    }

    /**
     * Register the primary database bindings.
     *
     * @return void
     */
    protected function registerConnectionServices()
    {
        // The connection factory is used to create the actual connection instances on
        // the database. We will inject the factory into the manager so that it may
        // make the connections while they are actually needed and not of before.
        $this->app->singleton(''db.factory'', function ($app) {
            return new ConnectionFactory($app);
        });

        // The database manager is used to resolve various connections, since multiple
        // connections might be managed. It also implements the connection resolver
        // interface which may be used by other components requiring connections.
        $this->app->singleton(''db'', function ($app) {
            return new DatabaseManager($app, $app[''db.factory'']);
        });

        $this->app->bind(''db.connection'', function ($app) {
            return $app[''db'']->connection();
        });
    }
 .
 .
 .
}

我们看到它的 register () 方法调用了 registerConnectionServices,而在这个方法里面,我们看到第二步,把 DatabaseManager 对象以 ''db'' 的别名 注册到了 $app 容器中。

百转千回,山重水复,至此,真相终于大白。

原来 Facade 要解析的根对象,就来自于被注册到服务容器 $app 中的 服务提供者。

   /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $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];
    }

在 调用 DB Facade 中的 beginTransaction () 方法时,实际是由 Illuminate\Database\DatabaseServiceProvider 这个服务提供者 中的 DatabaseManager 去执行的,他们通过 Facade 的别名 ''db'',以及 服务提供者下的 DatabaseManager 的别名 ''db'' 进行关联的。

 

额外说一点,我们在说 将应用启动时,只讲了 RegisterFacades 和 RegisterProviders,其实下面还有一步 BootProviders. 它的步骤和 RegisterProviders 差不多,也是通过应用契约,最后让应用去调用每个 Provider 的方法,只不过 RegisterProviders 调用的是 register () 方法,而 BootProviders 调用的是 boot () 方法。一个是注册,一个是启动。

    /**
     * The bootstrap classes for the application.
     *
     * @var array
     */
    protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];

至此,我们把 Facade 在 Laravel 中的工作机制跑通了一遍,顺带着把服务提供者的注册和启动也过了一遍,让我们对 Laravel 的请求生命周期也有了更深的了解。

Facade---Laravel学习笔记

Facade---Laravel学习笔记

Laravel中的Facade提供了对于类的更简便的访问方法。在通过Facade访问类时,是不需要手动对类进行实例化的,对于类的所有可访问的方法(不管是是静态还是非静态方法),都可以采用访问静态方法的方式进行访问,即SomeObjectFacade::someMethod($parameters)

其实某个类的facade可以看作是具体的相关的类的实例的一个代理,而这个实例是在服务容器中注册的。

现在有一个类App\LearningTest\Bar:

<?php
namespace App\LearningTest;
class Bar
{
  public function test ()
  {
    echo ''Mes from Bar::test'';
    return ''Mes from Bar::test'';
  }
}

以下是这个类的facade的实现:

<?php

namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class Bar extends Facade
{
    protected static function getFacadeAccessor()
    {
      return ''bar'';
    }
}

在通过App\Facades\Bar访问相应的类时,实际上是通过getFacadeAccessor返回的字符串''bar''去容器中查询该字符串绑定的所注册的类的实例。即

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
        $this->app->bind(''bar'', function () {
            return new \App\LearningTest\Bar();
        });

        $this->app->alias(''bar'', ''App\LearningTest\Bar'');
    }
}

可见,字符串''bar''通过中间的容器将facade与具体的类的实例联系了起来。

最好,通过在config/app.php中将App\Facades\Bar注册到aliases数组中,即

''Bar'' => App\Facades\Bar::class

就可以在Laravel应用中通过该别名而不需要特别引入就可以访问App\LearningTest\Bar实例的相关方法了,比如在某个类中调用test方法:Bar::test(),但不需要明显的引入App\LearningTest\Bar。

laravel Facade

laravel Facade

Facade的调用原理和过程

创建一个类  CacheManager.PHP(默认所有组件都在同一目录下)

创建一个provider  CacheServiceProvider

class CacheServiceProvider extends ServiceProvider
{
    public function register()
{
    $this->app->singleton('cache',function($app){
       return new CacheManager($app);
});
}
}  

服务容器($this->app)在服务提供者(ServiceProvider)内绑定服务(CacheManager)

singleton将类绑定到容器中

 

在config/app.PHP中注册服务提供者

'providers' => [
        /*
         * Laravel Framework Service Providers...
         */
 'Illuminate\Cache\CacheServiceProvider',
]    

 

创建facades,调用容器方法

class Cache extends Facade
{
    protected static function getFacadeAccessor()
    {
       return 'cache';
    }
}

 

基类Facade

所有门脸类都是继承自Facade类,再该基类中定义了一个__callStatic方法,以至于我们可以轻松的使用facade

 public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }
 public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

getFacadeRoot方法将根据getFacadeAccessor()的返回值,从容器中取出对应的实例对象。

 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];
    }

 

 

 

 

Laravel Facade 解读

Laravel Facade 解读

大家好,今天带来的内容是 Laravel 的 Facade 机制实现原理。

Facade的简单使用

数据库的使用:

$users = DB::connection('foo')->select(...);

IOC容器

众所周知,IOC容器是 Laravel 框架的最最重要的部分。它提供了两个功能,IOC和容器。

IOC(Inversion of Control),也叫控制反转。说白了,就是控制对象的生成,使开发者不用关心对象的如何生成,只需要关心它的使用即可。

而通过IOC机制生成的对象实例需要一个存放的位置,以便之后继续使用,便是它的容器功能。

这次不准备讲解IOC容器的具体实现,之后会有文章详细解读它。关于IOC容器,读者只需要记住两点即可:

根据配置生成对象实例;

保存对象实例,方便随时取用;

简化后 Facade 类

PHP

namespace facades;

/**

* Class Test1

* @package facades

*

* @method static setoverRecommendInfo [设置播放完毕时的回调函数]

* @method static setHandlerPlayer [明确指定下一首时的执行类]

*/

class Test1Facade extends Facade

{

protected static function getFacadeAccessor()

{

return 'test1';

}

}

使用:

use facadesTest1Facade;

Test1Facade::hello(); // 这是 Facade 调用

解释:

facadesTest1Facade 调用静态方法 hello() 时,由于没有定义此方法,会调用 __callStatic;

在 __callStatic 中,首先是获取对应的实例,即 return static::$app->instances[$name];。这其中的 $name,即为 facadesTest1 里的 test1

$app, 即为 IOC 容器,类的实例化过程,就交由它来处理。

今天的关于Laravel Facade 的详细解读laravel facades的分享已经结束,谢谢您的关注,如果想了解更多关于Facade 在 Laravel 中的工作机制、Facade---Laravel学习笔记、laravel Facade、Laravel Facade 解读的相关知识,请在本站进行查询。

本文标签: