Maison  >  Article  >  développement back-end  >  À propos de l'injection de propriétés et de l'injection de méthodes du comportement des composants du framework Yii en PHP

À propos de l'injection de propriétés et de l'injection de méthodes du comportement des composants du framework Yii en PHP

不言
不言original
2018-06-19 15:11:581988parcourir

Cet article présente principalement l'explication détaillée de l'injection d'attributs et de l'injection de méthodes du comportement des composants dans le framework Yii de PHP, y compris une explication de l'injection de dépendances. Les amis dans le besoin peuvent se référer aux attributs et aux méthodes de

Principe de l'injection de méthodes

Nous avons appris plus haut que le but du comportement est d'injecter ses propres attributs et méthodes dans la classe attachée. Alors, comment Yii injecte-t-il les propriétés et méthodes d'un comportement yiibaseBehavior dans un yiibaseComponent ? Pour les propriétés, cela est réalisé grâce aux méthodes magiques __get() et __set(). Pour les méthodes, cela se fait via la méthode __call().

Injection de propriétés

Prenons la lecture comme exemple Si vous accédez à $Component->property1, que fait Yii dans les coulisses ? Jetons un coup d'œil à 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);
  }
}

Concentrez-vous sur les différences entre yiibaseComponent::__get() et yiibaseObject::__get() . Autrement dit, pour le traitement après une fonction getter non définie, yiibaseObject lève directement une exception, vous indiquant que la propriété à laquelle vous souhaitez accéder n'existe pas. Mais pour yiibaseComponent, s'il n'y a pas de getter, il doit encore vérifier s'il s'agit d'un attribut du comportement injecté :

Tout d'abord, $this->ensureBehaviors() est appelé. Cette méthode a déjà été mentionnée, principalement pour garantir que le comportement a été lié.

Après vous être assuré que le comportement a été lié, commencez à parcourir $this->_behaviors . Yii enregistre tous les comportements liés de la classe dans le tableau yiibaseComponent::$_behaviors[].
Enfin, déterminez si cette propriété est une propriété lisible du comportement lié via canGetProperty() du comportement. Si tel est le cas, renvoyez la propriété $behavior->name de ce comportement. Lecture complète des propriétés. Quant à canGetProperty(), il a été brièvement abordé dans la section :ref::property, et sera introduit de manière ciblée plus tard.
Pour le passeur, le code est similaire, il ne prendra donc pas de place ici.

L'injection de méthodes

est similaire à l'injection d'attributs via la méthode magique __get() __set() Yii implémente la méthode dans le comportement via __call(. ) méthode magique Injection :

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()");
}

Comme le montre le code ci-dessus, Yii appelle d'abord $this->ensureBehaviors() pour s'assurer que le le comportement a été lié Certainement.

Ensuite, il parcourt également le tableau yiibaseComponent::$_behaviros[] . Déterminez si la méthode existe via la méthode hasMethod(). Si la méthode à appeler dans le comportement lié existe, utilisez call_user_func_array() de PHP pour l'appeler. Quant à la méthode hasMethod(), nous en reparlerons plus tard.

Contrôle d'accès aux propriétés et méthodes injectées

Dans la section précédente, nous avons indiqué si les membres publics, privés et protégés du comportement sont accessibles dans la classe liée .des exemples précis. Ici, nous analysons les raisons au niveau du code.

Dans le contenu ci-dessus, nous savons que le fait qu'une propriété soit accessible ou non dépend principalement du comportement de canGetProperty() et canSetProperty(). Le fait qu'une méthode puisse être appelée ou non dépend principalement du comportement de hasMethod(). Puisque yiibaseBehavior hérite de notre vieil ami yiibaseObject, les codes des trois méthodes de jugement mentionnées ci-dessus sont en fait dans Object. Regardons-les une par une :

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);
}

Ces trois méthodes ne sont vraiment pas compliquées du tout. À cet égard, nous pouvons tirer les conclusions suivantes :

Lors de la lecture (écriture) d'une propriété sur un comportement lié à un composant, si le comportement définit un getter (setter) pour la propriété, il est accessible. Ou, si le comportement possède cette variable membre, il peut passer le jugement ci-dessus. À ce stade, la variable membre peut être publique, privée ou protégée. Mais au final, seules les variables publiques des membres sont accessibles correctement. La raison a été expliquée plus haut en parlant du principe de l’injection.

Lors de l'appel d'une méthode d'un comportement lié à un composant, si le comportement a défini la méthode, le jugement ci-dessus peut être porté. À l’heure actuelle, cette méthode peut être publique, privée ou protégée. Mais au final, seule la méthode publique peut être appelée correctement. Si vous comprenez la raison de la précédente, alors vous la comprendrez ici aussi.

Conteneur d'injection de dépendances
Le conteneur d'injection de dépendances (DI) est un objet qui sait initialiser et configurer l'objet et tous les objets dont il dépend. L'article de Martin explique déjà pourquoi les conteneurs DI sont utiles. Ici, nous expliquons principalement comment utiliser le conteneur DI fourni par Yii.

Injection de dépendances

Yii fournit des fonctionnalités de conteneur DI via la classe yiidiContainer. Il prend en charge les types d'injection de dépendances suivants :

  • Injection de constructeur

  • Injection de setter et de propriétés

  • Injection de rappel PHP.

  • Injection de constructeur

Le conteneur DI implémente le constructeur à l'aide de l'injection d'indices de type de paramètre. Lorsqu'un conteneur est utilisé pour créer un nouvel objet, les indications de type lui indiquent de quelles classes ou interfaces il dépend. Le conteneur tentera d'obtenir une instance de la classe ou de l'interface dont il dépend, puis l'injectera dans le nouvel objet via le constructeur. Par exemple :

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

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

Setter et injection de propriété

Setter 和属性注入是通过配置提供支持的。当注册一个依赖或创建一个新对象时,你可以提供一个配置,该配置会提供给容器用于通过相应的 Setter 或属性注入依赖。例如:

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'),
]);

PHP 回调注入

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

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

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

注册依赖关系

可以用 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);

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',
]);

解决依赖关系

注册依赖关系后,就可以使用 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]);

代码背后,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);

实践中的运用

当在应用程序的入口脚本中引入 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]);

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

echo \yii\widgets\LinkPager::widget();

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

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

另一个例子是借用 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);
  }
}

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

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

什么时候注册依赖关系

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

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

L'injection de dépendances et le localisateur de services sont deux modèles de conception populaires qui vous permettent de créer des logiciels dans un style entièrement découplé et plus convivial pour les tests. Je vous recommande fortement de lire l'article de Martin pour mieux comprendre l'injection de dépendances et les localisateurs de services.

Yii implémente son localisateur de services au-dessus du conteneur d'enregistrement des dépendances (DI). Lorsqu'un localisateur de services tente de créer une nouvelle instance d'objet, il transmet l'appel au conteneur DI. Ce dernier résoudra automatiquement les dépendances comme décrit ci-dessus.

Ce qui précède représente l'intégralité du contenu de cet article. J'espère qu'il sera utile à l'étude de chacun. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois !

Recommandations associées :

Analyse des composants du framework Yii et gestion du comportement des événements

À propos de la création du backend de Yii2 et de la mise en œuvre Contrôle des autorisations rbac

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn