>  기사  >  백엔드 개발  >  PHP 종속성 주입 예제 분석 이해

PHP 종속성 주입 예제 분석 이해

高洛峰
高洛峰원래의
2016-12-20 16:13:15993검색

이 기사의 예에서는 PHP 종속성 주입에 대한 이해를 설명합니다. 참고로 자세한 내용은 다음과 같습니다.

Laravel의 IoC 컨테이너 문서를 보면 예제만 소개되어 있을 뿐, 원리를 설명하지는 않았습니다. 예전에 MVC 프레임워크에서 우연히 봤는데 이렇게 상세한 소개가 갑자기 깨달은 것 같아서 복사해서 붙여넣었습니다. 오랫동안 아무것도 쓰지 않아서 지금은 정말 게으른 상태거든요!

먼저 SomeComponent라는 컴포넌트를 개발한다고 가정합니다. 이제 데이터베이스 연결이 이 구성요소에 주입됩니다.

이 예에서는 데이터베이스 연결이 구성 요소에서 생성됩니다. 이 방법은 실용적이지 않습니다. 이렇게 하면 데이터베이스 연결 매개 변수 및 데이터베이스 유형과 같은 일부 매개 변수를 변경할 수 없습니다.

class SomeComponent {
  /**
   * The instantiation of the connection is hardcoded inside
   * the component so is difficult to replace it externally
   * or change its behavior
   */
  public function someDbTask()
  {
    $connection = new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
    // ...
  }
}
$some = new SomeComponent();
$some->someDbTask();

위 문제를 해결하려면 사용하기 전에 외부 연결을 만들어서 컨테이너에 주입해야 합니다. 현재로서는 이것이 좋은 해결책처럼 보입니다.

class SomeComponent {
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection)
  {
    $this->_connection = $connection;
  }
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
}
$some = new SomeComponent();
//Create the connection
$connection = new Connection(array(
  "host" => "localhost",
  "username" => "root",
  "password" => "secret",
  "dbname" => "invo"
));
//Inject the connection in the component
$some->setConnection($connection);
$some->someDbTask();

이제 데이터베이스 연결이 여러 번 생성되는 애플리케이션의 다른 위치에서 이 구성 요소를 사용하는 문제를 고려해 보겠습니다. 일단 데이터베이스 연결 인스턴스를 생성하는 대신 여기에서 전역 레지스트리와 유사한 방법을 사용하여 데이터베이스 연결 인스턴스를 얻으십시오.

class Registry
{
  /**
   * Returns the connection
   */
  public static function getConnection()
  {
    return new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
  }
}
class SomeComponent
{
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection){
    $this->_connection = $connection;
  }
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
}
$some = new SomeComponent();
//Pass the connection defined in the registry
$some->setConnection(Registry::getConnection());
$some->someDbTask();

이제 구성 요소에 두 가지 메소드를 구현해야 한다고 가정해 보겠습니다. 그 중 첫 번째는 새 데이터베이스 연결을 생성해야 하고 두 번째 메소드는 항상 공유 연결을 얻습니다.

class Registry
{
  protected static $_connection;
  /**
   * Creates a connection
   */
  protected static function _createConnection()
  {
    return new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
  }
  /**
   * Creates a connection only once and returns it
   */
  public static function getSharedConnection()
  {
    if (self::$_connection===null){
      $connection = self::_createConnection();
      self::$_connection = $connection;
    }
    return self::$_connection;
  }
  /**
   * Always returns a new connection
   */
  public static function getNewConnection()
  {
    return self::_createConnection();
  }
}
class SomeComponent
{
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection){
    $this->_connection = $connection;
  }
  /**
   * This method always needs the shared connection
   */
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
  /**
   * This method always needs a new connection
   */
  public function someOtherDbTask($connection)
  {
  }
}
$some = new SomeComponent();
//This injects the shared connection
$some->setConnection(Registry::getSharedConnection());
$some->someDbTask();
//Here, we always pass a new connection as parameter
$some->someOtherDbTask(Registry::getConnection());

