En un proyecto PHP, ¿qué patrones existen para almacenar, acceder y organizar objetos auxiliares? [cerrado]

114

¿Cómo organiza y administra sus objetos auxiliares como el motor de base de datos, la notificación al usuario, el manejo de errores, etc. en un proyecto orientado a objetos basado en PHP?

Digamos que tengo un CMS PHP grande. El CMS está organizado en varias clases. Algunos ejemplos:

  • el objeto de la base de datos
  • Gestión de usuarios
  • una API para crear / modificar / eliminar elementos
  • un objeto de mensajería para mostrar mensajes al usuario final
  • un controlador de contexto que te lleva a la página correcta
  • una clase de barra de navegación que muestra botones
  • un objeto de registro
  • posiblemente, manejo de errores personalizado

etc.

Estoy lidiando con la eterna pregunta, cómo hacer que estos objetos sean más accesibles para cada parte del sistema que los necesita.

mi primer apporach, hace muchos años, fue tener una $ application global que contuviera instancias inicializadas de estas clases.

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

Luego cambié al patrón Singleton y una función de fábrica:

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

pero tampoco estoy contento con eso. Las pruebas unitarias y la encapsulación se vuelven cada vez más importantes para mí y, en mi opinión, la lógica detrás de los globales / singletons destruye la idea básica de POO.

Luego, por supuesto, existe la posibilidad de darle a cada objeto una serie de punteros a los objetos auxiliares que necesita, probablemente la forma más limpia, ahorradora de recursos y amigable con las pruebas, pero tengo dudas sobre la mantenibilidad de esto a largo plazo.

La mayoría de los frameworks PHP que he investigado utilizan el patrón singleton o funciones que acceden a los objetos inicializados. Ambos buenos enfoques, pero como dije, no estoy contento con ninguno.

Me gustaría ampliar mi horizonte sobre los patrones comunes que existen aquí. Busco ejemplos, ideas adicionales y punteros hacia los recursos que discuten este de un largo plazo , en el mundo real perspectiva.

Además, estoy interesado en escuchar acerca de enfoques especializados, de nicho o simplemente extraños al problema.

Pekka 웃
fuente
1
Acabo de hacer una pregunta extremadamente similar que también tenía una recompensa. Puede apreciar algunas respuestas allí: stackoverflow.com/questions/1967548/…
philfreo
3
Solo un aviso, devolver un nuevo objeto por referencia, no $mh=&factory("messageHandler");tiene sentido y no produce ningún beneficio de rendimiento. Además, está obsoleto en 5.3.
ryeguy

Respuestas:

68

Evitaría el enfoque Singleton sugerido por Flavius. Existen numerosas razones para evitar este enfoque. Viola los buenos principios de OOP. El blog de pruebas de Google tiene algunos buenos artículos sobre Singleton y cómo evitarlo:

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

Alternativas

  1. un proveedor de servicios

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. inyección de dependencia

    http://en.wikipedia.org/wiki/Dependency_injection

    y una explicación de php:

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

Este es un buen artículo sobre estas alternativas:

http://martinfowler.com/articles/injection.html

Implementación de la inyección de dependencia (DI):

Algunas reflexiones más sobre la solución de Flavius. No quiero que esta publicación sea una anti-publicación, pero creo que es importante ver por qué la inyección de dependencia es, al menos para mí, mejor que las globales.

Aunque no es una implementación "verdadera" de Singleton , sigo pensando que Flavius ​​se equivocó. El estado global es malo . Tenga en cuenta que estas soluciones también utilizan métodos estáticos difíciles de probar .

Sé que mucha gente lo hace, lo aprueba y lo usa. Pero leer los artículos del blog de Misko Heverys ( un experto en comprobabilidad de Google ), releerlos y digerir lentamente lo que dice alteró mucho la forma en que veo el diseño.

Si desea poder probar su aplicación, deberá adoptar un enfoque diferente para diseñar su aplicación. Cuando realice la programación de prueba primero, tendrá dificultades con cosas como esta: 'a continuación, quiero implementar el registro en este fragmento de código; Primero escribamos una prueba que registre un mensaje básico 'y luego desarrollemos una prueba que lo obligue a escribir y usar un registrador global que no se pueda reemplazar.

Todavía estoy luchando con toda la información que obtuve de ese blog, y no siempre es fácil de implementar, y tengo muchas preguntas. Pero no hay forma de que pueda volver a lo que hice antes (sí, estado global y Singletons (gran S)) después de comprender lo que Misko Hevery estaba diciendo :-)

koen
fuente
+1 para DI. Aunque no lo uso tanto como me gustaría, ha sido muy útil en las pequeñas cantidades en las que lo usé.
Anurag
1
@koen: ¿Le gustaría dar un ejemplo de PHP de una implementación DI / SP en PHP? ¿Quizás el código de @Flavius ​​se implementó usando los patrones alternativos que sugirió?
Alix Axel
Agregué un enlace a la implementación y el contenedor de DI en mi respuesta.
Thomas
Estoy leyendo todo esto ahora, pero todavía no lo he leído todo, me gustaría preguntar, ¿un marco de inyección de dependencia sería básicamente un Registro?
JasonDavis
No en realidad no. Pero un contenedor de inyección de dependencia también puede servir como registro. Solo lea los enlaces que he publicado en mi respuesta. El concepto de DI se explica realmente de manera práctica.
Thomas
16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

