De la procedural la obiect orientat PHP

Acest tutorial a fost inspirat de un discurs al lui Robert C. Martin, pe care l-am urmărit cu un an în urmă. Principalul subiect al discuției sale este despre posibilitatea de a alege Limba de programare ultima. Se referă la subiecte cum ar fi de ce ar trebui să existe o astfel de limbă? Și cum ar trebui să arate? Cu toate acestea, dacă ați citit între linii, a existat o altă idee interesantă care mi-a atras atenția: limitările pe care fiecare paradigmă de programare le impune asupra programatorilor noștri. Așadar, înainte de a intra în modul în care am putea să transformăm o aplicație bazată pe procedură PHP într-o aplicație orientată pe obiecte, vreau să acoperim un pic de teorie în prealabil.


Paradigme Limitations

Deci, fiecare paradigmă de programare ne limitează capacitatea de a face tot ce vrem să facem. Fiecare dintre ele ia ceva și oferă o alternativă pentru a obține același rezultat. Modularea programelor elimină dimensiunea nelimitată a programului. Acesta impune programatorului să utilizeze module de dimensiuni maxime, iar fiecare modul se termină cu o declarație "go-to" la un alt modul. Deci, prima limitare este după mărime. Apoi, programarea structurată și programarea procedurală elimină declarația "go-to" și limitează programatorul la secvență, selecție și repetare. Secvențele sunt asignări variabile, selecțiile sunt decizi dacă-altceva, iar iterațiile sunt bucle în timp. Acestea sunt elementele de bază ale majorității limbajelor de programare și paradigme de astăzi.

Programarea orientată pe obiecte îndepărtează indicatorii de la funcții și introduce polimorfismul. PHP nu folosește pointeri într-un mod care face C, dar o variantă a acestor pointeri la funcții poate fi observată în funcțiile variabile. Acest lucru permite unui programator să utilizeze valoarea unei variabile ca nume a unei funcții, astfel încât să se obțină ceva de genul:

function foo () echo "Aceasta este foo";  bara de funcții ($ param) echo "Aceasta este bara de spus: $ param";  $ function = 'foo'; funcția $ (); // Intră în foo () $ function = 'bar'; Funcția $ ( 'test'); // Intră în bar ()

Acest lucru poate să nu pară important la prima vedere. Dar gândiți-vă la ceea ce putem realiza cu un instrument atât de puternic. Putem trimite o variabilă ca parametru unei funcții și apoi lăsăm această funcție să se apeleze la cealaltă, referită de valoarea parametrului. Acest lucru este uimitor. Ne permite să modificăm funcționalitatea unei funcții fără să o cunoaștem. Fără funcția observând nici o diferență.

De fapt, putem face apeluri polimorfe și cu această tehnică.

Acum, în loc să vă gândiți la ce indicatori vă oferă funcțiile, gândiți-vă la modul în care funcționează. Nu sunt doar declarații ascunse "go-to"? De fapt, ele sunt, sau cel puțin ele sunt foarte asemănătoare cu cele indirecte "go-to". Ceea ce nu este foarte bun. Ceea ce avem aici este, de fapt, un mod inteligent de a face "go-to" fără a folosi direct. Trebuie să recunosc că în PHP, așa cum arată exemplul de mai sus, este destul de ușor de înțeles, dar poate deveni confuz cu proiecte mai mari și cu multe funcții diferite care au trecut de la o funcție la alta. În C, este și mai obscur și mai greu de înțeles.

Cu toate acestea, doar luarea departe indicii la funcții nu este suficient. Programarea orientată pe obiecte trebuie să ofere un înlocuitor, și într-un mod elegant. Oferă polimorfism cu o sintaxă ușoară. Și cu polimorfismul, vine cea mai mare valoare oferită de programarea oferită de obiecte: fluxul de control se opune dependenței de codul sursă.


