Het ontwerppatroon van de repository

Het Repository Design Pattern, gedefinieerd door Eric Evens in zijn Domain Driven Design-boek, is een van de meest bruikbare en meest toepasbare ontwerppatronen die ooit zijn uitgevonden. Elke applicatie moet werken met persistentie en met een soort lijst met items. Dit kunnen gebruikers, producten, netwerken, schijven of wat uw toepassing ook zijn. Als je bijvoorbeeld een blog hebt, moet je omgaan met lijsten met blogberichten en lijsten met opmerkingen. Het probleem dat al deze logica van lijstbeheer gemeen hebben, is hoe bedrijfslogica, fabrieken en persistentie met elkaar in verband worden gebracht.


Het fabrieksontwerppatroon

Zoals we in de inleidende paragraaf al vermeldden, verbindt een repository Fabrieken met gateways (persistentie). Dit zijn ook ontwerppatronen en als u niet bekend bent met deze, zal deze alinea enig licht werpen op het onderwerp.

Een fabriek is een eenvoudig ontwerppatroon dat een handige manier definieert om objecten te maken. Het is een klasse of reeks klassen die verantwoordelijk is voor het maken van de objecten die onze bedrijfslogica nodig heeft. Een fabriek heeft van oudsher een methode genaamd "maken()" en hij weet hoe hij alle informatie moet gebruiken die nodig is om een ​​object te bouwen en het object zelf te laten bouwen en een kant-en-klaar object terug te geven aan de bedrijfslogica.

Hier is een beetje meer over het fabriekspatroon in een oudere Nettuts + tutorial: een beginnershandleiding voor ontwerppatronen. Als je een beter beeld hebt van het fabriekspatroon, bekijk dan het eerste ontwerppatroon in de Agile Design Patterns-cursus die we over Tuts hebben+.


Het gatewaypatroon

Ook bekend als 'Table Data Gateway' is een eenvoudig patroon dat de verbinding biedt tussen de bedrijfslogica en de database zelf. De hoofdverantwoordelijkheid is om de query's in de database uit te voeren en de opgehaalde gegevens te leveren in een gegevensstructuur die typisch is voor de programmeertaal (zoals een array in PHP). Deze gegevens worden vervolgens meestal gefilterd en gewijzigd in de PHP-code, zodat we de informatie en variabelen kunnen verkrijgen die nodig zijn om onze objecten in te pakken. Deze informatie moet vervolgens worden doorgegeven aan de fabrieken.

Het Gateway-ontwerppatroon wordt in vrij gedetailleerde details uitgelegd en geïllustreerd in een Nettuts + tutorial over het evolueren naar een persistentie-laag. In dezelfde cursus Agile Design Patterns gaat de tweede les met ontwerppatronen over dit onderwerp.


De problemen die we moeten oplossen

Duplicatie door gegevensverwerking

Het is op het eerste gezicht misschien niet vanzelfsprekend, maar het verbinden van Gateways met Fabrieken kan tot veel duplicatie leiden. Elke software van aanzienlijke omvang moet dezelfde objecten vanaf verschillende plaatsen maken. In elke plaats moet u de gateway gebruiken om een ​​set onbewerkte gegevens op te halen, te filteren en te bewerken zodat deze klaar zijn om naar de fabrieken te worden verzonden. Vanuit al deze plaatsen belt u dezelfde fabrieken met dezelfde datastructuren, maar uiteraard met verschillende gegevens. Uw objecten worden door de fabrieken gemaakt en aan u geleverd. Dit zal onvermijdelijk leiden tot veel duplicatie in de tijd. En de duplicatie zal verspreid zijn over verre klassen of modules en zal moeilijk op te merken en op te lossen zijn.

Duplicatie door Data Retrieval Logic Reimplementation

