Da procedurale a PHP orientato agli oggetti

Questo tutorial è stato ispirato da un discorso tenuto da Robert C. Martin che ho visto un anno fa circa. L'argomento principale del suo discorso riguarda la possibilità di scegliere The Last Programming Language. Affronta argomenti come perché dovrebbe esistere un linguaggio del genere? E come dovrebbe essere? Tuttavia, se leggevi tra le righe, c'era un'altra idea interessante che attirò la mia attenzione: i limiti che ogni paradigma di programmazione impone a noi programmatori. Quindi, prima di entrare nel modo di convertire unapplicazione basata su procedurale in una object oriented, voglio coprire un po 'di teoria in anticipo.


Limitazioni del paradigma

Quindi, ogni paradigma di programmazione limita la nostra capacità di fare qualsiasi cosa vogliamo fare. Ognuno di loro prende qualcosa e fornisce un'alternativa per ottenere lo stesso risultato. La programmazione modulare elimina le dimensioni illimitate del programma. Impone al programmatore l'uso di moduli di dimensioni massime e ogni modulo termina con una dichiarazione "go-to" su un altro modulo. Quindi la prima limitazione è sulla dimensione. Quindi, la programmazione strutturata e la programmazione procedurale rimuovono l'istruzione "go-to" e limitano il programmatore alla sequenza, alla selezione e all'iterazione. Le sequenze sono assegnazioni variabili, le selezioni sono decisioni if-else e le iterazioni sono cicli do-while. Questi sono gli elementi costitutivi della maggior parte dei linguaggi e dei paradigmi di programmazione oggi.

La programmazione orientata agli oggetti rimuove i puntatori alle funzioni e introduce il polimorfismo. PHP non usa i puntatori come fa C, ma una variante di questi puntatori alle funzioni può essere osservata nelle Funzioni variabili. Ciò consente a un programmatore di utilizzare il valore di una variabile come nome di una funzione, in modo che si possa ottenere qualcosa come questo:

function foo () echo "Questo è foo";  function bar ($ param) echo "Questo è bar dicendo: $ param";  $ function = 'foo'; $ Function (); // Entra in foo () $ function = 'bar'; $ Funzione ( 'test'); // Entra nella barra ()

Questo potrebbe non sembrare importante a prima vista. Ma pensa a cosa possiamo ottenere con uno strumento così potente. Possiamo inviare una variabile come parametro a una funzione e quindi lasciare che quella funzione chiami l'altra, a cui fa riferimento il valore del parametro. È incredibile Ci consente di modificare la funzionalità di una funzione senza che essa la conosca. Senza la funzione nemmeno notando alcuna differenza.

In realtà possiamo anche effettuare chiamate polimorfiche con questa tecnica.

Ora, invece di pensare a quali indicazioni fornire le funzioni, pensa a come funzionano. Non sono semplicemente delle dichiarazioni "go-to" nascoste? In realtà, lo sono, o almeno sono molto simili ai "go-to" indiretti. Che non è molto buono Quello che abbiamo qui è in realtà un modo intelligente di fare "go-to" senza usarlo direttamente. Devo ammettere che in PHP, come mostra l'esempio sopra, è abbastanza facile da capire, ma potrebbe confondersi con progetti più grandi e molte funzioni diverse passate da una funzione all'altra. In C è ancora più oscuro e orribilmente difficile da comprendere.

Tuttavia, solo togliere i puntatori alle funzioni non è sufficiente. La programmazione orientata agli oggetti deve fornire una sostituzione, e lo fa, in modo elegante. Offre polimorfismo con una sintassi semplice. E con il polimorfismo, arriva la più grande offerta di programmazione orientata agli oggetti di valore: il flusso di controllo è opposto alla dipendenza dal codice sorgente.


Nell'immagine sopra abbiamo illustrato un semplice esempio di come le chiamate polimorfiche avvengono nei due diversi paradigmi. Nella programmazione procedurale o strutturale, il flusso del controllo è simile alla dipendenza del codice sorgente. Entrambi puntano verso l'implementazione più concreta del comportamento di stampa.

