Swoft Source Code Analysis-Implementation Principle of IOC Container in SW OFT

  php, swoole

Author:bromine
Links:https://www.jianshu.com/p/a23 …
Source: Simple Book
The copyright belongs to the author. This article has been reprinted with the authorization of the author and the original text has been rearranged.
Swoft Github:https://github.com/swoft-clou …

Preface

Swoft provides a completeIOC containerAs aDependency management scheme, which is the implementation basis of the functions of Swoft AOP, RPC module, etc.
He mainly solves three functions:
1. Avoid troublesome manual management of various nested dependencies among objects.
2. Dependencies of objects are no longer determined at compile time, providing more flexibility to change behavior at run time.
3. Objects can no longer rely on concrete implementations, but on abstract interfaces or abstract classes
Students interested in dependency management can refer to this article by Uncle Martin.<Inversion of Control Containers and the Dependency Injection pattern>

Service locator

Bean annotates through class level@BeanDefinition: after Bean is defined, the program can directly pass throughApp::getBean()Gets an instance of a Bean.

App::getBean()ProvideService locatorType of dependency management method is used to access a service locator to obtain specific instances. The service locator solves the problems of “instance construction, dependency management between instances, and concrete implementation of class selection” and shields users from relevant details.

Container->set()The method isApp::getBean()The underlying method of actually creating the bean. The principle is throughReflectionAnd all kinds ofannotation(Reference notes section) to construct a proxy object for the Bean.

//Swoft\Bean\Container.php
/**
 * 创建Bean
 *
 * @param string           $name             名称
 * @param ObjectDefinition $objectDefinition bean定义
 * @return object
 * @throws \ReflectionException
 * @throws \InvalidArgumentException
 */
private function set(string $name, ObjectDefinition $objectDefinition)
{
    // bean创建信息
    $scope = $objectDefinition->getScope();
    $className = $objectDefinition->getClassName();
    $propertyInjects = $objectDefinition->getPropertyInjections();
    $constructorInject = $objectDefinition->getConstructorInjection();

    //ref属性重定向依赖查找,一般用于在Interface这种需要具体实现类的Bean上,用于指定实际使用的实现类
    if (!empty($objectDefinition->getRef())) {
        $refBeanName = $objectDefinition->getRef();
        return $this->get($refBeanName);
    }

   // 构造函数参数注入
    $constructorParameters = [];
    if ($constructorInject !== null) {
        $constructorParameters = $this->injectConstructor($constructorInject);
    }

      
    $reflectionClass = new \ReflectionClass($className);
    $properties = $reflectionClass->getProperties();

    // 通过反射new实例
    $isExeMethod = $reflectionClass->hasMethod($this->initMethod);
    $object = $this->newBeanInstance($reflectionClass, $constructorParameters);

    // 属性注入
    $this->injectProperties($object, $properties, $propertyInjects);

    // 执行Swoft Bean约定的初始化方法`init()`
    if ($isExeMethod) {
        $object->{$this->initMethod}();
    }

    //动态代理,具体见AOP章节
    if (!$object instanceof AopInterface) {
        $object = $this->proxyBean($name, $className, $object);
    }

    // 单例处理
    if ($scope === Scope::SINGLETON) {
        $this->singletonEntries[$name] = $object;
    }

    return $object;
}

Dependency injection

relative toService locator,Dependency injectionIt is a more advanced dependency management practice.

InService locatorIn the mode, the client needs to call the service locator itself and depends on the service locator itself.
InDependency injectionIn the mode, the relationship between the client and the dependency injection manager is also reversed. The client does not know the existence of the dependency manager, and the dependency manager calls the client and injects specific dependency objects.

Swoft’s dependency injection management scheme is based on the service locator. There are three injection methods available:

Attribute injection

/**
 * @Reference("user")
 * @var \App\Lib\MdDemoInterface
 */
private $mdDemoService;

/**
 * @Inject()
 * @var \App\Models\Logic\UserLogic
 */
private $logic;

/**
 * the name of pool
 *
 * @Value(name="${config.service.user.name}", env="${USER_POOL_NAME}")
 * @var string
 */
protected $name = "";

The above @Reference,@Inject,@value are typical annotation declarations for attribute injection. attributes declaring these three annotations in a Bean class will be injected into specific ones respectivelyRpc client proxy object,Ordinary Bean proxy object, andProfile configuration values.

Analysis of Attribute Injection Meta Information

The injection information of Bean’s various attributes is completed in the annotation collection phase, i.e. in the startup phase of Swoft

//Swoft\Bean\Wrapper\AbstractWrapper.php
/**
 * 属性解析
 *
 * @param  array $propertyAnnotations
 * @param string $className
 * @param string $propertyName
 * @param mixed  $propertyValue
 *
 * @return array
 */
private function parsePropertyAnnotations(array $propertyAnnotations, string $className, string $propertyName, $propertyValue)
{
   
    $isRef = false;
    $injectProperty = "";

    // 没有任何注解
    if (empty($propertyAnnotations) || !isset($propertyAnnotations[$propertyName])
        || !$this->isParseProperty($propertyAnnotations[$propertyName])
    ) {
        return [null, false];
    }

    // 属性注解解析
    foreach ($propertyAnnotations[$propertyName] as $propertyAnnotation) {
        $annotationClass = get_class($propertyAnnotation);
        if (!in_array($annotationClass, $this->getPropertyAnnotations())) {
            continue;
        }

        // 使用具体的解析器(如ValueParser,ReferenceParser等)解析注入元信息
        $annotationParser = $this->getAnnotationParser($propertyAnnotation);
        if ($annotationParser === null) {
            $injectProperty = null;
            $isRef = false;
            continue;
        }
        list($injectProperty, $isRef) = $annotationParser->parser($className, $propertyAnnotation, $propertyName, "", $propertyValue);
    }
    return [$injectProperty, $isRef];
}

