Modelul de design al depozitului

Modelul de design al depozitului, definit de Eric Evens în cartea sa de proiectare pe domenii, este unul dintre cele mai utile și mai aplicabile modele de design inventate vreodată. Orice aplicație trebuie să funcționeze cu persistență și cu un fel de listă de articole. Acestea pot fi utilizatori, produse, rețele, discuri sau orice altă aplicație. Dacă aveți un blog, de exemplu, trebuie să vă ocupați de liste de postări pe blog și de liste de comentarii. Problema pe care toate aceste logici de gestionare a listelor le are în comun este cum să conectați logica de afaceri, fabricile și persistența.


Modelul fabricii de design

Așa cum am menționat în paragraful introductiv, un depozit va conecta fabricile cu gateway-urile (persistența). Acestea sunt, de asemenea, modele de design și, dacă nu sunteți familiarizați cu acestea, acest paragraf va arunca o lumină asupra subiectului.

O fabrică este un model simplu de design care definește o modalitate convenabilă de a crea obiecte. Este o clasă sau un set de clase responsabile pentru crearea obiectelor care au nevoie de logica noastră de afaceri. O fabrică are în mod tradițional o metodă numită "face()" și va ști cum să preia toate informațiile necesare pentru a construi un obiect și pentru a face obiectul construit în sine și pentru a returna un obiect gata de utilizare în logica de afaceri.

Iată un pic mai mult pe modelul fabricii într-un tutorial mai vechi Nettuts +: Ghidul începătorului pentru modelele de design. Dacă preferați o vedere mai profundă asupra modelului de fabrică, verificați primul model de design din cursul Agile Design Patterns pe care îl avem pe Tuts+.


Modelul gateway-ului

De asemenea, cunoscut sub numele de "Table Data Gateway" este un model simplu care oferă legătura între logica de afaceri și baza de date în sine. Principala sa responsabilitate este de a face interogări în baza de date și de a furniza datele preluate într-o structură de date tipică limbajului de programare (cum ar fi un array în PHP). Aceste date sunt de obicei filtrate și modificate în codul PHP, astfel încât să putem obține informațiile și variabilele necesare pentru a ne desena obiectele. Aceste informații trebuie apoi transmise fabricilor.

Modelul de design Gateway este explicat și exemplificat în detalii foarte detaliate într-un tutorial Nettuts + despre evoluția spre un strat de persistență. De asemenea, în același curs Agile Design Patterns, a doua lecție de model de design este despre acest subiect.


Problemele pe care trebuie să le rezolvăm

Dublarea prin manipularea datelor

Este posibil să nu fie evident la prima vedere, dar conectarea Gateways to Factories poate duce la o mulțime de duplicări. Orice software de dimensiuni considerabile trebuie să creeze aceleași obiecte din diferite locuri. În fiecare loc va trebui să utilizați Gateway pentru a prelua un set de date brute, pentru a filtra și a lucra ca datele să fie gata de a fi trimise către fabrici. Din toate aceste locuri, veți numi aceleași fabrici cu aceleași structuri de date, dar, evident, cu date diferite. Obiectele dvs. vor fi create și vă vor fi furnizate de fabrici. Acest lucru va conduce în mod inevitabil la o mulțime de duplicări la timp. Duplicarea va fi răspândită în cadrul unor clase sau module îndepărtate și va fi dificil de observat și de rezolvat.

Duplicarea prin reimplementarea logică a prelucrării datelor

