Spanish (Español) translation by steven (you can also view the original English article)
Este tutorial se inspiró en un discurso de Robert C. Martin que vi hace aproximadamente un año. El tema principal de su charla es sobre la posibilidad de elegir "El último lenguaje de programación". Aborda temas como ¿por qué debería existir un lenguaje así? ¿Y cómo debería verse? Sin embargo, si lees entre líneas, hubo otra idea interesante que me llamó la atención: las limitaciones que cada paradigma de programación nos impone a los programadores. Entonces, antes de entrar en 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 de paradigma
Entonces, cada paradigma de programación limita nuestra capacidad para hacer lo que queramos hacer. Cada uno de ellos quita algo y brinda una alternativa para lograr el mismo resultado. La programación modular elimina el tamaño ilimitado del programa. Obliga al programador a utilizar módulos de tamaño máximo y cada módulo termina con una instrucción "ir a" a otro módulo. Entonces, la primera limitación está en el tamaño. Luego, la programación estructurada y la programación procedimental eliminan la declaración "go-to" y limitan al programador a la secuencia, selección e iteración. Las secuencias son asignaciones de variables, las selecciones son decisiones if-else y las iteraciones son bucles do-while. Estos son los componentes básicos de la mayoría de los lenguajes de programación y paradigmas actuales.
La programación orientada a objetos elimina los indicadores de funciones e introduce polimorfismo. PHP no usa punteros como lo hace C, pero se puede observar una variante de estos punteros a funciones en Funciones variables. Esto permite que un programador use el valor de una variable como el nombre de una función, de modo que se pueda lograr algo como esto:
1 |
function foo() { |
2 |
echo "This is foo"; |
3 |
}
|
4 |
|
5 |
function bar($param) { |
6 |
echo "This is bar saying: $param"; |
7 |
}
|
8 |
|
9 |
$function = 'foo'; |
10 |
$function(); // Goes into foo() |
11 |
|
12 |
$function = 'bar'; |
13 |
$function('test'); // Goes into bar() |
Puede que esto no parezca importante a primera vista. Pero piensa 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 esta lo sepa. Sin que la función advirtiera ninguna diferencia.
De hecho, también podemos hacer llamadas polimórficas con esta técnica.
Ahora, en lugar de pensar en lo que proporcionan los punteros a las funciones, piensa en cómo funcionan. ¿No son solo declaraciones ocultas "a las que recurrir"? En realidad, lo son, o al menos son muy similares a los "go-to" indirectos. Lo que no es muy bueno. Lo que tenemos aquí es, de hecho, una forma inteligente de hacer un "go-to" sin usarlo directamente. Debo admitir que en PHP, como muestra el ejemplo anterior, es bastante fácil de entender, pero puede resultar confuso 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 eliminar los indicadores de 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 mayor valor 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 ocurren las llamadas polimórficas en los dos paradigmas diferentes. En la programación procedimental 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 la implementación más abstracta, mientras mantenemos el flujo de control apuntando a la implementación más concreta. Esto es esencial, porque queremos que nuestro control vaya y alcance la parte más concreta y volátil 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 fuera del camino, que sean fáciles de cambiar y afecten lo menos posible al resto de nuestro código. Deja que las partes volátiles cambien con frecuencia, pero mantén las partes más abstractas sin modificar. Puedes leer más sobre el principio de inversión de dependencia en el artículo 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 dentro de ellos. Primero tomaremos un enfoque procedimental, usando solo funciones simples y evitando cualquier tipo de clases u objetos. La aplicación te permitirá listar tus calendarios y eventos de Google. Luego, llevaremos el problema un paso más allá manteniendo nuestro código de procedimiento y comenzando a organizarlo por comportamiento. Finalmente lo transformaremos en una versión orientada a objetos.
Cliente PHP de la API de Google
Google proporciona un cliente PHP de la API de Google. Lo usaremos para conectarnos a nuestra cuenta de Google para poder manipular los calendarios allí. Si deseas ejecutar el código, debes configurar tu 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 repetir los pasos que debes seguir, te señalaré la documentación correcta. No te preocupes, es muy sencillo de configurar y solo lleva unos cinco minutos.
El código del cliente PHP de la API de Google se incluye en cada proyecto a partir del código de ejemplo adjunto a este tutorial. Te recomiendo que uses ese. Alternativamente, si tienes curiosidad sobre cómo instalarlo tú mismo, consulta la documentación oficial.
Luego sigue las instrucciones y completa la información en el archivo apiAccess.php
. Este archivo será requerido por los ejemplos de procedimiento y orientados a objetos, por lo que no es necesario que lo repitas. Dejé mis claves allí para que puedas identificar y completar las tuyas más fácilmente.
Si usas NetBeans, dejé los archivos del proyecto en las carpetas que contienen los diferentes ejemplos. De esta manera, puedes simplemente abrir los proyectos y ejecutarlos inmediatamente en un servidor PHP local (se requiere PHP 5.4 o superior) 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 envuelven en ellas 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 deseas probar rápidamente que tu código y conexión a la API de Google están funcionando, simplemente usa el código a continuación como tu archivo index.php
. Debes enumerar todos los calendarios que tienes en tu cuenta. Debe haber al menos un calendario con tu nombre en el campo summary. Si tienes un calendario con los cumpleaños de tus contactos, es posible que ese no funcione con esta API de Google, pero no te asustes, simplemente elige otro.
1 |
require_once './google-api-php-client/src/Google_Client.php'; |
2 |
require_once './google-api-php-client/src/contrib/Google_CalendarService.php'; |
3 |
require_once __DIR__ . '/../apiAccess.php'; |
4 |
require_once './functins_google_api.php'; |
5 |
require_once './functions.php'; |
6 |
session_start(); |
7 |
|
8 |
$client = createClient(); |
9 |
if(!authenticate($client)) return; |
10 |
listAllCalendars($client); |
Este archivo index.php
será el punto de entrada a nuestra aplicación. No usaremos un framework web ni nada sofisticado. Simplemente generaremos algo de código HTML.
Un enfoque procedimental directo
Ahora que sabemos lo que estamos desarrollando y lo que usaremos, descarga el código fuente adjunto. Proporcionaré fragmentos de él, pero para verlo todo, querrás tener acceso a la fuente original.
Para este enfoque, solo queremos que las cosas funcionen. Nuestro código estará organizado de una manera muy rudimentaria, con solo unos pocos archivos, así:
- index.php: El único archivo al que accedemos directamente desde el navegador y le pasamos los parámetros GET.
- functions_google_api.php: El contenedor de la API de Google de la que hablamos anteriormente.
- functions.php: Donde sucede todo.
functions.php
albergará todo lo que hace nuestra aplicación. Tanto la lógica de enrutamiento, las presentaciones y los valores y comportamientos que puedan 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 una larga declaración if-else
, qué otros métodos llamar en función de los parámetros de la variable GET
. Los métodos luego se conectan al calendario de Google usando la API e imprimen en la pantalla lo que queramos solicitar.
1 |
function printCalendarContents($client) { |
2 |
putTitle('These are you events for ' . getCalendar($client, $_GET['showThisCalendar'])['summary'] . ' calendar:'); |
3 |
foreach (retrieveEvents($client, $_GET['showThisCalendar']) as $event) { |
4 |
print('<div style="font-size:10px;color:grey;">' . date('Y-m-d H:m', strtotime($event['created']))); |
5 |
putLink('?showThisEvent=' . htmlentities($event['id']) . |
6 |
'&calendarId=' . htmlentities($_GET['showThisCalendar']), $event['summary']); |
7 |
print('</div>'); |
8 |
print('<br>'); |
9 |
}
|
10 |
}
|
Este ejemplo es probablemente la función más complicada de nuestro código. Llama a una función auxiliar llamada putTitle()
, que simplemente imprime algo de HTML formateado para el encabezado. El título contendrá el nombre de nuestro calendario que se puede obtener llamando a getCalendar()
desde functions_google_api.php
. El calendario devuelto será una matriz que contiene un campo summary
. Eso es lo que estamos buscando.
La variable $client
se pasa por todas partes en todas nuestras funciones. Sin embargo, es necesario conectarnos a la API de Google. Nos ocuparemos de esto más tarde.
A continuación, recorremos todos los eventos del calendario actual. Esta lista de matrices se obtiene ejecutando la llamada API encapsulada en retrieveEvents()
. Para cada evento, imprimimos la fecha en que se creó y luego su título.



