Home > Backend Development > PHP Tutorial > Detailed explanation of attribute injection and method injection of component behavior in PHP's Yii framework, yii framework_PHP tutorial

Detailed explanation of attribute injection and method injection of component behavior in PHP's Yii framework, yii framework_PHP tutorial

WBOY
Release: 2016-07-12 08:56:35
Original
761 people have browsed it

Detailed explanation of attribute injection and method injection of component behavior in PHP's Yii framework, yii framework

Principles of attribute and method injection of behavior

We learned above that the purpose of behavior is to inject its own properties and methods into the attached class. So how does Yii inject the properties and methods of a behavior yiibaseBehavior into a yiibaseComponent? For properties, this is achieved through the __get() and __set() magic methods. For methods, it is through the __call() method.

Injection of attributes

Take reading as an example. If you access $Component->property1 , what is Yii doing behind the scenes? Take a look at this yiibaseComponent::__get()

public function __get($name)
{
  $getter = 'get' . $name;
  if (method_exists($this, $getter)) {
    return $this->$getter();
  } else {
    // 注意这个 else 分支的内容,正是与 yii\base\Object::__get() 的
    // 不同之处
    $this->ensureBehaviors();
    foreach ($this->_behaviors as $behavior) {
      if ($behavior->canGetProperty($name)) {

        // 属性在行为中须为 public。否则不可能通过下面的形式访问呀。
        return $behavior->$name;
      }
    }
  }
  if (method_exists($this, 'set' . $name)) {
    throw new InvalidCallException('Getting write-only property: ' .
      get_class($this) . '::' . $name);
  } else {
    throw new UnknownPropertyException('Getting unknown property: ' .
      get_class($this) . '::' . $name);
  }
}

Copy after login

Focus on the differences between yiibaseComponent::__get() and yiibaseObject::__get(). That is, for the processing after undefined getter function, yiibaseObject directly throws an exception, telling you that the property you want to access does not exist. But for yiibaseComponent, after there is no getter, it still needs to check whether it is an attribute of the injected behavior:

First, $this->ensureBehaviors() is called. This method has been mentioned before, mainly to ensure that the behavior has been bound.
After ensuring that the behavior has been bound, start traversing $this->_behaviors . Yii saves all bound behaviors of the class in the yiibaseComponent::$_behaviors[] array.
Finally, determine whether this property is a readable property of the bound behavior through the behavior's canGetProperty(). If so, return the property $behavior->name of this behavior. Complete reading of properties. As for canGetProperty(), it has been briefly discussed in the :ref::property section, and will be introduced in a targeted manner later.
For the setter, the code is similar and will not take up space here.

Method Injection

Similar to the injection of attributes through the __get() __set() magic method, Yii implements the injection of methods in behaviors through the __call() magic method:

public function __call($name, $params)
{
  $this->ensureBehaviors();
  foreach ($this->_behaviors as $object) {
    if ($object->hasMethod($name)) {
      return call_user_func_array([$object, $name], $params);
    }
  }
  throw new UnknownMethodException('Calling unknown method: ' .
    get_class($this) . "::$name()");
}
Copy after login

As can be seen from the above code, Yii first calls $this->ensureBehaviors() to ensure that the behavior has been bound.

Then, it also traverses the yiibaseComponent::$_behaviros[] array. Determine whether the method exists through the hasMethod() method. If the method to be called in the bound behavior exists, use PHP's call_user_func_array() to call it. As for the hasMethod() method, we will talk about it later.

Access control of injected properties and methods

Earlier we gave specific examples of whether the public, private, and protected members in the behavior are accessible in the bound class. Here we analyze the reasons from the code level.

In the above content, we know that whether a property is accessible or not depends mainly on the behavior of canGetProperty() and canSetProperty(). Whether a method can be called or not depends mainly on the behavior of hasMethod(). Since yiibaseBehavior inherits from our old friend yiibaseObject, the codes for the three judgment methods mentioned above are actually in Object. Let’s look at them one by one:

public function canGetProperty($name, $checkVars = true)
{
  return method_exists($this, 'get' . $name) || $checkVars &&
    property_exists($this, $name);
}

public function canSetProperty($name, $checkVars = true)
{
  return method_exists($this, 'set' . $name) || $checkVars &&
    property_exists($this, $name);
}

public function hasMethod($name)
{
  return method_exists($this, $name);
}

Copy after login

These three methods are really not complicated at all. In this regard, we can draw the following conclusions:

When reading (writing) a property to a behavior bound to a Component, it can be accessed if the behavior defines a getter (setter) for the property. Or, if the behavior does have this member variable, it can pass the above judgment. At this time, the member variable can be public, private, or protected. But in the end only public member variables can be accessed correctly. The reason has been explained above when talking about the principle of injection.
When calling a method of a behavior bound to a Component, if the behavior has defined the method, the above judgment can be passed. At this time, this method can be public, private, or protected. But in the end only the public method can be called correctly. If you understand the reason for the previous one, then you will understand it here too.