O altă problemă este să exprimăm întrebările pe care trebuie să le facem cu ajutorul portalurilor. De fiecare dată când avem nevoie de informații de la Gateway, trebuie să ne gândim la ce avem nevoie exact? Avem nevoie de toate datele despre un singur subiect? Avem nevoie doar de informații specifice? Vrem să preluăm un anumit grup din baza de date și să facem filtrarea sau sortarea în limba noastră de programare? Toate aceste întrebări trebuie abordate de fiecare dată când preluăm informații din stratul de persistență prin intermediul portalului nostru. De fiecare dată când facem acest lucru, va trebui să găsim o soluție. În timp, pe măsură ce cererea noastră crește, vom fi confruntați cu aceleași dileme în diferite locuri ale aplicației noastre. Din neatenție vom veni cu soluții puțin diferite la aceleași probleme. Acest lucru nu numai că necesită timp și efort suplimentar, ci duce și la o duplicare subtilă, foarte dificil de recunoscut. Acesta este cel mai periculos tip de duplicare.

Duplicarea prin reimplementarea logicii persistenței datelor

În cele două paragrafe anterioare am vorbit numai despre recuperarea datelor. Dar Gateway-ul este bidirecțional. Logica noastră de afaceri este bidirecțională. Trebuie să persistăm cumva obiectele noastre. Acest lucru conduce din nou la o mulțime de repetări dacă vrem să implementăm această logică după cum este necesar în cadrul diferitelor module și clase ale aplicației noastre.


Conceptele principale

Depozit pentru recuperarea datelor

Un depozit poate funcționa în două moduri: recuperarea datelor și persistența datelor.


Atunci când se utilizează pentru a prelua obiectele din persistență, se va apela un Depozit cu o interogare personalizată. Această interogare poate fi o metodă specifică prin nume sau o metodă mai obișnuită cu parametri. Depozitul este responsabil să furnizeze și să implementeze aceste metode de interogare. Atunci când se cheamă o astfel de metodă, Depozitul va contacta Poarta de acces pentru a prelua datele brute din persistență. Gateway-ul va furniza date obiect brute (ca un tablou cu valori). Apoi depozitarul va lua aceste date, va efectua transformările necesare și va apela metodele corespunzătoare din fabrică. Fabrica va furniza obiectele construite cu datele furnizate de Depozit. Depozitul va colecta aceste obiecte și le va returna ca un set de obiecte (cum ar fi o serie de obiecte sau un obiect de colectare, așa cum este definit în lecția de model compozit în cursul Agile Design Patterns).

Depozit pentru persistența datelor

Cel de-al doilea mod în care poate funcționa un depozit este de a furniza logica necesară pentru a extrage informațiile dintr-un obiect și a le persista. Acest lucru poate fi la fel de simplu ca serializarea obiectului și trimiterea datelor serializate către Poarta de acces pentru al persista sau la fel de sofisticat ca crearea de tabele de informații cu toate câmpurile și starea unui obiect.


Atunci când se utilizează pentru a persista informații, clasa client este cea care comunică direct cu Fabrica. Imaginați-vă un scenariu atunci când un comentariu nou este postat pe o postare pe blog. Obiectul "Obiect" este creat de logica noastră de afaceri (clasa Client) și apoi trimis în Depozit pentru a fi persistent. Depozitarul va persista obiectele folosind Gateway-ul și le va cachea opțional într-o listă locală din memorie. Datele trebuie transformate deoarece există doar cazuri rare în care obiectele reale pot fi salvate direct într-un sistem de persistență.


Unind punctele

Imaginea de mai jos reprezintă o vizualizare la nivel mai înalt privind modul de integrare a depozitului între fabrici, gateway și client.


În centrul schemei este depozitul nostru. În stânga, este o interfață pentru poartă, o implementare și persistența în sine. În dreapta, există o interfață pentru fabrici și o implementare a fabricii. În cele din urmă, în partea de sus este clasa client.

Așa cum se poate observa din direcția săgeților, dependențele sunt inversate. Depozitul depinde numai de interfețele abstracte pentru fabricile și gateway-urile. Gateway-ul depinde de interfața sa și de persistența pe care o oferă. Fabrica depinde doar de interfața sa. Clientul depinde de Depozit, ceea ce este acceptabil deoarece depozitul tinde să fie mai puțin concret decât Clientul.


