pimple - php 7 dependency injection
Componer una clase de controlador con Dependency Injection en PHP (5)
¿Estás desarrollando un framework tú mismo? De lo contrario, su pregunta no se aplica, porque debe elegir entre marcos existentes y sus soluciones existentes. En este caso, su pregunta debe reformularse como "¿cómo hago pruebas unitarias / inyección de dependencia en el marco X"?
Si está desarrollando un marco por su cuenta, primero debe verificar cómo los que ya existen abordan este problema. Y también debe elaborar sus propios requisitos, y luego ir con la solución más simple posible. Sin requisitos, su pregunta es puramente estética y argumentativa.
Imho la solución más simple es tener propiedades públicas que se inicializan a los valores predeterminados proporcionados por su marco, de lo contrario puede inyectar sus burlas aquí. (Esto es igual a su solución de getters / setters, pero sin el bloat mencionado. No siempre necesita getters y setters). Opcionalmente, si realmente lo necesita, puede proporcionar un constructor para inicializar los de una sola llamada (como sugirió) .
Los Singleton son una solución elegante, pero nuevamente, debe preguntarse, ¿es aplicable en su caso? Si tiene que tener diferentes instancias del mismo tipo de objeto en su aplicación, no puede ir con él (por ejemplo, si desea burlarse de una clase solo en la mitad de su aplicación).
Por supuesto, es realmente increíble tener todas las opciones. Puede tener getters / setter, constructores, y cuando se omite la inicialización, los valores predeterminados se toman de una fábrica singleton. Pero tener demasiadas opciones cuando no es necesario, no es asombroso, es inquietante ya que el programador tiene que descubrir qué convención, opción y patrón usar. Definitivamente no quiero tomar docenas de decisiones de diseño solo para ejecutar un simple CRUD.
Si miras otros marcos, verás que no hay una solución mágica. A menudo, un solo marco utiliza diferentes técnicas según el contexto. En los controladores, DI es algo realmente sencillo, mire las variables $ helps, $ components de CakePHP, que instruyen a inyectar variables apropiadas en la clase de controlador. Para la aplicación en sí, un singleton sigue siendo una buena opción, ya que siempre hay una sola aplicación. Las propiedades menos cambiadas / burladas se inyectan utilizando propiedades públicas. En el caso de un MVC, la subclasificación también es una opción perfectamente viable: al igual que AppController, AppView, AppModel en CakePHP. Se insertan en la jerarquía de clases entre las estructuras y todas sus clases particulares de Controlador, Vista y Modelo. De esta forma, tiene un único punto para declarar globales para su tipo principal de clases.
En Java, debido a los cargadores de clase dinámicos y la reflexión, tiene incluso muchas más opciones para elegir. Pero, por otro lado, también debe admitir muchos más requisitos: solicitudes paralelas, objetos compartidos y estados entre subprocesos de trabajo, servidores de aplicaciones distribuidas, etc.
Solo puede responder a la pregunta qué es lo correcto para usted, si sabe lo que necesita en primer lugar. Pero en realidad, ¿por qué escribes simplemente otro nuevo marco de todos modos?
Cómo resolver el problema de componer una clase de controlador en PHP, que debería ser:
- fácilmente comprobable mediante el empleo de Dependency Injection,
- proporcionar objetos compartidos para el programador final
- proporcionar una forma de cargar nuevas bibliotecas de usuario
Mirar hacia abajo, para la creación de instancias del controlador con un marco de inyección de Dependency
El problema es que los Controladores derivados pueden usar cualquier recurso que el programador quiera (por ejemplo, el marco proporciona). ¿Cómo crear un acceso unificado a recursos compartidos (DB, usuario, almacenamiento, caché, ayudantes), clases definidas por el usuario u otras bibliotecas?
¿Una solución elegante?
Hay varias soluciones posibles a mi problema, pero ninguna parece ser una elegante
- Intentar pasar todos los objetos compartidos por constructor ? (puede crear constructor incluso con 10 marcadores de posición)
- Crear getters, settters ? (código
$controller->setApplication($app)
)$controller->setApplication($app)
- ¿Aplicar singletons en recursos compartidos ?
User::getInstance()
oDatabase::getInstance()
- ¿Utiliza el contenedor de Inyección de Dependencia como singleton para compartir objetos dentro del controlador?
- proporcionar una aplicación global singleton como una fábrica? (este se ve muy usado en frameworks php, pero va fuertemente en contra de los principios DI y la ley de Demeter)
Entiendo que la creación de clases fuertemente acopladas se desalienta y se desterró :), sin embargo, no sé cómo este paradigma se aplica a un punto de partida para otros programadores (una clase de controlador), en el que deberían poder acceder a los recursos compartidos proporcionados. a la arquitectura MVC. Creo que dividir la clase de controlador en clases más pequeñas de alguna manera destruiría el significado práctico de MVC.
Marco de inyección de dependencia
DI Framework parece una opción viable. Sin embargo, el problema aún persiste. Una clase como Controller no reside en la capa Application, sino en la capa RequestHandler / Response.
¿Cómo debe esta instancia crear una instancia del controlador?
- pasar el inyector DI a esta capa?
- DI Framework como singleton?
- poner configuración de marco de DI aislado solo para esta capa y crear una instancia de inyector DI por separado?
¿Qué tal refactorizar ?
De acuerdo, esa no era una de tus opciones, pero afirmas que el código es una clase ampliamente acoplada. ¿Por qué no dedicar este tiempo y esfuerzo a refactorizarlo a componentes más modulares y comprobables?
Los singletons son mal vistos cuando la Inyección de Dependencia es viable (y todavía tengo que encontrar un caso donde era necesario un Singleton).
Lo más probable es que tenga el control de la creación de instancias de los controladores, por lo que puede salirse con la mencionada $controller->setApplication($application)
, pero si es necesario puede usar métodos estáticos y variables (que son mucho menos perjudiciales para la ortogonalidad de una aplicación que Singletons); a saber, Controller::setApplication()
, y acceder a las variables estáticas a través de los métodos de instancia.
p.ej:
// defining the Application within the controller -- more than likely in the bootstrap $application = new Application(); Controller::setApplication($application); // somewhere within the Controller class definition public function setContentType($contentType) { self::$application->setContentType($contentType); }
He creado el hábito de separar las propiedades y los métodos estáticos y de instancia (cuando sea necesario, y aún agrupar las propiedades en la parte superior de la definición de la clase). Siento que esto es menos complicado que tener Singletons, ya que las clases siguen siendo bastante compactas.
Por lo que yo entiendo, la clase de Aplicación suya debe ser el despachador. Si es así, prefiero usar el constructor del controlador para pasar una instancia de la Aplicación, para que el controlador sepa quién la está invocando. En un punto posterior, si desea tener una instancia de aplicación diferente dependiendo de si el código se invoca desde CLI, puede tener una ApplicationInterface que Application / Http and Application / Cli implementaría y todo sería fácil de mantener.
También podría implementar algún patrón de fábrica para obtener una buena implementación de la DI. Por ejemplo, consulte el método createThroughReflection aquí: https://github.com/troelskn/bucket/blob/master/lib/bucket.inc.php
Espero que esto tenga sentido.
Saludos, Nick
También podría usar un ControllerFatory en el que le daría a su Aplicación o Router / Dispatcher
Sou puedes llamar $ controllerFactory-> createController ($ name);
Su aplicación no tendría idea de cómo crear sus controladores de fábrica. Como puede inyectar su propio ControllerFactory en su contenedor DI, puede gestionar todas las dependencias que desee dependiendo del controlador.
class ControllerFactory {
public function __construct(EvenDispatcher $dispatcher,
Request $request,
ResponseFactory $responseFactory,
ModelFactory $modelFactory,
FormFactory $formFactory) {
...
}
public function createController($name = ''Default'') {
switch ($name) {
case ''User'':
return new UserController($dispatcher,
$request,
$responseFactory->createResponse(''Html''),
$modelFactory->createModel(''User''),
$formFactory->createForm(''User''),...);
break;
case ''Ajax'':
return new AjaxController($dispatcher,
$request,
$responseFactory->createResponse(''Json''),
$modelFactory->createModel(''User''));
break;
default:
return new DefaultController($dispatcher, $request, $responseFactory->createResponse(''Html''));
}
}
}
Entonces solo necesita agregar esta fábrica en su contenedor DI y pasarla a su Aplicación. Siempre que necesite un nuevo controlador, lo agrega a la fábrica y si se requieren nuevas dependencias, se los entrega a la fábrica a través de su contenedor DI.
class App {
public function __construct(Router $router,Request $request, ControllerFactory $cf, ... ) {
...
}
public function execute() {
$controllerName = $this->router->getMatchedController();
$actionName $this->router->getMatchedAction();
$controller = $cf->createController($controllerName);
if(is_callable($controller, $actionName)) {
$response = $controller->$action(request);
$response->send();
}
}
}
Esto no es código de producción, no lo he probado, pero así es como desacoplo sus controladores de su aplicación. Observe aquí, sin embargo, que hay un acoplamiento malo aquí porque mi respuesta del controlador es una respuesta y ejecuto la respuesta en la aplicación. Pero como dije, esto es solo un pequeño ejemplo.
Por lo general, es una buena idea pasar fábricas para Modelos, Formularios y Controladores a sus respectivos padres, ya que terminará cargando todo su Objeto Gráfico en el tiempo de arranque que es realmente malo y consume memoria.
Sé que esta respuesta ya fue aprobada, pero son mis 2 centavos sobre el tema
Hay un buen artículo sobre el tema
http://miller.limethinking.co.uk/2011/07/07/dependency-injection-moving-from-basics-to-container/