Swoft| Source Code Interpretation Series II: What did swoft do during the startup phase?

  php, swoft, swoole

date: 2018-8-01 14:22:17
Title: swoft| Source Code Interpretation Series II: What did swoft do during the startup phase?
Description: read the sowft framework source code and learn about the sowft startup phase.

The little buddy will feel when he first touches swoft.The pressure is a little high., a more intuitive statement isDifficultThe development team does not approveDifficultAccording to this statement, swoft code is implemented in php, and php isThe best language in the worldThe swoft code is easy to read.

Then the development team will useSeries source codeWe believe that this will be an easy journey.

Swoft source code interpretation series 1: how difficult! Swoft demo can’t run. how can it be broken? Docker, get to know it ~
Swoft Source Code Interpretation Series II: What did swoft do during the startup phase?

Attach a community buddyWith the windFlowchart produced:

附上社区小伙伴 **随风** 制作的流程图

Program entry

SeenOfficial Documents-Service Startup and ManagementChapter, you will know the entrance of swoftphp bin/swoft startTo start http server. running this command opens the door to a new world.

root@e38a7e5aff40 /v/w/s/swoft# ps aux
PID   USER     TIME   COMMAND
    1 root       0:00 php -a
  708 root       0:01 php-swoft master process (bin/swoft)
  709 root       0:00 php-swoft manager process
  711 root       0:01 php-swoft task process
  712 root       0:01 php-swoft worker process
  713 root       0:49 php-swoft reload process
  779 root       0:00 ps aux

Familiar withswoole-wikiThe little partner, you can see the familiar:

  • Master process
  • Manager process
  • Worker process
  • Task-worker process

On swoole-wikiOperational flow chartAndProcess/Thread Structure DiagramIt is worth savoring carefully. This is the basis for us to understand and use swoole for server development later. Press here for the time being.

And in order to understandWhat did swoft do during the startup phase?, can run directlyphp bin/swoft, debug/output slowly. Yes, there is no advanced skill.var_dump() + die()Just do it

Tips for using tools to read source code

Yes, this tool isphpstorm, did not use phpstorm’s small partner to use quickly, the following shortcut keys under window, for example:

  • Shortcut key description: C->ctrl A->alt S->shift
  • C-b/C- mouse click: jump to the place where the method/function is defined
  • C-A- Left and Right Arrow Keys: Toggle the position before and after the cursor
  • C-e: view recently opened files
  • C-q: view the annotation description of the function (Do you know how important it is to write notes)
  • C-p: view function parameters (Or is it the importance of annotations?)

There are many useful functions, please check the menu barnavigateMenu bar, go find surprises ~

PS: comments! Comments! Comments!

Source: bootstrap

bin/swoftThe file is simple:

#!/usr/bin/env php
<?php
require_once __DIR__ . '/bootstrap.php';
$console = new \Swoft\Console\Console();
$console->run();

Let’s look at it first.bin/bootstrap.php:

require_once dirname(__DIR__) . '/vendor/autoload.php';
require_once dirname(__DIR__) . '/config/define.php';

// init the factory of bean
\Swoft\Bean\BeanFactory::init();

/* @var \Swoft\Bootstrap\Boots\Bootable $bootstrap*/
$bootstrap = \Swoft\App::getBean(\Swoft\Bootstrap\Bootstrap::class);
$bootstrap->bootstrap();

The first step is to load composer’s autoload file. Students who use composer should all know it.But do you know the principle of autoload?

The second step isconfig/define.phpFile, let’s go in and have a look:

// Project base path
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));

// Register alias
$aliases = [
    '@root'       => BASE_PATH,
];

\Swoft\App::setAliases($aliases);

Two things have been done:

  • Define PHP constants
  • Alias mechanism of swoft

The first feature of swoft-Alias mechanismHere it comes. It’s quite a new word. Its essence is very simple-String replacementJust, for example, we set up above@root, we directly print to see:

$tmp = \Swoft\App::getAlias('@root');
var_dump($tmp);die;

root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft
string(21) "/var/www/swoole/swoft"

Use to see:

$tmp1 = \Swoft\App::getAlias('@root');
$tmp2 = \Swoft\App::getAlias('@root/foo/bar');
var_dump($tmp1, $tmp2);die;