$isRefDetermines whether a property needs to be injected with a Bean or a scalar value
$injectPropertyRefers to the Bean name or specific scalar value to be injected for this attribute
The two will eventually be packaged into oneSwoft\Bean\ObjectDefinitionObject and save it in theAnnotationResource->$definitionsIn

Attribute injection

Property injection in calling service locatorApp::getBean()This occurs when the Bean is generated, when the service locator is based on the previously parsed$isRef,$injectPropertyInformation is injected into specific values into attributes.

// Swoft\Bean\Container.php
/**
 * 注入属性
 *
 * @param  mixed                $object
 * @param \ReflectionProperty[] $properties $properties
 * @param  mixed                $propertyInjects
 * @throws \InvalidArgumentException
 */
private function injectProperties($object, array $properties, $propertyInjects)
{
    foreach ($properties as $property) {
        //...
      
        // 属性是数组
        if (\is_array($injectProperty)) {
            $injectProperty = $this->injectArrayArgs($injectProperty);
        }

        // 属性是bean引用
        if ($propertyInject->isRef()) {
            $injectProperty = $this->get($injectProperty);
        }

        if ($injectProperty !== null) {
            $property->setValue($object, $injectProperty);
        }
  }

Attribute injection depends on the service locator. If an object is manually new by the user, the attribute injection function will not be obtained.

Method parameter injection

Swoft has many places where the framework directly calls Bean’s specific method according to the contract. for example, the framework will call an action method of Controllert when it receives a web request. if there is an appropriate AOP connection point, it will call the corresponding notification method. …..
It is basically supported in all kinds of methods called by these frameworks.Method parameter injection, Swoft will automatically fill the parameters of the method with appropriate values according to rules such as parameter type and parameter name.

<?php
//App\Controllers\RouteController.php;
/**
 *  这个例子中,除了Request 和Response 是固定的注入特定结构的对象,其他参数都是根据路由规则注入
 * @RequestMapping(route="user/{uid}/book/{bid}/{bool}/{name}")
 *
 * @param bool                $bool  参考RequestMapping
 * @param Request  $request     
 * @param int                 $bid  
 * @param string              $name
 * @param int                 $uid
 * @param Response $response
 *
 * @return array
 */
public function funcArgs(bool $bool, Request $request, int $bid, string $name, int $uid, Response $response)
{
    //...
}

The implementation of method injection is scattered. Each method injection point will have similar code to process the injected data. Here, look at the injection process of action. Action’s parameter injection processing code is in theHandlerAdapter->bindParams()In

//Swoft\Http\Server\Route\HandlerAdapter.php
/**
 * binding params of action method
 *
 * @param ServerRequestInterface $request request object
 * @param mixed $handler handler
 * @param array $matches route params info
 *
 * @return array
 * @throws \ReflectionException
 */
private function bindParams(ServerRequestInterface $request, $handler, array $matches)
{
    if (\is_array($handler)) {
        list($controller, $method) = $handler;
        $reflectMethod = new \ReflectionMethod($controller, $method);
        $reflectParams = $reflectMethod->getParameters();
    } else {
        $reflectMethod = new \ReflectionFunction($handler);
        $reflectParams = $reflectMethod->getParameters();
    }

    $bindParams = [];
    // $matches    = $info['matches'] ?? [];
    $response   = RequestContext::getResponse();

    // binding params
    foreach ($reflectParams as $key => $reflectParam) {
        $reflectType = $reflectParam->getType();
        $name        = $reflectParam->getName();

        // 未定义参数类型直接使用$matches对应值
        if ($reflectType === null) {
            if (isset($matches[$name])) {
                $bindParams[$key] = $matches[$name];
            } else {
                $bindParams[$key] = null;
            }
            continue;
        }

        /**
         * @notice \ReflectType::getName() is not supported in PHP 7.0, that is why use __toString()
         */
        $type = $reflectType->__toString();
        //若类型的特定类型如Request/Response,直接注入对应对象,否则注入类型转换后的$matches对应值
        if ($type === Request::class) {
            $bindParams[$key] = $request;
        } elseif ($type === Response::class) {
            $bindParams[$key] = $response;
        } elseif (isset($matches[$name])) {
            $bindParams[$key] = $this->parserParamType($type, $matches[$name]);//类型强转处理
        } else {
            $bindParams[$key] = $this->getDefaultValue($type);//提供一个指定类型的默认值(等价于0)
        }
    }

    return $bindParams;
}

$matchesThe corresponding is the specific value of the REST template routing specific field, for example. If the actual visit/user/100Which matches the route/user/{uid}, then$matchesWill store['uid'=>'100']Information.
OtherMethod parameter injection pointThe realization of the same

Constructor injection

The implementation of Swoft’s current constructor injection is not complete, and there may be changes, so let’s not talk about it here.

Swoft source code analysis series catalog:https://segmentfault.com/a/11 …