正文
一次在项目中要新增RabbitMQ队列服务器,原来有一个,就想着用同一个类就可以了,只是加一下配置,但调试时发现新队列服务器并没有接收到数据。 仔细查看下,发现使用的RabbitMQ队列服务的component是单例模式,后面的参数配置并没有起作用。现在怎么解决这个问题呢?
一种方案是,可以复制一份RabbitMQ队列服务的component,然后给这个复制组件配置参数实现功能。但这样这两个组件有大量相同的代码, 不符合主流开发思想。
怎么解决这个问题呢?想到了工厂模式(严格来说是建造者模式,也不能说是桥接模式), 传入不同的参数,实例化类为不同的对象。但使用的时候怎么实例化是个问题:需要把参数传进去,每一次使用都要实例化一次。 这么想来还是要用单例模式:一次实例化,多次使用。但又要是可配置单例,可以说是工厂模式加单例模式的复合体。
想来想去,突然想到了Yii2中数据库的配置,可以配置多个数据库,但使用的类是一个类。我们这里也可以使用这种方式。查询后,
发现Yii2使用了容器和依赖注入、还有服务定位,对component组件类有要求:
要继承自yii\base\Component
,要实现init()
方法,再就是配置参数要用public
申明。
项目加载阶段会把组件及其配置都加载好,使用到这个组件时,如Yii::$app->queue
,会调用get()
实例化这个组件对象,
以后不会再重复实例化,会直接使用这个实例化好的对象。
看一下配置:
"components" => [
'queue' => [
"class" => 'common\components\LRabbitQueue',
'credentials' => [
'host' => '192.168.0.1',
'port' => '5672',
'login' => 'mqadmin',
'password' => 'mqadmin'
]
],
'queueNew' => [
"class" => 'common\components\LRabbitQueue',
'credentials' => [
'host' => '192.168.0.2',
'port' => '5672',
'login' => 'mqadmin',
'password' => 'mqadmin'
]
],
],
原来的component类:
<?php
namespace common\components;
use Yii;
use yii\base\Component;
class LRabbitQueue extends Component
{
const LOG_PREFIX = 'common.components.LRabbitQueue.';
private $connection;
public $credentials;
public function init()
{
}
/**
* 初始化连接
*/
public function initConnection()
{
if (!$this->connection || !is_resource($this->connection)) {
// 这里的Yii::$app->queue->credentials固定死了配置,是导致新组件不可用的原因
$this->connection = new \AMQPConnection(Yii::$app->queue->credentials);
}
if (!$this->connection->isConnected()) {
$this->connect();
}
}
public function connect($tryNum=3) {
try {
$this->connection->connect();
}
catch(\Exception $e) {
if (--$tryNum) {
Yii::warning("msg[".$e->getMessage()."]", self::LOG_PREFIX . __FUNCTION__);
sleep(1);
$this->connect($tryNum);
} else {
Yii::error("msg[".$e->getMessage()."]", self::LOG_PREFIX . __FUNCTION__);
throw $e;
}
}
}
public function getConnection() {
$this->initConnection();
return $this->connection;
}
/**
* 日志进队
* @param $message
* @param $exchange
* @param $routing
* @return bool
*/
public function produce($message, $exchange, $routing)
{
$message = json_encode($message);
$channel = new \AMQPChannel($this->getConnection());
$ex = new \AMQPExchange($channel);
$ex->setName($exchange);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
$ex->declareExchange();
if (!$ex->publish($message, $routing, 1, ['delivery_mode' => 2])) {
return false;
}
return true;
}
public function batchProduce($messageList, $exchange, $routing)
{
$channel = new \AMQPChannel($this->getConnection());
$ex = new \AMQPExchange($channel);
$ex->setName($exchange);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
$ex->declareExchange();
foreach ($messageList as $message) {
$messageJson = json_encode($message);
if (!$ex->publish($messageJson, $routing, 1, ['delivery_mode' => 2])) {
return false;
}
}
return true;
}
/**
* @param $message
* @param $exchange
* @param $routing
* @param $ttl 消息生存时间(1000 = 1s)
* @return bool
* @created by Jhu
* 通用延迟消费进队方法,消息持久化
*/
public function produceTtl($message, $exchange, $routing, $ttl)
{
$message = json_encode($message);
$channel = new \AMQPChannel($this->getConnection());
$ex = new \AMQPExchange($channel);
$ex->setName($exchange);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
$ex->declareExchange();
$argument = array(
'delivery_mode' => 2,
'expiration' => $ttl
);
if (!$ex->publish($message, $routing, 1, $argument)) {
return false;
}
return true;
}
}
修改后的新component类:
<?php
namespace common\components;
use Yii;
use yii\base\Component;
class LRabbitQueue extends Component
{
const LOG_PREFIX = 'common.components.LRabbitQueue.';
private $connection;
public $credentials;
public function init()
{
parent::init();
$this->connection = new \AMQPConnection($this->credentials);
}
public function connect($tryNum=3) {
try {
$this->connection->connect();
}
catch(\Exception $e) {
if (--$tryNum) {
Yii::warning("msg[".$e->getMessage()."]", self::LOG_PREFIX . __FUNCTION__);
sleep(1);
$this->connect($tryNum);
} else {
Yii::error("msg[".$e->getMessage()."]", self::LOG_PREFIX . __FUNCTION__);
throw $e;
}
}
}
public function getConnection()
{
if (!$this->connection->isConnected()) {
$this->connect();
}
return $this->connection;
}
/**
* 日志进队
* @param $message
* @param $exchange
* @param $routing
* @return bool
*/
public function produce($message, $exchange, $routing)
{
$message = json_encode($message);
$channel = new \AMQPChannel($this->getConnection());
$ex = new \AMQPExchange($channel);
$ex->setName($exchange);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
$ex->declareExchange();
if (!$ex->publish($message, $routing, 1, ['delivery_mode' => 2])) {
return false;
}
return true;
}
public function batchProduce($messageList, $exchange, $routing)
{
$channel = new \AMQPChannel($this->getConnection());
$ex = new \AMQPExchange($channel);
$ex->setName($exchange);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
$ex->declareExchange();
foreach ($messageList as $message) {
$messageJson = json_encode($message);
if (!$ex->publish($messageJson, $routing, 1, ['delivery_mode' => 2])) {
return false;
}
}
return true;
}
/**
* @param $message
* @param $exchange
* @param $routing
* @param $ttl 消息生存时间(1000 = 1s)
* @return bool
* @created by Jhu
* 通用延迟消费进队方法,消息持久化
*/
public function produceTtl($message, $exchange, $routing, $ttl)
{
$message = json_encode($message);
$channel = new \AMQPChannel($this->getConnection());
$ex = new \AMQPExchange($channel);
$ex->setName($exchange);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE);
$ex->declareExchange();
$argument = array(
'delivery_mode' => 2,
'expiration' => $ttl
);
if (!$ex->publish($message, $routing, 1, $argument)) {
return false;
}
return true;
}
}
Yii2中要用到单例时都可以考虑使用服务定位的方式实现,方便以后维护。
这里$credentials这个参数并没有看到赋值,为什么可以访问?这是因为这个组件类继承自yii\base\Component
,
而yii\base\Component
又继承自yii\base\Object
,yii\base\Object
中有:
<?php
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
Yii::configure
源码:
<?php
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
你可能又发现一个疑问,$config这个参数怎么取到的?
这是因为在项目入口文件index.php
中有:
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
require __DIR__ . '/../../vendor/autoload.php';
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
require __DIR__ . '/../../common/config/bootstrap.php';
require __DIR__ . '/../config/bootstrap.php';
$config = yii\helpers\ArrayHelper::merge(
require __DIR__ . '/../../common/config/main.php',
require __DIR__ . '/../../common/config/main-local.php',
require __DIR__ . '/../config/main.php',
require __DIR__ . '/../config/main-local.php'
);
(new yii\web\Application($config))->run();
$config
这个数组会被 new yii\web\Application($config)
》 yii\base\Application->__construct($config)
-> yii\base\Component::__construct($config)
》 yii\base\Object::__construct($config)
-> Yii::configure($this, $config)
所调用:
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
错误追踪
可以看出Yii::configure($this, $config)
中的$this
肯定不是yii\web\Application
对象,而是我们上面说到的组件,如queueNew
等(当然这是错误的)。
图:Yii2 核心类继承关系
我们追踪下过程,new yii\web\Application($config)
其实调用的是yii\base\Application->__construct($config)
这个构造方法:
<?php
/**
* Constructor.
* @param array $config name-value pairs that will be used to initialize the object properties.
* Note that the configuration must contain both [[id]] and [[basePath]].
* @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
*/
public function __construct($config = [])
{
Yii::$app = $this;
static::setInstance($this);
$this->state = self::STATE_BEGIN;
$this->preInit($config);
$this->registerErrorHandler($config);
Component::__construct($config);
}
这里的Yii::$app = $this;
是说,Yii::$app
才是yii\web\Application
对象。
Component::__construct($config);
调用的其实是父类yii\base\Object::__construct($config)
方法:
<?php
/**
* Constructor.
* The default implementation does two things:
*
* - Initializes the object with the given configuration `$config`.
* - Call [[init()]].
*
* If this method is overridden in a child class, it is recommended that
*
* - the last parameter of the constructor is a configuration array, like `$config` here.
* - call the parent implementation at the end of the constructor.
*
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
追到这发现Yii::configure($this, $config)
中的$this
又是yii\web\Application
对象,
也是Yii::configure($object, $properties)
中的$object->$name = $value;
的$object
,而这个$name
则是配置参数中的components
,
$value
是components
的属性值(包含queue
、queueNew
等组件)。
看来玄机因该在yii\base\Object::__construct($config)
中$this->init();
上了。
这个$this->init();
调用的是yii\base\Application
的init()
:
<?php
/**
* @inheritdoc
*/
public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}
$this->bootstrap();
调用的是yii\web\Application
的bootstrap()
:
<?php
/**
* @inheritdoc
*/
protected function bootstrap()
{
$request = $this->getRequest();
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
parent::bootstrap();
}
parent::bootstrap();
调用的是yii\base\Application
的bootstrap()
:
<?php
/**
* Initializes extensions and executes bootstrap components.
* This method is called by [[init()]] after the application has been fully configured.
* If you override this method, make sure you also call the parent implementation.
*/
protected function bootstrap()
{
if ($this->extensions === null) {
$file = Yii::getAlias('@vendor/yiisoft/extensions.php');
$this->extensions = is_file($file) ? include($file) : [];
}
foreach ($this->extensions as $extension) {
if (!empty($extension['alias'])) {
foreach ($extension['alias'] as $name => $path) {
Yii::setAlias($name, $path);
}
}
if (isset($extension['bootstrap'])) {
$component = Yii::createObject($extension['bootstrap']);
if ($component instanceof BootstrapInterface) {
Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
foreach ($this->bootstrap as $class) {
$component = null;
if (is_string($class)) {
if ($this->has($class)) {
$component = $this->get($class);
} elseif ($this->hasModule($class)) {
$component = $this->getModule($class);
} elseif (strpos($class, '\\') === false) {
throw new InvalidConfigException("Unknown bootstrapping component ID: $class");
}
}
if (!isset($component)) {
$component = Yii::createObject($class);
}
if ($component instanceof BootstrapInterface) {
Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
vendor/yiisoft/extensions.php
内容:
<?php
$vendorDir = dirname(__DIR__);
return array (
'yiisoft/yii2-bootstrap' =>
array (
'name' => 'yiisoft/yii2-bootstrap',
'version' => '2.0.6.0',
'alias' =>
array (
'@yii/bootstrap' => $vendorDir . '/yiisoft/yii2-bootstrap',
),
),
'yiisoft/yii2-gii' =>
array (
'name' => 'yiisoft/yii2-gii',
'version' => '2.0.5.0',
'alias' =>
array (
'@yii/gii' => $vendorDir . '/yiisoft/yii2-gii',
),
),
'yiisoft/yii2-faker' =>
array (
'name' => 'yiisoft/yii2-faker',
'version' => '2.0.3.0',
'alias' =>
array (
'@yii/faker' => $vendorDir . '/yiisoft/yii2-faker',
),
),
'yiisoft/yii2-swiftmailer' =>
array (
'name' => 'yiisoft/yii2-swiftmailer',
'version' => '2.0.6.0',
'alias' =>
array (
'@yii/swiftmailer' => $vendorDir . '/yiisoft/yii2-swiftmailer',
),
),
'callmez/yii2-wechat-sdk' =>
array (
'name' => 'callmez/yii2-wechat-sdk',
'version' => '9999999-dev',
'alias' =>
array (
'@callmez/wechat/sdk' => $vendorDir . '/callmez/yii2-wechat-sdk',
),
),
'yiisoft/yii2-debug' =>
array (
'name' => 'yiisoft/yii2-debug',
'version' => '2.0.9.0',
'alias' =>
array (
'@yii/debug' => $vendorDir . '/yiisoft/yii2-debug',
),
),
'yiisoft/yii2-redis' =>
array (
'name' => 'yiisoft/yii2-redis',
'version' => '2.0.6.0',
'alias' =>
array (
'@yii/redis' => $vendorDir . '/yiisoft/yii2-redis',
),
),
);
Yii::createObject($class);
调用的是yii\BaseYii
的createObject()
:
<?php
/**
* Creates a new object using the given configuration.
*
* You may view this method as an enhanced version of the `new` operator.
* The method supports creating an object based on a class name, a configuration array or
* an anonymous function.
*
* Below are some usage examples:
*
*
* // create an object using a class name
* $object = Yii::createObject('yii\db\Connection');
*
* // create an object using a configuration array
* $object = Yii::createObject([
* 'class' => 'yii\db\Connection',
* 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
* 'username' => 'root',
* 'password' => '',
* 'charset' => 'utf8',
* ]);
*
* // create an object with two constructor parameters
* $object = \Yii::createObject('MyClass', [$param1, $param2]);
*
*
* Using [[\yii\di\Container|dependency injection container]], this method can also identify
* dependent objects, instantiate them and inject them into the newly created object.
*
* @param string|array|callable $type the object type. This can be specified in one of the following forms:
*
* - a string: representing the class name of the object to be created
* - a configuration array: the array must contain a `class` element which is treated as the object class,
* and the rest of the name-value pairs will be used to initialize the corresponding object properties
* - a PHP callable: either an anonymous function or an array representing a class method (`[$class or $object, $method]`).
* The callable should return a new instance of the object being created.
*
* @param array $params the constructor parameters
* @return object the created object
* @throws InvalidConfigException if the configuration is invalid.
* @see \yii\di\Container
*/
public static function createObject($type, array $params = [])
{
if (is_string($type)) {
return static::$container->get($type, $params);
} elseif (is_array($type) && isset($type['class'])) {
$class = $type['class'];
unset($type['class']);
return static::$container->get($class, $params, $type);
} elseif (is_callable($type, true)) {
return static::$container->invoke($type, $params);
} elseif (is_array($type)) {
throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
}
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}
追踪后发现bootstrap()
这一段未发现与我们组件直接相关。
可能关系隐藏在Yii::$app->get("queue"")
中。
注册服务
上面说到Yii::configure($this, $config)
中的$this
是yii\web\Application
对象,
也是Yii::configure($object, $properties)
中的$object->$name = $value;
的$object
,
而这个$name
则是配置参数中的components
,$value
是components
的属性值(包含queue
、queueNew
等组件)。
这个看到给yii\web\Application
对象设置components
属性,如果这个属性不存在,就会调用__set()
方法,
因为未重载过__set()
方法,所以调用的还是yii\base\Component
的__set()
方法:
<?php
/**
* Sets the value of a component property.
* This method will check in the following order and act accordingly:
*
* - a property defined by a setter: set the property value
* - an event in the format of "on xyz": attach the handler to the event "xyz"
* - a behavior in the format of "as xyz": attach the behavior named as "xyz"
* - a property of a behavior: set the behavior property value
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$component->property = $value;`.
* @param string $name the property name or the event name
* @param mixed $value the property value
* @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is read-only.
* @see __get()
*/
public function __set($name, $value)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
// set property
$this->$setter($value);
return;
} elseif (strncmp($name, 'on ', 3) === 0) {
// on event: attach event handler
$this->on(trim(substr($name, 3)), $value);
return;
} elseif (strncmp($name, 'as ', 3) === 0) {
// as behavior: attach behavior
$name = trim(substr($name, 3));
$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
return;
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = $value;
return;
}
}
if (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
}
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
可以看到这里提供了属性、事件、行为的支持。我们只看当前实例中有没有setcomponents()
方法,php大小写不敏感,
发现yii\di\ServiceLocator
中有setComponents()
方法:
<?php
/**
* Registers a set of component definitions in this locator.
*
* This is the bulk version of [[set()]]. The parameter should be an array
* whose keys are component IDs and values the corresponding component definitions.
*
* For more details on how to specify component IDs and definitions, please refer to [[set()]].
*
* If a component definition with the same ID already exists, it will be overwritten.
*
* The following is an example for registering two component definitions:
*
* ``php
* [
* 'db' => [
* 'class' => 'yii\db\Connection',
* 'dsn' => 'sqlite:path/to/file.db',
* ],
* 'cache' => [
* 'class' => 'yii\caching\DbCache',
* 'db' => 'db',
* ],
* ]
* ``
*
* @param array $components component definitions or instances
*/
public function setComponents($components)
{
foreach ($components as $id => $component) {
$this->set($id, $component);
}
}
循环调用set()
方法,yii\di\ServiceLocator
中有:
<?php
/**
* Registers a component definition with this locator.
*
* For example,
*
*
* // a class name
* $locator->set('cache', 'yii\caching\FileCache');
*
* // a configuration array
* $locator->set('db', [
* 'class' => 'yii\db\Connection',
* 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
* 'username' => 'root',
* 'password' => '',
* 'charset' => 'utf8',
* ]);
*
* // an anonymous function
* $locator->set('cache', function ($params) {
* return new \yii\caching\FileCache;
* });
*
* // an instance
* $locator->set('cache', new \yii\caching\FileCache);
*
*
* If a component definition with the same ID already exists, it will be overwritten.
*
* @param string $id component ID (e.g. `db`).
* @param mixed $definition the component definition to be registered with this locator.
* It can be one of the following:
*
* - a class name
* - a configuration array: the array contains name-value pairs that will be used to
* initialize the property values of the newly created object when [[get()]] is called.
* The `class` element is required and stands for the the class of the object to be created.
* - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`).
* The callable will be called by [[get()]] to return an object associated with the specified component ID.
* - an object: When [[get()]] is called, this object will be returned.
*
* @throws InvalidConfigException if the definition is an invalid configuration array
*/
public function set($id, $definition)
{
if ($definition === null) {
unset($this->_components[$id], $this->_definitions[$id]);
return;
}
unset($this->_components[$id]);
if (is_object($definition) || is_callable($definition, true)) {
// an object, a class name, or a PHP callable
$this->_definitions[$id] = $definition;
} elseif (is_array($definition)) {
// a configuration array
if (isset($definition['class'])) {
$this->_definitions[$id] = $definition;
} else {
throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");
}
} else {
throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));
}
}
这就是定义服务(给$_definitions
参数写入数据)。
不过只是定义而已,未说明依赖,也没有实例化。在使用的时候,才会自动加载依赖,才会实例化,这是获取服务的事了。
获取服务
我们获取queue
队列服务时,一般会用:
Yii::$app->queue;
获取对象属性会用到__get()
方法,yii\di\ServiceLocator
重载了yii\base\Component
的__get()
方法:
<?php
/**
* Getter magic method.
* This method is overridden to support accessing components like reading properties.
* @param string $name component or property name
* @return mixed the named property value
*/
public function __get($name)
{
if ($this->has($name)) {
return $this->get($name);
} else {
return parent::__get($name);
}
}
/**
* Returns a value indicating whether the locator has the specified component definition or has instantiated the component.
* This method may return different results depending on the value of `$checkInstance`.
*
* - If `$checkInstance` is false (default), the method will return a value indicating whether the locator has the specified
* component definition.
* - If `$checkInstance` is true, the method will return a value indicating whether the locator has
* instantiated the specified component.
*
* @param string $id component ID (e.g. `db`).
* @param bool $checkInstance whether the method should check if the component is shared and instantiated.
* @return bool whether the locator has the specified component definition or has instantiated the component.
* @see set()
*/
public function has($id, $checkInstance = false)
{
return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]);
}
/**
* Returns the component instance with the specified ID.
*
* @param string $id component ID (e.g. `db`).
* @param bool $throwException whether to throw an exception if `$id` is not registered with the locator before.
* @return object|null the component of the specified ID. If `$throwException` is false and `$id`
* is not registered before, null will be returned.
* @throws InvalidConfigException if `$id` refers to a nonexistent component ID
* @see has()
* @see set()
*/
public function get($id, $throwException = true)
{
if (isset($this->_components[$id])) {
return $this->_components[$id];
}
if (isset($this->_definitions[$id])) {
$definition = $this->_definitions[$id];
if (is_object($definition) && !$definition instanceof Closure) {
return $this->_components[$id] = $definition;
} else {
return $this->_components[$id] = Yii::createObject($definition);
}
} elseif ($throwException) {
throw new InvalidConfigException("Unknown component ID: $id");
} else {
return null;
}
}
上面把直接调用的两个方法也写出来了。看来问题的核心在Yii::createObject($definition);
创建对象上。
这里可以看一下服务定位器,
不过要想看明白,还是要先看完依赖注入和依赖注入容器。
附一下yii\BaseYii
的createObject()
方法:
<?php
/**
* Creates a new object using the given configuration.
*
* You may view this method as an enhanced version of the `new` operator.
* The method supports creating an object based on a class name, a configuration array or
* an anonymous function.
*
* Below are some usage examples:
*
* ``php
* // create an object using a class name
* $object = Yii::createObject('yii\db\Connection');
*
* // create an object using a configuration array
* $object = Yii::createObject([
* 'class' => 'yii\db\Connection',
* 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
* 'username' => 'root',
* 'password' => '',
* 'charset' => 'utf8',
* ]);
*
* // create an object with two constructor parameters
* $object = \Yii::createObject('MyClass', [$param1, $param2]);
* ``
*
* Using [[\yii\di\Container|dependency injection container]], this method can also identify
* dependent objects, instantiate them and inject them into the newly created object.
*
* @param string|array|callable $type the object type. This can be specified in one of the following forms:
*
* - a string: representing the class name of the object to be created
* - a configuration array: the array must contain a `class` element which is treated as the object class,
* and the rest of the name-value pairs will be used to initialize the corresponding object properties
* - a PHP callable: either an anonymous function or an array representing a class method (`[$class or $object, $method]`).
* The callable should return a new instance of the object being created.
*
* @param array $params the constructor parameters
* @return object the created object
* @throws InvalidConfigException if the configuration is invalid.
* @see \yii\di\Container
*/
public static function createObject($type, array $params = [])
{
if (is_string($type)) {
return static::$container->get($type, $params);
} elseif (is_array($type) && isset($type['class'])) {
$class = $type['class'];
unset($type['class']);
return static::$container->get($class, $params, $type);
} elseif (is_callable($type, true)) {
return static::$container->invoke($type, $params);
} elseif (is_array($type)) {
throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
}
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}
说到这,我们思考一下LRabbitQueue::init()
何时调用的呢?
在依赖注入通过Reflection->newInstanceArgs()
获取服务组件实例时,会调用类的__construct()
,
一般继承自yii\base\Object
的类实例化时都会调用它的构建方法:
<?php
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
在这里看到了init()
方法。
容器
这里拓展说一下上面多处看到的 static::$container
。
static::$container
指的是 yii\BaseYii
中的属性public static $container;
。
Yii 继承了 BaseYii:class Yii extends \yii\BaseYii
。
赋值的地方是/vendor/yiisoft/yii2/Yii.php
文件中的一行代码Yii::$container = new yii\di\Container();
:
<?php
/**
* @link https://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license https://www.yiiframework.com/license/
*/
require __DIR__ . '/BaseYii.php';
/**
* Yii is a helper class serving common framework functionalities.
*
* It extends from [[\yii\BaseYii]] which provides the actual implementation.
* By writing your own Yii class, you can customize some functionalities of [[\yii\BaseYii]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Yii extends \yii\BaseYii
{
}
spl_autoload_register(['Yii', 'autoload'], true, true);
Yii::$classMap = require __DIR__ . '/classes.php';
Yii::$container = new yii\di\Container();
应用 yii\base\Application
类中 preInit(&$config)
方法的一段代码$this->setContainer($config['container']);
设定容器参数:
public function preInit(&$config)
{
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration for the Application is required.');
}
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
unset($config['basePath']);
} else {
throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
}
if (isset($config['vendorPath'])) {
$this->setVendorPath($config['vendorPath']);
unset($config['vendorPath']);
} else {
// set "@vendor"
$this->getVendorPath();
}
if (isset($config['runtimePath'])) {
$this->setRuntimePath($config['runtimePath']);
unset($config['runtimePath']);
} else {
// set "@runtime"
$this->getRuntimePath();
}
if (isset($config['timeZone'])) {
$this->setTimeZone($config['timeZone']);
unset($config['timeZone']);
} elseif (!ini_get('date.timezone')) {
$this->setTimeZone('UTC');
}
if (isset($config['container'])) {
$this->setContainer($config['container']);
unset($config['container']);
}
// merge core components with custom components
foreach ($this->coreComponents() as $id => $component) {
if (!isset($config['components'][$id])) {
$config['components'][$id] = $component;
} elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
$config['components'][$id]['class'] = $component['class'];
}
}
}
$this->setContainer()
也是 yii\base\Application
类中方法:
public function setContainer($config)
{
Yii::configure(Yii::$container, $config);
}
这里就是给yii\BaseYii
中的 属性$container
对象 赋值了。
接下来容器具体使用部分,查看 https://ibaiyang.github.io/blog/yii2/2019/06/28/深入理解Yii2.0-依赖注入和依赖注入容器.html
参考资料
PHP 常用设计模式-单例模式 https://ibaiyang.github.io/blog/php/2019/07/30/PHP-常用设计模式.html#单例模式singleton
深入理解Yii2.0 依赖注入和依赖注入容器 https://ibaiyang.github.io/blog/yii2/2019/06/28/深入理解Yii2.0-依赖注入和依赖注入容器.html
深入理解Yii2.0 服务定位器 https://ibaiyang.github.io/blog/yii2/2019/06/29/深入理解Yii2.0-服务定位器.html