Esta es la forma en que lo haría. Crea el objeto a pedido:

Application::foo()->bar();

Es la forma en que lo estoy haciendo, respeta los principios de programación orientada a objetos, es menos código que cómo lo estás haciendo ahora, y el objeto se crea solo cuando el código lo necesita por primera vez.

Nota : lo que he presentado ni siquiera es un patrón singleton real. Un singleton permitiría solo una instancia de sí mismo al definir el constructor (Foo :: __ constructor ()) como privado. Es solo una variable "global" disponible para todas las instancias de "Aplicación". Por eso creo que su uso es válido, ya que NO ignora los buenos principios de POO. Por supuesto, como cualquier otra cosa en el mundo, este "patrón" tampoco debe usarse en exceso.

He visto que esto se usa en muchos frameworks PHP, Zend Framework y Yii entre ellos. Y deberías usar un marco. No te voy a decir cuál.

Anexo Para aquellos de ustedes que se preocupan por TDD , aún pueden hacer algunos cables para inyectarlo en dependencia. Podría verse así:

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

Hay suficiente margen de mejora. Es solo un PoC, usa tu imaginación.

¿Por qué lo hace así? Bueno, la mayoría de las veces la aplicación no se probará por unidad, en realidad se ejecutará, con suerte en un entorno de producción . La fuerza de PHP es su velocidad. PHP NO es y nunca será un "lenguaje OOP limpio", como Java.

Dentro de una aplicación, solo hay una clase de Aplicación y solo una instancia de cada uno de sus ayudantes, como máximo (según la carga diferida como se indicó anteriormente). Claro, los singleton son malos, pero, de nuevo, solo si no se adhieren al mundo real. En mi ejemplo, lo hacen.

Las "reglas" estereotipadas como "los solteros son malos" son la fuente del mal, son para personas perezosas que no están dispuestas a pensar por sí mismas.

Sí, lo sé, el manifiesto de PHP es MALO, técnicamente hablando. Sin embargo, es un lenguaje exitoso, a su manera hacker.

Apéndice

Estilo de una función:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();
Flavius
fuente
2
Rechacé la respuesta porque creo que sugerir el patrón singleton para manejar el problema va en contra de los principios sólidos de OOP.
koen
1
@koen: lo que estás diciendo es cierto, en términos generales, PERO hasta donde he entendido su problema, está hablando de ayudantes para la aplicación, y dentro de una aplicación solo hay una ... uhm, aplicación.
Flavius
Nota: lo que he presentado ni siquiera es un patrón singleton real. Un singleton permitiría solo una instancia de una clase definiendo el constructor como privado. Es solo una variable "global" disponible para todas las instancias de "Aplicación". Por eso creo que su validez NO ignora los buenos principios de programación orientada a objetos. Por supuesto, como todo en el mundo, este "patrón" tampoco debe usarse en exceso.
Flavius
-1 de mí también. Puede que solo sea la mitad del Singleton DP, pero es el más feo: "proporcionar acceso global a él".
solo alguien
2
De hecho, esto hace que su enfoque actual sea mucho más limpio.
Daniel Von Fange
15

Me gusta el concepto de inyección de dependencia:

"La inyección de dependencia es donde los componentes reciben sus dependencias a través de sus constructores, métodos o directamente en los campos. (Desde el sitio web de Pico Container )"

Fabien Potencier escribió una serie de artículos muy buenos sobre la inyección de dependencia y la necesidad de usarlos. También ofrece un contenedor de inyección de dependencia agradable y pequeño llamado Pimple que realmente me gusta usar (más información en github ).

Como se indicó anteriormente, no me gusta el uso de Singletons. Puede encontrar un buen resumen de por qué los Singletons no son de buen diseño aquí, en el blog de Steve Yegge .

4 revoluciones
fuente
Me gusta la implementación a través de Closures en PHP, lectura muy interesante
Juraj Blahunka
Yo también y él tiene algunas otras cosas necesarias con respecto a los cierres en su sitio: fabien.potencier.org/article/17/…
Thomas
2
esperemos que las casas web convencionales migren a PHP 5.3 pronto, ya que todavía no es común ver un servidor php 5.3 con todas las funciones
Juraj Blahunka
Tendrán que hacerlo, cuando más y más proyectos requieran PHP 5.3 como Zend Framework 2.0, framework.zend.com/wiki/display/ZFDEV2/…
Thomas
1
La inyección de dependencia también se aceptó como respuesta a la pregunta sobre decupling from GOD object: stackoverflow.com/questions/1580210/… con un ejemplo muy agradable
Juraj Blahunka
9

El mejor enfoque es tener algún tipo de contenedor para esos recursos. Algunas de las formas más comunes de implementar este contenedor :