root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft
string(21) "/var/www/swoole/swoft"
string(29) "/var/www/swoole/swoft/foo/bar"

At present, the alias mechanism in swoft is used inFile directory/pathOn, familiar withYii frameworkMy little friends know that the alias mechanism in yii uses more scenes and can splice url and other places. however, no matter how many scenes are used, the essence isString replacement.

Then why not use the PHP constant as the normal method, but use the alias mechanism instead?Isn’t the alias mechanism more elegant

Frame core: BeanFactory

At the core of the framework, read this code with patience:

// init the factory of bean
\Swoft\Bean\BeanFactory::init();

Enterinit()First, look at the first:

$properties = self::getProperties(); // 获取 property 配置
var_dump($properties);die;

Look at the source code and debugging verification assistance: readingconfig/propertiesThe following configuration (file),mergeIn the same array

Looking at the second step,The core of the coreHere comes the Container, which will not be repeated here.Dependency injection DI/ control inversion IoCSuch as basic knowledge, unfamiliar partners to supplement oh ~

self::$container = new Container();
self::$container->setProperties($properties);
self::$container->autoloadServerAnnotation();

    /**
     * Register the annotation of server
     */
    public function autoloadServerAnnotation()
    {
        $bootScan = $this->getScanNamespaceFromProperties('bootScan'); // 获取 property 配置中的 bootScan 配置项
        var_dump($bootScan);
        $resource = new ServerAnnotationResource($this->properties);
        $resource->addScanNamespace($bootScan); // 关键在这一句, 要扫描哪些命名空间(文件)
        $definitions = $resource->getDefinitions();
        var_dump($definitions);die;

        $this->definitions = array_merge($definitions, $this->definitions);
    }

Let’s focus on it.$resource->addScanNamespace($bootScan)

The First Half of Annotation: Which Documents to Scan

$resource->addScanNamespace($bootScan)I inherited the abstract base class and went around, and finally came here.

<?php

namespace Swoft\Bean\Resource;

use Swoft\App;
use Swoft\Helper\ComponentHelper;

/**
 * The annotation resource of server
 */
class ServerAnnotationResource extends AnnotationResource
{
    /**
     * Register the scaned namespace
     */
    public function registerNamespace() // 继承了抽象基类绕了一下, 最后其实走到了这里
    {
        $swoftDir      = dirname(__FILE__, 5); // 默认扫描路径, swoft 框架各个组件目录
        var_dump($swoftDir);
        var_dump(App::getAlias('@vendor/swoft')); // 使用 alias 可以得出一样的结果, 可以思考一下为什么这里不用别名机制呢?
        $componentDirs = scandir($swoftDir);
        foreach ($componentDirs as $component) {
            if ($component == '.' || $component == '..') {
                continue;
            }

            $componentDir = $swoftDir . DS . $component;
            $componentCommandDir = $componentDir . DS . 'src';
            if (! is_dir($componentCommandDir)) {
                continue;
            }

            $ns = ComponentHelper::getComponentNamespace($component, $componentDir);
            $this->componentNamespaces[] = $ns;

            // console component
            if ($component == $this->consoleName) { // console 组件特殊处理
                $this->scanNamespaces[$ns] = $componentCommandDir;
                continue;
            }

            foreach ($this->serverScan as $dir) { // 预定义的命名空间
                $scanDir = $componentCommandDir . DS . $dir;
                if (!is_dir($scanDir)) {
                    continue;
                }

                $scanNs                        = $ns . "\\" . $dir;
                $this->scanNamespaces[$scanNs] = $scanDir;
            }
        }
    }
}

    /**
     * @var array
     */
    protected $serverScan
        = [
            'Command',
            'Bootstrap',
            'Aop',
        ];
// $this->scanNamespaces 的内容示例
  ["Swoft\WebSocket\Server\Bootstrap"]=>
  string(65) "/var/www/swoole/swoft/vendor/swoft/websocket-server/src/Bootstrap"

Congratulations, you have already understood half of the annotation function here:

  • The swoft framework is composed of functional components one by one, and the details can be moved step by step.Swoft Frame Modular Transformation
  • The default scan comment contains 2 parts:
    config/propertiesunderbootScanNamespace configured
    Under all components of swoftCommand Bootstrap AopNamespace, whereconsoleSpecial handling of components