Dependency Injection Container
Dependency Injection (DI) container is an object. It knows how to initialize and configure the object and all the objects it depends on. Martin's article already explains why DI containers are useful. Here we mainly explain how to use the DI container provided by Yii.

Dependency Injection

Yii provides DI container features through the yiidiContainer class. It supports the following types of dependency injection:

  • Constructor injection;
  • Setter and property injection;
  • PHP callback injection.
  • Constructor injection

With the help of parameter type hints, the DI container implements constructor injection. When a container is used to create a new object, type hints tell it what classes or interfaces it depends on. The container will try to obtain an instance of the class or interface it depends on, and then inject it into the new object through the constructor. For example:

class Foo
{
  public function __construct(Bar $bar)
  {
  }
}

$foo = $container->get('Foo');
// 上面的代码等价于:
$bar = new Bar;
$foo = new Foo($bar);

Copy after login

Setter and Property Injection

Setter and property injection are supported through configuration. When registering a dependency or creating a new object, you can provide a configuration that will be provided to the container for injecting the dependency through the corresponding setter or property. For example:

use yii\base\Object;

class Foo extends Object
{
  public $bar;

  private $_qux;

  public function getQux()
  {
    return $this->_qux;
  }

  public function setQux(Qux $qux)
  {
    $this->_qux = $qux;
  }
}

$container->get('Foo', [], [
  'bar' => $container->get('Bar'),
  'qux' => $container->get('Qux'),
]);

Copy after login

PHP 回调注入

这种情况下,容器将使用一个注册过的 PHP 回调创建一个类的新实例。回调负责解决依赖并将其恰当地注入新创建的对象。例如:

$container->set('Foo', function () {
  return new Foo(new Bar);
});

$foo = $container->get('Foo');

Copy after login

注册依赖关系

可以用 yii\di\Container::set() 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。

$container = new \yii\di\Container;

// 注册一个同类名一样的依赖关系,这个可以省略。
$container->set('yii\db\Connection');

// 注册一个接口
// 当一个类依赖这个接口时,相应的类会被初始化作为依赖对象。
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');

// 注册一个别名。
// 你可以使用 $container->get('foo') 创建一个 Connection 实例
$container->set('foo', 'yii\db\Connection');

// 通过配置注册一个类
// 通过 get() 初始化时,配置将会被使用。
$container->set('yii\db\Connection', [
  'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
  'username' => 'root',
  'password' => '',
  'charset' => 'utf8',
]);

// 通过类的配置注册一个别名
// 这种情况下,需要通过一个 “class” 元素指定这个类
$container->set('db', [
  'class' => 'yii\db\Connection',
  'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
  'username' => 'root',
  'password' => '',
  'charset' => 'utf8',
]);

// 注册一个 PHP 回调
// 每次调用 $container->get('db') 时,回调函数都会被执行。
$container->set('db', function ($container, $params, $config) {
  return new \yii\db\Connection($config);
});

// 注册一个组件实例
// $container->get('pageCache') 每次被调用时都会返回同一个实例。
$container->set('pageCache', new FileCache);

Copy after login

Tip: 如果依赖关系名称和依赖关系的定义相同,则不需要通过 DI 容器注册该依赖关系。
通过 set() 注册的依赖关系,在每次使用时都会产生一个新实例。可以使用 yii\di\Container::setSingleton() 注册一个单例的依赖关系:

$container->setSingleton('yii\db\Connection', [
  'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
  'username' => 'root',
  'password' => '',
  'charset' => 'utf8',
]);
Copy after login

解决依赖关系

注册依赖关系后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系,将依赖实例化并注入新创建的对象。依赖关系的解决是递归的,如果一个依赖关系中还有其他依赖关系,则这些依赖关系都会被自动解决。

可以使用 yii\di\Container::get() 创建新的对象。该方法接收一个依赖关系名称,它可以是一个类名,一个接口名或一个别名。依赖关系名或许是通过 set() 或 setSingleton() 注册的。你可以随意地提供一个类的构造器参数列表和一个configuration 用于配置新创建的对象。例如:

// "db" 是前面定义过的一个别名
$db = $container->get('db');

// 等价于: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]);
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]);

Copy after login

代码背后,DI 容器做了比创建对象多的多的工作。容器首先将检查类的构造方法,找出依赖的类或接口名,然后自动递归解决这些依赖关系。

如下代码展示了一个更复杂的示例。UserLister 类依赖一个实现了 UserFinderInterface 接口的对象;UserFinder 类实现了这个接口,并依赖于一个 Connection 对象。所有这些依赖关系都是通过类构造器参数的类型提示定义的。通过属性依赖关系的注册,DI 容器可以自动解决这些依赖关系并能通过一个简单的 get('userLister') 调用创建一个新的 UserLister 实例。