El resto del código es similar a lo que ya hemos discutido e incluso más fácil de entender. Siéntete libre de jugar con él antes de continuar con la siguiente sección.
Organización del código procedimental
Nuestro código actual está bien, pero creo que podemos hacerlo mejor y organizarlo de una manera más apropiada. Puedes encontrar el proyecto con el código completo y organizado bajo el nombre de "GoogleCalProceduralOrganized" en el código fuente adjunto.
Usar una variable global de tipo cliente
Lo primero que me molesta de nuestro código desorganizado es que estamos pasando esta variable $client
como un argumento por todas partes, varios niveles dentro de las funciones anidadas. La programación por procedimientos tiene una forma inteligente de resolver esto, una variable global. Dado que $client
está definido en index.php
y en el ámbito global, todo lo que tenemos que cambiar es cómo lo usan nuestras funciones. Entonces, en lugar de esperar un parámetro $client
, podemos usar:
1 |
function printCalendars() { |
2 |
global $client; |
3 |
putTitle('These are your calendars:'); |
4 |
foreach (getCalendarList($client)['items'] as $calendar) { |
5 |
putLink('?showThisCalendar=' . htmlentities($calendar['id']), $calendar['summary']); |
6 |
print('<br>'); |
7 |
}
|
8 |
}
|
Compara el código actual con el código recién organizado para ver la diferencia. En lugar de pasar $client
como parámetro, usamos global $client
para pasarlo 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 la variable $client
del alcance global, pero creo que es mejor mantener la API lo más independiente posible.
Separar la presentación de la lógica
Algunas funciones son claramente, solo para imprimir cosas en la pantalla, otras son para decidir qué hacer y algunas son un poco de ambas cosas. 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 usan únicamente para imprimir cosas en la pantalla, estas se moverán a un archivo functions_display.php
. Míralos a continuación.
1 |
function printHome() { |
2 |
print('Welcome to Google Calendar over NetTuts Example'); |
3 |
}
|
4 |
|
5 |
function printMenu() { |
6 |
putLink('?home', 'Home'); |
7 |
putLink('?showCalendars', 'Show Calendars'); |
8 |
putLink('?logout', 'Log Out'); |
9 |
print('<br><br>'); |
10 |
}
|
11 |
|
12 |
function putLink($href, $text) { |
13 |
print(sprintf('<a href="%s" style="font-size:12px;margin-left:10px;">%s</a> | ', $href, $text)); |
14 |
}
|
15 |
|
16 |
function putTitle($text) { |
17 |
print(sprintf('<h3 style="font-size:16px;color:green;">%s</h3>', $text)); |
18 |
}
|
19 |
|
20 |
function putBlock($text) { |
21 |
print('<div display="block">'.$text.'</div>'); |
22 |
}
|
El resto de este proceso de separar nuestra presentación de la lógica requiere que extraigamos la parte de presentación de nuestros métodos. Así es como lo hicimos con uno de los métodos.
1 |
function printEventDetails() { |
2 |
global $client; |
3 |
foreach (retrieveEvents($_GET['calendarId']) as $event) |
4 |
if ($event['id'] == $_GET['showThisEvent']) { |
5 |
putTitle('Details for event: '. $event['summary']); |
6 |
putBlock('This event has status ' . $event['status']); |
7 |
putBlock('It was created at ' . |
8 |
date('Y-m-d H:m', strtotime($event['created'])) . |
9 |
' and last updated at ' . |
10 |
date('Y-m-d H:m', strtotime($event['updated'])) . '.'); |
11 |
putBlock('For this event you have to <strong>' . $event['summary'] . '</strong>.'); |
12 |
}
|
13 |
}
|
Claramente, podemos ver que todo lo que está dentro de la declaración if
es solo código de presentación y el resto es la lógica del negocio. En lugar de que una función voluminosa maneje todo, la dividiremos en múltiples funciones:
1 |
function printEventDetails() { |
2 |
global $client; |
3 |
foreach (retrieveEvents($_GET['calendarId']) as $event) |
4 |
if (isCurrentEvent($event)) |
5 |
putEvent($event); |
6 |
}
|
7 |
|
8 |
function isCurrentEvent($event) { |
9 |
return $event['id'] == $_GET['showThisEvent']; |
10 |
}
|
Después de la separación, la lógica del negocio ahora es muy sencilla. Incluso hemos extraído 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 archivo functions_display.php
:
1 |
function putEvent($event) { |
2 |
putTitle('Details for event: ' . $event['summary']); |
3 |
putBlock('This event has status ' . $event['status']); |
4 |
putBlock('It was created at ' . |
5 |
date('Y-m-d H:m', strtotime($event['created'])) . |
6 |
' and last updated at ' . |
7 |
date('Y-m-d H:m', strtotime($event['updated'])) . '.'); |
8 |
putBlock('For this event you have to <strong>' . $event['summary'] . '</strong>.'); |
9 |
}
|
Aunque este método solo muestra información, debemos tener en cuenta que esto depende de un conocimiento íntimo sobre la estructura de $event
. Pero esto está bien por ahora. En cuanto al resto de métodos, se separaron de forma similar.
Eliminando las declaraciones if-else largas
Lo último que me molesta de nuestro código actual es la declaración if-else larga en nuestra función doUserAction()
, que se usa para decidir qué hacer para cada acción. Ahora, PHP es bastante flexible cuando se trata de metaprogramación (llamar a funciones por referencia). Este truco nos permite correlacionar los nombres de las funciones con los valores de la variable $_GET
. Entonces podemos introducir un solo parámetro action
en la variable $_GET
y usar el valor de eso como nombre de función.
1 |
function doUserAction() { |
2 |
putMenu(); |
3 |
if (!isset($_GET['action'])) return; |
4 |
$_GET['action'](); |
5 |
}
|
Con base en este enfoque, nuestro menú se generará así:
1 |
function putMenu() { |
2 |
putLink('?action=putHome', 'Home'); |
3 |
putLink('?action=printCalendars', 'Show Calendars'); |
4 |
putLink('?logout', 'Log Out'); |
5 |
print('<br><br>'); |
6 |
}
|
Como probablemente puedas 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 del negocio. Esto se parece a la inversión de dependencia de la que hablábamos en el capítulo introductorio. El flujo del control sigue siendo desde la lógica del negocio hacia la presentación, pero la dependencia del código fuente comenzó a transformarse en una dependencia inversa. Diría que en este punto es más como una dependencia bidireccional.
Otro indicio de un diseño orientado a objetos es la cantidad de metaprogramación que acabamos de hacer. Llamamos a un método del que 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 de nuestra aplicación. Dibujar todas las líneas habría sido demasiado complicado.