Nella programmazione orientata agli oggetti, possiamo invertire la dipendenza del codice sorgente e puntare verso un'implementazione più astratta, mantenendo il flusso del controllo puntato a un'implementazione più concreta. Questo è essenziale, perché vogliamo che il nostro controllo raggiunga la parte più possibile concreta e volatile del nostro codice in modo da ottenere il risultato esattamente come lo vogliamo, ma nel nostro codice sorgente vogliamo l'esatto opposto. Nel nostro codice sorgente vogliamo che le cose concrete e volatili rimangano fuori strada, siano facili da cambiare e influenzino il meno possibile il resto del nostro codice. Lasciate che le parti volatili cambino frequentemente, ma non modificate le parti più astratte. Puoi leggere di più sul Principio di Inversione di dipendenza nel documento di ricerca originale scritto da Robert C. Martin.


Il compito a portata di mano

In questo capitolo creeremo una semplice applicazione per elencare i calendari di Google e gli eventi al loro interno. Innanzitutto adotteremo un approccio procedurale, utilizzando solo funzioni semplici ed evitando qualsiasi tipo di classe o oggetto. L'applicazione ti consentirà di elencare i tuoi calendari ed eventi Google. Quindi faremo il problema un passo avanti mantenendo il nostro codice procedurale e iniziando ad organizzarlo per comportamento. Alla fine lo trasformeremo in una versione orientata agli oggetti.


PHP Google API Client

Google fornisce un client API per PHP. Lo useremo per connetterci al nostro account Google in modo da poter manipolare i calendari lì. Se desideri eseguire il codice, devi configurare il tuo account Google per accettare le query del calendario.

Anche se questo è un requisito per il tutorial, non è il suo soggetto principale. Quindi, invece di ripetere i passaggi che devi seguire, ti indicherò la documentazione giusta. Non preoccuparti, è molto semplice da configurare e richiede solo circa cinque minuti.

Il codice client dell'API di Google PHP è incluso in ogni progetto dal codice di esempio allegato a questo tutorial. Vi consiglio di usarlo. In alternativa, se sei curioso di sapere come installarlo da solo, consulta la documentazione ufficiale.

Quindi seguire le istruzioni e inserire le informazioni nel apiAccess.php file. Questo file sarà richiesto da entrambi gli esempi procedurali e orientati agli oggetti, quindi non è necessario ripeterlo. Ho lasciato le mie chiavi lì in modo da poter identificare e riempire più facilmente il tuo.

Se ti capita di utilizzare NetBeans, ho lasciato i file di progetto nelle cartelle contenenti i diversi esempi. In questo modo puoi semplicemente aprire i progetti ed eseguirli immediatamente su un server PHP locale (è richiesto PHP 5.4) semplicemente selezionando Esegui / Esegui progetto.

La libreria client per connettersi all'API di Google è orientata agli oggetti. Per il nostro esempio funzionale, ho scritto un piccolo insieme di funzioni che le avvolgono, le funzionalità di cui abbiamo bisogno. In questo modo, possiamo usare uno strato procedurale scritto sulla libreria client orientata agli oggetti in modo che il nostro codice non debba usare oggetti.

Se vuoi testare rapidamente che il tuo codice e la connessione all'API di Google funzionano, basta usare il codice sotto come tuo index.php file. Dovrebbe elencare tutti i calendari che hai nel tuo account. Ci dovrebbe essere almeno un calendario con il sommario il campo è il tuo nome Se hai un calendario con i compleanni del tuo contatto, questo potrebbe non funzionare con questa API di Google, ma non fatevi prendere dal panico, basta sceglierne un altro.

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 (); se (! authenticate ($ client)) restituisce; listAllCalendars ($ client);

Questo index.php il file sarà il punto di accesso alla nostra applicazione. Non useremo un framework web o qualcosa di stravagante. Eseguiremo semplicemente un po 'di codice HTML.


Un approccio procedurale diretto

Ora che sappiamo cosa stiamo costruendo e che cosa useremo, vai avanti e scarica il codice sorgente allegato. Fornirò snippet da esso, ma per poter vedere il tutto, vorrai avere accesso alla fonte originale.

