什么是容器
在開發過程中,經常會用到的一個概率就是依賴注入。我們借助依懶注入來解耦代碼,選擇性的按需加載服務,而這些通常都是借助容器來實現。
容器實現對類的統一管理,并且確保對象實例的唯一性
常用的容器網上有很多,如PHP-DI 、 YII-DI 等各種實現,通常他們要么大而全,要么高度適配特定業務,與實際需要存在沖突。
出于需要,我們自己造一個輕量級的輪子,為了保持規范,我們基于PSR-11 來實現。
PSR-11
PSR 是 php-fig 提供的標準建議,雖然不是官方組織,但是得到廣泛認可。PSR-11 提供了容器接口。他包含 ContainerInterface 和 兩個異常接口,提供使用建議。
04 | interface ContainerInterface |
16 | public function get( $id ); |
29 | public function has( $id ); |
實現示例
我們先來實現接口中要求的兩個方法
01 | abstract class AbstractContainer implements ContainerInterface |
04 | protected $resolvedEntries = []; |
09 | protected $definitions = []; |
11 | public function __construct( $definitions = []) |
13 | foreach ( $definitions as $id => $definition ) { |
14 | $this ->injection( $id , $definition ); |
18 | public function get( $id ) |
21 | if (! $this ->has( $id )) { |
22 | throw new NotFoundException( "No entry or class found for {$id}" ); |
25 | $instance = $this ->make( $id ); |
30 | public function has( $id ) |
32 | return isset( $this ->definitions[ $id ]); |
實際我們容器中注入的對象是多種多樣的,所以我們單獨抽出實例化方法。
001 | public function make( $name ) |
003 | if (! is_string ( $name )) { |
004 | throw new \InvalidArgumentException(sprintf( |
005 | 'The name parameter must be of type string, %s given' , |
006 | is_object ( $name ) ? get_class( $name ) : gettype ( $name ) |
010 | if (isset( $this ->resolvedEntries[ $name ])) { |
011 | return $this ->resolvedEntries[ $name ]; |
014 | if (! $this ->has( $name )) { |
015 | throw new NotFoundException( "No entry or class found for {$name}" ); |
018 | $definition = $this ->definitions[ $name ]; |
020 | if ( is_array ( $definition ) && isset( $definition [ 'class' ])) { |
021 | $params = $definition ; |
022 | $definition = $definition [ 'class' ]; |
023 | unset( $params [ 'class' ]); |
026 | $object = $this ->reflector( $definition , $params ); |
028 | return $this ->resolvedEntries[ $name ] = $object ; |
031 | public function reflector( $concrete , array $params = []) |
033 | if ( $concrete instanceof \Closure) { |
034 | return $concrete ( $params ); |
035 | } elseif ( is_string ( $concrete )) { |
036 | $reflection = new \ReflectionClass( $concrete ); |
037 | $dependencies = $this ->getDependencies( $reflection ); |
038 | foreach ( $params as $index => $value ) { |
039 | $dependencies [ $index ] = $value ; |
041 | return $reflection ->newInstanceArgs( $dependencies ); |
042 | } elseif ( is_object ( $concrete )) { |
051 | private function getDependencies( $reflection ) |
054 | $constructor = $reflection ->getConstructor(); |
055 | if ( $constructor !== null) { |
056 | $parameters = $constructor ->getParameters(); |
057 | $dependencies = $this ->getParametersByDependencies( $parameters ); |
060 | return $dependencies ; |
069 | private function getParametersByDependencies( array $dependencies ) |
072 | foreach ( $dependencies as $param ) { |
073 | if ( $param ->getClass()) { |
074 | $paramName = $param ->getClass()->name; |
075 | $paramObject = $this ->reflector( $paramName ); |
076 | $parameters [] = $paramObject ; |
077 | } elseif ( $param ->isArray()) { |
078 | if ( $param ->isDefaultValueAvailable()) { |
079 | $parameters [] = $param ->getDefaultValue(); |
083 | } elseif ( $param ->isCallable()) { |
084 | if ( $param ->isDefaultValueAvailable()) { |
085 | $parameters [] = $param ->getDefaultValue(); |
087 | $parameters [] = function ( $arg ) { |
091 | if ( $param ->isDefaultValueAvailable()) { |
092 | $parameters [] = $param ->getDefaultValue(); |
094 | if ( $param ->allowsNull()) { |
095 | $parameters [] = null; |
097 | $parameters [] = false; |
如你所見,到目前為止我們只實現了從容器中取出實例,從哪里去提供實例定義呢,所以我們還需要提供一個方水法
06 | public function injection( $id , $concrete ) |
08 | if ( is_array ( $concrete ) && !isset( $concrete [ 'class' ])) { |
09 | throw new ContainerException( '數組必須包含類定義' ); |
12 | $this ->definitions[ $id ] = $concrete ; |
只有這樣嗎?對的,有了這些操作我們已經有一個完整的容器了,插箱即用。
不過為了使用方便,我們可以再提供一些便捷的方法,比如數組式訪問。
01 | class Container extends AbstractContainer implements \ArrayAccess |
04 | public function offsetExists( $offset ) |
06 | return $this ->has( $offset ); |
09 | public function offsetGet( $offset ) |
11 | return $this ->get( $offset ); |
14 | public function offsetSet( $offset , $value ) |
16 | return $this ->injection( $offset , $value ); |
19 | public function offsetUnset( $offset ) |
21 | unset( $this ->resolvedEntries[ $offset ]); |
22 | unset( $this ->definitions[ $offset ]); |
這樣我們就擁有了一個功能豐富,使用方便的輕量級容器了,趕快整合到你的項目中去吧。
點擊這里查看完整代碼
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。