If you find it difficult to understand here, you need to add some basic knowledge:

  • Composer Basics: autoload Mechanism, Namespace
  • Swoft component related knowledge, based on composer basic knowledge.

In addition, the test code added abovevar_dump(App::getAlias('@vendor/swoft'));, you can think about swoft alias mechanism is to solvePath problemWhy not use it here?

The Second Half of Annotation: Scan Results

$definitions = $resource->getDefinitions();Corresponding contents:

    /**
     * 获取已解析的配置beans
     *
     * @return array
     * <pre>
     * [
     *     'beanName' => ObjectDefinition,
     *      ...
     * ]
     * </pre>
     */
    public function getDefinitions()
    {

        // 获取扫描的PHP文件
        $classNames     = $this->registerLoaderAndScanBean(); // 扫描上一步注册进来的命名空间
        $fileClassNames = $this->scanFilePhpClass(); // 额外配置的扫描文件, 大家可以尝试一下在哪配置的哦
        $classNames     = array_merge($classNames, $fileClassNames); // 获取到所有需要扫面的类

        foreach ($classNames as $className) {
            $this->parseBeanAnnotations($className); // 解析bean注解
        }
        $this->parseAnnotationsData(); // 解析注解数据, 存放到 $this->definitions 中

        return $this->definitions; // 最后, 我们使用这个就可以获取到注解解析出来的了类啦
    }
// 看一看注解解析出来的例子
  ["Swoft\WebSocket\Server\Bootstrap\CoreBean"]=>
  object(Swoft\Bean\ObjectDefinition)#126 (7) {
    ["name":"Swoft\Bean\ObjectDefinition":private]=>
    string(41) "Swoft\WebSocket\Server\Bootstrap\CoreBean"
    ["className":"Swoft\Bean\ObjectDefinition":private]=>
    string(41) "Swoft\WebSocket\Server\Bootstrap\CoreBean"
    ["scope":"Swoft\Bean\ObjectDefinition":private]=>
    int(1)
    ["ref":"Swoft\Bean\ObjectDefinition":private]=>
    string(0) ""
    ["constructorInjection":"Swoft\Bean\ObjectDefinition":private]=>
    NULL
    ["propertyInjections":"Swoft\Bean\ObjectDefinition":private]=>
    array(0) {
    }
    ["methodInjections":"Swoft\Bean\ObjectDefinition":private]=>
    array(0) {
    }
  }

The details of scanning different types of annotations are hidden here, because we will encounter them one by one when reading the source codes of different components later, so long as we understand the general principles here.

The following two sentences are relatively simple:

$definition = self::getServerDefinition();
self::$container->addDefinitions($definition);

    /**
     * @return array
     * @throws \InvalidArgumentException
     */
    private static function getServerDefinition(): array
    {
        $file             = App::getAlias('@console');
        $configDefinition = [];

        if (\is_readable($file)) {
            $configDefinition = require_once $file;
        }

        $coreBeans  = self::getCoreBean(BootBeanCollector::TYPE_SERVER);
        var_dump($coreBeans);die;

        return ArrayHelper::merge($coreBeans, $configDefinition);
    }

The results can be known by a simple print:

root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft
array(1) {
  ["commandRoute"]=>
  array(1) {
    ["class"]=>
    string(35) "Swoft\Console\Router\HandlerMapping"
  }
}

Done: Initialize Bean

