容器

    目录

    imi 框架的容器采用了依赖注入 (Dependency Injection,简称 DI) 的设计模式,用于管理和注入对象及其依赖关系。

    imi 框架的容器包括全局容器、服务器容器和请求上下文容器。

    在 imi 框架中,通过注解实现的功能,对象如果是直接 new 出来的,是不会生效的,必须使用容器!因为 imi 框架的容器可以管理对象及其依赖关系,并实现依赖注入。

    使用容器可以更方便地管理对象的生命周期和依赖关系,提高应用程序的可维护性和性能表现。

    实例化对象

    实例化对象用法可以代替 new

    // 具体的类名
    \Imi\Bean\BeanFactory::newInstance(XXX::class);
    // 传入构造方法的参数
    \Imi\Bean\BeanFactory::newInstance(XXX::class, 1, 2);

    容器分类

    • 全局容器
    • 服务器容器(继承全局容器)
    • 请求上下文容器(继承服务器容器或全局容器)
    • 全局单例容器(不支持 AOP、注解等)

    全局容器

    全局容器是全局共享的,用于存储应用程序中的单例对象。

    全局容器存活于框架整个生命周期,是一种全局的、共享的容器,可以被整个应用程序的代码访问。

    获取对象:

    $object = \Imi\App::getBean('XXX');
    $object = \Imi\App::getBean('XXX', 1, 2, 3); // 支持实例化参数
    
    // 获取容器对象
    $container = \Imi\App::getContainer();
    $object = $container->get('XXX');

    配置类别名:

    方法一-注解:

    <?php
    use Imi\Bean\Annotation\Bean;
    
    /**
     * @Bean("XXX")
     */
    class Test
    {
    
    }

    方法二-项目配置文件:

    [
        'imi' => [
            'beans' => [
                'XXX' => Test::class,
            ],
        ],
    ]

    如上配置后,就可以使用 \Imi\App::getBean('XXX') 等方式实例化了

    同理,你甚至可以使用自己写的类,配置覆盖 imi 内置的同名类

    动态绑定:

    use Imi\App;
    
    // 绑定
    App::getContainer()->bind('aaa', XXX::class);
    
    // 绑定带参数,非单例模式,禁用递归依赖
    App::getContainer()->bind('aaa', XXX::class, \Imi\Bean\Annotation\Bean::INSTANCE_TYPE_EACH_NEW, false);
    
    // 实例化,带缓存
    $obj = App::getBean('aaa');
    
    // 实例化,带参数,不缓存
    $obj = App::getBean('aaa', 1);
    
    // 绑定回调
    App::getContainer()->bindCallable('bbb', function(string $id, int $a) {
        var_dump($id); // aaa
        var_dump($a);  // 123
        // 返回你要实例化的对象,这里只是示例
        return new \stdClass;
    });
    $obj = App::getBean('aaa', 123);
    
    // 设置实例
    App::getContainer()->set('ccc', new \stdClass);
    App::getContainer()->getBean('ccc');
    禁用递归依赖可以规避服务启动后,第一次访问概率报错问题

    服务器容器

    服务器容器是针对每个子服务器独立的,用于存储服务器级别的对象。

    服务器容器中存储的对象是针对目标服务器的,不同服务器之间的容器是互相独立的。

    在服务器容器中,目前存储了该服务器的路由对象及配置等等。

    比如,如果你的项目同时监听了 Http、TCP 两个协议端口,那么在 Http 请求接口进来时,通过服务器容器获取到的对象只会是该 Http 服务独有的,在 TCP 服务中是无法访问的。

    获取对象:

    $object = \Imi\Server\ServerManager::getServer('main')->getBean('XXX');
    
    // 获取容器对象
    $container = \Imi\Server\ServerManager::getServer('main')->getContainer();
    $object = $container->get('XXX');

    配置类别名:

    方法一-注解:

    <?php
    use Imi\Bean\Annotation\Bean;
    
    /**
     * @Bean("XXX")
     */
    class Test
    {
    
    }

    方法二-项目配置文件:

    默认情况下继承全局容器配置,服务器容器配置因环境不同而有所差异,具体细节可以参考各环境的服务器配置文档。

    动态绑定:

    use Imi\Server\ServerManager;
    
    // 绑定
    ServerManager::getServer()->getContainer()->bind('aaa', XXX::class);
    
    // 绑定带参数,非单例模式,禁用递归依赖
    ServerManager::getServer()->getContainer()->bind('aaa', XXX::class, \Imi\Bean\Annotation\Bean::INSTANCE_TYPE_EACH_NEW, false);
    
    // 实例化,带缓存
    $obj = ServerManager::getServer()->getContainer()->getBean('aaa');
    
    // 实例化,带参数,不缓存
    $obj = ServerManager::getServer()->getContainer()->getBean('aaa', 1);
    禁用递归依赖可以规避服务启动后,第一次访问概率报错问题

    请求上下文容器

    请求上下文容器是当前请求有效的容器,请求结束时即销毁。

    请求上下文容器中存储的对象是针对当前请求有效的,请求结束后会自动销毁。

    有关更多细节可以参考请求上下文

    请求上下文容器可以为每个请求提供独立的容器实例,以存储请求处理中所需的对象,例如请求参数、数据库连接等。请求上下文容器的使用可以避免多个请求之间的对象冲突,提高应用程序的可靠性和稳定性。

    获取对象:

    $object = \Imi\RequestContext::getBean('XXX');
    
    // 获取容器对象
    $container = \Imi\RequestContext::getContainer();
    $object = $container->get('XXX');

    配置类别名:

    方法一-注解:

    <?php
    use Imi\Bean\Annotation\Bean;
    
    /**
     * @Bean("XXX")
     */
    class Test
    {
    
    }

    方法二-项目配置文件:

    继承全局容器或服务器容器配置

    动态绑定:

    请求上下文容器:

    use Imi\RequestContext;
    
    // 绑定
    RequestContext::getContainer()->bind('aaa', XXX::class);
    
    // 绑定带参数,非单例模式,禁用递归依赖
    RequestContext::getContainer()->bind('aaa', XXX::class, \Imi\Bean\Annotation\Bean::INSTANCE_TYPE_EACH_NEW, false);
    
    // 实例化,带缓存
    $obj = RequestContext::getBean('aaa');
    
    // 实例化,带参数,不缓存
    $obj = RequestContext::getBean('aaa', 1);
    禁用递归依赖可以规避服务启动后,第一次访问概率报错问题

    全局单例容器

    全局单例容器可以用于实例化全局共享的单例对象,该对象不会被 AOP、注解等功能所影响,只是单纯的单例。

    可以通过全局单例容器在整个应用程序中共享同一个对象实例,提高应用程序的性能和效率。

    获取对象:

    $object = \Imi\App::getSingleton('XXX');

    配置类别名:

    同全局容器

    动态绑定:

    同全局容器

    全局容器实例化

    全局容器实例化方法可以用于每次调用时实例化并返回新的对象,同时 AOP、注解等功能仍然有效。

    通过全局容器实例化方法可以轻松地获取到全局范围内的新对象,可以为每个调用提供独立的对象实例,避免对象冲突和资源浪费。

    实例化对象:

    $object = \Imi\App::newInstance('XXX');
    $object = \Imi\App::newInstance('XXX', 1, 2, 3); // 支持实例化参数

    配置类别名:

    同全局容器

    动态绑定:

    同全局容器

    容器对象类 (Bean)

    在容器中,可以通过 getBean() 方法来获取已经注册的 Bean 对象。在调用 getBean() 方法时,可以传递带有完整命名空间的类名或者别名。如果需要定义别名,可以使用 @Bean 注解进行定义。

    除此之外,在 Bean 类中还可以定义一个名为 __init() 的方法,该方法会作为第二个构造方法被执行。具体的执行顺序为:__construct() -> injectProps() -> __init()。其中,injectProps() 方法用于属性注入,具体内容可以参考 AOP 章节的相关内容。

    定义:

    namespace Test;
    
    /**
     * 下面两种写法相同(注意实际不要写多个 Bean 注解)
     * @Bean("MyTest")
     * @Bean(name="MyTest")
     *
     * 下面是禁用递归依赖和设置实例化类型,可以根据实际情况设置
     * @Bean(name="MyTest", instanceType=\Imi\Bean\Annotation\Bean::INSTANCE_TYPE_SINGLETON, recursion=false)
     *
     * 下面是限制生效的环境,支持一个或多个
     * @Bean(name="MyTest", env="swoole")
     * @Bean(name="MyTest", env={"swoole", "workerman"})
     */
    class ABCDEFG
    {
        public function __construct($id)
        {
            echo 'first', PHP_EOL;
        }
    
        public function __init($id): void
        {
            echo 'second first', PHP_EOL;
        }
    }

    获得实例:

    App::getBean('MyTest');
    App::getBean(\Test\ABCDEFG::class);

    注解继承

    默认情况下,子类继承父类时,父类的注解是不会生效的。但是有时候我们需要让父类的注解生效。在这种情况下,可以在类、方法、属性或常量上使用 @Inherit 注解来实现。

    类名:Imi\Bean\Annotation\Inherit

    参数:

    /**
     * 允许的注解类,为 null 则不限制,支持字符串或数组
     *
     * @var string|string[]
     */
    public $annotation;

    例子:

    下面是一个模型的例子,我们可以在父类中定义一些结构,然后在子类中编写自定义代码。如果重新生成模型的父类代码,这些自定义代码不会被覆盖掉。

    父类:

    <?php
    namespace Imi\Test\Component\Model\Base;
    
    use Imi\Model\Model;
    use Imi\Model\Annotation\Table;
    use Imi\Model\Annotation\Column;
    use Imi\Model\Annotation\Entity;
    
    /**
     * ArticleBase
     * @Entity
     * @Table(name="tb_article", id={"id"})
     * @property int $id
     * @property string $title
     * @property string $content
     * @property string $time
     */
    abstract class ArticleBase extends Model
    {
        /**
         * id
         * @Column(name="id", type="int", length=10, accuracy=0, nullable=false, default="", isPrimaryKey=true, primaryKeyIndex=0, isAutoIncrement=true)
         * @var int
         */
        protected $id;
    
        /**
         * 获取 id
         *
         * @return int
         */
        public function getId()
        {
            return $this->id;
        }
    
        /**
         * 赋值 id
         * @param int $id id
         * @return static
         */
        public function setId($id)
        {
            $this->id = $id;
            return $this;
        }
    
        /**
         * title
         * @Column(name="title", type="varchar", length=255, accuracy=0, nullable=false, default="", isPrimaryKey=false, primaryKeyIndex=-1, isAutoIncrement=false)
         * @var string
         */
        protected $title;
    
        /**
         * 获取 title
         *
         * @return string
         */
        public function getTitle()
        {
            return $this->title;
        }
    
        /**
         * 赋值 title
         * @param string $title title
         * @return static
         */
        public function setTitle($title)
        {
            $this->title = $title;
            return $this;
        }
    
        /**
         * content
         * @Column(name="content", type="mediumtext", length=0, accuracy=0, nullable=false, default="", isPrimaryKey=false, primaryKeyIndex=-1, isAutoIncrement=false)
         * @var string
         */
        protected $content;
    
        /**
         * 获取 content
         *
         * @return string
         */
        public function getContent()
        {
            return $this->content;
        }
    
        /**
         * 赋值 content
         * @param string $content content
         * @return static
         */
        public function setContent($content)
        {
            $this->content = $content;
            return $this;
        }
    
        /**
         * time
         * @Column(name="time", type="timestamp", length=0, accuracy=0, nullable=false, default="CURRENT_TIMESTAMP", isPrimaryKey=false, primaryKeyIndex=-1, isAutoIncrement=false)
         * @var string
         */
        protected $time;
    
        /**
         * 获取 time
         *
         * @return string
         */
        public function getTime()
        {
            return $this->time;
        }
    
        /**
         * 赋值 time
         * @param string $time time
         * @return static
         */
        public function setTime($time)
        {
            $this->time = $time;
            return $this;
        }
    
    }

    子类:

    <?php
    namespace Imi\Test\Component\Model;
    
    use Imi\Bean\Annotation\Inherit;
    use Imi\Test\Component\Model\Base\ArticleBase;
    
    /**
     * Article
     * @Inherit
     */
    class Article extends ArticleBase
    {
    
    }

    配置扫描命名空间

    应用启动时,会扫描配置文件中指定的命名空间,所有被扫描到的类才可以通过容器来获取。

    在项目和子服务器的配置文件中,可以通过配置来指定需要扫描的命名空间。

    return [
        'beanScan'  =>  [
            'ImiApp\Model',
            'ImiApp\Service',
        ],
    ];
    imi v2.0 版本开始已经不一定需要配置 beanScan