Een ander probleem dat we hebben, is hoe we de vragen die we moeten doen met behulp van de Gateways kunnen uitdrukken. Elke keer dat we wat informatie van de Gateway nodig hebben, moeten we nadenken over wat we precies nodig hebben? Hebben we alle gegevens over één onderwerp nodig? Hebben we alleen wat specifieke informatie nodig? Willen we een specifieke groep uit de database halen en de sortering of verfijnde filtering in onze programmeertaal uitvoeren? Al deze vragen moeten worden aangepakt telkens wanneer we via onze gateway informatie van de persistentielaag ophalen. Elke keer dat we dit doen, zullen we een oplossing moeten bedenken. Na verloop van tijd zullen onze toepassingen toenemen naarmate onze applicatie groeit en we op dezelfde manier geconfronteerd worden met dezelfde dilemma's op verschillende plaatsen in onze applicatie. Onbedoeld zullen we iets verschillende oplossingen bedenken voor dezelfde problemen. Dit kost niet alleen extra tijd en moeite, maar leidt ook tot een subtiel, meestal erg moeilijk te herkennen, duplicatie. Dit is het gevaarlijkste type duplicatie.

Duplicatie door Data Persistence Logic Reimplementation

In de vorige twee paragrafen hebben we alleen gesproken over het ophalen van gegevens. Maar de Gateway is bidirectioneel. Onze bedrijfslogica is bidirectioneel. We moeten onze objecten op de een of andere manier volhouden. Dit leidt weer tot veel herhaling als we deze logica indien nodig willen implementeren in verschillende modules en klassen van onze applicatie.


De belangrijkste concepten

Repository voor Data Retrieval

Een repository kan op twee manieren werken: gegevens ophalen en gegevenspersistentie.


Wanneer gebruikt om objecten uit persistentie op te halen, wordt een repository aangeroepen met een aangepaste query. Deze query kan een specifieke methode zijn op naam of een meer gebruikelijke methode met parameters. De repository is verantwoordelijk voor het leveren en implementeren van deze zoekmethoden. Wanneer een dergelijke methode wordt aangeroepen, neemt de repository contact op met de gateway om de onbewerkte gegevens van de persistentie op te halen. De gateway levert onbewerkte objectgegevens (zoals een array met waarden). Vervolgens neemt de repository deze gegevens, voert de nodige transformaties uit en roept de juiste fabrieksmethoden aan. De fabrieken leveren de objecten die zijn opgebouwd met de gegevens die door de repository worden verstrekt. De repository verzamelt deze objecten en retourneert ze als een set objecten (zoals een array van objecten of een verzameling object zoals gedefinieerd in de les Composite Pattern in de cursus Agile Design Patterns).

Repository voor Data Persistence

De tweede manier waarop een repository kan werken, is om de logica te bieden die nodig is om de informatie uit een object te extraheren en deze voort te zetten. Dit kan net zo eenvoudig zijn als het serialiseren van het object en het verzenden van de geserialiseerde gegevens naar de gateway om het voort te zetten of zo verfijnd als het maken van arrays van informatie met alle velden en status van een object.


Wanneer gebruikt om informatie vast te houden, is de clientklasse degene die rechtstreeks communiceert met de fabriek. Stel je een scenario voor wanneer een nieuwe opmerking in een blogbericht wordt geplaatst. Een Commentaar-object wordt gemaakt door onze bedrijfslogica (de klasse Client) en vervolgens verzonden naar de Repository om aan te houden. De repository zal de objecten met behulp van de gateway behouden en desgewenst cachen in een lokaal in de geheugenlijst. Gegevens moeten worden getransformeerd, omdat er slechts zeldzame gevallen zijn waarin echte objecten direct in een persistentiesysteem kunnen worden opgeslagen.


De punten verbinden

De onderstaande afbeelding is een weergave op een hoger niveau over het integreren van de repository tussen de fabrieken, de gateway en de client.


In het midden van het schema bevindt zich onze repository. Aan de linkerkant is een interface voor de gateway, een implementatie en de persistentie zelf. Aan de rechterkant is er een interface voor de fabrieken en een fabrieksimplementatie. Ten slotte is er bovenaan de clientklasse.

Zoals het kan worden waargenomen vanuit de richting van de pijlen, zijn de afhankelijkheden omgekeerd. Repository is alleen afhankelijk van de abstracte interfaces voor fabrieken en gateways. Gateway is afhankelijk van de interface en de persistentie die het biedt. De fabriek is alleen afhankelijk van de interface. De client is afhankelijk van Repository, wat acceptabel is omdat de Repository minder concreet is dan de Client.


In het verlengde daarvan respecteert de bovenstaande paragraaf onze architectuur op hoog niveau en de richting van afhankelijkheden die we willen bereiken.


Reacties beheren op blogposts met een repository

