inyeccion injection ejemplo dependency dependencias control arquitectura php design-patterns dependency-injection

php - injection - Inyección de dependencia: tirando de los componentes requeridos cuando realmente son necesarios



inyeccion de dependencias php (4)

Últimamente he estado pensando mucho en este problema al planear un proyecto importante que quiero hacer tan bien como sea humanamente posible (atenerse a LoD, sin dependencias codificadas, etc.). Mi primer pensamiento fue también el enfoque de "inyectar una fábrica", pero no estoy seguro de que ese sea el camino a seguir. Las conversaciones del Código de Limpieza de Google hicieron la afirmación de que si atraviesa un objeto para obtener el objeto que realmente desea, está violando la LdD. Eso parece descartar la idea de inyectar una fábrica, porque tienes que llegar a través de la fábrica para obtener lo que realmente quieres. Tal vez he perdido algún punto allí que lo hace estar bien, pero hasta que esté seguro, estoy considerando otros enfoques.

¿Cómo se hace la función de inyección? Me imagino que está pasando una devolución de llamada que hace la creación de instancias del objeto que desea, pero un ejemplo de código sería bueno.

Si pudiera actualizar su pregunta con ejemplos de código de cómo hacer los tres estilos que mencionó, podría ser útil. Estoy especialmente interesado en ver "inyectar el inyector" incluso si es un antipatrón.

Lo esencial detrás de DI es evitar que una clase cree y prepare objetos de los que depende y los empuja. Esto suena muy razonable, pero a veces una clase no necesita todos los objetos que se están empujando para llevar a cabo su función. La razón detrás de esto es un "retorno anticipado" que ocurre con una entrada de usuario no válida o una excepción lanzada por uno de los objetos requeridos anteriormente o la indisponibilidad de un cierto valor necesario para instanciar un objeto hasta que se ejecuta un bloque de código.

Más ejemplos prácticos:

  • inyectar un objeto de conexión de base de datos que nunca se utilizará, porque los datos del usuario no pasan la validación (siempre que no se utilicen activadores para validar estos datos)
  • inyectar objetos similares a Excel (p. ej., PHPExcel) que recopilan datos de entrada (pesados ​​para cargar y crear instancias porque una biblioteca completa se introduce y nunca se usa, porque la validación produce una excepción antes de que se produzca una escritura)
  • un valor variable que se determina dentro de una clase, pero no el inyector en tiempo de ejecución; por ejemplo, un componente de enrutamiento que determina la clase y el método del controlador (o comando) que se debe llamar en función de la entrada del usuario
  • aunque esto podría ser un problema de diseño, pero una clase de servicio sustancial, eso depende de muchos componentes, pero usa solo como 1/3 de ellos por solicitud (la razón por la cual tiendo a usar clases de comando en lugar de controladores)

Por lo tanto, en cierto modo, presionar todos los componentes necesarios contradice la "carga perezosa" en la forma en que algunos componentes se crean y nunca se usan, lo que es un poco impracticable e impactante en el rendimiento. En lo que respecta a PHP, se cargan, analizan y compilan más archivos. Esto es especialmente doloroso, si los objetos que están siendo empujados tienen sus propias dependencias.

Veo 3 formas de evitarlo, 2 de las cuales no suenan muy bien:

  • inyectando una fábrica
  • inyectando el inyector (un anti-patrón)
  • inyectar alguna función externa, que se llama desde dentro de la clase una vez que se alcanza un punto relevante (smtg como "recuperar una instancia de PHPExcel una vez que finaliza la validación de datos"); Esto es lo que tiendo a usar debido a su flexibilidad.

La pregunta es ¿cuál es la mejor manera de lidiar con tales situaciones / qué usan ustedes?

ACTUALIZACIÓN : @GordonM aquí están los ejemplos de 3 enfoques:

//inject factory example interface IFactory{ function factory(); } class Bartender{ protected $_factory; public function __construct(IFactory $f){ $this->_factory = $f; } public function order($data){ //validating $data //... return or throw exception //validation passed, order must be saved $db = $this->_factory->factory(); //! factory instance * num necessary components $db->insert(''orders'', $data); //... } } /* inject provider example assuming that the provider prepares necessary objects (i.e. injects their dependencies as well) */ interface IProvider{ function get($uid); } class Router{ protected $_provider; public function __construct(IProvider $p){ $this->_provider = $p; } public function route($str){ //... match $str against routes to resolve class and method $inst = $this->_provider->get($class); //... } } //inject callback (old fashion way) class MyProvider{ protected $_db; public function getDb(){ $this->_db = $this->_db ? $this->_db : new mysqli(); return $this->_db; } } class Bartender{ protected $_db; public function __construct(array $callback){ $this->_db = $callback; } public function order($data){ //validating $data //... return or throw exception //validation passed, order must be saved $db = call_user_func_array($this->_db, array()); $db->insert(''orders'', $data); //... } } //the way it works under the hood: $provider = new MyProvider(); $db = array($provider, ''getDb''); new Bartender($db); //inject callback (the PHP 5.3 way) class Bartender{ protected $_db; public function __construct(Closure $callback){ $this->_db = $callback; } public function order($data){ //validating $data //... return or throw exception //validation passed, order must be saved $db = call_user_func_array($this->_db, array()); $db->insert(''orders'', $data); //... } } //the way it works under the hood: static $conn = null; $db = function() use ($conn){ $conn = $conn ? $conn : new mysqli(); return $conn; }; new Bartender($db);


