>  기사  >  백엔드 개발  >  PHP에서 Yii 프레임워크의 구성 요소 동작에 대한 속성 주입 및 메서드 주입 정보

PHP에서 Yii 프레임워크의 구성 요소 동작에 대한 속성 주입 및 메서드 주입 정보

不言
不言원래의
2018-06-19 15:11:581982검색

이 글에서는 종속성 주입에 대한 설명을 포함하여 PHP Yii 프레임워크의 구성 요소 동작에 대한 속성 주입 및 메서드 주입에 대한 자세한 설명을 주로 소개합니다. 필요한 친구는

속성 및 동작 메서드 주입의 원리

를 참조할 수 있습니다. 위에서 언급한 내용은 동작의 목적이 연결된 클래스에 자체 속성과 메서드를 주입하는 것임을 배웠습니다. 그렇다면 Yii는 어떻게 yiibaseBehavior 동작의 속성과 메서드를 yiibaseComponent에 주입합니까? 속성의 경우 이는 __get() 및 __set() 매직 메서드를 통해 달성됩니다. 메소드의 경우 __call() 메소드를 통해 이루어집니다.

속성 주입

예를 들어 $Component->property1에 액세스하면 Yii는 뒤에서 무엇을 하고 있나요? 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);
  }
}

을 살펴보겠습니다. yiibaseComponent::__get()과 yiibaseObject::__get()의 차이점에 집중하세요. 즉, 정의되지 않은 getter 함수 이후의 처리를 위해 yiibaseObject는 접근하려는 속성이 존재하지 않는다는 예외를 직접 발생시킵니다. 그러나 yiibaseComponent의 경우 getter가 없는 후에도 이것이 주입된 동작의 속성인지 확인해야 합니다.

먼저 $this->ensureBehaviors()가 호출됩니다. 이 방법은 이전에 주로 동작이 바인딩되었는지 확인하기 위해 언급되었습니다.
동작이 바인딩되었는지 확인한 후 $this->_behaviors 탐색을 시작하세요. Yii는 클래스의 모든 바인딩된 동작을 yiibaseComponent::$_behaviors[] 배열에 저장합니다.
마지막으로 동작의 canGetProperty()를 사용하여 이 속성이 바인딩된 동작의 읽기 가능한 속성인지 확인합니다. 그렇다면 이 동작의 $behavior->name 속성을 반환합니다. 속성을 완전히 읽습니다. canGetProperty()에 대해서는 :ref::property 섹션에서 간략하게 논의했으며, 나중에 타겟 방식으로 소개할 예정입니다.
세터의 경우 코드가 유사하므로 여기서는 공간을 차지하지 않습니다.

메서드 주입

은 __get() __set() 매직 메소드를 통한 속성 주입과 유사합니다. Yii는 __call() 매직 메소드를 통해 동작에 메소드 주입을 구현합니다.

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

위에서 코드에서 볼 수 있듯이 Yii는 먼저 $this->ensureBehaviors()를 호출하여 동작이 바인딩되었는지 확인합니다.

그런 다음 yiibaseComponent::$_behaviros[] 배열도 순회합니다. hasMethod() 메소드를 통해 해당 메소드가 존재하는지 확인합니다. 바인딩된 동작에서 호출할 메서드가 존재하는 경우 PHP의 call_user_func_array()를 사용하여 호출합니다. hasMethod() 메소드에 대해서는 나중에 다루겠습니다.

주입된 속성 및 메서드의 액세스 제어

앞에서 동작의 공개, 비공개 및 보호 멤버가 바인딩된 클래스에서 액세스 가능한지 여부에 대한 구체적인 예를 제시했습니다. 여기서는 코드 수준에서 이유를 분석합니다.

위 내용에서 우리는 속성에 액세스할 수 있는지 여부가 주로 canGetProperty() 및 canSetProperty()의 동작에 따라 결정된다는 것을 알고 있습니다. 메소드 호출 가능 여부는 주로 hasMethod()의 동작에 따라 달라집니다. yiibaseBehavior는 우리의 오랜 친구인 yiibaseObject로부터 상속받았기 때문에 위에서 언급한 세 가지 판단 방법에 대한 코드는 실제로 Object에 있습니다. 하나씩 살펴보겠습니다.

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