Marcamos con líneas azules, el procedimiento de las llamadas. Como puedes ver, fluyen en la misma dirección que antes. Además tenemos las líneas verdes que marcan las llamadas indirectas. Todos estos pasan por doUserAction()
. Estos dos tipos de líneas representan el flujo de control y se puede observar que básicamente no cambia.
Sin embargo, las líneas rojas introducen un concepto diferente. Representan una dependencia rudimentaria del código fuente. Me refiero a rudimentario, porque no es tan obvio. El método putMenu()
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 demás métodos que crean enlaces. Dependen del comportamiento de las otras funciones.
Aquí también se puede ver un segundo tipo de dependencia. La dependencia de los datos. Anteriormente mencioné $calendar
y $event
. Las funciones de impresión deben tener un conocimiento profundo de la estructura interna de estos arreglos para hacer su trabajo.
Entonces, después de todo eso, creo que tenemos muchas razones para dar el último paso.
Una solución orientada a objetos
Independientemente del paradigma que se utilice, no existe una solución perfecta para un problema. Así que así es como propongo organizar nuestro código de una manera orientada a objetos.
Primer instinto
Ya comenzamos a separar las preocupaciones en la lógica del negocio y la presentación. Incluso presentamos nuestro método doUserAction()
como una entidad separada. Entonces, mi primer instinto es crear tres clases Presenter
, Logic
y Router
. Lo más probable es que estos cambien más adelante, pero necesitamos un lugar para comenzar, ¿verdad?
Router
contendrá solo un método y seguirá siendo bastante similar a la implementación anterior.
1 |
class Router { |
2 |
|
3 |
function doUserAction() { |
4 |
(new Presenter())->putMenu(); |
5 |
if (!isset($_GET['action'])) |
6 |
return; |
7 |
(new Logic())->$_GET['action'](); |
8 |
}
|
9 |
|
10 |
}
|
Entonces ahora tenemos que llamar explícitamente a nuestro método putMenu()
usando un nuevo objeto Presenter
y el resto de las acciones serán llamadas usando un objeto Logic
. Sin embargo, esto causa un problema de inmediato. Tenemos una acción que no está en la clase Logic. putHome()
está en la clase Presenter
. Necesitamos introducir una acción en Logic que se delegará en el método putHome()
del presentador. Recuerda, por el momento solo queremos envolver nuestro código existente en las tres clases que identificamos como posibles candidatos para un diseño orientado a objetos. Queremos hacer solo lo absolutamente necesario para que el diseño funcione. Una vez que tengamos el código de trabajo, lo cambiaremos más.
Tan pronto como ponemos un método putHome()
en la clase Logic, tenemos un dilema. ¿Cómo llamar a métodos desde Presenter? Bueno, podríamos crear y pasar un objeto Presenter a Logic para que siempre tenga una referencia a la presentación. Hagámoslo desde nuestro Router.
1 |
class Router { |
2 |
|
3 |
function doUserAction() { |
4 |
(new Presenter())->putMenu(); |
5 |
if (!isset($_GET['action'])) |
6 |
return; |
7 |
(new Logic(new Presenter))->$_GET['action'](); |
8 |
}
|
9 |
|
10 |
}
|
Ahora podemos agregar un constructor en Logic y agregar la delegación hacia putHome()
en Presenter.
1 |
class Logic { |
2 |
|
3 |
private $presenter; |
4 |
|
5 |
function __construct(Presenter $presenter) { |
6 |
$this->presenter = $presenter; |
7 |
}
|
8 |
|
9 |
function putHome() { |
10 |
$this->presenter->putHome(); |
11 |
}
|
12 |
|
13 |
[...] |
14 |
|
15 |
}
|
Con algunos ajustes menores en index.php
y con Presenter envolviendo los viejos métodos de visualización, Logic envolviendo las viejas funciones de lógica del negocio y Router envolviendo el antiguo selector de acciones, podemos ejecutar nuestro código y hacer que el elemento del menú "Inicio" funcione.
1 |
require_once './google-api-php-client/src/Google_Client.php'; |
2 |
require_once './google-api-php-client/src/contrib/Google_CalendarService.php'; |
3 |
require_once __DIR__ . '/../apiAccess.php'; |
4 |
require_once './functins_google_api.php'; |
5 |
require_once './Presenter.php'; |
6 |
require_once './Logic.php'; |
7 |
require_once './Router.php'; |
8 |
session_start(); |
9 |
|
10 |
$client = createClient(); |
11 |
if(!authenticate($client)) return; |
12 |
|
13 |
(new Router())->doUserAction(); |
Y aquí está en acción.