único

No se recomienda porque es difícil de probar e implica un estado global. (Singletonitis)

Registro

Elimina la singletonitis, error. No recomendaría el registro también, porque también es una especie de singleton. (Prueba unitaria difícil)

Herencia

Lástima, no hay herencia múltiple en PHP, por lo que esto limita todo a la cadena.

Inyección de dependencia

Este es un mejor enfoque, pero un tema más amplio.

Tradicional

La forma más sencilla de hacer esto es usando constructor o inyección de setter (pasar el objeto de dependencia usando setter o en el constructor de clase).

Frameworks

Puede lanzar su propio inyector de dependencia o usar algunos de los marcos de inyección de dependencia, por ejemplo. Yadif

Recurso de aplicación

Puede inicializar cada uno de sus recursos en el bootstrap de la aplicación (que actúa como un contenedor) y acceder a ellos en cualquier lugar de la aplicación que acceda al objeto bootstrap.

Este es el enfoque implementado en Zend Framework 1.x

Cargador de recursos

Una especie de objeto estático que carga (crea) los recursos necesarios solo cuando es necesario. Este es un enfoque muy inteligente. Puede verlo en acción, por ejemplo, implementando el componente de inyección de dependencia de Symfony

Inyección a capa específica

Los recursos no siempre se necesitan en ninguna parte de la aplicación. A veces solo los necesita, por ejemplo, en los controladores (MV C ). Entonces puede inyectar los recursos solo allí.

El enfoque común para esto es usar comentarios de docblock para agregar metadatos de inyección.

Vea mi enfoque para esto aquí:

¿Cómo usar la inyección de dependencia en Zend Framework? - Desbordamiento de pila

Al final, me gustaría agregar una nota sobre algo muy importante aquí: el almacenamiento en caché.
En general, a pesar de la técnica que elija, debe pensar cómo se almacenarán en caché los recursos. La caché será el recurso en sí.

Las aplicaciones pueden ser muy grandes y cargar todos los recursos en cada solicitud es muy costoso. Hay muchos enfoques, incluido este appserver-in-php: Project Hosting en Google Code .

2 revoluciones
fuente
6

Si desea que los objetos estén disponibles globalmente, el patrón de registro podría ser interesante para usted. En busca de inspiración, eche un vistazo a Zend Registry .

Así también la pregunta de Registry vs. Singleton .

Felix Kling
fuente
Si no desea utilizar Zend Framework, aquí hay una buena implementación del patrón de registro para PHP5: phpbar.de/w/Registry
Thomas
Prefiero un patrón de registro escrito, como Registry :: GetDatabase ("master"); Registro :: GetSession ($ usuario-> SessionKey ()); Registry :: GetConfig ("local"); [...] y definiendo una interfaz para cada tipo. De esta manera, se asegura de no sobrescribir accidentalmente una clave utilizada para algo diferente (es decir, es posible que tenga una "base de datos maestra" y una "configuración maestra". Al usar interfaces, se asegura de que solo se utilicen objetos válidos. También se implementará mediante el uso de múltiples clases de Registro, pero en mi humilde opinión, una sola es más simple y más fácil de usar, pero aún tiene las ventajas.
Morfildur
O, por supuesto, el integrado en PHP - $ _GLOBALS
Gnuffo1
4

Los objetos en PHP ocupan una buena cantidad de memoria, como probablemente haya visto en sus pruebas unitarias. Por lo tanto, es ideal destruir los objetos innecesarios lo antes posible para ahorrar memoria para otros procesos. Con eso en mente, encuentro que cada objeto encaja en uno de dos moldes.

1) El objeto puede tener muchos métodos útiles o necesita ser llamado más de una vez, en cuyo caso implemento un singleton / registro:

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) El objeto solo existe durante la vida del método / función que lo llama, en cuyo caso una creación simple es beneficiosa para evitar que las referencias a objetos persistentes mantengan vivos a los objetos durante demasiado tiempo.

$object = new Class();

El almacenamiento de objetos temporales en CUALQUIER LUGAR puede provocar pérdidas de memoria porque las referencias a ellos pueden olvidarse para mantener el objeto en la memoria durante el resto del script.

Xeoncross
fuente
3

Yo iría por la función que devuelve objetos inicializados:

A('Users')->getCurrentUser();

En el entorno de prueba, puede definirlo para devolver maquetas. Incluso puede detectar dentro quién llama a la función usando debug_backtrace () y devolver diferentes objetos. Puede registrarse dentro de él quién quiere obtener qué objetos para obtener información sobre lo que realmente está sucediendo dentro de su programa.

Kamil Szot
fuente
-1

¿Por qué no leer el excelente manual?

http://php.net/manual/en/language.oop5.autoload.php

gcb
fuente
Gracias gcb, pero la carga de clases no es de mi incumbencia, mi pregunta es de carácter más arquitectónico.
Pekka
Si bien esto puede responder teóricamente a la pregunta, sería preferible incluir aquí las partes esenciales de la respuesta y proporcionar el enlace como referencia.
jjnguy