În perspectivă, paragraful de mai sus respectă arhitectura noastră de nivel înalt și direcția dependențelor pe care vrem să o realizăm.


Gestionarea comentariilor la mesajele de blog cu un depozit

Acum, că am văzut teoria, este timpul pentru un exemplu practic. Imaginați-vă că avem un blog în care avem obiecte Post și obiecte Comentariu. Comentariile aparțin postărilor și trebuie să găsim o cale de a le persista și de a le recupera.

Comentariul

Vom începe cu un test care ne va forța să ne gândim la ceea ce ar trebui să conțină obiectul Comentariu.

classRepositoryTest extinde PHPUnit_Framework_TestCase function testACommentHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe are o opinie despre modelul depozitului"; $ commentBody = "Cred că este o idee bună să folosiți modelul de depozitare pentru a persista și a prelua obiecte."; $ comment = Comentariu nou ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); 

La prima vedere, un comentariu va fi doar un obiect de date. Este posibil să nu aibă nicio funcționalitate, însă este în contextul cererii noastre de a decide. Pentru acest exemplu, presupune doar că este un obiect de date simplu. Construit cu un set de variabile.

clasă Comentariu 

Doar prin crearea unei clase goale și necesitând-o în test o face să treacă.

requ_once '... /Comment.php'; classRepositoryTest extinde PHPUnit_Framework_TestCase [...]

Dar asta e departe de a fi perfect. Testul nostru nu testează nimic încă. Să ne obligăm să scriem toți cei care primesc clasa Comentariu.

funcția testACommentsHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe are o opinie despre modelul depozitului"; $ commentBody = "Cred că este o idee bună să folosiți modelul de depozitare pentru a persista și a prelua obiecte."; $ comment = Comentariu nou ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); $ acest-> assertEquals ($ postId, $ comment-> getPostId ()); $ acest-> assertEquals ($ commentAuthor, $ comment-> getAuthor ()); $ acest-> assertEquals ($ commentAuthorEmail, $ comment-> getAuthorEmail ()); $ acest-> assertEquals ($ commentSubject, $ comment-> getSubject ()); $ this-> assertEquals ($ comentariuBody, $ comment-> getBody ()); 

Pentru a controla durata tutorialului, am scris imediat toate afirmațiile și le vom implementa imediat. În viața reală, luați-le unul câte unul.

 clasa Comentariu private $ postId; autor privat $; privat $ autorEmail; subiect privat $; corp privat; funcția __construct ($ postId, $ autor, $ autorEmail, $ subiect, $ corp) $ this-> postId = $ postId; $ this-> author = $ autor; $ this-> authorEmail = $ autorEmail; $ this-> subject = $ subject; $ this-> body = $ body;  funcția publică getPostId () return $ this-> postId;  funcția publică getAuthor () return $ this-> author;  funcția publică getAuthorEmail () return $ this-> authorEmail;  funcția publică getSubject () return $ this-> subject;  funcția publică getBody () return $ this-> body; 

Cu excepția listei de variabile private, restul codului a fost generat de IDE-ul meu, NetBeans, astfel încât testarea codului generat automat poate fi un pic de cheltuieli de câteva ori. Dacă nu scrieți aceste linii de unul singur, simțiți-le liber să le faceți direct și să nu vă faceți griji cu testele pentru setteri și constructori. Cu toate acestea, testul ne-a ajutat să ne expunem mai bine ideile și să documentăm mai bine ce va conține clasa Comentariu.

De asemenea, putem considera aceste metode de testare și clase de testare ca fiind clasele noastre "Client" din schemele.


Portalul nostru către persistență

Pentru a păstra acest exemplu cât mai simplu posibil, vom implementa doar o InMemoryPersistence astfel încât să nu complicăm existența noastră cu sisteme de fișiere sau baze de date.

