├── .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 | [](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 |
--------------------------------------------------------------------------------