当前位置:编程学习 > > 正文

laravel请求处理流程(详解如何实现Laravel的服务容器的方法示例)

时间:2022-01-28 01:30:30类别:编程学习

laravel请求处理流程

详解如何实现Laravel的服务容器的方法示例

1. 容器的本质

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • // 服务容器
  • $container = [
  •   // 原始值
  •   'text' => '这是一个字符串',
  •   // 自定义服务名
  •   'customName' => new StdClass(),
  •   // 使用类名作为服务名
  •   'StdClass' => new StdClass(),
  •   // 使用接口名作为服务名
  •   'Namespace\\StdClassInterface' => new StdClass(),
  • ];
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • // 绑定服务到容器
  • $container['standard'] = new StdClass();
  • // 获取服务
  • $standard = $container['standard'];
  • var_dump($standard);
  • 2. 封装成类

    为了方便维护,我们把上面的数组封装到类里面。

    $instances还是上面的容器数组。我们增加两个方法,instance用来绑定服务,get用来从容器中获取服务。

  • ?
  • 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
  • class BaseContainer
  • {
  •  
  •   // 已绑定的服务
  •   protected $instances = [];
  •  
  •   // 绑定服务
  •   public function instance($name, $instance)
  •   {
  •     $this->instances[$name] = $instance;
  •   }
  •  
  •   // 获取服务
  •   public function get($name)
  •   {
  •     return isset($this->instances[$name]) ? $this->instances[$name] : null;
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • $container = new BaseContainer();
  • // 绑定服务
  • $container->instance('StdClass', new StdClass());
  • // 获取服务
  • $stdClass = $container->get('StdClass');
  • var_dump($stdClass);
  • 3. 按需实例化

    现在我们在绑定一个对象服务的时候,就必须要先把类实例化,如果绑定的服务没有被用到,那么类就会白白实例化,造成性能浪费。

    为了解决这个问题,我们增加一个bind函数,它支持绑定一个回调函数,在回调函数中实例化类。这样一来,我们只有在使用服务时,才回调这个函数,这样就实现了按需实例化。

    这时候,我们获取服务时,就不只是从数组中拿到服务并返回了,还需要判断如果是回调函数,就要执行回调函数。所以我们把get方法的名字改成make。意思就是生产一个服务,这个服务可以是已绑定的服务,也可以是已绑定的回调函数,也可以是一个类名,如果是类名,我们就直接实例化该类并返回。

    然后,我们增加一个新数组$bindings,用来存储绑定的回调函数。然后我们把bind方法改一下,判断下$instance如果是一个回调函数,就放到$bindings数组,否则就用make方法实例化类。

  • ?
  • 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
  • class DeferContainer extend BaseContainer
  • {
  •   // 已绑定的回调函数
  •   protected $bindings = [];
  •  
  •   // 绑定服务
  •   public function bind($name, $instance)
  •   {
  •     if ($instance instanceof Closure) {
  •       // 如果$instance是一个回调函数,就绑定到bindings。
  •       $this->bindings[$name] = $instance;
  •     } else {
  •       // 调用make方法,创建实例
  •       $this->instances[$name] = $this->make($name);
  •     }
  •   }
  •  
  •   // 获取服务
  •   public function make($name)
  •   {
  •     if (isset($this->instances[$name])) {
  •       return $this->instances[$name];
  •     }
  •  
  •     if (isset($this->bindings[$name])) {
  •       // 执行回调函数并返回
  •       $instance = call_user_func($this->bindings[$name]);
  •     } else {
  •       // 还没有绑定到容器中,直接new.
  •       $instance = new $name();
  •     }
  •  
  •     return $instance;
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • $container = new DeferContainer();
  • // 绑定服务
  • $container->bind('StdClass', function () {
  •   echo "我被执行了\n";
  •   return new StdClass();
  • });
  • // 获取服务
  • $stdClass = $container->make('StdClass');
  • var_dump($stdClass);
  • StdClass这个服务绑定的是一个回调函数,在回调函数中才会真正的实例化类。如果没有用到这个服务,那回调函数就不会被执行,类也不会被实例化。

    4. 单例

    从上面的代码中可以看出,每次调用make方法时,都会执行一次回调函数,并返回一个新的类实例。但是在某些情况下,我们希望这个实例是一个单例,无论make多少次,只实例化一次。

    这时候,我们给bind方法增加第三个参数$shared,用来标记是否是单例,默认不是单例。然后把回调函数和这个标记都存到$bindings数组里。

    为了方便绑定单例服务,再增加一个新的方法singleton,它直接调用bind,并且$shared参数强制为true。

    对于make方法,我们也要做修改。在执行$bindings里的回调函数以后,做一个判断,如果之前绑定时标记的shared是true,就把回调函数返回的结果存储到$instances里。由于我们是先从$instances里找服务,所以这样下次再make的时候就会直接返回,而不会再次执行回调函数。这样就实现了单例的绑定。

  • ?
  • 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
  • class SingletonContainer extends DeferContainer
  • {
  •   // 绑定服务
  •   public function bind($name, $instance, $shared = false)
  •   {
  •     if ($instance instanceof Closure) {
  •       // 如果$instance是一个回调函数,就绑定到bindings。
  •       $this->bindings[$name] = [
  •         'callback' => $instance,
  •         // 标记是否单例
  •         'shared' => $shared
  •       ];
  •     } else {
  •       // 调用make方法,创建实例
  •       $this->instances[$name] = $this->make($name);
  •     }
  •   }
  •  
  •   // 绑定一个单例
  •   public function singleton($name, $instance)
  •   {
  •     $this->bind($name, $instance, true);
  •   }
  •  
  •   // 获取服务
  •   public function make($name)
  •   {
  •     if (isset($this->instances[$name])) {
  •       return $this->instances[$name];
  •     }
  •  
  •     if (isset($this->bindings[$name])) {
  •       // 执行回调函数并返回
  •       $instance = call_user_func($this->bindings[$name]['callback']);
  •  
  •       if ($this->bindings[$name]['shared']) {
  •         // 标记为单例时,存储到服务中
  •         $this->instances[$name] = $instance;
  •       }
  •     } else {
  •       // 还没有绑定到容器中,直接new.
  •       $instance = new $name();
  •     }
  •  
  •     return $instance;
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • $container = new SingletonContainer();
  • // 绑定服务
  • $container->singleton('anonymous', function () {
  •   return new class
  •   {
  •     public function __construct()
  •     {
  •       echo "我被实例化了\n";
  •     }
  •   };
  • });
  • // 无论make多少次,只会实例化一次
  • $container->make('anonymous');
  • $container->make('anonymous');
  • // 获取服务
  • $anonymous = $container->make('anonymous');
  • var_dump($anonymous)
  • 上面的代码用singleton绑定了一个名为anonymous的服务,回调函数里返回了一个匿名类的实例。这个匿名类在被实例化时会输出一段文字。无论我们make多少次anonymous,这个回调函数只会被执行一次,匿名类也只会被实例化一次。

    5. 自动注入

    自动注入是Ioc容器的核心,没有自动注入就无法做到控制反转。

    自动注入就是指,在实例化一个类时,用反射类来获取__construct所需要的参数,然后根据参数的类型,从容器中找到已绑定的服务。我们只要有了__construct方法所需的所有参数,就能自动实例化该类,实现自动注入。

    现在,我们增加一个build方法,它只接收一个参数,就是类名。build方法会用反射类来获取__construct方法所需要的参数,然后返回实例化结果。

    另外一点就是,我们之前在调用make方法时,如果传的是一个未绑定的类,我们直接new了这个类。现在我们把未绑定的类交给build方法来构建,因为它支持自动注入。

  • ?
  • 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
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • class InjectionContainer extends SingletonContainer
  • {
  •  
  •   // 获取服务
  •   public function make($name)
  •   {
  •     if (isset($this->instances[$name])) {
  •       return $this->instances[$name];
  •     }
  •     if (isset($this->bindings[$name])) {
  •       // 执行回调函数并返回
  •       $instance = call_user_func($this->bindings[$name]['callback']);
  •  
  •       if ($this->bindings[$name]['shared']) {
  •         // 标记为单例时,存储到服务中
  •         $this->instances[$name] = $instance;
  •       }
  •     } else {
  •       // 使用build方法构建此类
  •       $instance = $this->build($name);
  •     }
  •  
  •     return $instance;
  •   }
  •  
  •   // 构建一个类,并自动注入服务
  •   public function build($class)
  •   {
  •  
  •     $reflector = new ReflectionClass($class);
  •  
  •     $constructor = $reflector->getConstructor();
  •  
  •     if (is_null($constructor)) {
  •       // 没有构造函数,直接new
  •       return new $class();
  •     }
  •  
  •     $dependencies = [];
  •  
  •     // 获取构造函数所需的参数
  •     foreach ($constructor->getParameters() as $dependency) {
  •       if (is_null($dependency->getClass())) {
  •         // 参数类型不是类时,无法从容器中获取依赖
  •         if ($dependency->isDefaultValueAvailable()) {
  •           // 查找参数的默认值,如果有就使用默认值
  •           $dependencies[] = $dependency->getDefaultValue();
  •         } else {
  •           // 无法提供类所依赖的参数
  •           throw new Exception('找不到依赖参数:' . $dependency->getName());
  •         }
  •       } else {
  •         // 参数类型是类时,就用make方法构建该类
  •         $dependencies[] = $this->make($dependency->getClass()->name);
  •       }
  •     }
  •  
  •     return $reflector->newInstanceArgs($dependencies);
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • class Redis
  • {
  • }
  •  
  • class Cache
  • {
  •   protected $redis;
  •  
  •   // 构造函数中依赖Redis服务
  •   public function __construct(Redis $redis)
  •   {
  •     $this->redis = $redis;
  •   }
  • }
  •  
  • $container = new InjectionContainer();
  • // 绑定Redis服务
  • $container->singleton(Redis::class, function () {
  •   return new Redis();
  • });
  • // 构建Cache类
  • $cache = $container->make(Cache::class);
  • var_dump($cache);
  • 6. 自定义依赖参数

    现在有个问题,如果类依赖的参数不是类或接口,只是一个普通变量,这时候就无法从容器中获取依赖参数了,也就无法实例化类了。

    那么接下来我们就支持一个新功能,在调用make方法时,支持传第二个参数$parameters,这是一个数组,无法从容器中获取的依赖,就从这个数组中找。

    当然,make方法是用不到这个参数的,因为它不负责实例化类,它直接传给build方法。在build方法寻找依赖的参数时,就先从$parameters中找。这样就实现了自定义依赖参数。

    需要注意的一点是,build方法是按照参数的名字来找依赖的,所以parameters中的键名也必须跟__construct中参数名一致。

  • ?
  • 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
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • class ParametersContainer extends InjectionContainer
  • {
  •   // 获取服务
  •   public function make($name, array $parameters = [])
  •   {
  •     if (isset($this->instances[$name])) {
  •       return $this->instances[$name];
  •     }
  •     if (isset($this->bindings[$name])) {
  •       // 执行回调函数并返回
  •       $instance = call_user_func($this->bindings[$name]['callback']);
  •  
  •       if ($this->bindings[$name]['shared']) {
  •         // 标记为单例时,存储到服务中
  •         $this->instances[$name] = $instance;
  •       }
  •     } else {
  •       // 使用build方法构建此类
  •       $instance = $this->build($name, $parameters);
  •     }
  •  
  •     return $instance;
  •   }
  •  
  •   // 构建一个类,并自动注入服务
  •   public function build($class, array $parameters = [])
  •   {
  •     $reflector = new ReflectionClass($class);
  •  
  •     $constructor = $reflector->getConstructor();
  •  
  •     if (is_null($constructor)) {
  •       // 没有构造函数,直接new
  •       return new $class();
  •     }
  •  
  •     $dependencies = [];
  •  
  •     // 获取构造函数所需的参数
  •     foreach ($constructor->getParameters() as $dependency) {
  •  
  •       if (isset($parameters[$dependency->getName()])) {
  •         // 先从自定义参数中查找
  •         $dependencies[] = $parameters[$dependency->getName()];
  •         continue;
  •       }
  •  
  •       if (is_null($dependency->getClass())) {
  •         // 参数类型不是类或接口时,无法从容器中获取依赖
  •         if ($dependency->isDefaultValueAvailable()) {
  •           // 查找默认值,如果有就使用默认值
  •           $dependencies[] = $dependency->getDefaultValue();
  •         } else {
  •           // 无法提供类所依赖的参数
  •           throw new Exception('找不到依赖参数:' . $dependency->getName());
  •         }
  •       } else {
  •         // 参数类型是类时,就用make方法构建该类
  •         $dependencies[] = $this->make($dependency->getClass()->name);
  •       }
  •     }
  •  
  •     return $reflector->newInstanceArgs($dependencies);
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • class Redis
  • {
  • }
  •  
  • class Cache
  • {
  •   protected $redis;
  •  
  •   protected $name;
  •  
  •   protected $default;
  •  
  •   // 构造函数中依赖Redis服务和name参数,name的类型不是类,无法从容器中查找
  •   public function __construct(Redis $redis, $name, $default = '默认值')
  •   {
  •     $this->redis = $redis;
  •     $this->name = $name;
  •     $this->default = $default;
  •   }
  • }
  •  
  • $container = new ParametersContainer();
  • // 绑定Redis服务
  • $container->singleton(Redis::class, function () {
  •   return new Redis();
  • });
  • // 构建Cache类
  • $cache = $container->make(Cache::class, ['name' => 'test']);
  • var_dump($cache);
  • 提示:实际上,Laravel容器的build方法并没有第二个参数$parameters,它是用类属性来维护自定义参数。原理都是一样的,只是实现方式不一样。这里为了方便理解,不引入过多概念。

    7. 服务别名

    别名可以理解成小名、外号。服务别名就是给已绑定的服务设置一些外号,使我们通过外号也能找到该服务。

    这个就比较简单了,我们增加一个新的数组$aliases,用来存储别名。再增加一个方法alias,用来让外部注册别名。

    唯一需要我们修改的地方,就是在make时,要先从$aliases中找到真实的服务名。

  • ?
  • 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
  • class AliasContainer extends ParametersContainer
  • {
  •   // 服务别名
  •   protected $aliases = [];
  •  
  •   // 给服务绑定一个别名
  •   public function alias($alias, $name)
  •   {
  •     $this->aliases[$alias] = $name;
  •   }
  •  
  •   // 获取服务
  •   public function make($name, array $parameters = [])
  •   {
  •     // 先用别名查找真实服务名
  •     $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
  •  
  •     return parent::make($name, $parameters);
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • $container = new AliasContainer();
  •  
  • // 绑定服务
  • $container->instance('text', '这是一个字符串');
  • // 给服务注册别名
  • $container->alias('string', 'text');
  • $container->alias('content', 'text');
  •  
  • var_dump($container->make('string'));
  • var_dump($container->make('content'));
  • 8. 扩展绑定

    有时候我们需要给已绑定的服务做一个包装,这时候就用到扩展绑定了。我们先看一个实际的用法,理解它的作用后,才看它是如何实现的。

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • // 绑定日志服务
  • $container->singleton('log', new Log());
  •  
  • // 对已绑定的服务再次包装
  • $container->extend('log', function(Log $log){
  •   // 返回了一个新服务
  •   return new RedisLog($log);
  • });
  • 现在我们看它是如何实现的。增加一个$extenders数组,用来存放扩展器。再增加一个extend方法,用来注册扩展器。

    然后在make方法返回$instance之前,按顺序依次调用之前注册的扩展器。

  • ?
  • 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
  • class ExtendContainer extends AliasContainer
  • {
  •   // 存放扩展器的数组
  •   protected $extenders = [];
  •  
  •   // 给服务绑定扩展器
  •   public function extend($name, $extender)
  •   {
  •     if (isset($this->instances[$name])) {
  •       // 已经实例化的服务,直接调用扩展器
  •       $this->instances[$name] = $extender($this->instances[$name]);
  •     } else {
  •       $this->extenders[$name][] = $extender;
  •     }
  •   }
  •  
  •   // 获取服务
  •   public function make($name, array $parameters = [])
  •   {
  •     $instance = parent::make($name, $parameters);
  •  
  •     if (isset($this->extenders[$name])) {
  •       // 调用扩展器
  •       foreach ($this->extenders[$name] as $extender) {
  •         $instance = $extender($instance);
  •       }
  •     }
  •  
  •     return $instance;
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • class Redis
  • {
  •   public $name;
  •  
  •   public function __construct($name = 'default')
  •   {
  •     $this->name = $name;
  •   }
  •  
  •   public function setName($name)
  •   {
  •     $this->name = $name;
  •   }
  • }
  •  
  • $container = new ExtendContainer();
  •  
  • // 绑定Redis服务
  • $container->singleton(Redis::class, function () {
  •   return new Redis();
  • });
  •  
  • // 给Redis服务绑定一个扩展器
  • $container->extend(Redis::class, function (Redis $redis) {
  •   $redis->setName('扩展器');
  •   return $redis;
  • });
  • $redis = $container->make(Redis::class);
  • var_dump($redis->name);
  • 9. 上下文绑定

    有时侯我们可能有两个类使用同一个接口,但希望在每个类中注入不同的实现,例如两个控制器,分别为它们注入不同的Log服务。

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • class ApiController
  • {
  •   public function __construct(Log $log)
  •   {
  •   }
  • }
  •  
  • class WebController
  • {
  •   public function __construct(Log $log)
  •   {
  •   }
  • }
  • 最终我们要用以下方式实现:

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • // 当ApiController依赖Log时,给它一个RedisLog
  • $container->addContextualBinding('ApiController','Log',new RedisLog());
  •  
  • // 当WebController依赖Log时,给它一个FileLog
  • $container->addContextualBinding('WebController','Log',new FileLog());
  • 为了更直观更方便更语义化的使用,我们把这个过程改成链式操作:

  • ?
  • 1
  • 2
  • 3
  • $container->when('ApiController')
  •     ->needs('Log')
  •     ->give(new RedisLog());
  • 我们增加一个$context数组,用来存储上下文。同时增加一个addContextualBinding方法,用来注册上下文绑定。以ApiController为例,$context的真实模样是:

  • ?
  • 1
  • $context['ApiController']['Log'] = new RedisLog();
  • 然后build方法实例化类时,先从上下文中查找依赖参数,就实现了上下文绑定。

    接下来,看看链式操作是如何实现的。

    首先定义一个类Context,这个类有两个方法,needs和give。

    然后在容器中,增加一个when方法,它返回一个Context对象。在Context对象的give方法中,我们已经具备了注册上下文所需要的所有参数,所以就可以在give方法中调用addContextualBinding来注册上下文了。

  • ?
  • 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
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • class ContextContainer extends ExtendContainer
  • {
  •   // 依赖上下文
  •   protected $context = [];
  •  
  •   // 构建一个类,并自动注入服务
  •   public function build($class, array $parameters = [])
  •   {
  •     $reflector = new ReflectionClass($class);
  •  
  •     $constructor = $reflector->getConstructor();
  •  
  •     if (is_null($constructor)) {
  •       // 没有构造函数,直接new
  •       return new $class();
  •     }
  •  
  •     $dependencies = [];
  •  
  •     // 获取构造函数所需的参数
  •     foreach ($constructor->getParameters() as $dependency) {
  •  
  •       if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) {
  •         // 先从上下文中查找
  •         $dependencies[] = $this->context[$class][$dependency->getName()];
  •         continue;
  •       }
  •  
  •       if (isset($parameters[$dependency->getName()])) {
  •         // 从自定义参数中查找
  •         $dependencies[] = $parameters[$dependency->getName()];
  •         continue;
  •       }
  •  
  •       if (is_null($dependency->getClass())) {
  •         // 参数类型不是类或接口时,无法从容器中获取依赖
  •         if ($dependency->isDefaultValueAvailable()) {
  •           // 查找默认值,如果有就使用默认值
  •           $dependencies[] = $dependency->getDefaultValue();
  •         } else {
  •           // 无法提供类所依赖的参数
  •           throw new Exception('找不到依赖参数:' . $dependency->getName());
  •         }
  •       } else {
  •         // 参数类型是一个类时,就用make方法构建该类
  •         $dependencies[] = $this->make($dependency->getClass()->name);
  •       }
  •     }
  •  
  •     return $reflector->newInstanceArgs($dependencies);
  •   }
  •  
  •   // 绑定上下文
  •   public function addContextualBinding($when, $needs, $give)
  •   {
  •     $this->context[$when][$needs] = $give;
  •   }
  •  
  •   // 支持链式方式绑定上下文
  •   public function when($when)
  •   {
  •     return new Context($when, $this);
  •   }
  • }
  •  
  • class Context
  • {
  •   protected $when;
  •  
  •   protected $needs;
  •  
  •   protected $container;
  •  
  •   public function __construct($when, ContextContainer $container)
  •   {
  •     $this->when = $when;
  •     $this->container = $container;
  •   }
  •  
  •   public function needs($needs)
  •   {
  •     $this->needs = $needs;
  •  
  •     return $this;
  •   }
  •  
  •   public function give($give)
  •   {
  •     // 调用容器绑定依赖上下文
  •     $this->container->addContextualBinding($this->when, $this->needs, $give);
  •   }
  • }
  •  
  • // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
  •  
  • class Dog
  • {
  •   public $name;
  •  
  •   public function __construct($name)
  •   {
  •     $this->name = $name;
  •   }
  • }
  •  
  • class Cat
  • {
  •   public $name;
  •  
  •   public function __construct($name)
  •   {
  •     $this->name = $name;
  •   }
  • }
  •  
  • $container = new ContextContainer();
  •  
  • // 给Dog类设置上下文绑定
  • $container->when(Dog::class)
  •   ->needs('name')
  •   ->give('小狗');
  • // 给Cat类设置上下文绑定
  • $container->when(Cat::class)
  •   ->needs('name')
  •   ->give('小猫');
  •  
  • $dog = $container->make(Dog::class);
  • $cat = $container->make(Cat::class);
  • var_dump('Dog:' . $dog->name);
  • var_dump('Cat:' . $cat->name);
  • 10. 完整代码

  • ?
  • 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
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • class Container
  • {
  •   // 已绑定的服务
  •   protected $instances = [];
  •   // 已绑定的回调函数
  •   protected $bindings = [];
  •   // 服务别名
  •   protected $aliases = [];
  •   // 存放扩展器的数组
  •   protected $extenders = [];
  •   // 依赖上下文
  •   protected $context = [];
  •  
  •   // 绑定服务实例
  •   public function instance($name, $instance)
  •   {
  •     $this->instances[$name] = $instance;
  •   }
  •  
  •   // 绑定服务
  •   public function bind($name, $instance, $shared = false)
  •   {
  •     if ($instance instanceof Closure) {
  •       // 如果$instance是一个回调函数,就绑定到bindings。
  •       $this->bindings[$name] = [
  •         'callback' => $instance,
  •         // 标记是否单例
  •         'shared' => $shared
  •       ];
  •     } else {
  •       // 调用make方法,创建实例
  •       $this->instances[$name] = $this->make($name);
  •     }
  •   }
  •  
  •   // 绑定一个单例
  •   public function singleton($name, $instance)
  •   {
  •     $this->bind($name, $instance, true);
  •   }
  •  
  •   // 给服务绑定一个别名
  •   public function alias($alias, $name)
  •   {
  •     $this->aliases[$alias] = $name;
  •   }
  •  
  •   // 给服务绑定扩展器
  •   public function extend($name, $extender)
  •   {
  •     if (isset($this->instances[$name])) {
  •       // 已经实例化的服务,直接调用扩展器
  •       $this->instances[$name] = $extender($this->instances[$name]);
  •     } else {
  •       $this->extenders[$name][] = $extender;
  •     }
  •   }
  •  
  •   // 获取服务
  •   public function make($name, array $parameters = [])
  •   {
  •     // 先用别名查找真实服务名
  •     $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
  •  
  •     if (isset($this->instances[$name])) {
  •       return $this->instances[$name];
  •     }
  •  
  •     if (isset($this->bindings[$name])) {
  •       // 执行回调函数并返回
  •       $instance = call_user_func($this->bindings[$name]['callback']);
  •  
  •       if ($this->bindings[$name]['shared']) {
  •         // 标记为单例时,存储到服务中
  •         $this->instances[$name] = $instance;
  •       }
  •     } else {
  •       // 使用build方法构建此类
  •       $instance = $this->build($name, $parameters);
  •     }
  •  
  •     if (isset($this->extenders[$name])) {
  •       // 调用扩展器
  •       foreach ($this->extenders[$name] as $extender) {
  •         $instance = $extender($instance);
  •       }
  •     }
  •  
  •     return $instance;
  •   }
  •  
  •   // 构建一个类,并自动注入服务
  •   public function build($class, array $parameters = [])
  •   {
  •     $reflector = new ReflectionClass($class);
  •  
  •     $constructor = $reflector->getConstructor();
  •  
  •     if (is_null($constructor)) {
  •       // 没有构造函数,直接new
  •       return new $class();
  •     }
  •  
  •     $dependencies = [];
  •  
  •     // 获取构造函数所需的参数
  •     foreach ($constructor->getParameters() as $dependency) {
  •  
  •       if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) {
  •         // 先从上下文中查找
  •         $dependencies[] = $this->context[$class][$dependency->getName()];
  •         continue;
  •       }
  •  
  •       if (isset($parameters[$dependency->getName()])) {
  •         // 从自定义参数中查找
  •         $dependencies[] = $parameters[$dependency->getName()];
  •         continue;
  •       }
  •  
  •       if (is_null($dependency->getClass())) {
  •         // 参数类型不是类或接口时,无法从容器中获取依赖
  •         if ($dependency->isDefaultValueAvailable()) {
  •           // 查找默认值,如果有就使用默认值
  •           $dependencies[] = $dependency->getDefaultValue();
  •         } else {
  •           // 无法提供类所依赖的参数
  •           throw new Exception('找不到依赖参数:' . $dependency->getName());
  •         }
  •       } else {
  •         // 参数类型是一个类时,就用make方法构建该类
  •         $dependencies[] = $this->make($dependency->getClass()->name);
  •       }
  •     }
  •  
  •     return $reflector->newInstanceArgs($dependencies);
  •   }
  •  
  •   // 绑定上下文
  •   public function addContextualBinding($when, $needs, $give)
  •   {
  •     $this->context[$when][$needs] = $give;
  •   }
  •  
  •   // 支持链式方式绑定上下文
  •   public function when($when)
  •   {
  •     return new Context($when, $this);
  •   }
  • }
  •  
  • class Context
  • {
  •   protected $when;
  •  
  •   protected $needs;
  •  
  •   protected $container;
  •  
  •   public function __construct($when, Container $container)
  •   {
  •     $this->when = $when;
  •     $this->container = $container;
  •   }
  •  
  •   public function needs($needs)
  •   {
  •     $this->needs = $needs;
  •  
  •     return $this;
  •   }
  •  
  •   public function give($give)
  •   {
  •     // 调用容器绑定依赖上下文
  •     $this->container->addContextualBinding($this->when, $this->needs, $give);
  •   }
  • }
  • 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持开心学习网。

    原文链接:https://segmentfault.com/a/1190000018864154

    上一篇下一篇

    猜您喜欢

    热门推荐