A continuación, en nuestra clase Logic, necesitamos cambiar correctamente las llamadas para mostrar la lógica, para trabajar con $this->presenter
. Luego tenemos dos métodos, isCurrentEvent()
y retrieveEvents()
, que se usan solo dentro de la clase Logic. Los haremos privados y cambiaremos las llamadas en consecuencia.
Luego, tomaremos el mismo enfoque con la clase Presenter. Cambiaremos todas las llamadas a métodos para que apunten a $this->alguna_cosa
y hagamos que putTitle()
, putLink()
y putBlock()
sean privados, ya que solo se usan desde Presenter. Consulta el código en el directorio GoogleCalObjectOrientedInitial en el código fuente adjunto si tienes dificultades para hacer todos estos cambios tú mismo.
En este punto, tenemos una aplicación que funciona. Es principalmente código de procedimiento envuelto en sintaxis OO, que todavía usa la variable global $client
y tiene toneladas de otros olores orientados a anti-objetos, pero funciona.
Si dibujamos el diagrama de clases 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 por 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 SÓLIDO 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:
A: Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones y B: Las abstracciones no deben depender de detalles. Los detalles deben depender de abstracciones.
En pocas palabras, esto significa que las implementaciones concretas deben depender de clases abstractas. A medida que tus clases se vuelven abstractas, menos tienden a cambiar. Entonces puedes percibir el problema como: las clases que cambian con frecuencia deberían depender de otras clases mucho más estables. Entonces, 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 obvia esta inversión de dependencia.
Primero haremos que nuestro enrutador use solo Presenter y rompa su dependencia de Logic.
1 |
class Router { |
2 |
|
3 |
function doUserAction() { |
4 |
(new Presenter())->putMenu(); |
5 |
if (!isset($_GET['action'])) |
6 |
return; |
7 |
(new Presenter())->$_GET['action'](); |
8 |
}
|
9 |
|
10 |
}
|
Luego cambiaremos Presenter para usar una instancia de Logic y le 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 Factories cree los objetos relacionados con la lógica del negocio y los inyecte en la capa de presentación.
Ahora, la función putHome()
, presente en las clases Logic y Presenter, desaparecerá de Logic. Esta es una buena señal, ya que estamos eliminando la duplicidad. 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 Presenter.
1 |
class Presenter { |
2 |
|
3 |
private $businessLogic; |
4 |
|
5 |
function __construct() { |
6 |
$this->businessLogic = new Logic(); |
7 |
}
|
8 |
|
9 |
function putHome() { |
10 |
print('Welcome to Google Calendar over NetTuts Example'); |
11 |
}
|
12 |
|
13 |
[...] |
14 |
|
15 |
}
|
Sin embargo, después de esos cambios, hacer clic en Mostrar calendarios producirá un error agradable. Debido a que todas nuestras acciones desde 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:
1 |
Fatal error: Call to undefined method Presenter::printCalendars() |
2 |
in /[...]/GoogleCalObjectOrientedFinal/Router.php on line 9 |
Entonces, nuestro Router quiere llamar a un método que no existe en Presenter, printCalendars()
. Creemos ese método en Presenter y verifiquemos lo que pasó en Logic. Imprimió un título y luego pasó por algunos calendarios y llamó a putCalendar()
. En Presenter, el método printCalendars()
se verá así:
1 |
function printCalendars() { |
2 |
$this->putCalendarListTitle(); |
3 |
foreach ($this->businessLogic->getCalendars() as $calendar) { |
4 |
$this->putCalendarListElement($calendar); |
5 |
}
|
6 |
}
|
Por otro lado, en Logic, el método se vuelve bastante anémico. Solo una llamada de reenvío a la biblioteca de API de Google.
1 |
function getCalendars() { |
2 |
global $client; |
3 |
return getCalendarList($client)['items']; |
4 |
}
|
Esto puede hacer que te hagas dos preguntas: "¿Realmente necesitamos una clase Logic?" y "¿Nuestra aplicación tiene alguna lógica?". Bueno, todavía no lo sabemos. Por el momento, continuaremos con el proceso anterior, hasta que todo el código funcione y Logic ya no dependa de Presenter.
Entonces, usaremos un método printCalendarContents()
en Presenter, como el siguiente:
1 |
function printCalendarContents() { |
2 |
$this->putCalendarTitle(); |
3 |
foreach ($this->businessLogic->getEventsForCalendar() as $event) { |
4 |
$this->putEventListElement($event); |
5 |
}
|
6 |
}
|
Lo que a su vez, nos permitirá simplificar getEventsForCalendar()
en Logic, en algo como esto.
1 |
function getEventsForCalendar() { |
2 |
global $client; |
3 |
return getEventList($client, htmlspecialchars($_GET['showThisCalendar']))['items']; |
4 |
}
|
Ahora bien, esto funciona, pero tengo una preocupación aquí. La variable $_GET
se utiliza en las clases Logic y Presenter. ¿No debería usar $_GET
solo la clase Presenter? Quiero decir, Presenter absolutamente necesita saber acerca de $_GET
porque tiene que crear enlaces que llenen esta variable $_GET
. Entonces eso significaría que $_GET
está estrictamente relacionado con HTTP. Ahora, queremos que nuestro código funcione con una CLI o una interfaz de usuario gráfica de escritorio. Por eso queremos mantener este conocimiento solo en Presenter. Esto hace que los dos métodos de arriba se transformen en los dos de abajo.
1 |
function getEventsForCalendar($calendarId) { |
2 |
global $client; |
3 |
return getEventList($client, $calendarId)['items']; |
4 |
}
|
1 |
function printCalendarContents() { |
2 |
$this->putCalendarTitle(); |
3 |
$eventsForCalendar = $this->businessLogic->getEventsForCalendar(htmlspecialchars($_GET['showThisCalendar'])); |
4 |
foreach ($eventsForCalendar as $event) { |
5 |
$this->putEventListElement($event); |
6 |
}
|
7 |
}
|
Ahora, la última función con la que tenemos que ocuparnos es la de 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 por nosotros mismos. Ahora nuestra clase Logic es útil. Es un lugar perfecto para manipular listas de eventos y buscar un ID específico:
1 |
function getEventById($eventId, $calendarId) { |
2 |
foreach ($this->getEventsForCalendar($calendarId) as $event) |
3 |
if ($event['id'] == $eventId) |
4 |
return $event; |
5 |
}
|
Y luego la llamada correspondiente en Presenter se encargará de imprimirlo:
1 |
function printEventDetails() { |
2 |
$this->putEvent( |
3 |
$this->businessLogic->getEventById( |
4 |
$_GET['showThisEvent'], |
5 |
$_GET['calendarId'] |
6 |
)
|
7 |
);
|
8 |
}
|
Eso es. Aquí estamos. ¡Dependencia invertida!