requ_once '... /InMemoryPersistence.php'; clasa InMemoryPersistenceTest extinde PHPUnit_Framework_TestCase function testItCanPerisistAndRetrieveASingleDataArray () $ data = array ('data'); $ persistence = nouInMemoryPersistence (); $ Persistence-> persistă (date $); $ this-> assertEquals ($ date, $ persistență-> retrieve (0)); 

Ca de obicei, începem cu cel mai simplu test care ar putea eșua și, de asemenea, ne obligă să scriem un cod. Acest test creează un nou test InMemoryPersistence obiect și încearcă să persiste și să recupereze o matrice numită date.

requ_once __DIR__. '/Persistence.php'; clasa InMemoryPersistence implementează Persistența private $ data = array (); funcția persistă ($ date) $ this-> data = $ data;  recuperare funcție ($ id) return $ this-> data; 

Cel mai simplu cod pentru a trece este doar pentru a păstra intrarea $ date într - o variabilă privată și returnați - o în recupera metodă. Codul, așa cum se întâmplă acum, nu-i pasă de trimiterea $ id variabil. Este cel mai simplu lucru care ar putea face testul. De asemenea, am luat libertatea de a introduce și implementa o interfață numită persistență.

Interfața Persistența funcția persistă ($ date); recuperarea funcției ($ ids); 

Această interfață definește cele două metode pe care trebuie să le implementeze Gateway-ul. Persista și recupera. După cum probabil ați ghicit deja, Gateway-ul nostru este al nostru InMemoryPersistence clasa și persistența noastră fizică este variabila privată care ține datele noastre în memorie. Dar să ne întoarcem la punerea în aplicare a acestui lucru în persistența memoriei.

funcția testItCanPerisistSeveralElementsAndRetrieveAnyOfThem () $ data1 = array ('data1'); $ data2 = matrice ('data2'); $ persistence = nouInMemoryPersistence (); $ Persistence-> persistă ($ data1); $ Persistence-> persistă ($ data2); $ this-> assertEquals ($ date1, $ persistență-> retrieve (0)); $ this-> assertEquals ($ date2, $ persistență-> retrieve (1)); 

Am adăugat un alt test. În aceasta persistăm două matrice de date diferite. Ne așteptăm să putem recupera fiecare dintre ei în mod individual.

requ_once __DIR__. '/Persistence.php'; clasa InMemoryPersistence implementează Persistența private $ data = array (); funcția persistă ($ date) $ this-> data [] = $ date;  funcția retrieve ($ id) retur $ this-> data [$ id]; 

Testul ne-a forțat să ne modificăm ușor codul. Acum trebuie să adăugăm date la matricea noastră, nu doar să o înlocuim cu cea trimisă persistă (). Trebuie, de asemenea, să luăm în considerare $ id parametru și returnați elementul la acel index.

Acest lucru este suficient pentru noi InMemoryPersistence. Dacă este necesar, îl putem modifica mai târziu.


Fabrica noastra

Avem un Client (testele noastre), o persistență cu un Gateway și obiectele Comentariu care persistă. Următorul lucru lipsă este Fabrica noastră.

Am început să codificăm cu a RepositoryTest fişier. Acest test, cu toate acestea, a creat de fapt a cometariu obiect. Acum trebuie să creăm teste pentru a verifica dacă Fabrica noastră va fi capabilă să creeze cometariu obiecte. Se pare că am avut o eroare în judecată și testul nostru este mai probabil un test pentru Fabrica noastră viitoare decât pentru Depozitul nostru. Putem să-l mutăm într-un alt fișier de test, CommentFactoryTest.

requ_once '... /Comment.php'; clasa CommentFactoryTest extinde PHPUnit_Framework_TestCase function testACommentsHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe are o opinie despre modelul depozitului"; $ commentBody = "Cred că este o idee bună să folosiți modelul de depozitare pentru a persista și a prelua obiecte."; $ comment = Comentariu nou ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); $ acest-> assertEquals ($ postId, $ comment-> getPostId ()); $ acest-> assertEquals ($ commentAuthor, $ comment-> getAuthor ()); $ acest-> assertEquals ($ commentAuthorEmail, $ comment-> getAuthorEmail ()); $ acest-> assertEquals ($ commentSubject, $ comment-> getSubject ()); $ this-> assertEquals ($ comentariuBody, $ comment-> getBody ()); 