În imaginea de mai sus am ilustrat un exemplu simplu al modului în care apelurile polimorfe se întâmplă în cele două paradigme diferite. În programarea procedurală sau structurală, fluxul de control este similar cu dependența de cod sursă. Ambele indică o implementare mai concretă a comportamentului de tipărire.

În programarea orientată pe obiecte, putem inversa dependența de codul sursă și putem să ne îndreptăm spre o implementare mai abstractă, menținând în același timp fluxul de control care indică implementarea mai concretă. Acest lucru este esențial, pentru că vrem ca controlul nostru să meargă și să atingă cea mai mare parte posibilă și volatilă a codului nostru, astfel încât să putem obține rezultatul exact așa cum îl dorim, dar în codul sursă ne dorim contrariul exact. În codul nostru sursă dorim ca lucrurile concrete și volatile să nu se afle în cale, să fie ușor de schimbat și să afecteze cât mai puțin posibil restul codului nostru. Lăsați părțile volatile să se schimbe frecvent, dar să păstreze părțile mai abstracte nemodificate. Puteți citi mai multe despre principiul inversării dependenței în lucrarea de cercetare originală scrisă de Robert C. Martin.


Sarcina la mână

În acest capitol vom crea o aplicație simplă pentru a lista calendarele Google și evenimentele din cadrul acestora. Mai întâi vom lua o abordare procedurală, folosind doar funcții simple și evitând orice fel de clase sau obiecte. Aplicația vă va permite să vă listați calendarele și evenimentele Google. Apoi, vom trece problema cu un pas mai departe prin păstrarea codului nostru procedural și începerea de a ne organiza prin comportament. În cele din urmă o vom transforma într-o versiune orientată pe obiecte.


PHP Google Client API

Google oferă un client API pentru PHP. Îl vom folosi pentru a vă conecta la contul Google, astfel încât să putem manipula calendarele acolo. Dacă doriți să executați codul, trebuie să vă configurați contul Google pentru a accepta interogările din calendar.

Chiar dacă aceasta este o cerință pentru tutorial, nu este subiectul său principal. Deci, în loc să repet pașii pe care trebuie să-i luați, vă voi îndruma către documentația potrivită. Nu vă faceți griji, este foarte simplu de configurat și durează doar aproximativ cinci minute.

Codul client PHP API al PHP este inclus în fiecare proiect din exemplul de cod atașat la acest tutorial. Vă recomandăm să o utilizați. Alternativ, dacă sunteți curios cum să vă instalați singur, verificați documentația oficială.

Apoi urmați instrucțiunile și completați informațiile din apiAccess.php fişier. Acest fișier va fi solicitat atât de exemplele procedurale, cât și de cele orientate pe obiecte, deci nu trebuie să le repetați. Mi-am lăsat cheile acolo, pentru a putea identifica mai ușor și pentru a le umple.

Dacă întâmpinați utilizarea NetBeans, am lăsat fișierele de proiect în dosarele care conțin diferitele exemple. În acest fel puteți să deschideți proiectele și să le executați imediat pe un server PHP local (este necesar PHP 5.4) prin selectarea pur și simplu Run / Run Project.

Biblioteca clientului care se conectează la API-ul Google este orientată spre obiect. De dragul exemplului nostru funcțional, am scris un set mic de funcții care le înfășoară, funcționalitățile de care avem nevoie. În acest fel, putem folosi un strat procedural scris peste biblioteca client orientată pe obiecte, astfel încât codul nostru să nu trebuiască să utilizeze obiecte.

Dacă doriți să testați rapid faptul că codul și conexiunea la API-ul Google funcționează, utilizați codul de mai jos ca fiind dvs. index.php fişier. Ar trebui să listați toate calendarele pe care le aveți în cont. Ar trebui să existe cel puțin un calendar cu rezumat câmpul fiind numele dvs. Dacă aveți un calendar cu zilele de naștere ale persoanei dvs. de contact, este posibil ca acesta să nu funcționeze cu acest API Google, dar nu vă faceți griji, alegeți altul.