이 세 가지 방법은 실제로 전혀 복잡하지 않습니다. 이와 관련하여 다음과 같은 결론을 내릴 수 있습니다.

구성 요소에 바인딩된 동작에 대한 속성을 읽을(쓰기) 때 해당 동작이 해당 속성에 대한 getter(setter)를 정의하면 액세스할 수 있습니다. 또는 동작에 이 멤버 변수가 있으면 위의 판단을 통과할 수 있습니다. 이때 멤버 변수는 public, private 또는 protected일 수 있습니다. 그러나 결국에는 공용 멤버 변수만 올바르게 액세스할 수 있습니다. 그 이유는 위에서 주입 원리를 말할 때 설명했습니다.
컴포넌트에 바인딩된 동작의 메서드를 호출할 때 해당 동작이 메서드를 정의했다면 위의 판단을 통과할 수 있습니다. 이때 이 메소드는 public, private, protected일 수 있습니다. 그러나 결국에는 공개 메소드만이 올바르게 호출될 수 있습니다. 이전 이유를 이해했다면 여기서도 이해하게 될 것입니다.

종속성 주입 컨테이너
DI(종속성 주입) 컨테이너는 개체와 개체가 종속된 모든 개체를 초기화하고 구성하는 방법을 알고 있습니다. Martin의 기사에서는 이미 DI 컨테이너가 유용한 이유를 설명하고 있습니다. 여기에서는 주로 Yii에서 제공하는 DI 컨테이너를 사용하는 방법을 설명합니다.

종속성 주입

Yii는 yiidiContainer 클래스를 통해 DI 컨테이너 기능을 제공합니다. 다음 유형의 종속성 주입을 지원합니다:

  • Setter 및 속성 주입

  • 생성자 주입

  • 도움 의 DI 컨테이너는 생성자 주입을 구현합니다. 컨테이너가 새 객체를 생성하는 데 사용될 때 유형 힌트는 해당 객체가 의존하는 클래스나 인터페이스를 알려줍니다. 컨테이너는 자신이 의존하는 클래스나 인터페이스의 인스턴스를 얻은 다음 생성자를 통해 새 개체에 주입하려고 시도합니다. 예:

  • class Foo
    {
      public function __construct(Bar $bar)
      {
      }
    }
    
    $foo = $container->get('Foo');
    // 上面的代码等价于:
    $bar = new Bar;
    $foo = new Foo($bar);

Setter 및 속성 주입

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 的实例就会被创建并被作为第三个参数注入到控制器的构造器中。

什么时候注册依赖关系

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

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

종속성 주입과 서비스 로케이터는 모두 완전히 분리되고 테스트 친화적인 스타일로 소프트웨어를 구축할 수 있는 인기 있는 디자인 패턴입니다. 종속성 주입 및 서비스 로케이터에 대해 더 깊이 이해하려면 Martin의 기사를 읽어 보시기 바랍니다.

Yii는 DI(종속성 체크인) 컨테이너 위에 서비스 로케이터를 구현합니다. 서비스 로케이터가 새 객체 인스턴스를 생성하려고 시도하면 호출을 DI 컨테이너로 전달합니다. 후자는 위에서 설명한 대로 종속성을 자동으로 해결합니다.

위 내용은 이 글의 전체 내용입니다. 모든 분들의 학습에 도움이 되었으면 좋겠습니다. 더 많은 관련 내용은 PHP 중국어 홈페이지를 주목해주세요!

관련 권장 사항:

Yii 프레임워크 구성 요소 및 이벤트 동작 관리 분석

Yii2 백엔드 구축 및 rbac 권한 제어 구현 정보

위 내용은 PHP에서 Yii 프레임워크의 구성 요소 동작에 대한 속성 주입 및 메서드 주입 정보의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.