Acum, acest test trece în mod evident. Și, deși este un test corect, ar trebui să luăm în considerare modificarea acestuia. Vrem să creăm a Fabrică obiect, treceți într-o matrice și cereți-i să creeze o cometariu pentru noi.

requ_once '... /CommentFactory.php'; clasa CommentFactoryTest extinde PHPUnit_Framework_TestCase function testACommentsHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe are o opinie despre modelul depozitului"; $ commentBody = "Cred că este o idee bună să folosiți modelul de depozitare pentru a persista și a prelua obiecte."; $ commentData = matrice ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); $ comment = (ComentariuFactory nou ()) -> make ($ commentData); $ acest-> assertEquals ($ postId, $ comment-> getPostId ()); $ acest-> assertEquals ($ commentAuthor, $ comment-> getAuthor ()); $ acest-> assertEquals ($ commentAuthorEmail, $ comment-> getAuthorEmail ()); $ acest-> assertEquals ($ commentSubject, $ comment-> getSubject ()); $ this-> assertEquals ($ comentariuBody, $ comment-> getBody ()); 

Nu ar trebui să ne numim clasele noastre pe baza modelului de proiectare pe care îl pun în aplicare, dar Fabrică și Repertoriu reprezintă mai mult decât modelul de design în sine. Personal nu am nimic împotriva includerii acestor două cuvinte în numele clasei noastre. Cu toate acestea, eu încă recomand cu tărie și respectă conceptul de a nu numim clasele noastre după modelele de design pe care le folosim pentru restul modelelor.

Acest test este doar puțin diferit de cel precedent, dar nu reușește. Încearcă să creeze o CommentFactory obiect. Această clasă încă nu există. Încercăm de asemenea să sunăm a face() metoda pe ea cu o matrice care conține toate informațiile dintr-un comentariu ca un matrice. Această metodă este definită în Fabrică interfață.

interfata Factory function make ($ data); 

Acesta este un lucru foarte comun Fabrică interfață. Acesta a definit singura metodă necesară pentru o fabrică, metoda care creează, de fapt, obiectele pe care le dorim.

requ_once __DIR__. '/Factory.php'; requ_once __DIR__. '/Comment.php'; clasa CommentFactory implementează Factory make make ($ components) returnează noul Comentariu ($ components [0], $ components [1], $ components [2], $ components [3], $ components [4]); 

Și CommentFactory implementează Fabrică interfață cu succes prin luarea componente $ parametru în face() metoda, creează și returnează un nou cometariu obiect cu informațiile de acolo.

Vom păstra logica noastră de persistență și creație obiect cât mai simplă posibil. Putem, pentru acest tutorial, să ignorăm în siguranță orice manipulare a erorilor, validarea și aruncarea excepțiilor. Ne vom opri aici cu implementarea creării persistenței și a obiectului.


Utilizarea unui depozit pentru a persista comentariile

După cum am văzut mai sus, putem folosi un depozit în două moduri. Pentru a prelua informațiile din persistență și, de asemenea, pentru a persista informații despre stratul de persistență. Folosind TDD este, de cele mai multe ori, mai ușor să începeți cu partea de salvare (persistentă) a logicii și apoi să utilizați acea implementare existentă pentru a testa regăsirea datelor.

requ_once '... / ... / ... / vendor/autoload.php'; requ_once '... /CommentRepository.php'; requ_once '... /CommentFactory.php'; class RepositoryTest extinde PHPUnit_Framework_TestCase funcția protejată tearDown () \ Mockery :: close ();  funcția testItCallsThePersistenceWhenAddingAComment () $ persistanceGateway = \ Mockery :: mock ("Persistență"); $ commentRepository = nou comentariuRepository ($ persistanceGateway); $ commentData = array (1, 'x', 'x', 'x', 'x'); $ comment = (ComentariuFactory nou ()) -> make ($ commentData); $ PersistanceGateway-> shouldReceive ( 'persista') -> o dată () -> cu ($ commentData); $ CommentRepository-> adaugă ($ comentariu); 

