Desde el procedimiento a PHP orientado a objetos

Este tutorial fue inspirado por un discurso dado por Robert C. Martin que vi hace un año o así. El tema principal de su charla es sobre la posibilidad de elegir El último lenguaje de programación. Él aborda temas como ¿por qué debería existir tal lenguaje? ¿Y qué aspecto debería tener? Sin embargo, si leía entre líneas, había otra idea interesante que me llamó la atención: las limitaciones que cada paradigma de programación nos impone a los programadores. Así que antes de ver cómo podríamos convertir una aplicación PHP basada en procedimientos en una orientada a objetos, quiero cubrir un poco de teoría de antemano.


Limitaciones del paradigma

Entonces, cada paradigma de programación limita nuestra capacidad de hacer lo que queramos hacer. Cada uno de ellos quita algo y proporciona una alternativa para lograr el mismo resultado. La programación modular quita el tamaño ilimitado del programa. Exige al programador el uso de módulos de tamaño máximo y cada módulo finaliza con una declaración "ir a" a otro módulo. Así que la primera limitación es sobre el tamaño. Luego, la programación estructurada y la programación de procedimientos quitan la frase "ir a" y limitan al programador a la secuencia, selección e iteración. Las secuencias son asignaciones variables, las selecciones son decisiones if-else y las iteraciones son bucles do-while. Estos son los bloques de construcción de la mayoría de los lenguajes de programación y paradigmas de hoy..

La programación orientada a objetos quita los punteros a las funciones e introduce el polimorfismo. PHP no usa los punteros de una manera en que C lo hace, pero una variante de estos punteros a funciones se puede observar en Funciones de variables. Esto permite que un programador utilice el valor de una variable como el nombre de una función, de modo que se pueda lograr algo como esto:

function foo () echo "Esto es foo";  barra de funciones ($ param) echo "Esto es un dicho de barra: $ param";  $ function = 'foo'; $ function (); // entra en foo () $ function = 'bar'; $ function ('prueba'); // entra en la barra ()

Esto puede no parecer importante a primera vista. Pero piense en lo que podemos lograr con una herramienta tan poderosa. Podemos enviar una variable como parámetro a una función y luego dejar que esa función llame a la otra, referenciada por el valor del parámetro. Esto es increíble. Nos permite alterar la funcionalidad de una función sin que ésta lo sepa. Sin la función, incluso notando alguna diferencia..

En realidad, también podemos hacer llamadas polimórficas con esta técnica..

Ahora, en lugar de pensar qué aportan los punteros a las funciones, piense cómo funcionan. ¿No son simplemente declaraciones ocultas? En realidad, lo son, o al menos son muy similares a los "go-to" indirectos. Lo cual no es muy bueno. Lo que tenemos aquí es, de hecho, una forma inteligente de hacer "ir a" sin usarlo directamente. Debo admitir que en PHP, como muestra el ejemplo anterior, es bastante fácil de entender, pero puede confundirse con proyectos más grandes y muchas funciones diferentes que se pasan de una función a otra. En C es aún más oscuro y horriblemente difícil de entender..

Sin embargo, no basta con quitar los punteros a las funciones. La programación orientada a objetos debe proporcionar un reemplazo, y lo hace, de una manera elegante. Ofrece polimorfismo con una sintaxis sencilla. Y con el polimorfismo, viene el valor más grande que ofrece la programación orientada a objetos: el flujo de control se opone a la dependencia del código fuente.


En la imagen de arriba ilustramos un ejemplo simple de cómo suceden las llamadas polimórficas en los dos paradigmas diferentes. En la programación de procedimiento o estructural, el flujo de control es similar a la dependencia del código fuente. Ambos apuntan hacia la implementación más concreta del comportamiento de impresión..

En la programación orientada a objetos, podemos revertir la dependencia del código fuente y hacer que apunte hacia una implementación más abstracta, mientras mantenemos el flujo de control apuntando hacia una implementación más concreta. Esto es esencial, porque queremos que nuestro control vaya y llegue a la parte más concreta y volátil posible de nuestro código para que podamos obtener nuestro resultado exactamente como lo queremos, pero en nuestro código fuente queremos exactamente lo contrario. En nuestro código fuente queremos que las cosas concretas y volátiles se mantengan apartadas, que sean fáciles de cambiar y que afecten lo menos posible al resto de nuestro código. Deje que las partes volátiles cambien con frecuencia, pero mantenga las partes más abstractas sin modificar. Puede leer más sobre el principio de inversión de dependencia en el trabajo de investigación original escrito por Robert C. Martin.


