├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Application │ └── Service │ │ ├── LatestPostsFeedRequest.php │ │ └── LatestPostsFeedService.php ├── Domain │ └── Model │ │ ├── Body.php │ │ ├── CollectionPostRepository.php │ │ ├── PersistentPostRepository.php │ │ ├── Post.php │ │ ├── PostId.php │ │ ├── PostRepository.php │ │ └── PostSpecificationFactory.php └── Infrastructure │ └── Persistence │ ├── Doctrine │ ├── DoctrineLatestPostSpecification.php │ ├── DoctrinePostRepository.php │ ├── DoctrinePostSpecification.php │ ├── DoctrinePostSpecificationFactory.php │ ├── Mapping │ │ └── Domain.Model.Post.dcm.xml │ └── Types │ │ ├── BodyType.php │ │ └── PostIdType.php │ ├── InMemory │ ├── InMemoryLatestPostSpecification.php │ ├── InMemoryPostRepository.php │ ├── InMemoryPostSpecification.php │ └── InMemoryPostSpecificationFactory.php │ ├── Redis │ ├── RedisLatestPostSpecification.php │ ├── RedisPostRepository.php │ ├── RedisPostSpecification.php │ └── RedisPostSpecificationFactory.php │ └── Sql │ ├── SqlLatestPostSpecification.php │ ├── SqlPostRepository.php │ ├── SqlPostSpecification.php │ └── SqlPostSpecificationFactory.php └── tests ├── Application └── Service │ └── LatestPostsFeedServiceTest.php ├── Domain └── Model │ ├── BodyTest.php │ └── PostTest.php └── Infrastructure └── Persistence ├── CollectionPostRepositoryTest.php ├── Doctrine └── DoctrinePostRepositoryTest.php ├── InMemory └── InMemoryPostRepositoryTest.php ├── PersistentPostRepositoryTest.php ├── PostRepositoryTest.php ├── Redis └── RedisPostRepositoryTest.php └── Sql └── SqlPostRepositoryTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | bin/ 4 | vendor/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | services: 4 | - redis-server 5 | 6 | php: 7 | - 5.4 8 | - 5.5 9 | - 5.6 10 | 11 | before_script: 12 | - curl -s http://getcomposer.org/installer | php 13 | - php composer.phar install 14 | 15 | script: bin/phpunit 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Keyvan Akbary 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repository Examples 2 | 3 | [![Build Status](https://secure.travis-ci.org/dddinphp/repository-examples.svg?branch=master)](http://travis-ci.org/dddinphp/repository-examples) 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dddinphp/repository-examples", 3 | "description": "Repository examples", 4 | "keywords": ["repository", "ddd"], 5 | "license": "MIT", 6 | "minimum-stability": "stable", 7 | "authors": [ 8 | { 9 | "name": "Keyvan Akbary", 10 | "email": "kiwwito@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4", 15 | "doctrine/orm": "~2.4", 16 | "rhumsaa/uuid": "~2.8", 17 | "predis/predis": "~1.0" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "~4.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { "": "src/" } 24 | }, 25 | "config": { 26 | "bin-dir": "bin" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | tests 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Application/Service/LatestPostsFeedRequest.php: -------------------------------------------------------------------------------- 1 | since = $since; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Application/Service/LatestPostsFeedService.php: -------------------------------------------------------------------------------- 1 | postRepository = $postRepository; 19 | $this->postSpecificationFactory = $postSpecificationFactory; 20 | } 21 | 22 | /** 23 | * @param LatestPostsFeedRequest $request 24 | */ 25 | public function execute($request) 26 | { 27 | $posts = $this->postRepository->query( 28 | $this->postSpecificationFactory->createLatestPosts($request->since) 29 | ); 30 | 31 | return array_map(function(Post $post) { 32 | return [ 33 | 'id' => $post->id()->id(), 34 | 'content' => $post->body()->content(), 35 | 'created_at' => $post->createdAt() 36 | ]; 37 | }, $posts); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Domain/Model/Body.php: -------------------------------------------------------------------------------- 1 | setContent(trim($content)); 15 | } 16 | 17 | private function setContent($content) 18 | { 19 | $this->assertNotEmpty($content); 20 | $this->assertFitsLength($content); 21 | 22 | $this->content = $content; 23 | } 24 | 25 | private function assertNotEmpty($content) 26 | { 27 | if (empty($content)) { 28 | throw new \DomainException('Empty body'); 29 | } 30 | } 31 | 32 | private function assertFitsLength($content) 33 | { 34 | if (strlen($content) < self::MIN_LENGTH) { 35 | throw new \DomainException('Body is too sort'); 36 | } 37 | 38 | if (strlen($content) > self::MAX_LENGTH) { 39 | throw new \DomainException('Body is too long'); 40 | } 41 | } 42 | 43 | public function content() 44 | { 45 | return $this->content; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Domain/Model/CollectionPostRepository.php: -------------------------------------------------------------------------------- 1 | id = $anId; 19 | $this->body = $aBody; 20 | $this->createdAt = $createdAt ?: new \DateTime(); 21 | } 22 | 23 | public function editBody(Body $aNewBody) 24 | { 25 | if ($this->editExpired()) { 26 | throw new \RuntimeException('Edit time expired'); 27 | } 28 | 29 | $this->body = $aNewBody; 30 | } 31 | 32 | private function editExpired() 33 | { 34 | $expiringTime = $this->createdAt->getTimestamp() + self::EXPIRE_EDIT_TIME; 35 | 36 | return $expiringTime < time(); 37 | } 38 | 39 | public function id() 40 | { 41 | return $this->id; 42 | } 43 | 44 | public function body() 45 | { 46 | return $this->body; 47 | } 48 | 49 | public function createdAt() 50 | { 51 | return $this->createdAt; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Domain/Model/PostId.php: -------------------------------------------------------------------------------- 1 | id = $id ?: Uuid::uuid4()->toString(); 14 | } 15 | 16 | public function id() 17 | { 18 | return $this->id; 19 | } 20 | 21 | public function equals(PostId $anId) 22 | { 23 | return $this->id === $anId->id(); 24 | } 25 | 26 | public function __toString() 27 | { 28 | return $this->id; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Domain/Model/PostRepository.php: -------------------------------------------------------------------------------- 1 | since = $since; 14 | } 15 | 16 | public function buildQuery(EntityManager $em) 17 | { 18 | return $em->createQueryBuilder() 19 | ->select('p') 20 | ->from('Domain\Model\Post', 'p') 21 | ->where('p.createdAt > :since') 22 | ->setParameter(':since', $this->since) 23 | ->getQuery(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/Doctrine/DoctrinePostRepository.php: -------------------------------------------------------------------------------- 1 | em = $em; 17 | } 18 | 19 | public function add(Post $aPost) 20 | { 21 | $this->em->persist($aPost); 22 | } 23 | 24 | public function remove(Post $aPost) 25 | { 26 | $this->em->remove($aPost); 27 | } 28 | 29 | public function postOfId(PostId $anId) 30 | { 31 | return $this->em->find('Domain\Model\Post', $anId); 32 | } 33 | 34 | public function latestPosts(\DateTime $sinceADate) 35 | { 36 | return $this->em->createQueryBuilder() 37 | ->select('p') 38 | ->from('Domain\Model\Post', 'p') 39 | ->where('p.createdAt > :since') 40 | ->setParameter(':since', $sinceADate) 41 | ->getQuery() 42 | ->getResult(); 43 | } 44 | 45 | /** 46 | * @param DoctrinePostSpecification $specification 47 | * 48 | * @return Post[] 49 | */ 50 | public function query($specification) 51 | { 52 | return $specification->buildQuery($this->em)->getResult(); 53 | } 54 | 55 | public function nextIdentity() 56 | { 57 | return new PostId(); 58 | } 59 | 60 | public function size() 61 | { 62 | return $this->em->createQueryBuilder() 63 | ->select('count(p.id)') 64 | ->from('Domain\Model\Post', 'p') 65 | ->getQuery() 66 | ->getSingleScalarResult(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/Doctrine/DoctrinePostSpecification.php: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/Doctrine/Types/BodyType.php: -------------------------------------------------------------------------------- 1 | getVarcharTypeDeclarationSQL($fieldDeclaration); 14 | } 15 | 16 | /** 17 | * @param string $value 18 | * @return Body 19 | */ 20 | public function convertToPHPValue($value, AbstractPlatform $platform) 21 | { 22 | return new Body($value); 23 | } 24 | 25 | /** 26 | * @param Body $value 27 | */ 28 | public function convertToDatabaseValue($value, AbstractPlatform $platform) 29 | { 30 | return $value->content(); 31 | } 32 | 33 | public function getName() 34 | { 35 | return 'body'; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/Doctrine/Types/PostIdType.php: -------------------------------------------------------------------------------- 1 | getGuidTypeDeclarationSQL($fieldDeclaration); 14 | } 15 | 16 | /** 17 | * @param string $value 18 | * @return PostId 19 | */ 20 | public function convertToPHPValue($value, AbstractPlatform $platform) 21 | { 22 | return new PostId($value); 23 | } 24 | 25 | /** 26 | * @param PostId $value 27 | */ 28 | public function convertToDatabaseValue($value, AbstractPlatform $platform) 29 | { 30 | return $value->id(); 31 | } 32 | 33 | public function getName() 34 | { 35 | return 'post_id'; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/InMemory/InMemoryLatestPostSpecification.php: -------------------------------------------------------------------------------- 1 | since = $since; 14 | } 15 | 16 | public function specifies(Post $aPost) 17 | { 18 | return $aPost->createdAt() > $this->since; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/InMemory/InMemoryPostRepository.php: -------------------------------------------------------------------------------- 1 | posts[$aPost->id()->id()] = $aPost; 16 | } 17 | 18 | public function remove(Post $aPost) 19 | { 20 | unset($this->posts[$aPost->id()->id()]); 21 | } 22 | 23 | public function postOfId(PostId $anId) 24 | { 25 | if (isset($this->posts[$anId->id()])) { 26 | return $this->posts[$anId->id()]; 27 | } 28 | 29 | return null; 30 | } 31 | 32 | public function latestPosts(\DateTime $sinceADate) 33 | { 34 | return $this->filterPosts( 35 | function(Post $post) use ($sinceADate) { 36 | return $post->createdAt() > $sinceADate; 37 | } 38 | ); 39 | } 40 | 41 | private function filterPosts(callable $fn) 42 | { 43 | return array_values(array_filter($this->posts, $fn)); 44 | } 45 | 46 | /** 47 | * @param InMemoryPostSpecification $specification 48 | * 49 | * @return Post[] 50 | */ 51 | public function query($specification) 52 | { 53 | return $this->filterPosts( 54 | function(Post $post) use ($specification) { 55 | return $specification->specifies($post); 56 | } 57 | ); 58 | } 59 | 60 | public function nextIdentity() 61 | { 62 | return new PostId(); 63 | } 64 | 65 | public function size() 66 | { 67 | return count($this->posts); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/InMemory/InMemoryPostSpecification.php: -------------------------------------------------------------------------------- 1 | since = $since; 14 | } 15 | 16 | public function specifies(Post $aPost) 17 | { 18 | return $aPost->createdAt() > $this->since; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/Redis/RedisPostRepository.php: -------------------------------------------------------------------------------- 1 | client = $client; 17 | } 18 | 19 | public function save(Post $aPost) 20 | { 21 | $this->client->hset('posts', (string) $aPost->id(), serialize($aPost)); 22 | } 23 | 24 | public function remove(Post $aPost) 25 | { 26 | $this->client->hdel('posts', (string) $aPost->id()); 27 | } 28 | 29 | public function postOfId(PostId $anId) 30 | { 31 | if ($data = $this->client->hget('posts', (string) $anId)) { 32 | return unserialize($data); 33 | } 34 | 35 | return null; 36 | } 37 | 38 | public function latestPosts(\DateTime $sinceADate) 39 | { 40 | return $this->filterPosts( 41 | function(Post $post) use ($sinceADate) { 42 | return $post->createdAt() > $sinceADate; 43 | } 44 | ); 45 | } 46 | 47 | private function filterPosts(callable $fn) 48 | { 49 | return array_values(array_filter(array_map(function($data) { 50 | return unserialize($data); 51 | }, $this->client->hgetall('posts')), $fn)); 52 | } 53 | 54 | /** 55 | * @param RedisPostSpecification $specification 56 | * 57 | * @return Post[] 58 | */ 59 | public function query($specification) 60 | { 61 | return $this->filterPosts( 62 | function(Post $post) use ($specification) { 63 | return $specification->specifies($post); 64 | } 65 | ); 66 | } 67 | 68 | public function nextIdentity() 69 | { 70 | return new PostId(); 71 | } 72 | 73 | public function size() 74 | { 75 | return $this->client->hlen('posts'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/Redis/RedisPostSpecification.php: -------------------------------------------------------------------------------- 1 | since = $since; 12 | } 13 | 14 | public function toSqlClauses() 15 | { 16 | return "created_at > '" . $this->since->format('Y-m-d H:i:s') . "'"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Infrastructure/Persistence/Sql/SqlPostRepository.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 19 | } 20 | 21 | public function save(Post $aPost) 22 | { 23 | ($this->exist($aPost)) ? $this->update($aPost) : $this->insert($aPost); 24 | } 25 | 26 | private function exist(Post $aPost) 27 | { 28 | $count = $this 29 | ->execute('SELECT COUNT(*) FROM posts WHERE id = :id', [ 30 | ':id' => $aPost->id()->id() 31 | ]) 32 | ->fetchColumn(); 33 | 34 | return $count == 1; 35 | } 36 | 37 | private function insert(Post $aPost) 38 | { 39 | $sql = 'INSERT INTO posts (id, body, created_at) VALUES (:id, :body, :created_at)'; 40 | 41 | $this->execute($sql, [ 42 | 'id' => $aPost->id()->id(), 43 | 'body' => $aPost->body()->content(), 44 | 'created_at' => $aPost->createdAt()->format(self::DATE_FORMAT) 45 | ]); 46 | } 47 | 48 | private function update(Post $aPost) 49 | { 50 | $sql = 'UPDATE posts SET body = :body WHERE id = :id'; 51 | 52 | $this->execute($sql, [ 53 | 'id' => $aPost->id()->id(), 54 | 'body' => $aPost->body()->content() 55 | ]); 56 | } 57 | 58 | private function execute($sql, array $parameters) 59 | { 60 | $st = $this->pdo->prepare($sql); 61 | 62 | $st->execute($parameters); 63 | 64 | return $st; 65 | } 66 | 67 | public function remove(Post $aPost) 68 | { 69 | $this->execute('DELETE FROM posts WHERE id = :id', [ 70 | 'id' => $aPost->id()->id() 71 | ]); 72 | } 73 | 74 | public function postOfId(PostId $anId) 75 | { 76 | $st = $this->execute('SELECT * FROM posts WHERE id = :id', [ 77 | 'id' => $anId->id() 78 | ]); 79 | 80 | if ($row = $st->fetch(\PDO::FETCH_ASSOC)) { 81 | return $this->buildPost($row); 82 | } 83 | 84 | return null; 85 | } 86 | 87 | private function buildPost($row) 88 | { 89 | return new Post( 90 | new PostId($row['id']), 91 | new Body($row['body']), 92 | new \DateTime($row['created_at']) 93 | ); 94 | } 95 | 96 | public function latestPosts(\DateTime $sinceADate) 97 | { 98 | return $this->retrieveAll('SELECT * FROM posts WHERE created_at > :since_date', [ 99 | 'since_date' => $sinceADate->format(self::DATE_FORMAT) 100 | ]); 101 | } 102 | 103 | private function retrieveAll($sql, array $parameters = []) 104 | { 105 | $st = $this->pdo->prepare($sql); 106 | 107 | $st->execute($parameters); 108 | 109 | return array_map(function ($row) { 110 | return $this->buildPost($row); 111 | }, $st->fetchAll(\PDO::FETCH_ASSOC)); 112 | } 113 | 114 | /** 115 | * @param SqlPostSpecification $specification 116 | * 117 | * @return Post[] 118 | */ 119 | public function query($specification) 120 | { 121 | return $this->retrieveAll( 122 | 'SELECT * FROM posts WHERE ' . $specification->toSqlClauses() 123 | ); 124 | } 125 | 126 | public function nextIdentity() 127 | { 128 | return new PostId(); 129 | } 130 | 131 | public function size() 132 | { 133 | return 134 | $this->pdo 135 | ->query('SELECT COUNT(*) FROM posts') 136 | ->fetchColumn(); 137 | } 138 | 139 | public function initSchema() 140 | { 141 | $this->pdo->exec(<<latestPostsFeedService = new LatestPostsFeedService( 26 | $this->postRepository = new InMemoryPostRepository(), 27 | new InMemoryPostSpecificationFactory() 28 | ); 29 | } 30 | 31 | /** 32 | * @test 33 | */ 34 | public function shouldBuildAFeedFromLatestsPosts() 35 | { 36 | $this->addPost(1, 'first', '-2 hours'); 37 | $this->addPost(2, 'second', '-3 hours'); 38 | $this->addPost(3, 'third', '-5 hours'); 39 | 40 | $feed = $this->latestPostsFeedService->execute( 41 | new LatestPostsFeedRequest(new \DateTime('-4 hours')) 42 | ); 43 | 44 | $this->assertFeedContains([ 45 | ['id' => 1, 'content' => 'first'], 46 | ['id' => 2, 'content' => 'second'] 47 | ], $feed); 48 | } 49 | 50 | private function addPost($id, $content, $createdAt) 51 | { 52 | $this->postRepository->add(new Post( 53 | new PostId($id), 54 | new Body($content), 55 | new \DateTime($createdAt) 56 | )); 57 | } 58 | 59 | private function assertFeedContains($expected, $feed) 60 | { 61 | foreach ($expected as $index => $contents) { 62 | $this->assertArraySubset($contents, $feed[$index]); 63 | $this->assertNotNull($feed[$index]['created_at']); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Domain/Model/BodyTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('content', $body->content()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Domain/Model/PostTest.php: -------------------------------------------------------------------------------- 1 | createPost('old content'); 13 | 14 | $post->editBody(new Body('new content')); 15 | 16 | $this->assertEquals('new content', $post->body()->content()); 17 | } 18 | 19 | private function createPost($content, $createdAt = null) 20 | { 21 | return new Post(new PostId(), new Body($content), $createdAt); 22 | } 23 | 24 | /** 25 | * @test 26 | * @expectedException \RuntimeException 27 | */ 28 | public function editExpiredPostShouldThrowException() 29 | { 30 | $post = $this->createPost('content', new \DateTime('-1 year')); 31 | 32 | $post->editBody(new Body('new content')); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Infrastructure/Persistence/CollectionPostRepositoryTest.php: -------------------------------------------------------------------------------- 1 | postRepository->add($post); 16 | } 17 | 18 | /** 19 | * @test 20 | */ 21 | public function entityChangesShouldBePersistedAutomatically() 22 | { 23 | $post = $this->persistPost(); 24 | $post->editBody(new Body('new content')); 25 | 26 | $result = $this->postRepository->postOfId($post->id()); 27 | 28 | $this->assertEquals('new content', $result->body()->content()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Infrastructure/Persistence/Doctrine/DoctrinePostRepositoryTest.php: -------------------------------------------------------------------------------- 1 | addCustomTypes(); 17 | $em = $this->initEntityManager(); 18 | $this->initSchema($em); 19 | 20 | return new PrecociousDoctrinePostRepository($em); 21 | } 22 | 23 | private function addCustomTypes() 24 | { 25 | if (!Type::hasType('post_id')) { 26 | Type::addType('post_id', 'Infrastructure\Persistence\Doctrine\Types\PostIdType'); 27 | } 28 | 29 | if (!Type::hasType('body')) { 30 | Type::addType('body', 'Infrastructure\Persistence\Doctrine\Types\BodyType'); 31 | } 32 | } 33 | 34 | protected function initEntityManager() 35 | { 36 | return EntityManager::create( 37 | ['url' => 'sqlite:///:memory:'], 38 | Tools\Setup::createXMLMetadataConfiguration( 39 | [__DIR__ . '/../../../../src/Infrastructure/Persistence/Doctrine/Mapping'], 40 | $devMode = true 41 | ) 42 | ); 43 | } 44 | 45 | private function initSchema(EntityManager $em) 46 | { 47 | $tool = new Tools\SchemaTool($em); 48 | $tool->createSchema([ 49 | $em->getClassMetadata('Domain\Model\Post') 50 | ]); 51 | } 52 | 53 | protected function createLatestPostSpecification(\DateTime $since) 54 | { 55 | return new DoctrineLatestPostSpecification($since); 56 | } 57 | } 58 | 59 | class PrecociousDoctrinePostRepository extends DoctrinePostRepository 60 | { 61 | public function add(Post $aPost) 62 | { 63 | parent::add($aPost); 64 | 65 | $this->em->flush(); 66 | } 67 | 68 | public function remove(Post $aPost) 69 | { 70 | parent::remove($aPost); 71 | 72 | $this->em->flush(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Infrastructure/Persistence/InMemory/InMemoryPostRepositoryTest.php: -------------------------------------------------------------------------------- 1 | postRepository->save($post); 15 | } 16 | 17 | /** 18 | * @test 19 | */ 20 | public function itShouldReplacePersistedPost() 21 | { 22 | $post = $this->persistPost(); 23 | $this->persistPost('new content', null, $post->id()); 24 | 25 | $result = $this->postRepository->postOfId($post->id()); 26 | 27 | $this->assertEquals('new content', $result->body()->content()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Infrastructure/Persistence/PostRepositoryTest.php: -------------------------------------------------------------------------------- 1 | postRepository = $this->createPostRepository(); 17 | } 18 | 19 | abstract protected function createPostRepository(); 20 | 21 | /** 22 | * @test 23 | */ 24 | public function itShouldRemovePost() 25 | { 26 | $post = $this->persistPost(); 27 | 28 | $this->postRepository->remove($post); 29 | 30 | $this->assertPostExist($post->id()); 31 | } 32 | 33 | protected function persistPost($body = 'irrelevant body', \DateTime $createdAt = null, PostId $id = null) 34 | { 35 | $this->persist( 36 | $post = new Post( 37 | $id ?: $this->postRepository->nextIdentity(), 38 | new Body($body), 39 | $createdAt 40 | ) 41 | ); 42 | 43 | return $post; 44 | } 45 | 46 | abstract protected function persist(Post $post); 47 | 48 | private function assertPostExist($id) 49 | { 50 | $result = $this->postRepository->postOfId($id); 51 | $this->assertNull($result); 52 | } 53 | 54 | /** 55 | * @test 56 | */ 57 | public function itShouldFetchLatestPostsByMethod() 58 | { 59 | $this->assertPostsAreFetchedWith(function() { 60 | return $this->postRepository->latestPosts(new \DateTime('-24 hours')); 61 | }); 62 | } 63 | 64 | private function assertPostsAreFetchedWith(callable $fetchPostsFn) { 65 | $this->persistPost('a year ago', new \DateTime('-1 year')); 66 | $this->persistPost('a month ago', new \DateTime('-1 month')); 67 | $this->persistPost('few hours ago', new \DateTime('-3 hours')); 68 | $this->persistPost('few minutes ago', new \DateTime('-2 minutes')); 69 | 70 | $this->assertPostContentsEquals(['few hours ago', 'few minutes ago'], $fetchPostsFn()); 71 | } 72 | 73 | private function assertPostContentsEquals($expectedContents, array $posts) 74 | { 75 | $postContents = array_map(function(Post $post) { 76 | return $post->body()->content(); 77 | }, $posts); 78 | 79 | $this->assertEquals( 80 | array_diff($expectedContents, $postContents), 81 | array_diff($postContents, $expectedContents) 82 | ); 83 | } 84 | 85 | /** 86 | * @test 87 | */ 88 | public function sizeShouldMatchNumberOfStoredPosts() 89 | { 90 | $this->persistPost(); 91 | $this->persistPost(); 92 | 93 | $size = $this->postRepository->size(); 94 | 95 | $this->assertEquals(2, $size); 96 | } 97 | 98 | /** 99 | * @test 100 | */ 101 | public function itShouldFetchLatestPostsBySpecification() 102 | { 103 | $this->assertPostsAreFetchedWith(function() { 104 | return $this->postRepository->query( 105 | $this->createLatestPostSpecification(new \DateTime('-24 hours')) 106 | ); 107 | }); 108 | } 109 | 110 | abstract protected function createLatestPostSpecification(\DateTime $since); 111 | } 112 | -------------------------------------------------------------------------------- /tests/Infrastructure/Persistence/Redis/RedisPostRepositoryTest.php: -------------------------------------------------------------------------------- 1 | flushall(); 15 | 16 | return new RedisPostRepository($client); 17 | } 18 | 19 | protected function createLatestPostSpecification(\DateTime $since) 20 | { 21 | return new RedisLatestPostSpecification($since); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Infrastructure/Persistence/Sql/SqlPostRepositoryTest.php: -------------------------------------------------------------------------------- 1 | setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 16 | 17 | $repository = new SqlPostRepository($pdo); 18 | $repository->initSchema(); 19 | 20 | return $repository; 21 | } 22 | 23 | protected function createLatestPostSpecification(\DateTime $since) 24 | { 25 | return new SqlLatestPostSpecification($since); 26 | } 27 | } 28 | --------------------------------------------------------------------------------