Folosim batjocoritorul pentru a ne batjocori persistența și a injecta acel obiect bătut în Depozit. Apoi sunăm adăuga() pe depozit. Această metodă are un parametru de tip cometariu. Ne așteptăm ca persistența să fie apelată cu o serie de date similare $ commentData.

requ_once __DIR__. '/InMemoryPersistence.php'; class CommentRepository private $ persistence; funcția __construct (Persistență $ persistence = null) $ this-> persistence = $ persistență? : nouInMemoryPersistence ();  adăugați funcția (comment $ comment) $ this-> persistență-> persist (array ($ comment-> getPostId (), $ comment-> getAuthor (), $ comment-> getAuthorEmail ), $ comment-> getBody ())); 

După cum puteți vedea, adăuga() metoda este destul de inteligentă. Acesta încapsulează cunoștințele despre transformarea unui obiect PHP într-o matrice simplă utilizabilă de persistență. Amintiți-vă, gateway-ul nostru de persistență este, de obicei, un obiect general pentru toate datele noastre. Se poate și va persista toate datele aplicației noastre, așadar trimiterea obiectelor ar face prea mult: atât conversie, cât și persistență efectivă.

Când ai un InMemoryPersistence ca și noi, este foarte rapid. O putem folosi ca o alternativă la batjocorirea gateway-ului.

funcția testAPesistedCommentCanBeRetrievedFromTheGateway () $ persistanceGateway = InMemoryPersistence (); $ commentRepository = nou comentariuRepository ($ persistanceGateway); $ commentData = array (1, 'x', 'x', 'x', 'x'); $ comment = (ComentariuFactory nou ()) -> make ($ commentData); $ CommentRepository-> adaugă ($ comentariu); $ this-> assertEquals ($ commentData, $ persistentGateway-> retrieve (0)); 

Desigur, dacă nu ai o implementare în memorie a persistenței tale, batjocura este singurul mod rezonabil de a merge. În caz contrar, testul dvs. va fi prea lent pentru a fi practic.

funcția testItCanAddMultipleCommentsAtOnce () $ persistanceGateway = \ Mockery :: mock ("Persistență"); $ commentRepository = nou comentariuRepository ($ persistanceGateway); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> face ($ commentData1); $ commentData2 = array (2, 'y', 'y', 'y', 'y'); $ comment2 = (noul CommentFactory ()) -> make ($ commentData2); $ PersistanceGateway-> shouldReceive ( 'persista') -> o dată () -> cu ($ commentData1); $ PersistanceGateway-> shouldReceive ( 'persista') -> o dată () -> cu ($ commentData2); $ commentRepository-> add (array ($ comment1, $ comment2)); 

Următorul nostru pas logic este de a implementa o modalitate de a adăuga mai multe comentarii simultan. Proiectul dvs. poate să nu necesite această funcție și nu este ceva cerut de model. De fapt, Modelul depozitarului spune doar că va oferi o limbă personalizată de interogare și persistență pentru logica noastră de afaceri. Deci, dacă logica noastră de blândețe simte nevoia de a adăuga mai multe comentarii simultan, Depozitul este locul unde ar trebui să locuiască logica.

($ commentData) ($ commentData as $ comment) $ this-> persistence-> persist (array ($ comment-> getPostId (), $ comment-> getAuthor $ comment-> getAuthorEmail (), $ comment-> getSubject (), $ comment-> getBody ())); altceva $ this-> persistence-> persist (array ($ commentData-> getPostId (), $ commentData-> getAuthor (), $ commentData-> getAuthorEmail (), $ commentData-> getSubject ))); 