requ_once './google-api-php-client/src/Google_Client.php'; requ_once './google-api-php-client/src/contrib/Google_CalendarService.php'; requ_once __DIR__. '/ ... /apiAccess.php'; require_once './functins_google_api.php'; require_once './functions.php'; session_start (); $ client = createClient (); dacă (! autentify ($ client)) returnează; listAllCalendars ($ client);

Acest index.php fișierul va fi punctul de intrare la cererea noastră. Nu vom folosi un cadru web sau ceva fantezist. Vom trimite doar pur și simplu un cod HTML.


O abordare procedurală directă

Acum, că știm ce construim și ce vom folosi, continuați și descărcați codul sursă atașat. Voi oferi fragmente din el, dar pentru a vedea totul, veți dori să aveți acces la sursa originală.

Pentru această abordare, vrem doar să facem lucrurile să funcționeze. Codul nostru va fi organizat într-un mod foarte rudimentar, cu doar câteva fișiere, cum ar fi:

  • index.php - singurul fișier pe care îl accesăm direct din browser și treceți la parametrii GET.
  • functions_google_api.php - învelișul peste API-ul Google despre care am vorbit mai sus.
  • functions.php - unde totul se întâmplă.

functions.php va găzdui tot ceea ce face cererea noastră. Atât logica de rutare, cât și prezentările și orice alte valori și comportamente pot fi îngropate acolo. Această aplicație este destul de simplă, principala logică este după cum urmează.


Avem o singură funcție numită doUserAction (), care decide cu un lung if-else declarație, care alte metode de a apela pe baza parametrilor din OBȚINE variabil. Metodele se conectează apoi la calendarul Google utilizând API-ul și imprimă pe ecran ceea ce dorim să solicităm.

functie printCalendarContents ($ client) putTitle ('Acestea sunt evenimentele pentru'.) getCalendar ($ client, $ _GET ['showThisCalendar']) ['summary']. foreach (retrieveEvents ($ client, $ _GET ['showThisCalendar']) ca $ eveniment) print ('
". data ('Y-m-d H: m', strtotime ($ event ['creat']))); putLink ('? showThisEvent ='. htmlentities ($ event ['id']). '& calendarId ='. htmlentities ($ _GET ['showThisCalendar']); imprimare('
„); imprimare('
„);

Acest exemplu este probabil cea mai complicată funcție din codul nostru. Se numește o funcție de ajutor putTitle (), care imprimă doar câteva HTML formatate pentru titlu. Titlul va conține numele pentru calendarul nostru care poate fi obținut prin apelare getCalendar () din functions_google_api.php. Calendarul returnat va fi o matrice care conține a rezumat camp. Asta am urmat.

$ client variabilă este trecut peste tot în toate funcțiile noastre. Este necesar să vă conectați la API-ul Google. Vom face acest lucru mai târziu.

În continuare, vom trece peste toate evenimentele din calendarul curent. Această listă de matrice este obținută prin rularea apelului API încapsulat în retrieveEvents (). Pentru fiecare eveniment, tipărim data la care a fost creată și apoi titlul acesteia.


Restul codului este similar cu ceea ce am discutat deja și chiar mai ușor de înțeles. Simțiți-vă liber să jucați cu el înainte de a continua în secțiunea următoare.


Organizarea Codului de Procedură

Codul nostru actual este OK, dar cred că putem face mai bine și îl putem organiza într-un mod mai adecvat. Puteți găsi proiectul cu codul organizat finalizat sub numele "GoogleCalProceduralOrganized" în codul sursă atașat.

Utilizarea unei variabile globale a clientului

Primul lucru care mă deranjează în legătură cu codul nostru neorganizat este că noi trecem prin asta $ client variabilă ca un argument peste tot, mai multe niveluri adânci în cadrul funcțiilor imbricate. Programarea procedurală are un mod inteligent de a rezolva această problemă, o variabilă globală. De cand $ client este definit în index.php și în domeniul global, tot ce trebuie să schimbăm este modul în care funcțiile noastre o folosesc. Deci, în loc să aștepți a $ client parametru, putem folosi:

funcția printCalendars () global $ client; putTitle ('Acestea sunt calendarele tale:'); foreach ($ client) ['items'] ca $ calendar) putLink ('? showThisCalendar ='. htmlentities ($ calendar ['id']), $ calendar ['summary']); imprimare('
„);

Comparați codul curent cu codul nou organizat pentru a vedea diferența. În loc să treacă $ client ca parametru, am folosit client global $ în toate funcțiile noastre și le-a transmis ca parametru numai funcțiilor Google API. Din punct de vedere tehnic, chiar funcțiile Google API ar fi putut folosi $ client variabilă din domeniul global, dar cred că este mai bine să menținem API cât mai independent posibil.

Separarea prezentării de logică

Unele funcții sunt în mod clar, numai pentru a imprima lucrurile pe ecran, altele pentru a decide ce să facă, iar altele sunt puțin de ambele. Când se întâmplă acest lucru, uneori este mai bine să mutați aceste funcții specifice în propriul fișier. Vom începe cu funcțiile utilizate exclusiv pentru tipărirea lucrurilor pe ecran, acestea vor fi mutate în a functions_display.php fişier. Vedeți-le mai jos.

funcția printHome () print ('Bine ați venit în Google Calendar prin Exemplu NetTuts');  funcția printMenu () putLink ('? home', 'Home'); putLink ('? showCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); imprimare('

„); funcția putLink ($ href, $ text) print (sprintf ('% s' ', $ href, $ text)); funcția putTitle ($ text) print (sprintf ('

% s

', $ text)); funcția putBlock ($ text) print ('
'$ Text.'
„);

Restul acestui proces de separare a prezentării noastre de logică ne obligă să extragem partea de prezentare din metodele noastre. Iată cum am făcut-o cu una dintre metode.

funcția printEventDetails () global $ client; foreach ($ _GET ['calendarId']) ca $ eveniment) dacă $ event ['id'] == $ _GET ['showThisEvent']) putTitle „]); putBlock ('Acest eveniment are statut'. $ event ['status']); A fost creat la data ('Ymd H: m', strtotime ($ event ['creat'])) 'și actualizată ultima dată la' ('Ymd H: m', strtotime ['la curent'])) . '.'); putBlock ("Pentru acest eveniment trebuie să ". $ event ['rezumat']. '.„); 

În mod clar putem vedea că orice este în interiorul dacă declarația este doar codul de prezentare, iar restul este logica de afaceri. În locul unei funcții voluminoase care manipulează totul, o vom rupe în mai multe funcții:

funcția printEventDetails () global $ client; foreware (retrieveEvents ($ _GET ['calendarId']) ca $ event) dacă (isCurrentEvent ($ event)) putEvent ($ event);  funcția isCurrentEvent ($ event) return $ event ['id'] == $ _GET ['showThisEvent']; 

După separare, logica de afaceri este acum foarte simplă. Am extras chiar și o mică metodă pentru a determina dacă evenimentul este unul curent. Tot codul de prezentare este acum responsabilitatea unei funcții numite putEvent ($ eveniment) care se află în functions_display.php fişier:

funcția putEvent ($ event) putTitle ('Detalii pentru eveniment:'. $ event ['summary']); putBlock ('Acest eveniment are statut'. $ event ['status']); A fost creat la data ('Ymd H: m', strtotime ($ event ['creat'])) 'și actualizată ultima dată la' ('Ymd H: m', strtotime ['la curent'])) . '.'); putBlock ("Pentru acest eveniment trebuie să ". $ event ['rezumat']. '.„); 

Chiar dacă această metodă afișează doar informații, trebuie să ținem cont de faptul că depinde de cunoașterea intimă a structurii $ eveniment. Dar, acum este OK. În ceea ce privește restul metodelor, ele au fost separate într-o manieră similară.

Eliminarea declarațiilor Long if-else

Ultimul lucru care mă deranjează în legătură cu codul nostru actual este declarația lungă dacă-alta în cadrul nostru doUserAction () funcție, care este folosit pentru a decide ce să facă pentru fiecare acțiune. Acum, PHP este destul de flexibil atunci când vine vorba de meta-programare (apelând funcții prin referință). Acest truc ne permite sa corelam numele functiilor cu $ _GET valorile variabilelor. Deci, putem introduce un single acțiune parametru în $ _GET variabilă și folosiți valoarea din aceasta ca nume de funcție.

funcția doUserAction () putMenu (); dacă (! isset ($ _GET ['action'])) retur; $ _GET [ 'action'] (); 

Pe baza acestei abordări, meniul nostru va fi generat astfel:

funcția putMenu () putLink ('? action = putHome', 'Home'); putLink ('? action = printCalendars', 'Show Calendars'); putLink ('? logout', 'Log Out'); imprimare('

„);

După cum probabil veți vedea, această reorganizare ne-a împins deja spre un design orientat spre obiecte. Nu este clar ce fel de obiecte avem și cu ce comportament exact, dar avem niște indicii aici și acolo.

Avem prezentări care depind de tipurile de date din logica de afaceri. Aceasta seamănă cu inversarea dependenței despre care am vorbit în capitolul introductiv. Fluxul de control este încă de la logica de afaceri spre prezentare, dar dependența de cod sursă a început să se transforme într-o dependență inversată. Aș spune că în acest moment este mai mult o dependență bidirecțională.

Un alt indiciu al unui design orientat pe obiecte este micul program de meta-programare pe care tocmai l-am făcut. Noi numim o metodă despre care nu știm nimic. Poate fi orice și este ca și când avem de-a face cu un nivel scăzut de polimorfism.

Analiza dependenței

Pentru codul nostru curent am putea desena o schemă, ca cea de mai jos, pentru a ilustra primii câțiva pași prin intermediul aplicației noastre. Desenarea tuturor liniilor ar fi fost prea complicată.


Am marcat cu linii albastre, apelurile de procedura. După cum puteți vedea, curg în aceeași direcție ca înainte. În plus, avem linii verzi care marchează apeluri indirecte. Acestea trec prin toate doUserAction (). Aceste două tipuri de linii reprezintă fluxul de control și puteți observa că acesta este în esență neschimbat.

Cu toate acestea, liniile roșii introduc un concept diferit. Acestea reprezintă o dependență rudimentară de cod sursă. Vreau să spun rudimentar, pentru că nu este așa de evident. putMenu () metoda include numele funcțiilor care trebuie să fie solicitate pentru acea legătură particulară. Aceasta este o dependență și aceeași regulă se aplică tuturor celorlalte metode care creează legături. ei depinde privind comportamentul celorlalte funcții.

Un al doilea tip de dependență poate fi de asemenea văzut aici. Dependența de date. Am menționat anterior calendar $ și $ eveniment. Funcțiile de tipărire trebuie să aibă cunoștințe intime despre structura internă a acestor rețele pentru a-și face treaba.

Deci, după toate acestea, cred că avem multe motive să ne îndreptăm spre ultimul nostru pas.


O soluție orientată pe obiecte

Indiferent de paradigma utilizată, nu există o soluție perfectă pentru o problemă. Deci, iată cum propun să organizăm codul nostru într-un mod orientat pe obiecte.

Primul instinct

Am început deja să ne separăm preocupările în logica și prezentarea afacerii. Ne-am prezentat chiar și pe noi doUserAction () ca entitate separată. Deci, primul meu instinct este să creez trei clase Prezentator, Logică, și Router. Acestea se vor schimba mai târziu, dar avem nevoie de un loc pentru a începe, corect?

Router va conține o singură metodă și va rămâne destul de similar cu implementarea anterioară.

Router clasa function doUserAction () (prezentator nou ()) -> putMenu (); dacă (! isset ($ _GET ['action'])) retur; (noua logică ()) -> $ _ GET ['action'] (); 

Deci, acum trebuie să ne sunăm în mod explicit putMenu () folosind o metodă nouă Prezentator obiect și restul acțiunilor vor fi chemați folosind a Logică obiect. Cu toate acestea, aceasta provoacă imediat o problemă. Avem o acțiune care nu este în clasa Logic. putHome () este în clasa Presenter. Trebuie să introducem o acțiune în Logică care vor fi delegate la prezentatori putHome () metodă. Amintiți-vă, pentru moment nu dorim decât să înfășurăm codul existent în cele trei clase pe care le-am identificat drept posibili candidați pentru un design OO. Vrem să facem doar ceea ce este absolut necesar pentru a face munca de proiectare. După ce avem codul de lucru, îl vom schimba mai departe.

De îndată ce punem a putHome () în clasa Logic avem o dilemă. Cum de a apela metode de la Prezentator? Putem crea și transmite un obiect de prezentator în Logică, astfel încât acesta să aibă întotdeauna o referință la prezentare. Să facem asta de la Routerul nostru.

Router clasa function doUserAction () (prezentator nou ()) -> putMenu (); dacă (! isset ($ _GET ['action'])) retur; (noul Logic (nou prezentator)) -> $ _ GET ['action'] (); 

Acum putem adăuga un constructor în Logic și adăugăm în delegație spre putHome () în Prezentator.

class Logic privat $ prezentator; funcția __construct (prezentator $ prezentator) $ this-> presenter = $ presenter;  funcția putHome () $ this-> prezentator-> putHome ();  [...]

Cu câteva ajustări minore în index.php și având rolul prezentatorului de a împacheta metodele vechi de afișare, de înfășurare logică a funcțiilor vechi de logică de afaceri și de routerul care înfășoară selectorul de acțiune vechi, putem executa de fapt codul nostru și avem elementul de meniu "Acasă".

requ_once './google-api-php-client/src/Google_Client.php'; requ_once './google-api-php-client/src/contrib/Google_CalendarService.php'; requ_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 (); dacă (! autentify ($ client)) returnează; (nou Router ()) -> doUserAction ();

Și aici este în acțiune.


În continuare, în clasa noastră Logic, trebuie să schimbăm corect apelurile pentru afișarea logicii, pentru a lucra cu $ This-> prezentator. Apoi avem două metode - isCurrentEvent () și retrieveEvents () - care sunt utilizate numai în clasa Logic. Le vom face private și vom schimba apelurile în consecință.

Apoi vom lua aceeași abordare cu clasa Presenter. Vom schimba toate apelurile la metode pentru a indica $ This-> ceva si fa putTitle (), putLink (), și putBlock () privat, deoarece acestea sunt utilizate numai de la Prezentator. Verificați codul din GoogleCalObjectOrientedInitial directorul din codul sursă atașat dacă aveți dificultăți în a face toate aceste modificări de unul singur.

În acest moment avem o aplicație de lucru. Este mai mult cod procedural înfășurat în sintaxa OO, care încă folosește $ client variabilă globală și are multe alte mirosuri orientate spre obiecte, dar funcționează.

Dacă desenați diagrama de clase cu dependențe pentru acest cod, acesta va arăta astfel:>


Atât dependența de control al debitului, cât și dependența de codul sursă trece prin router, apoi prin logică și, în final, prin prezentare. Această ultimă schimbare ne-a scăpat de fapt o mică parte din inversiunea de dependență pe care am observat-o în etapa anterioară. Dar nu te lăsa păcălit. Principiul este acolo, trebuie doar să facem acest lucru evident.

Revenirea dependenței de cod sursă

Este greu de spus că un principiu SOLID este mai important decât altul, dar cred că principiul inversării dependenței are cel mai mare impact imediat asupra designului. Acest principiu prevede:

A: Modulele de nivel înalt nu ar trebui să depindă de modulele de nivel scăzut. Ambele ar trebui să depindă de abstracții și B: Abstracțiile nu ar trebui să depindă de detalii. Detaliile ar trebui să depindă de abstracții.

Pentru a spune pur și simplu, aceasta înseamnă că implementările concrete ar trebui să depindă de clase abstracte. Pe măsură ce clasele tale devin abstracte, cu atât mai puțin tind să se schimbe. Deci, puteți percepe problema deoarece: schimbarea frecventă a claselor ar trebui să depindă de alte clase mult mai stabile. Deci, partea cea mai volatilă a oricărei aplicații este, probabil, interfața sa de utilizator, care ar fi clasa Presenter în aplicația noastră. Să facem această inversare a dependenței evidentă.

Mai întâi vom face routerul nostru să folosească doar prezentatorul și să-i rupă dependența de Logic.

Router clasa function doUserAction () (prezentator nou ()) -> putMenu (); dacă (! isset ($ _GET ['action'])) retur; (nou prezentator ()) -> $ _ GET ['action'] (); 

Apoi vom schimba prezentatorul pentru a folosi o instanță a aplicației Logic și pentru ai cere informațiile necesare. În cazul nostru, consider că este acceptabil ca prezentatorul să creeze instanța logicii, dar în orice sistem de producție, probabil veți avea fabrici care creează obiecte logice de afaceri și le vor injecta în stratul de prezentare.

Acum, funcția putHome (), prezent în clasele Logic și Presenter, vor dispărea de la Logic. Acesta este un semn bun, deoarece eliminăm duplicarea. Constructorul și referința la Prezentator dispare și din Logic. Pe de altă parte, un constructor care creează un obiect Logic trebuie scris pe Prezentator.

prezentator de clasă private $ businessLogic; funcția __construct () $ this-> businessLogic = logica nouă ();  funcția putHome () print ('Bine ați venit în Google Calendar prin exemplul NetTuts');  [...]

După aceste modificări, faceți clic pe Afișați calendarele va produce totuși o eroare frumoasă. Deoarece toate acțiunile noastre din cadrul legăturilor indică numele funcțiilor din clasa Logic, va trebui să facem unele schimbări mai consecvente pentru a inversa dependența dintre cele două. Să luăm o metodă odată. Primul mesaj de eroare spune:

Eroare fatală: Apel la metoda nedefinită Presenter :: printCalendars () în / [...] /GoogleCalObjectOrientedFinal/Router.php pe linia 9

Deci, Routerul nostru dorește să numească o metodă care nu există pe prezentator, printCalendars (). Să creăm acea metodă în Prezentator și să verificăm ce a făcut în Logic. A tipărit un titlu, apoi a trecut prin calendare și a sunat putCalendar (). În Prezentator printCalendars () metoda va arata astfel:

funcția printCalendare () $ this-> putCalendarListTitle (); ($ this-> businessLogic-> getCalendars () ca $ calendar) $ this-> putCalendarListElement ($ calendar); 

Pe de altă parte, în logică, metoda devine destul de anemică. Doar un apel către biblioteca Google API.

funcția getCalendars () global $ client; returnați getCalendarList ($ client) ['items']; 

Acest lucru vă poate face să vă puneți două întrebări: "Avem de fapt nevoie de o clasă de logică?" și "Are aplicația noastră nici o logică?". Ei bine, încă nu știm. Deocamdată, vom continua procesul de mai sus, până când tot codul va funcționa, iar Logicul nu mai depinde de prezentator.

Deci, vom folosi a printCalendarContents () în Prezentator, cum este cea de mai jos:

funcția printCalendarContents () $ this-> putCalendarTitle (); foreach ($ this-> businessLogic-> getEventsForCalendar () ca $ eveniment) $ this-> putEventListElement ($ event); 

Care, la rândul său, ne va permite să simplificăm getEventsForCalendar () în Logică, în ceva de genul asta.

funcția getEventsForCalendar () global $ client; returnează getEventList ($ client, htmlspecialchars ($ _GET ['showThisCalendar'])) ['elemente']; 

Acum funcționează, dar mă îngrijorează aici. $ _GET variabila este utilizată în clasele Logic și Presenter. Nu ar trebui să se folosească numai clasa Presenter $ _GET? Adică, Presenterul trebuie să știe $ _GET deoarece trebuie să creeze legături care să ocupe acest lucru $ _GET variabil. Așa ar însemna asta $ _GET este strict legat de HTTP. Acum, dorim ca codul nostru să funcționeze cu un interfață grafică CLI sau desktop. Așadar, dorim să păstrăm această cunoaștere numai în prezentator. Aceasta face ca cele doua metode de mai sus sa se transforme in cele de mai jos.

funcția getEventsForCalendar ($ calendarId) global $ client; returna getEventList ($ client, $ calendarId) ['items']; 
funcția printCalendarContents () $ this-> putCalendarTitle (); $ eventsForCalendar = $ this-> afaceriLogic-> getEventsForCalendar (htmlspecialchars ($ _ GET ['showThisCalendar'])); foreach ($ evenimenteForCalendar ca $ eveniment) $ this-> putEventListElement ($ event); 

Acum, ultima funcție pe care trebuie să o rezolvăm este de a tipări un anumit eveniment. De dragul acestui exemplu, să presupunem că nu există niciun mod în care să putem recupera direct un eveniment și trebuie să îl găsim prin noi înșine. Acum, clasa noastră Logic vine la îndemână. Este un loc perfect pentru a manipula liste de evenimente și a căuta un ID specific:

funcția getEventById ($ eventId, $ calendarId) foreach ($ this-> getEventsForCalendar ($ calendarId) ca eveniment $) dacă ($ event ['id'] == $ eventId) 

Apoi, apelul corespunzător pe Prezentator se va ocupa de tipărirea acestuia:

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

Asta e. Iată-ne. Dependența este inversată!


Controlul continuă să curgă de la Logic la Prezentator. Conținutul prezentat este definit în întregime de Logic. Dacă, de exemplu, mâine, ne dorim să ne conectăm la un alt serviciu de calendar, putem crea o altă Logică, o vom injecta în prezentator, iar prezentatorul nu va observa nicio diferență. De asemenea, dependența de codul sursă a fost inversată cu succes. Prezentatorul este singurul care creează și depinde direct de Logic. Această dependență este crucială pentru a permite prezentatorului să schimbe modul în care afișează datele, fără a face nimic în logică. În plus, ne permite să ne schimbăm Prezentatorul HTML cu un Prezentator CLI sau orice altă metodă de afișare a informațiilor către utilizator.

Scapa de variabila globală

Probabil ultimul defect de design grav rămas este utilizarea unei variabile globale pentru $ client. Toate codurile din aplicația noastră au acces la ele. Miraculos, singura clasă care are de fapt nevoie $ client este clasa noastră de logică. Pasul evident este de a face o variabilă de clasă privată. Dar acest lucru ne cere să ne propagăm $ client prin Router, în Prezentator, astfel încât să poată crea un obiect Logic cu $ client variabil. Acest lucru rezolvă puțin din problemele noastre. Trebuie să construim clasele noastre într-un loc izolat și să injectăm în mod corespunzător dependențele unul în celălalt.

Pentru orice structură de clasă mai mare am fi folosit fabrici, dar pentru exemplul nostru mic, index.php fișierul va fi un loc minunat pentru a ține logica creării. Și fiind punctul de intrare în aplicația noastră, alcătuită din fișierul "principal" din schema de arhitectură de nivel înalt, aceasta este încă în afara limitelor logicii noastre de afaceri.


Deci ne schimbăm index.php în codul de mai jos, păstrând toate comenzile include și comanda session_start ():

$ client = createClient (); dacă


Cod