El control sigue fluyendo de Logic hacia Presenter. El contenido presentado está totalmente definido por Logic. Si, por ejemplo, mañana queremos conectarnos a otro servicio de calendario, podemos crear otro Logic, inyectarlo en Presenter, y Presenter ni notará ninguna diferencia. Además, la dependencia del código fuente se invirtió con éxito. 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 Presenter de HTML con un presentador CLI o cualquier otro método de mostrar la información al usuario.
Deshacerse de la variable global
Probablemente, el último defecto de diseño serio que queda es el uso de una variable global para $client
. Todo el código de nuestra aplicación tiene acceso a él. Milagrosamente, la única clase que realmente necesita $client
es nuestra clase Logic. El paso obvio es hacer una variable de clase privada. Pero hacerlo requiere que propaguemos $client
a través de Router, al Presenter, para que pueda crear un objeto lógico con la variable $client
. Esto resuelve pocos de nuestros problemas. Necesitamos construir nuestras clases en un lugar aislado e inyectar adecuadamente las dependencias entre nosotros.
Para cualquier estructura de clases más grande, usaríamos Factories, pero para nuestro pequeño ejemplo, el archivo index.php
será un gran lugar para guardar la lógica de creación. Y siendo el punto de entrada a nuestra aplicación, también conocido como el archivo "principal" en el esquema de arquitectura de alto nivel, todavía está fuera de los límites de nuestra lógica del negocio.