Și cel mai simplu mod de a face testul este să verificăm doar dacă parametrul pe care îl obținem este un matrice sau nu. Dacă este o matrice, vom trece prin fiecare element și vom numi persistența cu matricea pe care o generăm de la un singur element cometariu obiect. Și în timp ce acest cod este corect din punct de vedere sintactic și face testul, introduce o ușoară duplicare pe care o putem scăpa destul de ușor.

funcția add ($ commentData) if (is_array ($ commentData)) foreach ($ commentData ca $ comentariu) $ this-> addOne ($ comment); altfel $ this-> addOne ($ commentData);  funcția privată addOne (Comentariu $ comment) $ this-> persistență-> persist (array ($ comment-> getPostId (), $ comment-> getAuthor (), $ comment-> getAuthorEmail (), $ comment-> getBody ())); 

Când toate testele sunt verde, este întotdeauna timpul pentru refactorizare înainte de a continua cu următorul test de eșec. Și am făcut doar asta cu adăuga() metodă. Am extras adăugarea unui singur comentariu într-o metodă privată și l-am sunat din două locuri diferite în publicul nostru adăuga() metodă. Acest lucru nu numai că a redus dublarea, dar a deschis și posibilitatea de a face adauga unu() metoda publică și lăsarea logicii de afaceri să decidă dacă dorește să adauge unul sau mai multe comentarii la un moment dat. Aceasta ar conduce la o implementare diferită a Depozitului nostru, cu un adauga unu() si altul addMany () metode. Ar fi o implementare perfect legitimă a modelului de depozitare.


Recuperarea comentariilor cu depozitul nostru

Un depozit oferă o limbă de interogare personalizată pentru logica de afaceri. Deci, numele și funcționalitățile metodelor de interogare ale unui Depozit sunt cu mult în conformitate cu cerințele logicii de afaceri. Construiți depozitul în timp ce construiți logica de afaceri, deoarece aveți nevoie de o altă metodă de interogare personalizată. Cu toate acestea, există cel puțin una sau două metode pe care le veți găsi pe aproape orice Depozit.

funcția testItCanFindAllComments () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> face ($ commentData1); $ commentData2 = array (2, 'y', 'y', 'y', 'y'); $ comment2 = (noul CommentFactory ()) -> make ($ commentData2); $ Repository-> adaugă ($ comment1); $ Repository-> adaugă ($ comment2); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findAll ()); 

Se numește prima astfel de metodă Găsiți toate(). Aceasta ar trebui să returneze toate obiectele de care este responsabil depozitul, în cazul nostru Comentarii. Testul este simplu, adaugam un comentariu, apoi altul si in final vrem sa sunam Găsiți toate() și obțineți o listă care conține ambele comentarii. Acest lucru nu este totuși realizabil cu noi InMemoryPersistence așa cum este în acest moment. Este necesară o mică actualizare.

funcția retrieveAll () returnează $ this-> data; 

Asta e. Am adăugat a retrieveAll () care returnează întregul $ date array din clasă. Simplu și eficient. E timpul să punem în aplicare Găsiți toate() pe CommentRepository acum.

funcția findAll () $ allCommentsData = $ this-> persistență-> retrieveAll (); $ comments = array (); foreach ($ allCommentsData ca $ commentData) $ comentarii [] = $ this-> commentFactory-> make ($ commentData); returneaza comentariile $; 

Găsiți toate() va apela retrieveAll () pe persistența noastră. Această metodă oferă o gamă brută de date. Găsiți toate() va trece prin fiecare element și va folosi datele necesare pentru a fi transmise fabricii. Fabrica va furniza una cometariu o vreme. O matrice cu aceste comentarii va fi construită și returnată la sfârșitul anului Găsiți toate(). Simplu și eficient.