지금까지 의존성 주입을 사용하여 문제를 해결하는 방법을 살펴보았습니다. 코드 내에서 종속성을 생성하는 대신 이를 매개변수로 전달합니다. 그러면 프로그램을 더 쉽게 유지 관리할 수 있고 프로그램 코드의 결합이 줄어들며 일종의 느슨한 결합이 달성됩니다. 그러나 장기적으로 볼 때 이러한 형태의 종속성 주입에는 몇 가지 단점도 있습니다.

예를 들어 구성 요소에 더 많은 종속성이 있는 경우 전달할 setter 메서드를 여러 개 생성하거나 전달할 생성자를 생성해야 합니다. 게다가 컴포넌트를 사용할 때마다 종속 컴포넌트를 생성해야 하므로 코드 유지 관리가 쉽지 않습니다.

//Create the dependencies or retrieve them from the registry
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
//Pass them as constructor parameters
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... or using setters
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);

제 생각에는 다음과 같은 코드를 생성해야 합니다. 이 개체는 응용 프로그램의 여러 위치에 있습니다. 종속 구성 요소가 필요하지 않은 경우 코드 삽입 부분으로 이동하여 생성자 또는 setter 메서드에서 매개 변수를 제거해야 합니다. 이 문제를 해결하기 위해 다시 한 번 전역 레지스트리를 사용하여 구성 요소를 만드는 방법으로 돌아갑니다. 그러나 객체를 생성하기 전에 새로운 추상화 계층을 추가합니다.

class SomeComponent
{
  // ...
  /**
   * Define a factory method to create SomeComponent instances injecting its dependencies
   */
  public static function factory()
  {
    $connection = new Connection();
    $session = new Session();
    $fileSystem = new FileSystem();
    $filter = new Filter();
    $selector = new Selector();
    return new self($connection, $session, $fileSystem, $filter, $selector);
  }
}

이 순간 문제의 시작으로 돌아가서 구성 요소 내부에 종속성을 생성하는 것 같습니다. 매번 수정하고 문제를 해결할 수 있는 방법을 찾아보지만 이는 좋은 접근 방식이 아닙니다.

이러한 문제를 해결하는 실용적이고 우아한 방법은 컨테이너 종속성 주입을 사용하는 것입니다. 앞에서 살펴본 것처럼 컨테이너는 전역 레지스트리 역할을 하며 컨테이너 종속성 주입을 브리지로 사용하여 문제를 해결할 수 있습니다. 코드가 덜 결합되고 구성 요소의 복잡성이 줄어듭니다.

class SomeComponent
{
  protected $_di;
  public function __construct($di)
  {
    $this->_di = $di;
  }
  public function someDbTask()
  {
    // Get the connection service
    // Always returns a new connection
    $connection = $this->_di->get('db');
  }
  public function someOtherDbTask()
  {
    // Get a shared connection service,
    // this will return the same connection everytime
    $connection = $this->_di->getShared('db');
    //This method also requires a input filtering service
    $filter = $this->_db->get('filter');
  }
}
$di = new Phalcon\DI();
//Register a "db" service in the container
$di->set('db', function(){
  return new Connection(array(
    "host" => "localhost",
    "username" => "root",
    "password" => "secret",
    "dbname" => "invo"
  ));
});
//Register a "filter" service in the container
$di->set('filter', function(){
  return new Filter();
});
//Register a "session" service in the container
$di->set('session', function(){
  return new Session();
});
//Pass the service container as unique parameter
$some = new SomeComponent($di);
$some->someTask();

이제 구성 요소는 특정 서비스에 액세스할 때만 필요합니다. 그렇지 않으면 리소스를 절약하기 위해 초기화되지도 않습니다. 구성요소가 고도로 분리되어 있습니다. 해당 동작이나 다른 측면은 구성 요소 자체에 영향을 주지 않습니다. 우리의 구현 방법