namespace app\models;

use yii\base\Object;
use yii\db\Connection;
use yii\di\Container;

interface UserFinderInterface
{
  function findUser();
}

class UserFinder extends Object implements UserFinderInterface
{
  public $db;

  public function __construct(Connection $db, $config = [])
  {
    $this->db = $db;
    parent::__construct($config);
  }

  public function findUser()
  {
  }
}

class UserLister extends Object
{
  public $finder;

  public function __construct(UserFinderInterface $finder, $config = [])
  {
    $this->finder = $finder;
    parent::__construct($config);
  }
}

$container = new Container;
$container->set('yii\db\Connection', [
  'dsn' => '...',
]);
$container->set('app\models\UserFinderInterface', [
  'class' => 'app\models\UserFinder',
]);
$container->set('userLister', 'app\models\UserLister');

$lister = $container->get('userLister');

// 等价于:

$db = new \yii\db\Connection(['dsn' => '...']);
$finder = new UserFinder($db);
$lister = new UserLister($finder);

Copy after login

实践中的运用

当在应用程序的入口脚本中引入 Yii.php 文件时,Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 Yii::$container 访问。当调用 Yii::createObject() 时,此方法实际上会调用这个容器的 yii\di\Container::get() 方法创建新对象。如上所述,DI 容器会自动解决依赖关系(如果有)并将其注入新创建的对象中。因为 Yii 在其多数核心代码中都使用了 Yii::createObject() 创建新对象,所以你可以通过 Yii::$container 全局性地自定义这些对象。

例如,你可以全局性自定义 yii\widgets\LinkPager 中分页按钮的默认数量:

\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
Copy after login

这样如果你通过如下代码在一个视图里使用这个挂件,它的 maxButtonCount 属性就会被初始化为 5 而不是类中定义的默认值 10。

echo \yii\widgets\LinkPager::widget();
Copy after login

然而你依然可以覆盖通过 DI 容器设置的值:

echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]);
Copy after login

另一个例子是借用 DI 容器中自动构造方法注入带来的好处。假设你的控制器类依赖一些其他对象,例如一个旅馆预订服务。你可以通过一个构造器参数声明依赖关系,然后让 DI 容器帮你自动解决这个依赖关系。

namespace app\controllers;

use yii\web\Controller;
use app\components\BookingInterface;

class HotelController extends Controller
{
  protected $bookingService;

  public function __construct($id, $module, BookingInterface $bookingService, $config = [])
  {
    $this->bookingService = $bookingService;
    parent::__construct($id, $module, $config);
  }
}

Copy after login

如果你从浏览器中访问这个控制器,你将看到一个报错信息,提醒你 BookingInterface 无法被实例化。这是因为你需要告诉 DI 容器怎样处理这个依赖关系。

\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService');
现在如果你再次访问这个控制器,一个 app\components\BookingService 的实例就会被创建并被作为第三个参数注入到控制器的构造器中。

什么时候注册依赖关系

由于依赖关系在创建新对象时需要解决,因此它们的注册应该尽早完成。如下是推荐的实践:

如果你是一个应用程序的开发者,你可以在应用程序的入口脚本或者被入口脚本引入的脚本中注册依赖关系。
如果你是一个可再分发扩展的开发者,你可以将依赖关系注册到扩展的引导类中。
总结

依赖注入和服务定位器都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。强烈推荐你阅读 Martin 的文章,对依赖注入和服务定位器有个更深入的理解。

Yii 在依赖住入(DI)容器之上实现了它的服务定位器。当一个服务定位器尝试创建一个新的对象实例时,它会把调用转发到 DI 容器。后者将会像前文所述那样自动解决依赖关系。

Articles you may be interested in:

  • Methods to remove behaviors bound to components in PHP's Yii framework
  • Definition and binding of behaviors in PHP's Yii framework Explanation of certain methods
  • Detailed explanation of how to use Behaviors in PHP's Yii framework
  • In-depth explanation of properties in PHP's Yii framework
  • In PHP's Yii framework Tutorial on using database configuration and SQL operation examples
  • In-depth analysis of the event mechanism in PHP's Yii framework
  • Comprehensive interpretation of the logging function in PHP's Yii framework
  • Yii usage find findAll implementation method to find out the specified field
  • Parsing the addition, deletion, and modification of yii database
  • Yii PHP Framework practical introductory tutorial (detailed introduction)

www.bkjia.comtruehttp: //www.bkjia.com/PHPjc/1111895.htmlTechArticleDetailed explanation of attribute injection and method injection of component behavior in PHP's Yii framework, and the principle of attribute and method injection of yii framework behavior Above we learned that the purpose of behavior is to combine its own attributes with...
Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template