O altă metodă obișnuită pe care o găsiți în arhive este căutarea unui anumit obiect sau grup de obiecte pe baza cheii lor caracteristice. De exemplu, toate comentariile noastre sunt conectate la o postare de blog de către $ postID variabilă internă. Îmi pot imagina că, în logica de afaceri a blogului nostru, aproape că vom dori întotdeauna să găsim toate comentariile referitoare la o postare de blog atunci când este afișat acel blog. Așa că a fost numită o metodă findByPostId ($ id) sună rezonabil pentru mine.

funcția testItCanFindCommentsByBlogPostId () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> face ($ commentData1); $ commentData2 = matrice (1, 'y', 'y', 'y', 'y'); $ comment2 = (noul CommentFactory ()) -> make ($ commentData2); $ commentData3 = array (3, 'y', 'y', 'y', 'y'); $ comment3 = (new CommentFactory ()) -> face ($ commentData3); $ repository-> add (array ($ comment1, $ comment2)); $ Repository-> adaugă ($ comment3); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findByPostId (1)); 

Noi creăm doar trei comentarii simple. Primele două au același lucru $ postId = 1, a treia $ postID = 3. Le adaugam pe toate in depozit si apoi ne asteptam la o matrice cu primele doua cand facem a findByPostId () pentru $ postId = 1.

funcția ($ comment) ($ postId) return $ comment-> getPostId () == $ postId;); 

Implementarea nu ar putea fi mai simplă. Găsim toate comentariile folosind cele deja implementate Găsiți toate() și filtram matricea. Nu avem nici o cale de a cere persistența să facă filtrarea pentru noi, așa că o vom face aici. Codul va interoga fiecare cometariu Obiect și comparați-l $ postID cu cea pe care am trimis-o ca parametru. Grozav. Testul trece. Dar simt că am pierdut ceva.

funcția testItCanFindCommentsByBlogPostId () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> face ($ commentData1); $ commentData2 = matrice (1, 'y', 'y', 'y', 'y'); $ comment2 = (noul CommentFactory ()) -> make ($ commentData2); $ commentData3 = array (3, 'y', 'y', 'y', 'y'); $ comment3 = (new CommentFactory ()) -> face ($ commentData3); $ repository-> add (array ($ comment1, $ comment2)); $ Repository-> adaugă ($ comment3); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findByPostId (1)); $ this-> assertEquals (array ($ comment3), $ repository-> findByPostId (3)); 

Adăugarea unei a doua afirmații pentru obținerea celui de - al treilea comentariu cu findByPostID () metoda ne dezvăluie greșeala. Ori de câte ori puteți testa cu ușurință căi suplimentare sau cazuri, ca în cazul nostru, cu o simplă afirmație suplimentară, ar trebui. Aceste afirmații suplimentare simple sau metode de testare pot dezvălui probleme ascunse. Ca în cazul nostru, array_filter () nu reindexă matricea rezultată. Și în timp ce avem o matrice cu elementele corecte, indiciile sunt încurcate.

1) RepositoryTest :: testItCanFindCommentsByBlogPostId Nu a reușit să se afirme că două matrice sunt egale. --- Expectat +++ Actual @@@@ Array (- 0 => Obiect Comentariu (...) + 2 => Comentariu Obiect (...))

Acum, puteți considera acest lucru un neajuns al PHPUnit sau un neajuns al logicii dvs. de afaceri. Tind să fiu riguros cu indici de matrice, pentru că mi-am ars mâinile cu ei de câteva ori. Deci, ar trebui să considerăm că eroarea este o problemă cu logica noastră în CommentRepository.

funcția ($ comment) utilizează ($ postId) return $ comment-> getPostId () == $ postId;)); 

Da. Atat de simplu. Pur și simplu rulam rezultatul array_values ​​() înainte de ao returna. Ea va reda frumos matricea noastră. Misiune indeplinita.


Gândurile finale

Și aceasta este misiunea îndeplinită și pentru Depozitul nostru. Avem o clasă utilizabilă de orice altă clasă de logic

Cod