La tarea a mano

En este capítulo crearemos una aplicación sencilla para enumerar los calendarios de Google y los eventos que se encuentran dentro de ellos. Primero, adoptaremos un enfoque de procedimiento, utilizando solo funciones simples y evitando cualquier tipo de clase u objeto. La aplicación te permitirá listar tus calendarios y eventos de Google. Luego llevaremos el problema un paso más allá al mantener nuestro código de procedimiento y comenzar a organizarlo por comportamiento. Finalmente lo transformaremos en una versión orientada a objetos..


Cliente PHP API de Google

Google proporciona un cliente API para PHP. Lo utilizaremos para conectarnos a nuestra cuenta de Google para que podamos manipular los calendarios allí. Si desea ejecutar el código, debe configurar su cuenta de Google para aceptar consultas de calendario.

Aunque este es un requisito para el tutorial, no es su tema principal. Entonces, en lugar de que yo repita los pasos que debe seguir, le indicaré la documentación correcta. No se preocupe, es muy sencillo de configurar y solo demora unos cinco minutos..

El código de cliente de API de Google PHP se incluye en cada proyecto del código de ejemplo adjunto a este tutorial. Te recomiendo que uses esa. Alternativamente, si tiene curiosidad acerca de cómo instalarlo usted mismo, consulte la documentación oficial.

Luego siga las instrucciones y complete la información en el apiAccess.php expediente. Este archivo será requerido tanto por el procedimiento como por los ejemplos orientados a objetos, por lo que no es necesario que lo repita. Dejé mis llaves allí para que puedas identificar y rellenar las tuyas con mayor facilidad..

Si usas NetBeans, dejé los archivos del proyecto en las carpetas que contienen los diferentes ejemplos. De esta manera, simplemente puede abrir los proyectos y ejecutarlos inmediatamente en un servidor PHP local (se requiere PHP 5.4) simplemente seleccionando Ejecutar / Ejecutar Proyecto.

La biblioteca cliente para conectarse a la API de Google está orientada a objetos. Por el bien de nuestro ejemplo funcional, escribí un pequeño conjunto de funciones que las envuelven, las funcionalidades que necesitamos. De esta manera, podemos usar una capa de procedimiento escrita sobre la biblioteca cliente orientada a objetos para que nuestro código no tenga que usar objetos.

Si desea probar rápidamente que su código y su conexión a la API de Google están funcionando, simplemente use el código a continuación como su index.php expediente. Debe enumerar todos los calendarios que tienes en tu cuenta. Debe haber al menos un calendario con el resumen campo siendo tu nombre Si tiene un calendario con los cumpleaños de su contacto, es posible que ese no funcione con esta API de Google, pero no se preocupe, simplemente elija otro.

require_once './google-api-php-client/src/Google_Client.php'; require_once './google-api-php-client/src/contrib/Google_CalendarService.php'; require_once __DIR__. '/… /ApiAccess.php'; require_once './functins_google_api.php'; require_once './functions.php'; session_start (); $ client = createClient (); if (! authenticate ($ client)) return; listAllCalendars ($ client);

Esta index.php Archivo será el punto de entrada a nuestra aplicación. No utilizaremos un framework web ni nada sofisticado. Simplemente emitiremos un código HTML.


Un enfoque procesal directo

Ahora que sabemos lo que estamos construyendo y lo que usaremos, adelante, descargue el código fuente adjunto. Proporcionaré fragmentos de él, pero para ver todo esto, querrá tener acceso a la fuente original..

Para este enfoque, solo queremos que las cosas funcionen. Nuestro código se organizará de manera muy rudimentaria, con solo unos pocos archivos, como este:

  • index.php - El único archivo al que accedemos directamente desde el navegador y le pasamos los parámetros GET..
  • funciones_google_api.php - la envoltura sobre la API de Google de la que hablamos anteriormente.
  • funciones.php - donde todo sucede.