Nu we de theorie hebben gezien, is het tijd voor een praktisch voorbeeld. Stel je voor dat we een blog hebben waarop we objecten en opmerkingen kunnen plaatsen. Opmerkingen horen bij Berichten en we moeten een manier vinden om ze te behouden en op te halen.

De reactie

We zullen beginnen met een test die ons zal dwingen na te denken over wat ons commentaar-object zou moeten bevatten.

class RepositoryTest breidt PHPUnit_Framework_TestCase uit function testACommentHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe heeft een mening over het repositorypatroon"; $ commentBody = "Ik denk dat het een goed idee is om het repositorypatroon te gebruiken om objecten te behouden en op te halen."; $ comment = new Comment ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); 

Op het eerste gezicht is een opmerking slechts een gegevensobject. Het heeft misschien geen functionaliteit, maar dat is aan de context van onze applicatie om te beslissen. Neem voor dit voorbeeld aan dat het om een ​​eenvoudig gegevensobject gaat. Geconstrueerd met een reeks variabelen.

class Reactie 

Gewoon door een lege klasse te maken en deze in de test te laten slagen.

require_once '... /Comment.php'; class RepositoryTest breidt PHPUnit_Framework_TestCase uit [...]

Maar dat is verre van perfect. Onze test test nog niets. Laten we onszelf dwingen om alle getters in de Comment-klasse te schrijven.

function testACommentsHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe heeft een mening over het repositorypatroon"; $ commentBody = "Ik denk dat het een goed idee is om het repositorypatroon te gebruiken om objecten te behouden en op te halen."; $ comment = new Comment ($ 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 ()); 

Om de lengte van de tutorial te regelen, heb ik alle beweringen in één keer geschreven en we zullen ze ook meteen implementeren. In het echte leven, neem ze een voor een.

 class Comment private $ postId; privé $ auteur; privé $ authorEmail; privé $ onderwerp; privé $ body; function __construct ($ postId, $ author, $ authorEmail, $ subject, $ body) $ this-> postId = $ postId; $ this-> author = $ author; $ this-> authorEmail = $ authorEmail; $ this-> subject = $ subject; $ this-> body = $ body;  openbare functie getPostId () return $ this-> postId;  openbare functie getAuthor () return $ this-> author;  openbare functie getAuthorEmail () return $ this-> authorEmail;  openbare functie getSubject () return $ this-> onderwerp;  openbare functie getBody () return $ this-> body; 

Met uitzondering van de lijst met privévariabelen, werd de rest van de code gegenereerd door mijn IDE, NetBeans, dus het testen van de automatisch gegenereerde code kan soms een beetje overhead zijn. Als u deze regels niet alleen schrijft, neem dan gerust contact met hen op en doe geen moeite met tests voor setters en constructeurs. Desalniettemin heeft de test ons geholpen onze ideeën beter te ontmaskeren en beter te documenteren wat onze Commentaarklasse zal bevatten.

We kunnen deze testmethoden en testklassen ook beschouwen als onze "Client" -klassen uit de schema's.


Onze gateway naar persistentie

Om dit voorbeeld zo eenvoudig mogelijk te houden, zullen we alleen een InMemoryPersistence implementeren, zodat we ons bestaan ​​niet compliceren met bestandssystemen of databases.

require_once '... /InMemoryPersistence.php'; class InMemoryPersistenceTest breidt PHPUnit_Framework_TestCase uit function testItCanPerisistAndRetrieveASingleDataArray () $ data = array ('data'); $ persistence = new InMemoryPersistence (); $ Persistence-> aanhouden ($ data); $ this-> assertEquals ($ data, $ persistence-> retrieve (0)); 

Zoals gewoonlijk beginnen we met de eenvoudigste test die mogelijk kan mislukken en ons ook dwingen om wat code te schrijven. Deze test maakt een nieuw InMemoryPersistence object en probeert om een ​​geroepen array voort te zetten en op te halen gegevens.

require_once __DIR__. '/Persistence.php'; klasse InMemoryPersistence implementeert Persistence private $ data = array (); functie persist ($ data) $ this-> data = $ data;  functie ophalen ($ id) return $ this-> data; 

De eenvoudigste code om het te laten passeren is gewoon om de inkomende te houden $ data in een privévariabele en stuur het terug in de terugvinden methode. De code zoals deze nu is geeft niet om de verzonden gegevens $ id variabel. Het is het eenvoudigste dat de test kan laten slagen. We hebben ook de vrijheid genomen om een ​​interface te introduceren en implementeren volharding.