PhalconDI는 서비스의 의존성 주입 기능을 구현하는 컴포넌트이자 컨테이너 그 자체이기도 합니다.

Phalcon의 높은 수준의 분리로 인해 PhalconDI는 다른 구성요소를 통합하는 데 사용되는 프레임워크의 필수적인 부분입니다. 개발자는 이 구성요소를 사용하여 애플리케이션에서 다양한 클래스 파일의 인스턴스를 종속성 주입하고 관리할 수도 있습니다.

기본적으로 이 구성 요소는 Inversion of Control 패턴을 구현합니다. 이를 바탕으로 객체는 더 이상 생성자에서 매개변수를 받거나 setter를 사용하여 주입을 구현하지 않고 직접 서비스의 종속성 주입을 요청합니다. 구성 요소의 필수 종속성을 얻는 방법은 한 가지뿐이므로 전체 프로그램 복잡성이 크게 줄어듭니다.
또한 이 패턴은 코드의 테스트 가능성을 향상시켜 오류 발생 가능성을 줄여줍니다.
컨테이너에 서비스 등록

프레임워크 자체 또는 개발자가 서비스를 등록할 수 있습니다. 구성 요소 A가 구성 요소 B(또는 해당 클래스의 인스턴스)에 대한 호출을 요청할 때 구성 요소 B의 인스턴스를 만드는 대신 컨테이너에서 구성 요소 B에 대한 호출을 요청할 수 있습니다.

이러한 작업 방식은 우리에게 많은 이점을 제공합니다.

자체적으로 구성 요소를 교체하거나 제3자가 쉽게 만든 구성 요소를 교체할 수 있습니다.
컴포넌트가 출시되기 전에 객체의 초기화를 완전히 제어하고 객체에 대한 다양한 설정을 지정할 수 있습니다.

통일된 방법을 사용하여 구성 요소에서 구조화된 전역 인스턴스를 가져올 수 있습니다
서비스는 다음과 같은 방법으로 컨테이너에 주입될 수 있습니다.

//Create the Dependency Injector Container
$di = new Phalcon\DI();
//By its class name
$di->set("request", 'Phalcon\Http\Request');
//Using an anonymous function, the instance will lazy loaded
$di->set("request", function(){
  return new Phalcon\Http\Request();
});
//Registering directly an instance
$di->set("request", new Phalcon\Http\Request());
//Using an array definition
$di->set("request", array(
  "className" => 'Phalcon\Http\Request'
));

위 예에서, 프레임워크에서 요청 데이터에 대한 액세스를 요청할 때 먼저 이 "요청" 이름을 가진 서비스가 컨테이너에 존재하는지 확인합니다.

컨테이너는 요청된 데이터의 인스턴스를 반환하고 개발자는 마침내 원하는 구성 요소를 얻습니다.
위 예의 각 방법에는 장점과 단점이 있습니다. 어떤 방법을 사용할지는 개발 과정 중 특정 시나리오에 따라 다릅니다.

用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。

Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。

//Register a service "db" with a class name and its parameters
$di->set("db", array(
  "className" => "Phalcon\Db\Adapter\Pdo\Mysql",
  "parameters" => array(
     "parameter" => array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "blog"
     )
  )
));
//Using an anonymous function
$di->set("db", function(){
  return new Phalcon\Db\Adapter\Pdo\Mysql(array(
     "host" => "localhost",
     "username" => "root",
     "password" => "secret",
     "dbname" => "blog"
  ));
});

以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:

$di->setParameter("db", 0, array(
  "host" => "localhost",
  "username" => "root",
  "password" => "secret"
));

从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:

$request = $di->get("request");

   

或者通过下面这种魔术方法的形式调用:

$request = $di->getRequest();

   

Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
具体的 Phalcon\Http\Request 请求示例:

$request = $di->getShared("request");

   

参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:

$component = $di->get("MyComponent", array("some-parameter", "other"));

  希望本文所述对大家PHP程序设计有所帮助。

   更多对PHP依赖注入的理解实例分析相关文章请关注PHP中文网!

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