Repository Design Pattern, definert av Eric Evens i sin Domain Driven Design-bok, er et av de mest nyttige og mest anvendelige designmønstrene som noensinne har blitt oppfunnet. Enhver applikasjon må jobbe med utholdenhet og med en slags liste over gjenstander. Disse kan være brukere, produkter, nettverk, disker eller hva søknaden din handler om. Hvis du for eksempel har en blogg, må du håndtere lister over blogginnlegg og lister over kommentarer. Problemet med at alle disse listeadministrasjonsloggene har felles er hvordan man kobler forretningslogikk, fabrikker og utholdenhet.
Som vi nevnte i det innledende avsnittet, vil et Repository koble fabrikker med Gateways (utholdenhet). Disse er også designmønstre, og hvis du ikke er kjent med dem, vil dette avsnittet kaste litt lys over emnet.
En fabrikk er et enkelt designmønster som definerer en enkel måte å lage objekter på. Det er en klasse eller et sett av klasser som er ansvarlige for å skape de objekter som vår forretningslogikk trenger. En fabrikk har tradisjonelt en metode som kalles "
og det vil vite hvordan å ta all den informasjonen som trengs for å bygge et objekt og gjøre objektet bygningen selv og returnere en klar til bruk objekt til forretningslogikken.gjøre()
"
Her er litt mer på Factory Pattern i en eldre Nettuts + opplæring: En nybegynners guide til designmønstre. Hvis du foretrekker en dypere visning på fabrikkmønsteret, sjekk ut det første designmønsteret i Agile Design Patterns kurset vi har på Tuts+.
Også kjent som "Table Data Gateway" er et enkelt mønster som gir sammenhengen mellom forretningslogikken og selve databasen. Hovedansvaret er å gjøre spørringene i databasen og gi de hentede dataene i en datastruktur som er typisk for programmeringsspråket (som en matrise i PHP). Disse dataene blir deretter vanligvis filtrert og modifisert i PHP-koden, slik at vi kan få den informasjonen og variablene som trengs for å kasse våre objekter. Denne informasjonen må da sendes til fabrikkene.
Gateway Design Pattern er forklart og eksemplifisert i ganske stor detalj i en Nettuts + veiledning om utvikling mot et persistenslag. Også i samme Agile Design Patterns-kurset handler det andre designmønsteret om dette emnet.
Det kan ikke være åpenbart ved første blikk, men å knytte Gateways to Factories kan føre til mye duplisering. Enhver stor programvare trenger å skape de samme objektene fra forskjellige steder. På hvert sted må du bruke Gateway til å hente et sett med rå data, filtrere og jobbe dataene for å være klar til å bli sendt til fabrikkene. Fra alle disse stedene vil du kalle de samme fabrikkene med de samme datastrukturene, men åpenbart med forskjellige data. Dine objekter vil bli opprettet og levert til deg av Fabrikkene. Dette vil uunngåelig føre til mye duplisering i tide. Og duplisering vil bli spredt over fjerntliggende klasser eller moduler, og vil være vanskelig å legge merke til og fikse.
Et annet problem vi har, er hvordan du uttrykker spørsmålene vi trenger å gjøre ved hjelp av Gateways. Hver gang vi trenger litt informasjon fra Gateway, må vi tenke på hva vi trenger akkurat? Trenger vi alle dataene om et enkelt emne? Trenger vi bare noen spesifikk informasjon? Vil vi hente en bestemt gruppe fra databasen og gjøre sorteringen eller raffinert filtrering i vårt programmeringsspråk? Alle disse spørsmålene må tas opp hver gang vi henter informasjon fra persistenslaget gjennom vår Gateway. Hver gang vi gjør dette, må vi komme opp med en løsning. Etter hvert som vår søknad vokser, blir vi konfrontert med de samme dilemmaene på forskjellige steder i vår søknad. Uansett vil vi komme opp med litt forskjellige løsninger på de samme problemene. Dette tar ikke bare ekstra tid og krefter, men fører også til en subtil, for det meste svært vanskelig å gjenkjenne, duplisering. Dette er den farligste typen duplisering.
I de to foregående avsnittene snakket vi bare om datainnhenting. Men Gateway er toveis. Vår forretningslogikk er toveis. Vi må på en eller annen måte fortsette våre objekter. Dette fører igjen til mye gjentakelse hvis vi ønsker å implementere denne logikken etter behov gjennom ulike moduler og klasser av søknaden vår.
En Repository kan fungere på to måter: datainnhenting og data utholdenhet.
Når det brukes til å hente gjenstander fra utholdenhet, blir et Repository kalt med en tilpasset spørring. Denne spørringen kan være en bestemt metode etter navn eller en mer vanlig metode med parametere. Repositoriet er ansvarlig for å gi og gjennomføre disse spørringsmetodene. Når en slik metode kalles, vil Repositoriet kontakte Gateway for å hente de raske dataene fra persistensen. Gateway vil gi rå objektdata (som en matrise med verdier). Deretter vil Repository ta disse dataene, foreta de nødvendige transformasjonene og ringe de riktige fabrikkmetodene. Fabrikkene vil gi objektene konstruert med dataene fra Repository. Repository vil samle disse objektene og returnere dem som et sett med objekter (som en rekke objekter eller et samlingsobjekt som definert i Composite Pattern-leksjonen i kurset Agile Design Patterns).
Den andre måten et Repository kan fungere på, er å gi logikken som trengs for å trekke ut informasjonen fra et objekt og fortsette det. Dette kan være så enkelt som å serialisere objektet og sende de serialiserte dataene til Gateway for å vedvare det eller så sofistikert som å skape informasjonsrapporter med alle feltene og tilstanden til en gjenstand.
Når det brukes til å vedvare informasjon, er klientklassen den som kommuniserer direkte med fabrikken. Tenk deg et scenario når en ny kommentar blir lagt ut på et blogginnlegg. Et kommentarobjekt er opprettet av vår forretningslogikk (Client-klassen) og sendes deretter til Repository for å være vedvarende. Lageret vil fortsette gjenstandene ved hjelp av Gateway og eventuelt lagre dem i en lokal i minneliste. Data må transformeres fordi det bare er sjeldne tilfeller når ekte objekter kan lagres direkte til et persistenssystem.
Bildet nedenfor er et høyere nivåvisning på hvordan du integrerer arkivet mellom fabrikkene, gatewayen og klienten.
I midten av skjemaet er vårt Repository. Til venstre er et grensesnitt for Gateway, en implementering og utholdenheten selv. Til høyre er det et grensesnitt for fabrikkene og en fabrikkimplementering. Til slutt er det klientklassen på toppen.
Som det kan observeres fra pilens retning, er avhengighetene omvendt. Repository er bare avhengig av abstrakte grensesnitt for fabrikker og gateways. Gateway avhenger av grensesnittet og utholdenheten det tilbyr. Fabrikken er bare avhengig av grensesnittet. Klienten er avhengig av Repository, noe som er akseptabelt fordi Repository har en tendens til å være mindre konkret enn Klienten.
Sett i perspektiv respekterer avsnittet ovenfor vår høyt nivå arkitektur og retningen av avhengigheter vi ønsker å oppnå.
Nå som vi har sett teorien, er det tid for et praktisk eksempel. Tenk deg at vi har en blogg der vi har Post objekter og Kommentar objekter. Kommentarer tilhører Innlegg, og vi må finne en måte å vedvare dem og hente dem.
Vi vil starte med en test som vil tvinge oss til å tenke på hva vårt kommentarobjekt skal inneholde.
klasse RepositoryTest utvider PHPUnit_Framework_TestCase funksjonstestAvmerkingHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe har en mening om Repository Pattern"; $ commentBody = "Jeg synes det er en god ide å bruke Repository Pattern til å fortsette og hente objekter."; $ comment = ny kommentar ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody);
Ved første øyekast vil en kommentar bare være et dataobjekt. Det kan ikke ha noen funksjonalitet, men det er opp til konteksten av søknaden vår å bestemme. For dette eksemplet bare anta at det er et enkelt dataobjekt. Konstruert med et sett med variabler.
klasse kommentar
Bare ved å lage en tom klasse og kreve det i testen, blir det bestått.
require_once '... /Comment.php'; klasse RepositoryTest utvider PHPUnit_Framework_TestCase [...]
Men det er langt fra perfekt. Vår test tester ikke noe ennå. La oss tvinge oss til å skrive alle getters på kommentarklassen.
funksjonstestACommenterHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe har en mening om Repository Pattern"; $ commentBody = "Jeg synes det er en god ide å bruke Repository Pattern til å fortsette og hente objekter."; $ comment = ny kommentar ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); $ this-> assertEquals ($ postId, $ comment-> getPostId ()); $ this-> assertEquals ($ commentAuthor, $ comment-> getAuthor ()); $ this-> assertEquals ($ commentAuthorEmail, $ comment-> getAuthorEmail ()); $ this-> assertEquals ($ commentSubject, $ comment-> getSubject ()); $ this-> assertEquals ($ commentBody, $ comment-> getBody ());
For å kontrollere lengden på opplæringen, skrev jeg alle påstandene samtidig, og vi vil også implementere dem samtidig. I virkeligheten, ta dem en etter en.
klasse Kommentar private $ postId; privat $ forfatter; privat $ authorEmail; privat $ emne; privat $ kropp; funksjon __construct ($ postId, $ author, $ authorEmail, $ subject, $ body) $ this-> postId = $ postId; $ this-> author = $ author; $ this-> authorEmail = $ authorEmail; $ this-> subject = $ subject; $ this-> body = $ body; offentlig funksjon getPostId () return $ this-> postId; offentlig funksjon getAuthor () return $ this-> author; offentlig funksjon getAuthorEmail () return $ this-> authorEmail; offentlig funksjon getSubject () return $ this-> subject; offentlig funksjon getBody () return $ this-> body;
Bortsett fra listen over private variabler, ble resten av koden generert av min IDE, NetBeans, slik at testing av automatisk generert kode kan være litt overhead noen ganger. Hvis du ikke skriver disse linjene selv, kan du gjerne gjøre dem direkte og ikke bry deg med tester for settere og konstruktører. Testen hjalp oss likevel til å bedre utsette våre ideer og bedre dokumentere hva vår kommentarklasse vil inneholde.
Vi kan også vurdere disse testmetoder og testklasser som våre "Client" -klasser fra skjemaene.
For å holde dette eksemplet så enkelt som mulig, implementerer vi bare en InMemoryPersistence slik at vi ikke kompliserer vår eksistens med filsystemer eller databaser.
require_once '... /InMemoryPersistence.php'; klasse InMemoryPersistenceTest utvider PHPUnit_Framework_TestCase funksjon testItCanPerisistAndRetrieveASingleDataArray () $ data = array ('data'); $ utholdenhet = ny InMemoryPersistence (); $ Bestemt varighet> vedvare ($ data); $ this-> assertEquals ($ data, $ persistence-> retrieve (0));
Som vanlig starter vi med den enkleste testen som muligens kan mislykkes, og tvinge oss til å skrive noen kode. Denne testen oppretter en ny InMemoryPersistence
objekt og forsøker å fortsette og hente en oppringt gruppe data
.
require_once __DIR__. '/Persistence.php'; klasse InMemoryPersistence implementerer Persistence private $ data = array (); funksjonen vedvarer ($ data) $ this-> data = $ data; funksjonen hente ($ id) return $ this-> data;
Den enkleste koden for å få det til å passere, er bare for å holde innkommende $ data
i en privat variabel og returner den i hente
metode. Koden som den er akkurat nå bryr seg ikke om den sendte inn $ id
variabel. Det er den enkleste tingen som kan muligens gjøre testen pass. Vi tok også friheten til å introdusere og implementere et grensesnitt som heter Standhaftighet
.
grensesnitt Persistence funksjon vedvarer ($ data); funksjonen hente ($ ids);
Dette grensesnittet definerer de to metodene noen Gateway trenger å implementere. Fortsette
og hente
. Som du sannsynligvis allerede gjettet, er vår Gateway vår InMemoryPersistence
klasse og vår fysiske utholdenhet er den private variabelen som holder dataene våre i minnet. Men la oss komme tilbake til implementeringen av dette i minnet utholdenhet.
funksjon testItCanPerisistSeveralElementsAndRetrieveAnyOfThem () $ data1 = array ('data1'); $ data2 = array ('data2'); $ utholdenhet = ny InMemoryPersistence (); $ Bestemt varighet> vedvare ($ data1); $ Bestemt varighet> vedvare ($ data2); $ this-> assertEquals ($ data1, $ persistence-> retrieve (0)); $ this-> assertEquals ($ data2, $ persistence-> retrieve (1));
Vi har lagt til en annen test. I denne fortsetter vi to forskjellige datarammer. Vi regner med å kunne hente hver enkelt av dem individuelt.
require_once __DIR__. '/Persistence.php'; klasse InMemoryPersistence implementerer Persistence private $ data = array (); funksjonen vedvarer ($ data) $ this-> data [] = $ data; funksjonen hente ($ id) return $ this-> data [$ id];
Testen tvang oss til å endre vår kode litt. Vi trenger nå å legge til data i vårt utvalg, ikke bare erstatte det med den som sendes inn til vedvarer ()
. Vi må også vurdere $ id
parameter og returner elementet til den indeksen.
Dette er nok for vår InMemoryPersistence
. Om nødvendig kan vi endre det senere.
Vi har en klient (våre tester), en utholdenhet med en gateway og kommentarobjekter å fortsette. Den neste manglende tingen er vår fabrikk.
Vi startet vår koding med en RepositoryTest
fil. Denne testen opprettet imidlertid faktisk en Kommentar
gjenstand. Nå må vi lage tester for å verifisere om fabrikken vår vil kunne opprette Kommentar
objekter. Det virker som om vi hadde en feil i dommen, og vår test er mer sannsynlig en test for vår kommende Factory enn for vårt Repository. Vi kan flytte den til en annen testfil, CommentFactoryTest
.
require_once '... /Comment.php'; klasse CommentFactoryTest utvider PHPUnit_Framework_TestCase funksjonstestApplikasjonerHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe har en mening om Repository Pattern"; $ commentBody = "Jeg synes det er en god ide å bruke Repository Pattern til å fortsette og hente objekter."; $ comment = ny kommentar ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); $ this-> assertEquals ($ postId, $ comment-> getPostId ()); $ this-> assertEquals ($ commentAuthor, $ comment-> getAuthor ()); $ this-> assertEquals ($ commentAuthorEmail, $ comment-> getAuthorEmail ()); $ this-> assertEquals ($ commentSubject, $ comment-> getSubject ()); $ this-> assertEquals ($ commentBody, $ comment-> getBody ());
Nå går denne testen tydeligvis. Og mens det er en riktig test, bør vi vurdere å endre det. Vi ønsker å lage en Fabrikk
objekt, pass i en matrise og be den om å opprette en Kommentar
for oss.
require_once '... /CommentFactory.php'; klasse CommentFactoryTest utvider PHPUnit_Framework_TestCase funksjonstestApplikasjonerHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe har en mening om Repository Pattern"; $ commentBody = "Jeg synes det er en god ide å bruke Repository Pattern til å fortsette og hente objekter."; $ commentData = array ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); $ comment = (new CommentFactory ()) -> make ($ commentData); $ this-> assertEquals ($ postId, $ comment-> getPostId ()); $ this-> assertEquals ($ commentAuthor, $ comment-> getAuthor ()); $ this-> assertEquals ($ commentAuthorEmail, $ comment-> getAuthorEmail ()); $ this-> assertEquals ($ commentSubject, $ comment-> getSubject ()); $ this-> assertEquals ($ commentBody, $ comment-> getBody ());
Vi bør aldri nevne våre klasser basert på designmønsteret de implementerer, men Fabrikk
og Oppbevaringssted
representerer mer enn bare mønsteret selv. Jeg har ikke noe imot å inkludere disse to ordene i klassens navn. Men jeg anbefaler fortsatt og respekterer konseptet om ikke å navngi våre klasser etter designmønstrene vi bruker for resten av mønstrene.
Denne testen er bare litt forskjellig fra den forrige, men det mislykkes. Den prøver å skape en CommentFactory
gjenstand. Denne klassen eksisterer ikke ennå. Vi prøver også å ringe en gjøre()
metode på den med en matrise som inneholder all informasjonen til en kommentar som en matrise. Denne metoden er definert i Fabrikk
grensesnitt.
grensesnitt fabrikk funksjon lage ($ data);
Dette er en veldig vanlig Fabrikk
grensesnitt. Det definerte den eneste nødvendige metoden for en fabrikk, metoden som faktisk lager de objektene vi vil ha.
require_once __DIR__. '/Factory.php'; require_once __DIR__. '/Comment.php'; klasse CommentFactory redskaper Factory function make ($ components) returnere ny kommentar ($ komponenter [0], $ komponenter [1], $ komponenter [2], $ komponenter [3], $ komponenter [4]);
Og CommentFactory
implementerer Fabrikk
grensesnitt vellykket ved å ta $ komponenter
parameter i sin gjøre()
metode, skaper og returnerer en ny Kommentar
objekt med informasjonen derfra.
Vi vil holde vår vedholdenhet og objekt skapelseslogikk så enkelt som mulig. Vi kan, for denne opplæringen, ignorere feilaktig håndtering, validering og unntakskasting. Vi vil stoppe her med utførelsen av vedholdenhet og objektgjennomføring.
Som vi har sett ovenfor, kan vi bruke et Repository på to måter. Å hente informasjon fra utholdenhet og også for å vedvare informasjon om persistenslaget. Ved å bruke TDD er det mesteparten av tiden enklere å starte med lagring (vedvarende) del av logikken og deretter bruke den eksisterende implementeringen for å teste datainnhenting.
require_once '... / ... / ... /vendor/autoload.php'; require_once '... /CommentRepository.php'; require_once '... /CommentFactory.php'; klasse RepositoryTest utvider PHPUnit_Framework_TestCase beskyttet funksjon tearDown () \ Mockery :: close (); funksjon testItCallsThePersistenceWhenAddingAComment () $ persistanceGateway = \ Mockery :: mock ('Persistence'); $ commentRepository = nytt CommentRepository ($ persistanceGateway); $ commentData = array (1, 'x', 'x', 'x', 'x'); $ comment = (new CommentFactory ()) -> make ($ commentData); $ PersistanceGateway-> shouldReceive ( 'vedvarer') -> én gang () -> med ($ commentData); $ CommentRepository-> legge til ($ kommentar);
Vi bruker Mockery til å stryke vår utholdenhet og injisere det hånte objektet til Repository. Da kaller vi Legg til()
på depotet. Denne metoden har en parameter av typen Kommentar
. Vi forventer at utholdenheten skal kalles med en rekke data som ligner på $ commentData
.
require_once __DIR__. '/InMemoryPersistence.php'; class CommentRepository private $ persistence; funksjon __construct (Persistens $ persistence = null) $ this-> persistence = $ utholdenhet? : Ny InMemoryPersistence (); funksjonstilføye (Kommentar $ kommentare) $ this-> persistence-> persist (array ($ kommentar-> getPostId (), $ comment-> getAuthor (), $ comment-> getAuthorEmail (), $ comment-> getSubject ), $ comment-> getBody ()));
Som du kan se, er Legg til()
metoden er ganske smart. Den inkapsulerer kunnskapen om hvordan man omdanner et PHP-objekt til et vanlig array som kan brukes av utholdenhet. Husk at vår persistensgateway vanligvis er et generelt objekt for alle våre data. Det kan og vil fortsette alle dataene i søknaden vår, så ved å sende til det, vil objekter gjøre det for mye: både konvertering og effektiv utholdenhet.
Når du har en InMemoryPersistence
klasse som vi gjør, det er veldig fort. Vi kan bruke det som et alternativ til å mocking gatewayen.
funksjonstestAPersistedCommentCanBeRetrievedFromTheGateway () $ persistanceGateway = ny InMemoryPersistence (); $ commentRepository = nytt CommentRepository ($ persistanceGateway); $ commentData = array (1, 'x', 'x', 'x', 'x'); $ comment = (new CommentFactory ()) -> make ($ commentData); $ CommentRepository-> legge til ($ kommentar); $ this-> assertEquals ($ commentData, $ persistanceGateway-> hent (0));
Selvfølgelig, hvis du ikke har en i-minne-implementering av din utholdenhet, er mocking den eneste rimelige måten å gå. Ellers vil testen din være for treg til å være praktisk.
funksjon testItCanAddMultipleCommentsAtOnce () $ persistanceGateway = \ Mockery :: mock ('Persistence'); $ commentRepository = nytt CommentRepository ($ persistanceGateway); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (2, 'y', 'y', 'y', 'y'); $ comment2 = (new CommentFactory ()) -> make ($ commentData2); $ PersistanceGateway-> shouldReceive ( 'vedvarer') -> én gang () -> med ($ commentData1); $ PersistanceGateway-> shouldReceive ( 'vedvarer') -> én gang () -> med ($ commentData2); $ commentRepository-> add (array ($ comment1, $ comment2));
Vår neste logiske skritt er å implementere en måte å legge til flere kommentarer samtidig. Ditt prosjekt krever kanskje ikke denne funksjonaliteten, og det er ikke noe som kreves av mønsteret. Faktisk sier Repository Pattern bare at det vil gi et tilpasset spørrings- og utholdsspråk for vår forretningslogikk. Så hvis vår bushiness logikk føler behovet for å legge til flere kommentarer på en gang, er Repository stedet der logikken burde oppholde seg.
funksjon add ($ commentData) if (is_array ($ commentData)) foreach ($ commentData som $ comment) $ this-> persistence-> vedvarer (array ($ comment-> getPostId (), $ comment-> getAuthor $ comment-> getAuthorEmail (), $ comment-> getSubject (), $ comment-> getBody ())); ellers $ this-> persistence-> vedvarer (array ($ commentData-> getPostId (), $ commentData-> getAuthor (), $ commentData-> getAuthorEmail (), $ commentData-> getSubject (), $ commentData-> getBody )));
Og den enkleste måten å gjøre testpasset på er å bare kontrollere om parameteren vi får, er en matrise eller ikke. Hvis det er en matrise, vil vi sykle gjennom hvert element og kalle utholdenheten med matrisen vi genererer fra en enkelt Kommentar
gjenstand. Og mens denne koden er syntaktisk korrekt og gjør testen passerer, introduserer den en liten duplisering som vi kan bli kvitt ganske enkelt.
funksjon add ($ commentData) if (is_array ($ commentData)) foreach ($ commentData as $ comment) $ this-> addOne ($ kommentar); ellers $ this-> addOne ($ commentData); privat funksjon addOne (Kommentar $ kommentare) $ this-> persistence-> persist (array ($ comment-> getPostId (), $ comment-> getAuthor (), $ comment-> getAuthorEmail (), $ comment-> getSubject (), $ comment-> getBody ()));
Når alle tester er grønne, er det alltid tid til refactoring før vi fortsetter med neste feiltest. Og det gjorde vi bare med Legg til()
metode. Vi hentet tillegget av en enkelt kommentar til en privat metode og kalte den fra to forskjellige steder i vårt publikum Legg til()
metode. Dette reduserte ikke bare duplisering, men åpnet også muligheten for å lage addOne ()
metode offentlig og la virksomhetslogikken bestemme om det vil legge til en eller flere kommentarer om gangen. Dette ville føre til en annen implementering av vårt Repository, med en addOne ()
og en til addMany ()
metoder. Det ville være en perfekt legitim implementering av Repository Pattern.
Et Repository gir et tilpasset spørrespråk for forretningslogikken. Så navnene og funksjonalitetene til spørringsmetodene til et Repository er enormt opp til kravene til forretningslogikken. Du bygger opp ditt arkiv når du bygger forretningslogikken din, siden du trenger en annen tilpasset spørringsmetode. Det er imidlertid minst en eller to metoder som du finner på nesten hvilken som helst Repository.
funksjon testItCanFindAllComments () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (2, 'y', 'y', 'y', 'y'); $ comment2 = (new CommentFactory ()) -> make ($ commentData2); $ Repository-> legge til ($ comment1); $ Repository-> legge til ($ comment2); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findAll ());
Den første slik metode kalles findAll ()
. Dette bør returnere alle gjenstandene som er ansvarlig for, i vårt tilfelle kommentarer
. Testen er enkel, vi legger til en kommentar, så en annen, og til slutt vil vi ringe findAll ()
og få en liste som inneholder begge kommentarene. Dette er imidlertid ikke gjennomførbart med vår InMemoryPersistence
som det er på dette punktet. En liten oppdatering er nødvendig.
funksjonen retrieveAll () return $ this-> data;
Det er det. Vi la til en retrieveAll ()
metode som bare returnerer hele $ data
matrise fra klassen. Enkel og effektiv. Det er på tide å gjennomføre findAll ()
på CommentRepository
nå.
funksjon findAll () $ allCommentsData = $ this-> persistence-> retrieveAll (); $ comments = array (); foreach ($ allCommentsData som $ commentData) $ comments [] = $ this-> commentFactory-> lag ($ commentData); returner $ comments;
findAll ()
vil ringe retrieveAll ()
metode på utholdenhet. Denne metoden gir et ufattelig utvalg av data. findAll ()
vil sykle gjennom hvert element og bruke dataene som nødvendig for å bli sendt til fabrikken. Fabrikken vil gi en Kommentar
en gang. En rekke med disse kommentarene vil bli bygget og returnert på slutten av findAll ()
. Enkel og effektiv.
En annen vanlig metode du finner på repositories, er å søke etter et bestemt objekt eller en gruppe objekter basert på deres karakteristiske nøkkel. For eksempel er alle våre kommentarer koblet til et blogginnlegg av en $ postId
intern variabel. Jeg kan forestille meg at i bloggens forretningslogikk vil vi nesten alltid finne alle kommentarene knyttet til et blogginnlegg når bloggen vises. Så en metode kalt findByPostId ($ id)
høres rimelig ut for meg.
funksjon testItCanFindCommentsByBlogPostId () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (1, 'y', 'y', 'y', 'y'); $ comment2 = (new CommentFactory ()) -> make ($ commentData2); $ commentData3 = array (3, 'y', 'y', 'y', 'y'); $ comment3 = (new CommentFactory ()) -> make ($ commentData3); $ repository-> add (array ($ comment1, $ comment2)); $ Repository-> legge til ($ comment3); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findByPostId (1));
Vi lager bare tre enkle kommentarer. De to første har det samme $ postId = 1
, den tredje har $ postID = 3
. Vi legger alle sammen til depotet, og vi forventer en matrise med de to første når vi gjør en findByPostId ()
for $ postId = 1
.
funksjon findByPostId ($ postId) return array_filter ($ this-> findAll (), funksjon ($ kommentar) bruk ($ postId) return $ comment-> getPostId () == $ postId;);
Gjennomføringen kunne ikke vært enklere. Vi finner alle kommentarene ved hjelp av vår allerede implementerte findAll ()
metode og vi filtrerer arrayet. Vi har ingen måte å spørre utholdenheten om å gjøre filtreringen for oss, så vi vil gjøre det her. Koden vil spørre hver Kommentar
objekt og sammenligne dens $ postId
med den vi sendte inn som parameter. Flott. Testen går forbi. Men jeg føler at vi savnet noe.
funksjon testItCanFindCommentsByBlogPostId () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (new CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (1, 'y', 'y', 'y', 'y'); $ comment2 = (new CommentFactory ()) -> make ($ commentData2); $ commentData3 = array (3, 'y', 'y', 'y', 'y'); $ comment3 = (new CommentFactory ()) -> make ($ commentData3); $ repository-> add (array ($ comment1, $ comment2)); $ Repository-> legge til ($ comment3); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findByPostId (1)); $ this-> assertEquals (array ($ comment3), $ repository-> findByPostId (3));
Legge til et annet påstand for å få den tredje kommentaren med findByPostID ()
Metoden avslører vår feil. Når du enkelt kan teste ekstra baner eller tilfeller, som i vårt tilfelle med en enkel ekstra påstand, bør du. Disse enkle ekstra påstander eller testmetoder kan avsløre skjulte problemer. Som i vårt tilfelle, array_filter ()
reindexerer ikke den resulterende arrayen. Og mens vi har en matrise med de riktige elementene, blir indeksene ødelagt.
1) RepositoryTest :: testItCanFindCommentsByBlogPostId Failed å hevde at to arrays er like. --- Forventet +++ Faktisk @@@@ Array (- 0 => Kommentarobjekt (...) + 2 => Kommenter objekt (...))
Nå kan du vurdere dette en mangel på PHPUnit eller en mangel på forretningslogikken din. Jeg pleier å være streng med arrayindekser fordi jeg brente hendene mine med dem noen ganger. Så vi bør vurdere feilen et problem med vår logikk i CommentRepository
.
funksjon findByPostId ($ postId) return array_values (array_filter ($ this-> findAll (), funksjon ($ comment) bruk ($ postId) return $ comment-> getPostId () == $ postId;));
Jepp. Så enkelt. Vi driver bare resultatet gjennom array_values ()
før du returnerer den. Det vil pent reindexere vårt utvalg. Oppdrag utført.
Og det er oppdraget oppnådd for vårt Repository også. Vi har en klasse som kan brukes av enhver annen forretningslogikklasse som gir en enkel måte å fortsette og hente objekter på. Det avkaller også forretningslogikken fra fabrikkene og data utholdenhet gateways. Det reduserte logisk duplisering og forenkler vesentlig utholdenhet og gjenfinning operasjoner for våre kommentarer.
Husk at dette designmønsteret kan brukes til alle typer lister, og når du begynner å bruke det, vil du se dets brukbarhet. I utgangspunktet, når du må jobbe med flere objekter av samme type, bør du vurdere å introdusere et Repository for dem. Repositories er spesialisert etter objekttype og ikke generell. Så for et bloggprogram, kan du ha tydelige lagre for blogginnlegg, for kommentarer, for brukere, for brukerkonfigurasjoner, for temaer, for design, for eller noe du kanskje har flere forekomster av.
Og før du avslutter dette, kan et Repository ha sin egen liste over objekter, og det kan gjøre en lokal caching av objekter. Hvis en gjenstand ikke kan bli funnet i den lokale listen, henter vi den fra utholdenheten, ellers serverer vi den fra vår liste. Hvis det brukes med caching, kan et Repository vellykkes kombinert med Singleton Design Pattern.