interface Persistentie functie persist ($ data); functie ophalen ($ id's); 

Deze interface definieert de twee methoden die door een gateway moeten worden geïmplementeerd. aanhouden en terugvinden. Zoals u waarschijnlijk al vermoedde, is onze Gateway de onze InMemoryPersistence klasse en onze fysieke persistentie is de privévariabele die onze gegevens in het geheugen vasthoudt. Maar laten we teruggaan naar de implementatie hiervan in geheugenpersistentie.

function testItCanPerisistSeveralElementsAndRetrieveAnyOfThem () $ data1 = array ('data1'); $ data2 = array ('data2'); $ persistence = new InMemoryPersistence (); $ Persistence-> aanhouden ($ data1); $ Persistence-> aanhouden ($ gegevens2); $ this-> assertEquals ($ data1, $ persistence-> retrieve (0)); $ this-> assertEquals ($ data2, $ persistence-> retrieve (1)); 

We hebben nog een test toegevoegd. In deze houden we twee verschillende gegevensreeksen aan. We verwachten elk van hen afzonderlijk te kunnen terugvinden.

require_once __DIR__. '/Persistence.php'; klasse InMemoryPersistence implementeert Persistence private $ data = array (); functie persist ($ data) $ this-> data [] = $ data;  functie ophalen ($ id) return $ this-> data [$ id]; 

De test dwong ons om onze code enigszins aan te passen. We moeten nu gegevens aan onze array toevoegen, niet alleen deze vervangen door degene die is verzonden naar aanhoudt (). We moeten ook de $ id parameter en retourneer het element op die index.

Dit is genoeg voor onze InMemoryPersistence. Indien nodig kunnen we dit later aanpassen.


Onze fabriek

We hebben een Client (onze tests), een persistentie met een Gateway en commentaarobjecten om aan te houden. Het volgende ontbrekende ding is onze fabriek.

We begonnen onze codering met een RepositoryTest het dossier. Deze test heeft echter feitelijk een gemaakt Commentaar voorwerp. Nu moeten we tests maken om te controleren of onze fabriek in staat zal zijn om te creëren Commentaar voorwerpen. Het lijkt erop dat we een beoordelingsfout hebben gemaakt en onze test is waarschijnlijker een test voor onze aanstaande fabriek dan voor onze repository. We kunnen het naar een ander testbestand verplaatsen, CommentFactoryTest.

require_once '... /Comment.php'; class CommentFactoryTest breidt PHPUnit_Framework_TestCase uit function testACommentsHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe heeft een mening over het repositorypatroon"; $ commentBody = "Ik denk dat het een goed idee is om het repositorypatroon te gebruiken om objecten te behouden en op te halen."; $ comment = new Comment ($ 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 ()); 

Nu gaat deze test natuurlijk over. En hoewel het een correcte test is, kunnen we overwegen deze te wijzigen. We willen een maken Fabriek object, geef een array door en vraag deze om een ​​te maken Commentaar voor ons.

require_once '... /CommentFactory.php'; class CommentFactoryTest breidt PHPUnit_Framework_TestCase uit function testACommentsHasAllItsComposingParts () $ postId = 1; $ commentAuthor = "Joe"; $ commentAuthorEmail = "[email protected]"; $ commentSubject = "Joe heeft een mening over het repositorypatroon"; $ commentBody = "Ik denk dat het een goed idee is om het repositorypatroon te gebruiken om objecten te behouden en op te halen."; $ commentData = array ($ postId, $ commentAuthor, $ commentAuthorEmail, $ commentSubject, $ commentBody); $ comment = (nieuw 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 ()); 

We moeten onze klassen nooit noemen op basis van het ontwerppatroon dat ze implementeren, maar Fabriek en bewaarplaats vertegenwoordigen meer dan alleen het ontwerppatroon zelf. Ik heb persoonlijk niets tegen om deze twee woorden in de namen van onze klas op te nemen. Ik beveel echter nog steeds ten sterkste aan het concept van het niet benoemen van onze klassen na het ontwerppatroon dat we gebruiken voor de rest van de patronen, te respecteren.

Deze test is net iets anders dan de vorige, maar mislukt. Het probeert een te maken CommentFactory voorwerp. Die klasse bestaat nog niet. We proberen ook een te bellen maken() methode erop met een array die alle informatie van een opmerking als een array bevat. Deze methode is gedefinieerd in de Fabriek interface.

interface Factory function make ($ data); 

Dit is een heel gebruikelijk Fabriek interface. Het definieerde de enige vereiste methode voor een fabriek, de methode die de gewenste objecten daadwerkelijk maakt.

require_once __DIR__. '/Factory.php'; require_once __DIR__. '/Comment.php'; class CommentFactory implementeert Factory function make ($ components) return new Comment ($ components [0], $ components [1], $ components [2], $ components [3], $ components [4]); 

En CommentFactory implementeert de Fabriek interface succesvol door het nemen van de $ componenten parameter in zijn maken() methode, maakt en retourneert een nieuw Commentaar object met de informatie van daar.

We zullen onze persistentie en objectcreatielogica zo eenvoudig mogelijk houden. We kunnen, voor deze tutorial, veilig elke foutbehandeling, validatie en uitzonderingsworp negeren. We zullen hier stoppen met de implementatie van persistentie en het maken van objecten.


Gebruik een repository om reacties te behouden

Zoals we hierboven hebben gezien, kunnen we een repository op twee manieren gebruiken. Informatie ophalen van persistentie en ook informatie behouden op de persistentielaag. Met behulp van TDD is het meestal gemakkelijker om met het besparende (blijvende) deel van de logica te beginnen en vervolgens die bestaande implementatie te gebruiken om het ophalen van gegevens te testen.

require_once '... / ... / ... /vendor/autoload.php'; require_once '... /CommentRepository.php'; require_once '... /CommentFactory.php'; class RepositoryTest breidt PHPUnit_Framework_TestCase uit protected function tearDown () \ Mockery :: close ();  function testItCallsThePersistenceWhenAddingAComment () $ persistanceGateway = \ Mockery :: mock ('Persistence'); $ commentRepository = nieuwe CommentRepository ($ persistanceGateway); $ commentData = array (1, 'x', 'x', 'x', 'x'); $ comment = (nieuw CommentFactory ()) -> make ($ commentData); $ PersistanceGateway-> shouldReceive ( 'aanhouden') -> één keer () -> met ($ commentData); $ CommentRepository-> toe te voegen ($ reactie); 

We gebruiken spot om onze persistentie te bespotten en om dat bespotte object in de repository te injecteren. Dan bellen we toevoegen() op de repository. Deze methode heeft een parameter van het type Commentaar. We verwachten dat de persistentie wordt opgeroepen met een reeks vergelijkbare gegevens $ commentData.

require_once __DIR__. '/InMemoryPersistence.php'; class CommentRepository private $ persistence; function __construct (Persistence $ persistence = null) $ this-> persistence = $ persistence? : nieuwe InMemoryPersistence ();  functie add (Reactie $ comment) $ this-> persistence-> persist (array ($ comment-> getPostId (), $ comment-> getAuthor (), $ comment-> getAuthorEmail (), $ comment-> getSubject ( ), $ comment-> getBody ())); 

Zoals u kunt zien, de toevoegen() methode is behoorlijk slim. Het kapselt de kennis in over hoe een PHP-object kan worden omgezet in een gewone array die bruikbaar is door de persistentie. Vergeet niet dat onze persistentie-gateway meestal een algemeen object is voor al onze gegevens. Het kan en zal alle gegevens van onze applicatie behouden, dus het verzenden van objecten zou het te veel doen zijn: zowel conversie als effectieve persistentie.

Wanneer je een hebt InMemoryPersistence Klasse zoals wij doen, het is erg snel. We kunnen het gebruiken als alternatief voor het bespotten van de gateway.

function testAPersistedCommentCanBeRetrievedFromTheGateway () $ persistanceGateway = new InMemoryPersistence (); $ commentRepository = nieuwe CommentRepository ($ persistanceGateway); $ commentData = array (1, 'x', 'x', 'x', 'x'); $ comment = (nieuw CommentFactory ()) -> make ($ commentData); $ CommentRepository-> toe te voegen ($ reactie); $ this-> assertEquals ($ commentData, $ persistanceGateway-> retrieve (0)); 

Natuurlijk, als je geen persistentie in het geheugen hebt, is moppen de enige redelijke manier om te gaan. Anders zal je test gewoon te langzaam zijn om praktisch te zijn.

function testItCanAddMultipleCommentsAtOnce () $ persistanceGateway = \ Mockery :: mock ('Persistence'); $ commentRepository = nieuwe CommentRepository ($ persistanceGateway); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (nieuw CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (2, 'y', 'y', 'y', 'y'); $ comment2 = (nieuw CommentFactory ()) -> make ($ commentData2); $ PersistanceGateway-> shouldReceive ( 'aanhouden') -> één keer () -> met ($ commentData1); $ PersistanceGateway-> shouldReceive ( 'aanhouden') -> één keer () -> met ($ commentData2); $ commentRepository-> add (array ($ comment1, $ comment2)); 

Onze volgende logische stap is om een ​​manier te implementeren om meerdere opmerkingen tegelijkertijd toe te voegen. Uw project vereist mogelijk deze functionaliteit niet en het is niet iets dat door het patroon wordt vereist. Het Repository-patroon zegt alleen dat het een aangepaste query- en persistentietaal zal bieden voor onze bedrijfslogica. Dus als onze bushiness-logica de behoefte voelt om meerdere opmerkingen tegelijk toe te voegen, is de Repository de plaats waar de logica zich zou moeten bevinden.

functie add ($ commentData) if (is_array ($ commentData)) foreach ($ commentData as $ comment) $ this-> persistence-> persist (array ($ comment-> getPostId (), $ comment-> getAuthor (), $ comment-> getAuthorEmail (), $ comment-> getSubject (), $ comment-> getBody ())); else $ this-> persistence-> persist (array ($ commentData-> getPostId (), $ commentData-> getAuthor (), $ commentData-> getAuthorEmail (), $ commentData-> getSubject (), $ commentData-> getBody ( ))); 

En de eenvoudigste manier om de test te laten slagen, is door gewoon te verifiëren of de parameter die we krijgen een array is of niet. Als het een array is, zullen we elk element doorlopen en de persistentie oproepen met de array die we uit een enkele genereren Commentaar voorwerp. En hoewel deze code syntactisch correct is en de test doorgeeft, introduceert het een kleine duplicatie die we vrij gemakkelijk kunnen verwijderen.

functie add ($ commentData) if (is_array ($ commentData)) foreach ($ commentData as $ comment) $ this-> addOne ($ comment); else $ this-> addOne ($ commentata);  private functie addOne (Reactie $ commentaar) $ this-> persistence-> persist (array ($ comment-> getPostId (), $ comment-> getAuthor (), $ comment-> getAuthorEmail (), $ comment-> getSubject (), $ comment-> getBody ())); 

Als alle tests groen zijn, is het altijd tijd om te refactoren voordat we verdergaan met de volgende mislukkingstest. En dat deden we precies met de toevoegen() methode. We hebben de toevoeging van een enkele opmerking in een privémethode uitgepakt en deze op twee verschillende plaatsen in ons publiek genoemd toevoegen() methode. Dit verminderde niet alleen de duplicatie, maar opende ook de mogelijkheid om de Voeg een toe() methode openbaar en laat de bedrijfslogica beslissen of hij één of meerdere opmerkingen tegelijk wil toevoegen. Dit zou leiden tot een andere implementatie van onze repository, met een Voeg een toe() en een ander addMany () methoden. Het zou een volkomen legitieme implementatie van het Repository-patroon zijn.


Ophalen van reacties met onze repository

Een repository biedt een aangepaste querytaal voor de bedrijfslogica. Dus de namen en functionaliteiten van de query-methoden van een repository is enorm aan de vereisten van de bedrijfslogica. U bouwt uw repository terwijl u uw bedrijfslogica opbouwt, omdat u een andere aangepaste query-methode nodig hebt. Er zijn echter minstens één of twee methoden die u op bijna elke repository zult vinden.

function testItCanFindAllComments () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (nieuw CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (2, 'y', 'y', 'y', 'y'); $ comment2 = (nieuw CommentFactory ()) -> make ($ commentData2); $ Repository-> toe te voegen ($ comment1); $ Repository-> toe te voegen ($ comment2); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findAll ()); 

De eerste dergelijke methode wordt genoemd vind alle(). Dit zou alle objecten moeten retourneren waar de repository verantwoordelijk voor is, in ons geval Comments. De test is eenvoudig, we voegen een opmerking toe en vervolgens een nieuwe en uiteindelijk willen we bellen vind alle() en krijg een lijst met beide opmerkingen. Dit is echter niet mogelijk met onze InMemoryPersistence zoals het is op dit punt. Een kleine update is vereist.

functie retrieveAll () return $ this-> data; 

Dat is het. We hebben een toegevoegd retrieveAll () methode die alleen het geheel retourneert $ data array uit de klas. Eenvoudig en effectief. Het is tijd om te implementeren vind alle() op de CommentRepository nu.

function findAll () $ allCommentsData = $ this-> persistence-> retrievalAll (); $ comments = array (); foreach ($ allCommentsData als $ commentData) $ comments [] = $ this-> commentFactory-> make ($ commentData); $ reacties retourneren; 

vind alle() zal de retrieveAll () methode voor onze persistentie. Die methode biedt een onbewerkte array van gegevens. vind alle() zal door elk element lopen en de gegevens gebruiken zoals nodig om doorgegeven te worden aan de Fabriek. De fabriek zal er een leveren Commentaar een tijd. Een array met deze opmerkingen wordt aan het eind van de tijd gebouwd en geretourneerd vind alle(). Eenvoudig en effectief.

Een andere veel voorkomende methode die u op repositories vindt, is het zoeken naar een specifiek object of een groep objecten op basis van hun kenmerkende sleutel. Al onze opmerkingen zijn bijvoorbeeld verbonden aan een blogpost $ postId interne variabele. Ik kan me voorstellen dat we in de bedrijfslogica van onze blog bijna altijd alle opmerkingen over een blogpost willen vinden wanneer die blogpost wordt weergegeven. Dus een methode genaamd findByPostId ($ id) klinkt redelijk voor mij.

function testItCanFindCommentsByBlogPostId () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (nieuw CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (1, 'y', 'y', 'y', 'y'); $ comment2 = (nieuw CommentFactory ()) -> make ($ commentData2); $ commentData3 = array (3, 'y', 'y', 'y', 'y'); $ comment3 = (nieuw CommentFactory ()) -> make ($ commentData3); $ repository-> add (array ($ comment1, $ comment2)); $ Repository-> toe te voegen ($ comment3); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findByPostId (1)); 

We maken gewoon drie eenvoudige opmerkingen. De eerste twee hebben hetzelfde $ postId = 1, de derde heeft $ postID = 3. We voegen ze allemaal toe aan de repository en dan verwachten we een array met de eerste twee als we een a maken findByPostId () voor de $ postId = 1.

function findByPostId ($ postId) return array_filter ($ this-> findAll (), functie ($ comment) gebruik ($ postId) return $ comment-> getPostId () == $ postId;); 

De implementatie kan niet eenvoudiger zijn. We vinden alle opmerkingen met behulp van onze al geïmplementeerd vind alle() methode en we filteren de array. We kunnen niet de persistentie vragen om het filteren voor ons uit te voeren, dus we zullen het hier doen. De code zal elk opvragen Commentaar object en vergelijk het $ postId met degene die we als parameter hebben ingezonden. Super goed. De test gaat voorbij. Maar ik voel dat we iets hebben gemist.

function testItCanFindCommentsByBlogPostId () $ repository = new CommentRepository (); $ commentData1 = array (1, 'x', 'x', 'x', 'x'); $ comment1 = (nieuw CommentFactory ()) -> make ($ commentData1); $ commentData2 = array (1, 'y', 'y', 'y', 'y'); $ comment2 = (nieuw CommentFactory ()) -> make ($ commentData2); $ commentData3 = array (3, 'y', 'y', 'y', 'y'); $ comment3 = (nieuw CommentFactory ()) -> make ($ commentData3); $ repository-> add (array ($ comment1, $ comment2)); $ Repository-> toe te voegen ($ comment3); $ this-> assertEquals (array ($ comment1, $ comment2), $ repository-> findByPostId (1)); $ this-> assertEquals (array ($ comment3), $ repository-> findByPostId (3)); 

Het toevoegen van een tweede bewering om de derde opmerking te verkrijgen met de findByPostID () methode onthult onze fout. Wanneer u gemakkelijk extra paden of gevallen kunt testen, zoals in ons geval met een eenvoudige extra bewering, zou u dat moeten doen. Deze eenvoudige extra beweringen of testmethoden kunnen verborgen problemen on