Per questo approccio, vogliamo solo far funzionare le cose. Il nostro codice sarà organizzato in modo molto rudimentale, con solo pochi file, come questo:

  • index.php - l'unico file che accediamo direttamente dal browser e lo passiamo a GET parameters.
  • functions_google_api.php - il wrapper sulle API di Google di cui abbiamo parlato sopra.
  • functions.php - dove tutto accade.

functions.php ospiterà tutto ciò che fa la nostra applicazione. Sia la logica di routing, le presentazioni e qualunque valore e comportamento possono essere sepolti lì. Questa applicazione è piuttosto semplice, la logica principale è la seguente.


Abbiamo una singola funzione chiamata doUserAction (), che decide con un lungo se altro dichiarazione, quali altri metodi chiamare in base ai parametri in OTTENERE variabile. I metodi quindi si collegano al calendario di Google utilizzando l'API e stampano sullo schermo tutto ciò che vogliamo richiedere.

function printCalendarContents ($ client) putTitle ('Questi sono i tuoi eventi per'. getCalendar ($ client, $ _GET ['showThisCalendar']) ['summary']. 'calendar:'); foreach (retrieveEvents ($ client, $ _GET ['showThisCalendar']) come $ event) print ('
'. data ('Y-m-d H: m', strtotime ($ event ['created']))); putLink ('? showThisEvent ='. htmlentities ($ event ['id']). '& calendarId ='. htmlentities ($ _ GET ['showThisCalendar']), $ event ['summary']); stampare('
'); stampare('
');

Questo esempio è probabilmente la funzione più complicata del nostro codice. Chiama una funzione di supporto chiamata putTitle (), che stampa solo un codice HTML formattato per l'intestazione. Il titolo conterrà il nome del nostro calendario che può essere ottenuto chiamando getCalendar () a partire dal functions_google_api.php. Il calendario restituito sarà un array, contenente a sommario campo. Questo è ciò che cerchiamo.

Il $ cliente variabile è passata dappertutto in tutte le nostre funzioni. È necessario connettersi all'API di Google. Ci occuperemo di questo più tardi.

Successivamente, ciclichiamo su tutti gli eventi nel calendario corrente. Questo elenco di array si ottiene eseguendo la chiamata API incapsulata in retrieveEvents (). Per ogni evento, stampiamo la data di creazione e quindi il titolo.


Il resto del codice è simile a quello che abbiamo già discusso e ancora più facile da capire. Sentiti libero di giocarci prima di continuare con la sezione successiva.


Organizzazione del codice procedurale

Il nostro codice attuale è OK, ma penso che possiamo fare meglio e organizzarlo in modo più appropriato. Puoi trovare il progetto con il codice organizzato completo sotto il nome "GoogleCalProceduralOrganized" nel codice sorgente allegato.

Utilizzando una variabile client globale

La prima cosa che mi infastidisce del nostro codice non organizzato è che stiamo passando questo $ cliente variabile come argomento in tutto il luogo, diversi livelli nel profondo delle funzioni annidate. La programmazione procedurale ha un modo intelligente per risolvere questo problema, una variabile globale. Da $ cliente è definito in index.php e nell'ambito globale, tutto ciò che dobbiamo cambiare è come le nostre funzioni lo usano. Quindi invece di aspettarmi un $ cliente parametro, possiamo usare:

function printCalendars () global $ client; putTitle ('Questi sono i tuoi calendari:'); foreach (getCalendarList ($ client) ['items'] as $ calendar) putLink ('? showThisCalendar ='. htmlentities ($ calendar ['id']), $ calendar ['summary']); stampare('
');

Confronta il codice corrente con il codice appena organizzato per vedere la differenza. Invece di entrare $ cliente come parametro, abbiamo usato cliente $ globale in tutte le nostre funzioni e lo abbiamo passato come parametro solo alle funzioni dell'API di Google. Tecnicamente, anche le funzioni dell'API di Google avrebbero potuto utilizzare il $ cliente variabile dall'ambito globale, ma penso che sia meglio mantenere l'API il più indipendente possibile.

Separare la presentazione dalla logica

Alcune funzioni sono chiaramente, solo per stampare cose sullo schermo, altre sono per decidere cosa fare, e alcune sono un po 'di entrambe. In questo caso, a volte è meglio spostare queste funzioni specifiche nel proprio file. Inizieremo con le funzioni utilizzate esclusivamente per stampare elementi sullo schermo, che verranno spostati in a functions_display.php file. Li vedi sotto.

function printHome () print ('Benvenuto in Google Calendar su NetTuts Example');  function printMenu () putLink ('? home', 'Home'); putLink ('? showCalendars', 'Show Calendars'); putLink ('? logout', 'Logout'); stampare('

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

%S

', $ testo)); function putBlock ($ text) print ('
'$ Testo.'
');

Il resto di questo processo di separazione della nostra presentazione dalla logica ci impone di estrarre la parte della presentazione dai nostri metodi. Ecco come l'abbiamo fatto con uno dei metodi.

function printEventDetails () global $ client; foreach (retrieveEvents ($ _ GET ['calendarId']) come $ event) if ($ event ['id'] == $ _GET ['showThisEvent']) putTitle ('Dettagli per l'evento:'. $ event ['riepilogo ']); putBlock ('Questo evento ha stato'. $ event ['status']); putBlock ('È stato creato a'. date ('Ymd H: m', strtotime ($ event ['created'])). 'e l'ultimo aggiornamento a'. date ('Ymd H: m', strtotime ($ event ['aggiornato'])). '.'); putBlock ('Per questo evento devi '. $ event ['summary']. '.'); 

Chiaramente possiamo vedere che tutto ciò che c'è dentro Se la dichiarazione è solo un codice di presentazione e il resto è una logica aziendale. Invece di una funzione ingombrante che gestisce tutto, la suddivideremo in molteplici funzioni:

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

Dopo la separazione, la logica aziendale è ora molto semplice. Abbiamo anche estratto un piccolo metodo per determinare se l'evento è quello corrente. Tutto il codice di presentazione è ora responsabilità di una funzione denominata putEvent ($ event) che risiede nel functions_display.php file:

function putEvent ($ event) putTitle ('Dettagli per l'evento:'. $ event ['summary']); putBlock ('Questo evento ha stato'. $ event ['status']); putBlock ('È stato creato a'. date ('Ymd H: m', strtotime ($ event ['created'])). 'e l'ultimo aggiornamento a'. date ('Ymd H: m', strtotime ($ event ['aggiornato'])). '.'); putBlock ('Per questo evento devi '. $ event ['summary']. '.'); 

Anche se questo metodo mostra solo informazioni, dobbiamo tenere presente che dipende dalla conoscenza intima della struttura di $ evento. Ma, questo è OK per ora. Per quanto riguarda il resto dei metodi, sono stati separati in modo simile.

Eliminazione di dichiarazioni Long if-else

L'ultima cosa che mi infastidisce del nostro codice attuale è la lunga dichiarazione di if-else nella nostra doUserAction () funzione, che è usata per decidere cosa fare per ogni azione. Ora, PHP è abbastanza flessibile quando si tratta di meta-programmazione (chiamando le funzioni per riferimento). Questo trucco ci permette di correlare i nomi delle funzioni con il $ _GET i valori della variabile. Quindi possiamo introdurre un singolo azione parametro nel $ _GET variabile e usa il valore da questo come nome di una funzione.

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

Sulla base di questo approccio, il nostro menu sarà generato in questo modo:

function putMenu () putLink ('? action = putHome', 'Home'); putLink ('? action = printCalendars', 'Show Calendars'); putLink ('? logout', 'Logout'); stampare('

');

Come probabilmente potete vedere, questa riorganizzazione ci ha già spinti verso un design orientato agli oggetti. Non è chiaro che tipo di oggetti abbiamo e con quale comportamento esatto, ma abbiamo alcuni indizi qui e là.

Abbiamo presentazioni che dipendono dai tipi di dati dalla logica aziendale. Questo assomiglia all'inversione di dipendenza di cui stavamo parlando nel capitolo introduttivo. Il flusso del controllo è ancora dalla logica aziendale verso la presentazione, ma la dipendenza del codice sorgente ha iniziato a trasformarsi in una dipendenza invertita. Direi che a questo punto è più come una dipendenza bidirezionale.

Un altro suggerimento di un design orientato agli oggetti è il poco di meta-programmazione che abbiamo appena fatto. Chiamiamo un metodo, di cui non sappiamo nulla. Può essere qualsiasi cosa ed è come se avessimo a che fare con un basso livello di polimorfismo.

Analisi delle dipendenze

Per il nostro codice corrente potremmo disegnare uno schema, come quello sotto, per illustrare i primi passi attraverso la nostra applicazione. Disegnare tutte le linee sarebbe stato troppo complicato.


Abbiamo contrassegnato con linee blu, la procedura chiama. Come puoi vedere, fluttuano nella stessa direzione di prima. Inoltre abbiamo le linee verdi che segnano le chiamate indirette. Questi sono tutti di passaggio doUserAction (). Questi due tipi di linee rappresentano il flusso del controllo e puoi osservare che è sostanzialmente invariato.

Le linee rosse tuttavia introducono un concetto diverso. Rappresentano una dipendenza da codice sorgente rudimentale. Intendo rudimentale, perché non è così ovvio. Il putMenu () metodo include i nomi delle funzioni che devono essere chiamate per quel particolare collegamento. Questa è una dipendenza e la stessa regola si applica a tutti gli altri metodi che creano collegamenti. Essi dipendere sul comportamento delle altre funzioni.

Un secondo tipo di dipendenza può anche essere visto qui. La dipendenza dai dati. Ho già menzionato $ calendario e $ evento. Le funzioni di stampa devono avere una conoscenza approfondita della struttura interna di questi array per svolgere il proprio lavoro.

Quindi, dopo tutto ciò, penso che abbiamo molte ragioni per passare al nostro ultimo passo.


Una soluzione orientata agli oggetti

Indipendentemente dal paradigma in uso, non esiste una soluzione perfetta per un problema. Quindi ecco come propongo di organizzare il nostro codice in modo orientato agli oggetti.

Primo istinto

Abbiamo già iniziato a separare le preoccupazioni nella logica e nella presentazione aziendale. Abbiamo anche presentato il nostro doUserAction () metodo come entità separata. Quindi il mio primo istinto è di creare tre classi Presentatore, Logica, e Router. Molto probabilmente cambieranno più tardi, ma abbiamo bisogno di un punto di partenza, giusto?

Il Router conterrà solo un metodo e rimarrà abbastanza simile all'implementazione precedente.

class Router function doUserAction () (new Presenter ()) -> putMenu (); se (! isset ($ _ GET ['action'])) restituisce; (nuova logica ()) -> $ _ OTTIENI ['action'] (); 

Quindi ora dobbiamo chiamare esplicitamente il nostro putMenu () metodo usando un nuovo Presentatore oggetto e il resto delle azioni saranno chiamate usando a Logica oggetto. Tuttavia, questo causa immediatamente un problema. Abbiamo un'azione che non è nella classe Logic. putHome () è nella classe Presenter. Dobbiamo introdurre un'azione in Logica che delegherà al presentatore putHome () metodo. Ricorda, per il momento, vogliamo solo avvolgere il nostro codice esistente nelle tre classi che abbiamo identificato come possibili candidati per un progetto OO. Vogliamo fare solo ciò che è assolutamente necessario per far funzionare il progetto. Dopo aver elaborato il codice, lo cambieremo ulteriormente.

Non appena abbiamo messo un putHome () metodo nella classe Logica abbiamo un dilemma. Come chiamare i metodi da Presenter? Bene, potremmo creare e passare un oggetto Presenter in Logic in modo che abbia sempre un riferimento alla presentazione. Facciamolo dal nostro router.

class Router function doUserAction () (new Presenter ()) -> putMenu (); se (! isset ($ _ GET ['action'])) restituisce; (nuova logica (nuovo presentatore)) -> $ _ OTTIENI ['azione'] (); 

Ora possiamo aggiungere un costruttore in Logic e aggiungere la delega verso putHome () in Presenter.

class Logic private $ presenter; function __construct (Presenter $ presenter) $ this-> presenter = $ presenter;  function putHome () $ this-> presenter-> putHome ();  [...]

Con alcune piccole modifiche in index.php e avendo Presenter che avvolge i vecchi metodi di visualizzazione, Logica che avvolge le vecchie funzioni di business logic e Router che avvolge il vecchio selettore di azioni, possiamo effettivamente eseguire il nostro codice e avere l'elemento del menu "Home" funzionante.

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 (); se (! authenticate ($ client)) restituisce; (nuovo Router ()) -> doUserAction ();

E qui è in azione.


Successivamente, nella nostra classe Logica, abbiamo bisogno di cambiare correttamente le chiamate per visualizzare la logica, con cui lavorare $ This-> presentatore. Quindi abbiamo due metodi - isCurrentEvent () e retrieveEvents () - che vengono utilizzati solo all'interno della classe Logic. Li renderemo privati ​​e cambieremo di conseguenza le chiamate.

Avremo quindi lo stesso approccio con la classe Presenter. Modificheremo tutte le chiamate ai metodi per puntare a $ This-> qualcosa e fare putTitle (), putLink (), e putBlock () privato, dal momento che sono utilizzati solo da Presenter. Controlla il codice nel GoogleCalObjectOrientedInitial directory nel codice sorgente allegato se hai difficoltà a fare tutte queste modifiche da solo.

A questo punto abbiamo un'app funzionante. È per lo più codice procedurale avvolto nella sintassi OO, che usa ancora il $ cliente variabile globale e ha tonnellate di altri odori anti-object-oriented, ma funziona.

Se disegniamo il diagramma delle classi con dipendenze per questo codice, apparirà come segue:>


Sia il controllo del flusso che le dipendenze del codice sorgente passano attraverso il router, poi la logica e infine attraverso la presentazione. Quest'ultimo cambiamento che abbiamo effettivamente attenuato un po 'l'inversione di dipendenza che abbiamo osservato nel nostro passaggio precedente. Ma non lasciarti ingannare. Il principio è lì, dobbiamo solo renderlo ovvio.

Ripristino della dipendenza dal codice sorgente

È difficile dire che un principio SOLID è più importante di un altro, ma penso che il principio di inversione delle dipendenze abbia l'impatto più grande e immediato sul tuo progetto. Questo principio afferma:

UN: I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni e B: Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

In parole povere, ciò significa che le implementazioni concrete dovrebbero dipendere da classi astratte. Man mano che le lezioni diventano astratte, meno tendono a cambiare. Quindi puoi percepire il problema come: le classi che cambiano frequentemente dovrebbero dipendere da altre classi molto più stabili. Quindi la parte più volatile di qualsiasi applicazione è probabilmente la sua interfaccia utente, che sarebbe la classe Presenter nella nostra applicazione. Facciamo in modo che questa inversione di dipendenza sia ovvia.

Innanzitutto, faremo in modo che il nostro router utilizzi solo Presenter e interrompa la sua dipendenza dalla logica.

class Router function doUserAction () (new Presenter ()) -> putMenu (); se (! isset ($ _ GET ['action'])) restituisce; (nuovo Presenter ()) -> $ _ GET ['action'] (); 

Quindi cambieremo Presenter per usare un'istanza di Logic e chiedergli le informazioni che deve presentare. Nel nostro caso, ritengo accettabile che Presenter crei effettivamente l'istanza di Logic, ma in qualsiasi sistema di produzione, è probabile che le fabbriche creino gli oggetti correlati alla logica di business e li iniettino nello strato di presentazione.

Ora, la funzione putHome (), presente nelle classi Logic e Presenter, scomparirà dalla logica. Questo è un buon segno, poiché stiamo rimuovendo la duplicazione. Anche il costruttore e il riferimento a Presenter scompaiono da Logic. D'altra parte, un costruttore che crea un oggetto Logic deve essere scritto su Presenter.

class Presenter private $ businessLogic; function __construct () $ this-> businessLogic = new Logic ();  function putHome () print ('Benvenuto in Google Calendar su NetTuts Example');  [...]

Dopo queste modifiche, fare clic su Mostra calendari produrrà comunque un bel errore. Poiché tutte le nostre azioni all'interno dei link puntano ai nomi di funzioni nella classe Logic, dovremo fare alcune modifiche più consistenti per invertire la dipendenza tra i due. Prendiamolo un metodo alla volta. Il primo messaggio di errore dice:

Errore irreversibile: chiamata a un metodo non definito Presenter :: printCalendars () in / [...] /GoogleCalObjectOrientedFinal/Router.php sulla riga 9

Quindi, il nostro router vuole chiamare un metodo che non esiste su Presenter, printCalendars (). Creiamo quel metodo in Presenter e controlliamo cosa ha fatto in Logic. Ha stampato un titolo e poi ha pedalato attraverso alcuni calendari e chiamato putCalendar (). In Presentatore il printCalendars () il metodo sarà simile a questo:

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

D'altra parte, in Logic, il metodo diventa piuttosto anemico. Basta una chiamata in avanti alla libreria dell'API di Google.

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

Questo potrebbe farti fare due domande: "Abbiamo davvero bisogno di una lezione di logica?" e "La nostra applicazione ha persino qualche logica?". Bene, non lo sappiamo ancora. Per ora continueremo il processo sopra, fino a quando tutto il codice funziona, e la logica non dipende più da Presenter.

Quindi, useremo a printCalendarContents () metodo in Presenter, come quello qui sotto:

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

Che a sua volta, ci permetterà di semplificare il getEventsForCalendar () in Logic, in qualcosa di simile.

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

Ora funziona, ma ho una preoccupazione qui. Il $ _GET variabile viene utilizzata in entrambe le classi Logic e Presenter. Non dovrebbe usare solo la classe Presenter $ _GET? Voglio dire, Presenter ha assolutamente bisogno di sapere $ _GET perché deve creare collegamenti che lo popolano $ _GET variabile. Questo significherebbe questo $ _GET è strettamente correlato all'HTTP. Ora, vogliamo che il nostro codice funzioni con un'interfaccia grafica grafica o desktop. Quindi vogliamo mantenere questa conoscenza solo nel Presenter. Questo rende i due metodi dall'alto, trasformati nei due seguenti.

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

Ora l'ultima funzione che dobbiamo affrontare è la stampa di un evento specifico. Per questo esempio, supponiamo che non ci sia modo di recuperare direttamente un evento e dobbiamo trovarlo da soli. Ora la nostra lezione di logica è utile. È un luogo perfetto per manipolare elenchi di eventi e cercare un ID specifico:

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

E poi la chiamata corrispondente su Presenter si prenderà cura di stamparla:

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

Questo è tutto. Eccoci qui. Dipendenza invertita!


Il controllo scorre ancora da Logic verso Presenter. Il contenuto presentato è totalmente definito da Logic. Se, ad esempio domani, desideriamo collegarci a un altro servizio di calendario, possiamo creare un'altra logica, inserirla in Presenter e Presenter non noterà alcuna differenza. Inoltre, la dipendenza del codice sorgente è stata invertita correttamente. Presenter è l'unico che crea e dipende direttamente da Logic. Questa dipendenza è cruciale nel consentire a Presenter di cambiare il modo in cui mostra i dati, senza influenzare nulla in Logic. Inoltre, ci consente di cambiare il nostro Presentatore HTML con un Presenter CLI o qualsiasi altro metodo di visualizzazione delle informazioni all'utente.

Sbarazzarsi della variabile globale

Probabilmente l'ultimo difetto di progettazione serio rimanente è l'uso di una variabile globale per $ cliente. Tutto il codice nella nostra applicazione ha accesso ad esso. Miracolosamente, l'unica classe che effettivamente ha bisogno $ cliente è la nostra classe di logica. Il passaggio ovvio è rendere una variabile di cla