Hemos cambiado el archivo index.php
por el siguiente código, manteniendo todas las inclusiones y el comando session_start():
1 |
$client = createClient(); |
2 |
if(!authenticate($client)) return; |
3 |
|
4 |
$logic = new Logic($client); |
5 |
$presenter = new Presenter($logic); |
6 |
(new Router($presenter))->doUserAction(); |
Pensamientos finales
Y hemos terminado. Seguramente hay otras cosas que podríamos hacer para mejorar aún más nuestro diseño. Si nada más, podríamos escribir un par de pruebas para nuestros métodos en la clase Logic. Quizás nuestra clase Logic también podría cambiarse de nombre a algo más representativo, como GoogleCalendarGateway. O podríamos crear las clases "Event" y "Calendar" para controlar aún mejor los datos y el comportamiento de estos conceptos y romper la dependencia de Presenter de una matriz para estos tipos de datos. Otra mejora y extensión sería la de crear clases de acción polimórficas en lugar de simplemente llamar a funciones por referencia desde $_GET
. Hay un sinfín de pequeñas cosas que podríamos hacer para mejorar aún más este diseño simple. Te dejaré tener esta gran oportunidad de experimentar, comenzando con esta versión final del código que puedes encontrar en el archivo adjunto dentro del directorio GoogleCalObjectOrientedFinal.
Si eres aventurero, puedes extender esta pequeña aplicación para conectarte a otros servicios de calendario y presentar información de diferentes formas y en diferentes plataformas. Para todos los que usan NetBeans, cada carpeta de código fuente contiene un proyecto NetBeans, por lo que deberías poder abrirlos directamente. En la versión final, PHPUnit también está preparado para ti, pero en el resto de proyectos, lo eliminé porque no hicimos ninguna prueba.
Gracias por leer.