self::$container->initBeans(); // 进去查看

    /**
     * @throws \InvalidArgumentException
     * @throws \ReflectionException
     */
    public function initBeans()
    {
        $autoInitBeans = $this->properties['autoInitBean'] ?? false;
        if (!$autoInitBeans) {
            return;
        }

        // 循环初始化
        foreach ($this->definitions as $beanName => $definition) {
            $this->get($beanName);
        }
    }

    /**
     * 获取一个bean
     *
     * @param string $name 名称
     *
     * @return mixed
     * @throws \ReflectionException
     * @throws \InvalidArgumentException
     */
    public function get(string $name)
    {
        // 已经创建
        if (isset($this->singletonEntries[$name])) { // 单例, 初始化过就直接返回
            return $this->singletonEntries[$name];
        }

        // 未定义
        if (!isset($this->definitions[$name])) {
            throw new \InvalidArgumentException(sprintf('Bean %s not exist', $name));
        }

        /* @var ObjectDefinition $objectDefinition */
        $objectDefinition = $this->definitions[$name];

        return $this->set($name, $objectDefinition); // 没有初始化则进行初始化
    }

    /**
     * 创建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();

        if ($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);

        // 执行初始化方法
        if ($isExeMethod) {
            $object->{$this->initMethod}();
        }

        if (!$object instanceof AopInterface) {
            $object = $this->proxyBean($name, $className, $object);
        }

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

        return $object;

    }

Here are all the details of Bean initialization:

  • All information about the class obtained after annotation parsing
  • Injection construct
  • Initializes the class (new), which executes the constructor
  • Inject a property
  • Execute the initialization method, which is why it is defined in the Beaninit()It will also be implemented.
  • AOP processing, find the actual proxy class
  • Single case processing
  • Returns the generated Bean object

Come hereCore of the whole swoft coreIt has already appeared in front of you and is very simple to sum up:

  • Where do I scan for comments when swoft starts up
  • How do comments scanned by swoft initialize Bean

i’ve got it\Swoft\Bean\BeanFactory::init();In the future, we need to use Bean, only need to:

\Swoft\Bean\BeanFactory::getBean('xxx');

// 下面的写法只是一层封装而已
\Swoft\App::getBean('xxx');

    /**
     * get bean
     *
     * @param string $name 名称
     *
     * @return mixed
     */
    public static function getBean(string $name)
    {
        return ApplicationContext::getBean($name);
    }

The end of the bootstrap phase: each configuration

By printing in the right place:

/* @var \Swoft\Bootstrap\Boots\Bootable $bootstrap*/
$bootstrap = \Swoft\App::getBean(\Swoft\Bootstrap\Bootstrap::class);
var_dump($bootstrap);
$bootstrap->bootstrap();

    /**
     * bootstrap
     */
    public function bootstrap()
    {
        $bootstraps = BootstrapCollector::getCollector(); // 需要执行哪些 bootstrap
        var_dump($bootstraps);die;
        $temp = \array_column($bootstraps, 'order');

        \array_multisort($temp, SORT_ASC, $bootstraps);

        foreach ($bootstraps as $bootstrapBeanName => $name){
            /* @var Bootable $bootstrap*/
            $bootstrap = App::getBean($bootstrapBeanName);
            $bootstrap->bootstrap();
        }
    }

The results are as follows:

root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft
object(Bootstrap_5b6dd8716a6dc)#209 (1) {
  ["__handler_5b6dd8716a6dc":"Bootstrap_5b6dd8716a6dc":private]=>
  object(Swoft\Proxy\Handler\AopHandler)#188 (1) { # 用到了 aop
    ["target":"Swoft\Proxy\Handler\AopHandler":private]=>
    object(Swoft\Bootstrap\Bootstrap)#186 (0) {
    }
  }
}
array(3) { # 真正执行的 bootstrap
  ["Swoft\Bootstrap\Boots\InitPhpEnv"]=> # init php env
  array(2) {
    ["name"]=>
    string(0) ""
    ["order"]=>
    int(2)
  }
  ["Swoft\Bootstrap\Boots\LoadEnv"]=> # 加载 .env 文件
  array(2) {
    ["name"]=>
    string(0) ""
    ["order"]=>
    int(1)
  }
  ["Swoft\Bootstrap\Boots\LoadInitConfiguration"]=> # 加载 config 目录的其他配置
  array(2) {
    ["name"]=>
    string(0) ""
    ["order"]=>
    int(3)
  }
}

At this point, all the work in bootstrap has been completed.

What exactly is a bean in swoft?

Before answering what bean is, remember:Everything is the object

We use the method of face-to-face object to abstract the problem, and use the abstract class instantiated objects to solve the problem, and the instantiated objects are one after another in swoft.Bean

Looking back at our entire bootstrap phase, we can summarize two things that have been done for automation:

  • Instantiate the Bean according to the default annotation scanning mechanism
  • According toconfig/ .envSuch as Bean/property in configuration, configure Bean in swoft (instantiate Bean or configure bean’s property)

This is a way to instantiate classes and configure object properties through configuration, which is very large in php framework, such as yii/laravel.