Elegí la inyección perezosa (es decir, la inyección de una clase Proxy):

class Class1 { /** * @Inject(lazy=true) * @var Class2 */ private $class2; public function doSomething() { // The dependency is loaded NOW return $this->class2->getSomethingElse(); }

Aquí, la dependencia (clase 2) no se inyecta directamente: se inyecta una clase proxy. Solo cuando se utiliza la clase proxy, se carga la dependencia.

Esto es posible en PHP-DI (marco de inyección de dependencias).

Descargo de responsabilidad: trabajo en este proyecto


Si desea implementar la carga diferida, básicamente tiene dos formas de hacerlo (como ya ha escrito en el tema):

  1. en lugar de inyectar una instancia de objeto que pueda necesitar, inyecte una Factory o un Builder . La diferencia entre ellos es que la instancia de Builder está hecha para devolver un tipo de objeto (tal vez con diferentes configuraciones), mientras que Factory crea diferentes tipos de instancias (con la misma vida útil y / o implementando la misma interfaz).

  2. Utilice la función anónima que le devolverá una instancia. Eso se vería algo así:

    $provider = function() { return new /PDO(''sqlite::memory:''); };

    Solo cuando llama a esta función anónima, se crea la instancia de PDO y se establece la conexión a la base de datos.

Lo que normalmente hago en mi código es combinar ambos . Puede equipar a la Factory con dicho provider . Esto, por ejemplo, le permite tener una conexión única para todos los objetos que fueron creados por dicha fábrica, y la conexión se crea solo cuando solicita una instancia de Factory primera vez.

La otra forma de combinar ambos métodos (que no he usado, sin embargo) sería crear una clase de Provider golpe completo, que en el constructor acepta una función anónima. Entonces, la fábrica podría pasar por esta misma instancia de Provider y el objeto costoso (PHPExcel, Doctrine, SwiftMailer o alguna otra instancia) solo se creará una vez que el Product de esa Factory recurra por primera vez al Provider (no se podría encontrar un nombre mejor). Describir todos los objetos creados por la misma fábrica y solicitarlo. Después de eso, este objeto caro se comparte entre todos los Products de Factory .

... mis 2 centavos


Una idea que ocurrió fue la de un objeto proxy. Implementa la (s) misma (s) interfaz (es) que el objeto real que desea transmitir, pero en lugar de implementar cualquier cosa, solo contiene una instancia de la clase real y el método de reenvío lo llama.

interface MyInterface { public function doFoo (); public function isFoo (); // etc } class RealClass implements MyInterface { public function doFoo () { return (''Foo!''); } public function isFoo () { return ($this -> doFoo () == ''Foo!''? true: false); } // etc } class RealClassProxy implements MyInterface { private $instance = NULL; /** * Do lazy instantiation of the real class * * @return RealClass */ private function getRealClass () { if ($this -> instance === NULL) { $this -> instance = new RealClass (); } return $this -> instance; } public function doFoo () { return $this -> getRealClass () -> doFoo (); } public function isFoo () { return $this -> getRealClass () -> isFoo (); } // etc }

Debido a que el proxy tiene la misma interfaz que la clase real, puede pasarlo como un argumento a cualquier función / método que escriba sugerencias para la interfaz. El principio de sustitución de Liskov se mantiene para el proxy porque responde a todos los mismos mensajes que la clase real y devuelve los mismos resultados (la interfaz lo hace cumplir, al menos para las firmas de métodos). Sin embargo, la clase real no se crea una instancia a menos que un mensaje se envíe al proxy, lo que hace que la clase real se oculte de la clase real detrás de la escena.

function sendMessageToRealClass (MyInterface $instance) { $instance -> doFoo (); } sendMessageToRealClass (new RealClass ()); sendMessageToRealClass (new RealClassProxy ());

Hay una capa adicional de direccionamiento indirecto involucrada con el objeto proxy, lo que obviamente significa que hay un pequeño impacto en el rendimiento por cada método que realice. Sin embargo, le permite realizar instancias perezosas, por lo que puede evitar la creación de instancias de clases que no necesita. Si esto vale la pena, depende del costo de crear una instancia del objeto real frente al costo de la capa adicional de indirección.

EDITAR: Originalmente escribí esta respuesta con la idea de subclasificar el objeto real para que pudiera usar la técnica con objetos que no implementan ninguna interfaz como PDO. Originalmente pensé que las interfaces eran la forma correcta de hacer esto, pero quería un enfoque que no dependiera de que la clase estuviera vinculada a una interfaz. Reflexionando sobre eso fue un gran error, así que actualicé la respuesta para reflejar lo que debería haber hecho en primer lugar. Sin embargo, esta versión significa que no puede aplicar directamente esta técnica a clases sin interfaz asociada. Tendrá que envolver esas clases en otra clase que proporcione una interfaz para que el enfoque de proxy sea viable, lo que significa otra capa de direccionamiento indirecto.