PHP

Laravel核心概念

2019-06-11  本文已影响0人  简跃

服务容器

Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。

容器

简单来说,容器是一个装载对象、对象的描述(类、接口)或者是提供对象的回调。

一个简单的容器类:

class Container
{
  protected $params = [];  
    
  public function __construct($params){
      $this -> params = $params;
  }  
    
  public function getMailTransport()
  {
    return new Zend_Mail_Transport_Smtp('smtp.gmail.com', [
      'auth'     => 'login',
      'username' => $this->params['username'],
      'password' => $this->params['password'],
      'ssl'      => 'ssl',
      'port'     => 465,
    ]);
  }

  public function getMailer()
  {
    $mailer = new Zend_Mail();
    $mailer->setDefaultTransport($this->getMailTransport());

    return $mailer;
  }
}

在了解服务容器之前,需要先了解什么是依赖注入。

依赖注入

通过构造函数,把一个外部的类的实例注入到某一个类的实例内部,而不是在这个类实例内部创建被注入的类的实例,就是依赖注入(Dependency Injection)。

假设有一个SessionStorage管理类和一个User类,User类需要依赖于SessionStorage类才能正常工作,代码如下:

Session管理类

class SessionStorage
{
    function __construct($cookieName='PHPSESSID')
    {
        session_name($cookieName);
        session_start();
    }

    function set($key, $value)
    {
        $_SESSION[$key] = $value;
    }

    function get($key)
    {
        return $_SESSION[$key];
    }

    // ...
}

用户管理类

class User
{
    protected $storage;

    function __construct()
    {
        $this->storage = new SessionStorage();
    }

    function setLanguage($language)
    {
        $this->storage->set('language', $language);
    }

    function getLanguage()
    {
        return $this->storage->get('language');
    }

    // ...
}

此时如果想要修改CookieName,可通过修改SessionStorage类中的参数,或者将CookieName作为User类的参数传入,但这两种方法均使跟User类无关的东西掺杂到了构造函数中。此时可以采用依赖注入的方法:

class User
{
    // 将 SessionStorage 类的实例作为参数 进行传递
    function __construct($storage)
    {
        $this->storage = $storage;
    }

    // ...
}

使用时:

$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);

通过构造函数,把一个外部SessionStorage的实例注入到User的实例内部,而不是在User实例内部创建SessionStorage实例,就是依赖注入

优势:当测试我们的类时,我们可以模拟依赖类并将其作为参数传递。每个类必须专注于一个特定的任务,而不应该关心解决它们的依赖性。这样,你将拥有一个更专注和可维护的应用程序。

扩展:依赖注入不仅限于在构造函数中使用,也可通过以下场景使用:

  1. Setter注入:

    class User
    {
        function setSessionStorage($storage)
        {
            $this->storage = $storage;
        }
    }
    
  2. 属性注入:

    class User
    {
        public $sessionStorage;
    }
    $user->sessionStorage = $storage;
    
  3. 接口注入:

    interface IUser{
        public function injectUser( SessionStorage $storage );
    }
    
    class UserProvider implements IUser{
        protected $storage;
    
        public function __construct(){
            ...
        }
    
        public function injectUser( SessionStorage $storage ){
            $this->storage = $storage;
        }
    }
    

作为经验, Constructor 注入 最适合必须的依赖关系,比如示例中的情况; Setter 注入 最适合可选依赖关系,比如缓存一个对象实例。

控制反转[1]

简单说来,就是一个类把自己的的控制权交给另外一个对象,类间的依赖由这个对象去解决。

刚刚列了一段代码:

$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);

在这里我们手动的创建了一个用户,并为其注入了Session存储实例,但显然,手动并不是我们的最终目标,我们需要自动化。为此,我们需要一种高级的生产车间,我们只需要向生产车间提交一个脚本,工厂便能够通过指令自动化生产。这种更为高级的工厂,也就是工厂模式的升华 —— 容器(IOC)。

class Container
{
    protected $binds;

    protected $instances;

    public function bind($abstract, $concrete)
    {
        if ($concrete instanceof Closure) {
            $this->binds[$abstract] = $concrete;
        } else {
            $this->instances[$abstract] = $concrete;
        }
    }

    public function make($abstract, $parameters = [])
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        array_unshift($parameters, $this);

        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}

这时候,一个十分粗糙的服务容器就诞生了。先来看看这个容器如何使用吧!

// 创建一个容器
$container = new Container;

// 向容器 添加User的生产脚本
$container->bind('user', function($container, $moduleName) {
    return new User($container->make($moduleName));
});

// 向容器添加 各存储 模块的脚本
$container->bind('storage', function($container) {
    return new SessionStorage;
});
...

// ****************** 华丽丽的分割线 **********************
// 开始启动生产
$user1 = $container->make('user', 'storage');

// ...随意添加

看到没?通过最初的 绑定(bind) 操作,我们向容器注册了一些生产脚本,这些生产脚本在生产指令下达之时便会执行。发现没有?我们彻底的解除了UserSessionStorage的依赖关系,更重要的是,容器类也丝毫没有和他们产生任何依赖!我们通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为生产一个类的实例的脚本,只有在真正的生产(make)操作被调用执行时,才会触发。

Laravel中的应用:

注入一个类:

App::bind('foo', function($app)
{
        return new FooBar;
});

这个例子的意思是创建一个别名为 foo 的类,使用时实际实例化的是 FooBar

使用这个类的方法是:

$value = App::make('foo');

$value 实际上是 FooBar 对象。

如果希望使用单例模式来实例化类,那么使用:

App::singleton('foo', function()
{
        return new FooBar;
});

这样的话每次实例化后的都是同一个对象。

门面模式

它为子系统中的一组接口提供一个統一的高层接口,使得子系統更容易使用。

Laravel中的使用:

Route::get('/',function(){
    return view('welcome');
})

我们来看看Route为何能以静态方法进行访问,简单说就是模拟一个类,提供一个静态魔术方法__callStatic,并将该静态方法映射到真正的方法上。

我们使用的Route类实际上是Illuminate\Support\Facades\Route通过class_alias()函数创造的别名而已,这个类被定义在文件vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php

<?php 
    namespace Illuminate\Support\Facades;

    /**
     * @see \Illuminate\Routing\Router
     */
    class Route extends Facade {

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

}

其实仔细看,会发现这个类继承了一个叫做Facade的类,到这里谜底差不多要解开了。上述定义中,我们看到了getFacadeAccessor方法返回了一个字符route,这是什么意思呢?事实上,这个值被一个 ServiceProvider注册过,大家应该知道注册了个什么,当然是那个真正的路由类。


  1. https://www.insp.top/learn-laravel-container

上一篇 下一篇

猜你喜欢

热点阅读