funciones.php Alojaremos todo lo que hace nuestra aplicación. Tanto la lógica de enrutamiento, las presentaciones, como los valores y el comportamiento pueden estar enterrados allí. Esta aplicación es bastante simple, la lógica principal es la siguiente.


Tenemos una sola función llamada doUserAction (), que decide con un largo si-si no declaración, qué otros métodos llamar según los parámetros en el OBTENER variable. Los métodos luego se conectan al calendario de Google utilizando la API e imprimen en la pantalla lo que queramos solicitar.

function printCalendarContents ($ client) putTitle ('Estos son tus eventos para'. getCalendar ($ client, $ _GET ['showThisCalendar']) ['summary']. 'calendar:'); foreach (retrieveEvents ($ client, $ _GET ['showThisCalendar']) como $ event) print ('
'. fecha ('Y-m-d H: m', strtotime ($ event ['created']))); putLink ('? showThisEvent ='. htmlentities ($ event ['id']). '& calendarId ='. htmlentities ($ _ GET ['showThisCalendar']), $ event ['summary']); impresión('
'); impresión('
');

Este ejemplo es probablemente la función más complicada de nuestro código. Llama a una función auxiliar llamada putTitle (), que acaba de imprimir algunos HTML con formato para el encabezado. El título contendrá el nombre de nuestro calendario que se puede obtener llamando al getCalendar () desde funciones_google_api.php. El calendario devuelto será una matriz, que contiene una resumen campo. Eso es lo que estamos buscando.

los $ cliente La variable se pasa por todas partes en todas nuestras funciones. Es necesario para conectarse a la API de Google. Nos ocuparemos de esto más tarde.

A continuación, hacemos un ciclo sobre todos los eventos en el calendario actual. Esta lista de matrices se obtiene ejecutando la llamada a la API encapsulada en retrieveEvents (). Para cada evento, imprimimos la fecha en que fue creado y luego su título.


El resto del código es similar a lo que ya hemos discutido y aún más fácil de entender. Siéntase libre de jugar con él antes de pasar a la siguiente sección.


Organizando el Código Procesal

Nuestro código actual está bien, pero creo que podemos hacerlo mejor y organizarlo de una manera más apropiada. Puede encontrar el proyecto con el código organizado completo bajo el nombre "GoogleCalProceduralOrganized" en el código fuente adjunto.

Usando una variable global de cliente

Lo primero que me molesta de nuestro código no organizado, es que estamos pasando esto $ cliente Variable como argumento en todo el lugar, varios niveles en las funciones anidadas. La programación de procedimientos tiene una forma inteligente de resolver esto, una variable global. Ya que $ cliente se define en index.php y en el ámbito global, todo lo que tenemos que cambiar es cómo lo utilizan nuestras funciones. Así que en lugar de esperar un $ cliente parámetro, podemos utilizar:

function printCalendars () global $ client; putTitle ('Estos son tus calendarios:'); foreach (getCalendarList ($ client) ['items'] como $ calendar) putLink ('? showThisCalendar ='. htmlentities ($ calendar ['id']), $ calendar ['summary']); impresión('
');

Compara el código actual con el código recién organizado para ver la diferencia. En lugar de pasar $ cliente como parámetro utilizamos cliente global $ en todas nuestras funciones y lo pasamos como parámetro solo a las funciones de la API de Google. Técnicamente, incluso las funciones de la API de Google podrían haber usado el $ cliente variable del ámbito global, pero creo que es mejor mantener la API lo más independiente posible.

Separando la presentación de la lógica

Algunas funciones son claras, solo para imprimir cosas en la pantalla, otras son para decidir qué hacer y otras son un poco de ambas. Cuando esto ocurre, a veces es mejor mover estas funciones de propósito específico a su propio archivo. Comenzaremos con las funciones que se utilizan únicamente para imprimir elementos en la pantalla, que se moverán a una funciones_display.php expediente. Véalos abajo.

function printHome () print ('Bienvenido a Google Calendar en NetTuts Example');  function printMenu () putLink ('? home', 'Home'); putLink ('? showCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); impresión('

'); function putLink ($ href, $ text) print (sprintf ('% s |', $ href, $ text)); function putTitle ($ text) print (sprintf ('

% s

', $ text)); función putBlock ($ texto) imprimir ('
'. $ texto'.
');

El resto de este proceso de separar nuestra presentación de la lógica nos obliga a extraer la parte de presentación de nuestros métodos. Así es como lo hicimos con uno de los métodos..

function printEventDetails () global $ client; foreach (retrieveEvents ($ _ GET ['calendarId']) as $ event) if ($ event ['id'] == $ _GET ['showThisEvent']) putTitle ('Details for event:'. $ event ['resumen ']); putBlock ('Este evento tiene estado'. $ evento ['estado']); putBlock ('Se creó en'. date ('Ymd H: m', strtotime ($ event ['created'])). 'y se actualizó por última vez en'. date ('Ymd H: m', strtotime ($ event ['actualizado'])) . '.'); putBlock ('Para este evento tienes que '. $ evento ['resumen']. '.'); 

Claramente podemos ver que todo lo que está dentro de la Si La declaración es solo el código de presentación y el resto es lógica de negocios. En lugar de una función voluminosa que maneja todo, la dividiremos en múltiples funciones:

function printEventDetails () global $ client; foreach (retrieveEvents ($ _ GET ['calendarId']) como $ event) if (isCurrentEvent ($ event)) putEvent ($ event);  function isCurrentEvent ($ event) return $ event ['id'] == $ _GET ['showThisEvent']; 

Después de la separación, la lógica de negocios es ahora muy simple. Incluso extrajimos un pequeño método para determinar si el evento es el actual. Todo el código de presentación ahora es responsabilidad de una función llamada putEvent ($ event) que reside en el funciones_display.php expediente:

function putEvent ($ event) putTitle ('Details for event:'. $ event ['summary']); putBlock ('Este evento tiene estado'. $ evento ['estado']); putBlock ('Se creó en'. date ('Ymd H: m', strtotime ($ event ['created'])). 'y se actualizó por última vez en'. date ('Ymd H: m', strtotime ($ event ['actualizado'])) . '.'); putBlock ('Para este evento tienes que '. $ evento ['resumen']. '.'); 

Aunque este método solo muestra información, debemos tener en cuenta que depende del conocimiento íntimo sobre la estructura de $ evento. Pero, esto está bien por ahora. En cuanto al resto de los métodos, se separaron de manera similar..

Eliminación de sentencias largas si-si no

Lo último que me molesta de nuestro código actual es la larga afirmación if-else en nuestro doUserAction () Función, que se utiliza para decidir qué hacer para cada acción. Ahora, PHP es bastante flexible cuando se trata de meta-programación (llamar a funciones por referencia). Este truco nos permite correlacionar nombres de funciones con $ _GET Valores de la variable. Así que podemos introducir una sola acción parámetro en el $ _GET variable y usar el valor de eso como un nombre de función.

function doUserAction () putMenu (); if (! isset ($ _ GET ['action'])) devolver; $ _GET ['action'] (); 

Basado en este enfoque, nuestro menú será generado así:

function putMenu () putLink ('? action = putHome', 'Home'); putLink ('? action = printCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); impresión('

');

Como probablemente pueda ver, esta reorganización ya nos empujó hacia un diseño orientado a objetos. No está claro qué tipo de objetos tenemos y con qué comportamiento exacto, pero tenemos algunas pistas aquí y allá..

Tenemos presentaciones que dependen de los tipos de datos de la lógica de negocios. Esto se asemeja a la inversión de dependencia de la que hablamos en el capítulo introductorio. El flujo del control sigue siendo de la lógica empresarial hacia la presentación, pero la dependencia del código fuente comenzó a transformarse en una dependencia invertida. Yo diría, en este punto es más como una dependencia bidireccional.

Otro indicio de un diseño orientado a objetos es el poco de meta-programación que acabamos de hacer. Llamamos a un método, del cual no sabemos nada. Puede ser cualquier cosa y es como si estuviéramos lidiando con un bajo nivel de polimorfismo.

Análisis de dependencia

Para nuestro código actual, podríamos dibujar un esquema, como el que se muestra a continuación, para ilustrar los primeros pasos a través de nuestra aplicación. Dibujar todas las líneas hubiera sido demasiado complicado..


Marcamos con líneas azules, el procedimiento convoca. Como puedes ver, fluyen en la misma dirección que antes. Adicionalmente tenemos las líneas verdes marcando llamadas indirectas. Todos estos están pasando por doUserAction (). Estos dos tipos de líneas representan el flujo de control, y se puede observar que básicamente no se modifica..

Las líneas rojas sin embargo introducen un concepto diferente. Representan una dependencia rudimentaria del código fuente. Quiero decir rudimentario, porque no es tan obvio. los putMenu () El método incluye los nombres de las funciones que deben llamarse para ese enlace en particular. Esta es una dependencia y la misma regla se aplica a todos los otros métodos que crean enlaces. Ellos depender Sobre el comportamiento de las otras funciones..

Un segundo tipo de dependencia también se puede ver aquí. La dependencia de los datos. He mencionado anteriormente $ calendario y $ evento. Las funciones de impresión deben tener un conocimiento íntimo de la estructura interna de estos arreglos para hacer su trabajo.

Entonces, después de todo eso, creo que tenemos muchas razones para avanzar a nuestro último paso.


Una solución orientada a objetos

Independientemente del paradigma en uso, no hay una solución perfecta para un problema. Así es como propongo organizar nuestro código de una manera orientada a objetos..

Primer instinto

Ya comenzamos a separar las preocupaciones en lógica de negocios y presentación. Incluso presentamos nuestra doUserAction () Método como una entidad separada. Así que mi primer instinto es crear tres clases. Presentador, Lógica, y Enrutador. Lo más probable es que esto cambie más adelante, pero necesitamos un lugar para comenzar, a la derecha.?

los Enrutador contendrá solo un método y seguirá siendo bastante similar a la implementación anterior.

class Router function doUserAction () (new Presenter ()) -> putMenu (); if (! isset ($ _ GET ['action'])) devolver; (nueva lógica ()) -> $ _ GET ['action'] (); 

Así que ahora tenemos que llamar explícitamente a nuestro putMenu () método usando un nuevo Presentador objeto y el resto de las acciones se llamará utilizando una Lógica objeto. Sin embargo, esto inmediatamente causa un problema. Tenemos una acción que no está en la clase lógica.. putHome () está en la clase de presentador. Necesitamos introducir una acción en Lógica que delegará en el presentador putHome () método. Recuerde, por el momento, solo queremos incluir nuestro código existente en las tres clases que identificamos como posibles candidatos para un diseño OO. Queremos hacer solo lo que sea absolutamente necesario para hacer que el diseño funcione. Después de que tengamos código de trabajo, lo cambiaremos aún más..

Tan pronto como pongamos un putHome () Método en la clase lógica tenemos un dilema. ¿Cómo llamar a los métodos de presentador? Bueno, podríamos crear y pasar un objeto Presenter a Logic para que siempre tenga una referencia a la presentación. Vamos a hacer eso desde nuestro router..

class Router function doUserAction () (new Presenter ()) -> putMenu (); if (! isset ($ _ GET ['action'])) devolver; (nueva lógica (nuevo presentador)) -> $ _ GET ['action'] (); 

Ahora podemos agregar un constructor en Logic y agregar la delegación hacia putHome () en presentador.

class Logic private $ presenter; function __construct (Presenter $ presenter) $ this-> presenter = $ presenter;  función putHome () $ this-> presenter-> putHome ();  […]

Con algunos ajustes menores en index.php y si Presenter envuelve los antiguos métodos de visualización, Logic envuelve las antiguas funciones de lógica de negocios y Router envuelve el antiguo selector de acción, podemos ejecutar nuestro código y hacer que funcione el elemento del menú "Inicio".

require_once './google-api-php-client/src/Google_Client.php'; require_once './google-api-php-client/src/contrib/Google_CalendarService.php'; require_once __DIR__. '/… /ApiAccess.php'; require_once './functins_google_api.php'; require_once './Presenter.php'; require_once './Logic.php'; require_once './Router.php'; session_start (); $ client = createClient (); if (! authenticate ($ client)) return; (nuevo Router ()) -> doUserAction ();

Y aquí está en acción..


A continuación, en nuestra clase de lógica, necesitamos cambiar adecuadamente las llamadas para mostrar la lógica, para trabajar con $ esto-> presentador. Entonces tenemos dos métodos - isCurrentEvent () y retrieveEvents () - que se utilizan sólo dentro de la clase lógica. Los haremos privados y modificaremos las llamadas en consecuencia..

Luego tomaremos el mismo enfoque con la clase de presentador. Cambiaremos todas las llamadas a métodos para señalar $ esto-> algo y hacer putTitle (), putLink (), y putBlock () Privado, ya que solo se utilizan desde Presenter. Echa un vistazo al código en el GoogleCalObjectOrientedInitial directorio en el código fuente adjunto si tiene dificultades para realizar todos estos cambios por su cuenta.

En este punto tenemos una aplicación de trabajo. Es principalmente un código de procedimiento envuelto en la sintaxis OO, que aún utiliza el $ cliente variable global y tiene un montón de otros olores orientados a objetos, pero funciona.

Si dibujamos el diagrama de clase con dependencias para este código, se verá así:>


Tanto el control de flujo como las dependencias del código fuente pasan por el enrutador, luego la lógica y, finalmente, por la presentación. Este último cambio que hicimos en realidad desvanece un poco la inversión de dependencia que observamos en nuestro paso anterior. Pero no te dejes engañar. El principio está ahí, solo tenemos que hacerlo obvio..

Revertir la dependencia del código fuente

Es difícil decir que un principio SOLID es más importante que otro, pero creo que el Principio de Inversión de Dependencia tiene el mayor impacto inmediato en su diseño. Este principio establece:

UNA: Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de las abstracciones y segundo: Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones..

En pocas palabras, esto significa que las implementaciones concretas deben depender de clases abstractas. A medida que sus clases se vuelven abstractas, menos tienden a cambiar. Para que pueda percibir el problema como: las clases que cambian con frecuencia deben depender de otras clases mucho más estables. Así que la parte más volátil de cualquier aplicación es probablemente su interfaz de usuario, que sería la clase Presenter en nuestra aplicación. Hagamos evidente esta inversión de dependencia..

Primero haremos que nuestro enrutador utilice solo el presentador y romperemos su dependencia de la lógica..

class Router function doUserAction () (new Presenter ()) -> putMenu (); if (! isset ($ _ GET ['action'])) devolver; (nuevo Presenter ()) -> $ _ GET ['action'] (); 

Luego, cambiaremos a Presenter para usar una instancia de Logic y pediremos la información que necesita presentar. En nuestro caso, considero aceptable que Presenter cree realmente la instancia de Logic, pero en cualquier sistema de producción, es probable que tenga Fábricas que creen los objetos relacionados con la lógica de negocios y los inyecten en la capa de presentación.

Ahora, la función putHome (), Presente en las clases de Logic y Presenter, desaparecerá de Logic. Esta es una buena señal, ya que estamos eliminando la duplicación. El constructor y la referencia a Presenter también desaparecen de Logic. Por otro lado, un constructor que crea un objeto lógico debe escribirse en el Presentador.

presentador de clase private $ businessLogic; función __construct () $ this-> businessLogic = new Logic ();  function putHome () print ('Bienvenido a Google Calendar en NetTuts Example');  […]

Después de esos cambios, haciendo clic en Mostrar calendarios Sin embargo producirá un error agradable. Debido a que todas nuestras acciones dentro de los enlaces apuntan a nombres de funciones en la clase Logic, tendremos que hacer algunos cambios más consistentes para revertir la dependencia entre los dos. Tomemos un método a la vez. El primer mensaje de error dice:

Error grave: llamada al método no definido Presenter :: printCalendars () en / […] /GoogleCalObjectOrientedFinal/Router.php en la línea 9

Por lo tanto, nuestro enrutador desea llamar a un método que no existe en Presenter, printCalendars (). Creemos ese método en Presenter y comprobemos lo que hizo en Logic. Imprimió un título y luego hizo un ciclo a través de algunos calendarios y llamó putCalendar (). En presentador el printCalendars () El método se verá así:

function printCalendars () $ this-> putCalendarListTitle (); foreach ($ this-> businessLogic-> getCalendars () as $ calendar) $ this-> putCalendarListElement ($ calendar); 

Por otro lado, en lógica, el método se vuelve bastante anémico. Sólo una llamada a la biblioteca de Google API.

function getCalendars () global $ client; devuelve getCalendarList ($ client) ['items']; 

Esto puede hacer que te hagas dos preguntas: "¿Realmente necesitamos una clase de lógica?" y "¿Nuestra aplicación tiene alguna lógica?". Bueno, todavía no lo sabemos. Por el momento continuaremos el proceso anterior, hasta que todo el código funcione, y Logic ya no dependa de Presenter..

Por lo tanto, vamos a utilizar una printCalendarContents () Método en Presenter, como el siguiente:

function printCalendarContents () $ this-> putCalendarTitle (); foreach ($ this-> businessLogic-> getEventsForCalendar () como $ event) $ this-> putEventListElement ($ event); 

Lo que a su vez, nos permitirá simplificar el getEventsForCalendar () En lógica, en algo como esto..

function getEventsForCalendar () global $ client; devuelve getEventList ($ client, htmlspecialchars ($ _ GET ['showThisCalendar'])) ['items']; 

Ahora esto funciona, pero tengo una preocupación aquí. los $ _GET La variable se está utilizando en las clases de Logic y Presenter. ¿No debería estar usando solo la clase Presenter? $ _GET? Quiero decir, el presentador necesita absolutamente saber sobre $ _GET Debido a que tiene que crear enlaces que están poblando este $ _GET variable. Eso significaría que $ _GET es estrictamente relacionado con HTTP. Ahora, queremos que nuestro código funcione con una CLI o una IU gráfica de escritorio. Por eso queremos mantener este conocimiento solo en el Presentador. Esto hace que los dos métodos de arriba, se transformen en los dos a continuación..

function getEventsForCalendar ($ calendarId) global $ client; devuelve getEventList ($ client, $ calendarId) ['items']; 
function printCalendarContents () $ this-> putCalendarTitle (); $ eventsForCalendar = $ this-> businessLogic-> getEventsForCalendar (htmlspecialchars ($ _ GET ['showThisCalendar'])); foreach ($ eventsForCalendar as $ event) $ this-> putEventListElement ($ event); 

Ahora, la última función con la que tenemos que lidiar es para imprimir un evento específico. Por el bien de este ejemplo, supongamos que no hay forma de que podamos recuperar un evento directamente y tenemos que encontrarlo nosotros mismos. Ahora nuestra clase de lógica es útil. Es un lugar perfecto para manipular listas de eventos y buscar una ID específica:

function getEventById ($ eventId, $ calendarId) foreach ($ this-> getEventsForCalendar ($ calendarId) as $ event) if ($ event ['id'] == $ eventId) return $ event; 

Y luego la llamada correspondiente a Presenter se encargará de imprimirlo:

function printEventDetails () $ this-> putEvent ($ this-> businessLogic-> getEventById ($ _GET ['showThisEvent'], $ _GET ['calendarId'])); 

Eso es. Aquí estamos. Dependencia invertida!


El control todavía fluye desde la lógica hacia el presentador. El contenido presentado está totalmente definido por Logic. Si, por ejemplo, mañana queremos conectarnos a otro servicio de calendario, podemos crear otra lógica, inyectarla en el presentador, y el presentador ni siquiera notará ninguna diferencia. Además, la dependencia del código fuente fue invertida exitosamente. Presenter es el único que crea y depende directamente de Logic. Esta dependencia es crucial para permitir que Presenter cambie la forma en que muestra los datos, sin afectar nada en Logic. Además, nos permite cambiar nuestro presentador HTML con un presentador CLI o cualquier otro método para mostrar la información al usuario.

Deshacerse de la variable global

Probablemente, la última falla seria de diseño que queda es el uso de una variable global para $ cliente. Todo el código en nuestra aplicación tiene acceso a él. Milagrosamente, la única clase que realmente necesita $ cliente Es nuestra clase de lógica. El paso obvio es hacer una variable de clase privada. Pero hacerlo requiere que nos propaguemos. $ cliente a t