├── app
├── config
│ ├── routing_prod.yml
│ ├── routing_dev.yml
│ ├── routing_test.yml
│ ├── services.yml
│ └── config.yml
├── console
└── AppKernel.php
├── tests
├── .env
├── bootstrap.php
└── SuperAwesome
│ └── Blog
│ └── Domain
│ └── Model
│ └── Post
│ └── Adapter
│ └── SuperAwesome
│ ├── Command
│ └── Handler
│ │ ├── CreatePostHandlerTest.php
│ │ ├── AbstractPostHandlerTest.php
│ │ ├── PostHandlerScenario.php
│ │ ├── TagPostHandlerTest.php
│ │ ├── UntagPostHandlerTest.php
│ │ └── PublishPostHandlerTest.php
│ ├── PostScenario.php
│ └── PostTest.php
├── .gitignore
├── .env.dist
├── src
└── SuperAwesome
│ ├── Common
│ ├── Domain
│ │ ├── Model
│ │ │ ├── AppliesRecordedEvents.php
│ │ │ ├── RecordsEvents.php
│ │ │ ├── EventSourcing.php
│ │ │ └── Adapter
│ │ │ │ └── Broadway
│ │ │ │ └── BroadwayModelRepository.php
│ │ └── ReadModel
│ │ │ └── Adapter
│ │ │ └── Broadway
│ │ │ └── PoorlyDesignedBroadwayDbalRepository.php
│ └── Infrastructure
│ │ ├── EventBus
│ │ ├── EventFromBusListener.php
│ │ ├── EventBus.php
│ │ ├── SimpleEventBus.php
│ │ └── SpyingEventBus.php
│ │ └── EventStore
│ │ ├── EventStore.php
│ │ ├── InMemoryEventStore.php
│ │ └── SpyingEventStore.php
│ ├── Symfony
│ └── BlogBundle
│ │ ├── SuperAwesomeBlogBundle.php
│ │ ├── Command
│ │ ├── CommandBusCommand.php
│ │ ├── UntagPostCommand.php
│ │ ├── CreatePostCommand.php
│ │ ├── TagPostCommand.php
│ │ ├── PostTagCountListCommand.php
│ │ ├── PostCategoryCountListCommand.php
│ │ ├── PostListCommand.php
│ │ ├── PublishPostCommand.php
│ │ ├── SchemaInitCommand.php
│ │ ├── AbstractSchemaEventStoreCommand.php
│ │ ├── SchemaEventStoreDropCommand.php
│ │ └── SchemaEventStoreCreateCommand.php
│ │ ├── DependencyInjection
│ │ └── SuperAwesomeBlogExtension.php
│ │ └── Resources
│ │ └── config
│ │ ├── read-model
│ │ ├── post-tag-count.xml
│ │ ├── published-post.xml
│ │ └── post-category-count.xml
│ │ └── model
│ │ └── post.xml
│ └── Blog
│ └── Domain
│ ├── Model
│ └── Post
│ │ ├── Command
│ │ ├── CreatePost.php
│ │ ├── Handler
│ │ │ ├── UntagPostHandler.php
│ │ │ ├── CreatePostHandler.php
│ │ │ ├── TagPostHandler.php
│ │ │ ├── PublishPostHandler.php
│ │ │ └── PostHandler.php
│ │ ├── TagPost.php
│ │ ├── UntagPost.php
│ │ └── PublishPost.php
│ │ ├── PostRepository.php
│ │ ├── Event
│ │ ├── PostWasCreated.php
│ │ ├── PostWasUntagged.php
│ │ ├── PostWasTagged.php
│ │ ├── PostWasCategorized.php
│ │ ├── PostWasUncategorized.php
│ │ └── PostWasPublished.php
│ │ ├── Adapter
│ │ ├── Broadway
│ │ │ ├── BroadwayPostRepository.php
│ │ │ └── BroadwayPostCommandHandler.php
│ │ └── SuperAwesome
│ │ │ └── SuperAwesomePostRepository.php
│ │ └── Post.php
│ └── ReadModel
│ ├── PublishedPost
│ ├── PublishedPostRepository.php
│ ├── Adapter
│ │ ├── Broadway
│ │ │ ├── BroadwayPublishedPostProjector.php
│ │ │ └── BroadwayPublishedPostRepository.php
│ │ └── SuperAwesome
│ │ │ └── Dbal
│ │ │ └── DbalPublishedPostRepository.php
│ ├── PublishedPostProjector.php
│ └── PublishedPost.php
│ ├── PostTagCount
│ ├── PostTagCountRepository.php
│ ├── PostTagCountProjector.php
│ ├── Adapter
│ │ ├── Broadway
│ │ │ ├── BroadwayPostTagCountProjector.php
│ │ │ └── BroadwayPostTagCountRepository.php
│ │ └── SuperAwesome
│ │ │ └── Redis
│ │ │ └── RedisPostTagCountRepository.php
│ └── PostTagCount.php
│ └── PostCategoryCount
│ ├── PostCategoryCountRepository.php
│ ├── PostCategoryCountProjector.php
│ ├── Adapter
│ ├── Broadway
│ │ ├── BroadwayPostCategoryCountProjector.php
│ │ └── BroadwayPostCategoryCountRepository.php
│ └── SuperAwesome
│ │ └── Eloquent
│ │ └── EloquentPostCategoryCountRepository.php
│ └── PostCategoryCount.php
├── phpunit.xml.dist
├── composer.json
└── README.md
/app/config/routing_prod.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/.env:
--------------------------------------------------------------------------------
1 | SYMFONY_ENV=test
2 | SYMFONY_DEBUG=1
3 |
4 | SYMFONY__SECRET=
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /app/cache/
2 | /app/data.sqlite
3 | /app/logs/
4 | /vendor/
5 | /.env
6 |
--------------------------------------------------------------------------------
/.env.dist:
--------------------------------------------------------------------------------
1 | SYMFONY_ENV=dev
2 | SYMFONY_DEBUG=1
3 |
4 | SYMFONY__SECRET=UNSAFE_DEFAULT
5 |
6 | SYMFONY__MONOLOG_ACTION_LEVEL=debug
7 |
8 | SYMFONY__ELASTICSEARCH_HOST=localhost
9 | SYMFONY__ELASTICSEARCH_PORT=9200
10 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Domain/Model/AppliesRecordedEvents.php:
--------------------------------------------------------------------------------
1 | id = $id;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Command/Handler/UntagPostHandler.php:
--------------------------------------------------------------------------------
1 | id = $id;
20 | $this->tag = $tag;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Command/UntagPost.php:
--------------------------------------------------------------------------------
1 | id = $id;
20 | $this->tag = $tag;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/PostRepository.php:
--------------------------------------------------------------------------------
1 | id);
13 |
14 | $this->getPostRepository()->save($post);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Command/Handler/TagPostHandler.php:
--------------------------------------------------------------------------------
1 | getPostRepository()->find($command->id);
12 | $post->addTag($command->tag);
13 |
14 | $this->getPostRepository()->save($post);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/CommandBusCommand.php:
--------------------------------------------------------------------------------
1 | getContainer()->get('broadway.command_handling.command_bus');
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PublishedPost/PublishedPostRepository.php:
--------------------------------------------------------------------------------
1 | getPostRepository()->find($command->id);
12 | $post->publish($command->title, $command->content, $command->category);
13 |
14 | $this->getPostRepository()->save($post);
15 | // TODO: Implement handle() method.
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Command/Handler/PostHandler.php:
--------------------------------------------------------------------------------
1 | postRepository = $postRepository;
17 | }
18 |
19 | /**
20 | * @return PostRepository
21 | */
22 | protected function getPostRepository()
23 | {
24 | return $this->postRepository;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Infrastructure/EventBus/SimpleEventBus.php:
--------------------------------------------------------------------------------
1 | listeners[] = $listener;
15 | }
16 |
17 | public function publish(array $events)
18 | {
19 | foreach ($events as $event) {
20 | foreach ($this->listeners as $listener) {
21 | $listener->handle($event);
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ./tests
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | src/Artensify
13 |
14 | src/SuperAwesome/Symfony/*Bundle/Resources
15 | src/SuperAwesome/Symfony/*Bundle/Tests
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
17 | $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';
18 |
19 | $kernel = new AppKernel($env, (bool)$debug);
20 | $application = new Application($kernel);
21 | $application->run($input);
22 |
23 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Command/PublishPost.php:
--------------------------------------------------------------------------------
1 | id = $id;
30 | $this->title = $title;
31 | $this->content = $content;
32 | $this->category = $category;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostTagCount/PostTagCountRepository.php:
--------------------------------------------------------------------------------
1 | id = $id;
17 | }
18 |
19 | /**
20 | * @return mixed The object instance
21 | */
22 | public static function deserialize(array $data)
23 | {
24 | return new static($data['id']);
25 | }
26 |
27 | /**
28 | * @return array
29 | */
30 | public function serialize(): array
31 | {
32 | return [
33 | 'id' => $this->id,
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Infrastructure/EventStore/InMemoryEventStore.php:
--------------------------------------------------------------------------------
1 | ensureEventsAreSetup($id);
12 |
13 | return $this->events[$id];
14 | }
15 |
16 | public function appendEvents($id, array $events)
17 | {
18 | $this->ensureEventsAreSetup($id);
19 |
20 | foreach ($events as $event) {
21 | $this->events[$id][] = $event;
22 | }
23 | }
24 |
25 | private function ensureEventsAreSetup($id)
26 | {
27 | if (isset($this->events[$id])) {
28 | return;
29 | }
30 |
31 | $this->events[$id] = [];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostTagCount/PostTagCountProjector.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
18 | }
19 |
20 | public function applyPostWasTagged(PostWasTagged $event)
21 | {
22 | $this->repository->increment($event->tag);
23 | }
24 |
25 | public function applyPostWasUntagged(PostWasUntagged $event)
26 | {
27 | $this->repository->decrement($event->tag);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostCategoryCount/PostCategoryCountProjector.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
18 | }
19 |
20 | public function applyPostWasCategorized(PostWasCategorized $event)
21 | {
22 | $this->repository->increment($event->category);
23 | }
24 |
25 | public function applyPostWasUncategorized(PostWasUncategorized $event)
26 | {
27 | $this->repository->decrement($event->category);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Event/PostWasUntagged.php:
--------------------------------------------------------------------------------
1 | id = $id;
22 | $this->tag = $tag;
23 | }
24 |
25 | /**
26 | * @return mixed The object instance
27 | */
28 | public static function deserialize(array $data)
29 | {
30 | return new static(
31 | $data['id'],
32 | $data['tag']
33 | );
34 | }
35 |
36 | /**
37 | * @return array
38 | */
39 | public function serialize()
40 | {
41 | return [
42 | 'id' => $this->id,
43 | 'tag' => $this->tag,
44 | ];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Event/PostWasTagged.php:
--------------------------------------------------------------------------------
1 | id = $id;
22 | $this->tag = $tag;
23 | }
24 |
25 | /**
26 | * @return mixed The object instance
27 | */
28 | public static function deserialize(array $data)
29 | {
30 | return new static(
31 | $data['id'],
32 | $data['tag']
33 | );
34 | }
35 |
36 | /**
37 | * @return array
38 | */
39 | public function serialize(): array
40 | {
41 | return [
42 | 'id' => $this->id,
43 | 'tag' => $this->tag,
44 | ];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/UntagPostCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:blog:post:untag');
14 | $this->setDescription('Untag a post.');
15 |
16 | $this->setDefinition([
17 | new InputArgument('id', InputArgument::REQUIRED, 'ID for the Post to be untagged'),
18 | new InputArgument('tag', InputArgument::REQUIRED, 'Tag'),
19 | ]);
20 | }
21 |
22 | protected function execute(InputInterface $input, OutputInterface $output)
23 | {
24 | $id = $input->getArgument('id');
25 | $tag = $input->getArgument('tag');
26 |
27 | // @TODO Implement
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/CreatePostCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:blog:post:create');
15 | $this->setDescription('Create post.');
16 |
17 | $this->setDefinition([
18 | new InputArgument('id', InputArgument::REQUIRED, 'ID for the new Post'),
19 | ]);
20 | }
21 |
22 | protected function execute(InputInterface $input, OutputInterface $output)
23 | {
24 | $id = $input->getArgument('id');
25 |
26 | $createPost = new CreatePost($id);
27 |
28 | $this->getCommandBus()->dispatch($createPost);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostTagCount/Adapter/Broadway/BroadwayPostTagCountProjector.php:
--------------------------------------------------------------------------------
1 | projector = $projector;
20 | }
21 |
22 | public function applyPostWasTagged(PostWasTagged $event)
23 | {
24 | $this->projector->applyPostWasTagged($event);
25 | }
26 |
27 | public function applyPostWasUntagged(PostWasUntagged $event)
28 | {
29 | $this->projector->applyPostWasUntagged($event);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Event/PostWasCategorized.php:
--------------------------------------------------------------------------------
1 | id = $id;
22 | $this->category = $category;
23 | }
24 |
25 | /**
26 | * @return mixed The object instance
27 | */
28 | public static function deserialize(array $data)
29 | {
30 | return new static(
31 | $data['id'],
32 | $data['category']
33 | );
34 | }
35 |
36 | /**
37 | * @return array
38 | */
39 | public function serialize(): array
40 | {
41 | return [
42 | 'id' => $this->id,
43 | 'category' => $this->category,
44 | ];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Event/PostWasUncategorized.php:
--------------------------------------------------------------------------------
1 | id = $id;
22 | $this->category = $category;
23 | }
24 |
25 | /**
26 | * @return mixed The object instance
27 | */
28 | public static function deserialize(array $data)
29 | {
30 | return new static(
31 | $data['id'],
32 | $data['category']
33 | );
34 | }
35 |
36 | /**
37 | * @return array
38 | */
39 | public function serialize(): array
40 | {
41 | return [
42 | 'id' => $this->id,
43 | 'category' => $this->category,
44 | ];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PublishedPost/Adapter/Broadway/BroadwayPublishedPostProjector.php:
--------------------------------------------------------------------------------
1 | projector = $projector;
20 | }
21 |
22 | public function applyPostWasCreated(PostWasCreated $event)
23 | {
24 | $this->projector->applyPostWasCreated($event);
25 | }
26 |
27 | public function applyPostWasPublished(PostWasPublished $event)
28 | {
29 | $this->projector->applyPostWasPublished($event);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dflydev/es-cqrs-broadway-workshop-one",
3 | "description": "Introduction to Event Sourcing and CQRS with Broadway",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Beau Simensen",
8 | "email": "beau@dflydev.com"
9 | }
10 | ],
11 | "autoload": {
12 | "psr-4": {
13 | "SuperAwesome\\": "src/SuperAwesome"
14 | }
15 | },
16 | "require": {
17 | "broadway/broadway": "^2",
18 | "vlucas/phpdotenv": "~1.1",
19 | "doctrine/doctrine-bundle": "~1.4",
20 | "doctrine/dbal": "~2.5",
21 | "symfony/symfony": "^2.8",
22 | "symfony/monolog-bundle": "^3.2",
23 | "predis/predis": "^1.0",
24 | "broadway/broadway-bundle": "^0.4.1",
25 | "broadway/event-store-dbal": "^0.2.1"
26 | },
27 | "autoload-dev": {
28 | "psr-4": {
29 | "SuperAwesome\\": "tests/SuperAwesome"
30 | }
31 | },
32 | "require-dev": {
33 | "phpunit/phpunit": "^7.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/Command/Handler/CreatePostHandlerTest.php:
--------------------------------------------------------------------------------
1 | markTestIncomplete('Post cannot be created.');
16 |
17 | $id = 'my-id';
18 |
19 | $this->scenario
20 | ->when(new CreatePost($id))
21 | ->then([
22 | new PostWasCreated($id),
23 | ])
24 | ;
25 | }
26 |
27 | protected function createCommandHandler(PostRepository $postRepository)
28 | {
29 | return new CreatePostHandler($postRepository);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Infrastructure/EventBus/SpyingEventBus.php:
--------------------------------------------------------------------------------
1 | eventBus = $eventBus;
20 | }
21 |
22 | public function subscribe(EventFromBusListener $listener)
23 | {
24 | $this->eventBus->subscribe($listener);
25 | }
26 |
27 | public function publish(array $events)
28 | {
29 | $this->eventBus->publish($events);
30 |
31 | foreach ($events as $event) {
32 | $this->recordedEvents[] = $event;
33 | }
34 | }
35 |
36 | public function getRecordedEvents()
37 | {
38 | return $this->recordedEvents;
39 | }
40 |
41 | public function clearRecordedEvents()
42 | {
43 | $this->recordedEvents = [];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Infrastructure/EventStore/SpyingEventStore.php:
--------------------------------------------------------------------------------
1 | eventStore = $eventStore;
20 | }
21 |
22 | public function getEvents($id)
23 | {
24 | return $this->eventStore->getEvents($id);
25 | }
26 |
27 | public function appendEvents($id, array $events)
28 | {
29 | $this->eventStore->appendEvents($id, $events);
30 |
31 | foreach ($events as $event) {
32 | $this->recordedEvents[] = $event;
33 | }
34 | }
35 |
36 | public function getRecordedEvents()
37 | {
38 | return $this->recordedEvents;
39 | }
40 |
41 | public function clearRecordedEvents()
42 | {
43 | $this->recordedEvents = [];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostCategoryCount/Adapter/Broadway/BroadwayPostCategoryCountProjector.php:
--------------------------------------------------------------------------------
1 | projector = $projector;
20 | }
21 |
22 | public function applyPostWasCategorized(PostWasCategorized $event)
23 | {
24 | $this->projector->applyPostWasCategorized($event);
25 | }
26 |
27 | public function applyPostWasUncategorized(PostWasUncategorized $event)
28 | {
29 | $this->projector->applyPostWasUncategorized($event);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/TagPostCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:blog:post:tag');
15 | $this->setDescription('Tag a post.');
16 |
17 | $this->setDefinition([
18 | new InputArgument('id', InputArgument::REQUIRED, 'ID for the Post to be tagged'),
19 | new InputArgument('tag', InputArgument::REQUIRED, 'Tag'),
20 | ]);
21 | }
22 |
23 | protected function execute(InputInterface $input, OutputInterface $output)
24 | {
25 | $id = $input->getArgument('id');
26 | $tag = $input->getArgument('tag');
27 |
28 | $tagPost = new TagPost($id, $tag);
29 |
30 | $this->getCommandBus()->dispatch($tagPost);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/PostTagCountListCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:blog:post:list-tag-counts');
15 | $this->setDescription('List post tag counts.');
16 | }
17 |
18 | protected function execute(InputInterface $input, OutputInterface $output)
19 | {
20 | /** @var $repository PostTagCountRepository */
21 | $repository = $this->getContainer()->get('superawesome.blog.domain.read_model.post_tag_count.repository');
22 |
23 | foreach ($repository->findAll() as $postTagCount) {
24 | $output->writeln(sprintf("%15s%3d", $postTagCount->getTag(), $postTagCount->getCount()));
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PublishedPost/PublishedPostProjector.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
18 | }
19 |
20 | public function applyPostWasCreated(PostWasCreated $event)
21 | {
22 | $publishedPost = new PublishedPost($event->id);
23 |
24 | $this->repository->save($publishedPost);
25 | }
26 |
27 | public function applyPostWasPublished(PostWasPublished $event)
28 | {
29 | $publishedPost = $this->repository->find($event->id);
30 |
31 | $publishedPost->title = $event->title;
32 | $publishedPost->content = $event->content;
33 | $publishedPost->category = $event->category;
34 |
35 | $this->repository->save($publishedPost);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/PostCategoryCountListCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:blog:post:list-category-counts');
15 | $this->setDescription('List post category counts.');
16 | }
17 |
18 | protected function execute(InputInterface $input, OutputInterface $output)
19 | {
20 | /** @var $repository PostCategoryCountRepository */
21 | $repository = $this->getContainer()->get('superawesome.blog.domain.read_model.post_category_count.repository');
22 |
23 | foreach ($repository->findAll() as $postCategoryCount) {
24 | $output->writeln(sprintf("%15s%3d", $postCategoryCount->getCategory(), $postCategoryCount->getCount()));
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Domain/Model/EventSourcing.php:
--------------------------------------------------------------------------------
1 | recordedEvents;
12 | }
13 |
14 | public function clearRecordedEvents()
15 | {
16 | $this->recordedEvents = [];
17 | }
18 |
19 | protected function recordEvent($event)
20 | {
21 | $this->handle($event);
22 |
23 | $this->recordedEvents[] = $event;
24 | }
25 |
26 | public function applyRecordedEvents(array $events)
27 | {
28 | foreach ($events as $event) {
29 | $this->handle($event);
30 | }
31 | }
32 |
33 | protected function handle($event)
34 | {
35 | $method = $this->getHandleMethod($event);
36 |
37 | if (! method_exists($this, $method)) {
38 | return;
39 | }
40 |
41 | $this->$method($event, $event);
42 | }
43 |
44 | private function getHandleMethod($event)
45 | {
46 | $classParts = explode('\\', get_class($event));
47 |
48 | return 'apply' . end($classParts);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/PostListCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:blog:post:list');
15 | $this->setDescription('List posts.');
16 | }
17 |
18 | protected function execute(InputInterface $input, OutputInterface $output)
19 | {
20 | /** @var $repository PublishedPostRepository */
21 | $repository = $this->getContainer()->get('superawesome.blog.domain.read_model.published_post.repository');
22 |
23 | foreach ($repository->findAll() as $publishedPost) {
24 | $output->writeln(sprintf("%-15s%-15s%-15s%15s",
25 | $publishedPost->id,
26 | $publishedPost->title,
27 | $publishedPost->content,
28 | $publishedPost->category
29 | ));
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/DependencyInjection/SuperAwesomeBlogExtension.php:
--------------------------------------------------------------------------------
1 | loadModels($loader);
20 | $this->loadReadModels($loader);
21 | }
22 |
23 | public function loadModels(LoaderInterface $loader)
24 | {
25 | $loader->load('model/post.xml');
26 | }
27 |
28 | public function loadReadModels(LoaderInterface $loader)
29 | {
30 | $loader->load('read-model/post-category-count.xml');
31 | $loader->load('read-model/post-tag-count.xml');
32 | $loader->load('read-model/published-post.xml');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/Command/Handler/AbstractPostHandlerTest.php:
--------------------------------------------------------------------------------
1 | scenario = new PostHandlerScenario(
25 | $this,
26 | $eventStore,
27 | $this->createCommandHandler($postRepository)
28 | );
29 | }
30 |
31 | abstract protected function createCommandHandler(PostRepository $postRepository);
32 | }
33 |
--------------------------------------------------------------------------------
/app/config/config.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | broadway.event_store.dbal.connection: 'default'
3 |
4 | framework:
5 | secret: %secret%
6 | router:
7 | resource: "%kernel.root_dir%/config/routing_%kernel.environment%.yml"
8 | strict_requirements: %kernel.debug%
9 | templating:
10 | engines: ['twig']
11 | profiler:
12 | enabled: %kernel.debug%
13 |
14 | monolog:
15 | handlers:
16 | main:
17 | type: fingers_crossed
18 | action_level: %monolog_action_level%
19 | handler: nested
20 | nested:
21 | type: stream
22 | path: "%kernel.logs_dir%/%kernel.environment%.log"
23 | level: debug
24 |
25 | doctrine:
26 | dbal:
27 | default_connection: default
28 | connections:
29 | default:
30 | driver: pdo_sqlite
31 | path: "%kernel.root_dir%/data.sqlite"
32 | #driver: %doctrine_dbal_db_driver%
33 | #dbname: %doctrine_dbal_db_name%
34 | #user: %doctrine_dbal_db_user%
35 | #password: %doctrine_dbal_db_password%
36 | #host: %doctrine_dbal_db_host%
37 |
38 | broadway:
39 | command_handling:
40 | logger: false
41 | event_store: "my_dbal_event_store"
42 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostTagCount/Adapter/SuperAwesome/Redis/RedisPostTagCountRepository.php:
--------------------------------------------------------------------------------
1 | redis = $redis;
18 | }
19 |
20 | public function increment($tag)
21 | {
22 | $this->redis->hincrby(static::KEY, $tag, 1);
23 | }
24 |
25 | public function decrement($tag)
26 | {
27 | $this->redis->hincrby(static::KEY, $tag, -1);
28 | }
29 |
30 | public function find($tag)
31 | {
32 | $count = $this->redis->hget(static::KEY, $tag);
33 | if (is_null($count)) {
34 | return null;
35 | }
36 |
37 | return new PostTagCount($tag, $count);
38 | }
39 |
40 | public function findAll()
41 | {
42 | $results = [];
43 | foreach ($this->redis->hgetall(static::KEY) as $tag => $count) {
44 | $results[] = new PostTagCount($tag, $count);
45 | }
46 |
47 | return $results;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Event/PostWasPublished.php:
--------------------------------------------------------------------------------
1 | id = $id;
32 | $this->title = $title;
33 | $this->content = $content;
34 | $this->category = $category;
35 | }
36 |
37 | /**
38 | * @return mixed The object instance
39 | */
40 | public static function deserialize(array $data)
41 | {
42 | return new static(
43 | $data['id'],
44 | $data['title'],
45 | $data['content'],
46 | $data['category']
47 | );
48 | }
49 |
50 | /**
51 | * @return array
52 | */
53 | public function serialize(): array
54 | {
55 | return [
56 | 'id' => $this->id,
57 | 'title' => $this->title,
58 | 'content' => $this->content,
59 | 'category' => $this->category,
60 | ];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PublishedPost/Adapter/Broadway/BroadwayPublishedPostRepository.php:
--------------------------------------------------------------------------------
1 | broadwayRepository = $broadwayRepository;
22 | }
23 |
24 | /**
25 | * @param string $id
26 | *
27 | * @return PublishedPost
28 | */
29 | public function find($id)
30 | {
31 | return $this->broadwayRepository->find($id);
32 | }
33 |
34 | /**
35 | * @return PublishedPost[]
36 | */
37 | public function findAll()
38 | {
39 | return $this->broadwayRepository->findAll();
40 | }
41 |
42 | /**
43 | * @param PublishedPost $publishedPost
44 | *
45 | * @return void
46 | */
47 | public function save(PublishedPost $publishedPost)
48 | {
49 | $this->broadwayRepository->save($publishedPost);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/PublishPostCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:blog:post:publish');
15 | $this->setDescription('Publish post.');
16 |
17 | $this->setDefinition([
18 | new InputArgument('id', InputArgument::REQUIRED, 'ID for the Post to be published'),
19 | new InputArgument('title', InputArgument::REQUIRED, 'Published title'),
20 | new InputArgument('content', InputArgument::REQUIRED, 'Published content'),
21 | new InputArgument('category', InputArgument::REQUIRED, 'Published category'),
22 | ]);
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output)
26 | {
27 | $id = $input->getArgument('id');
28 | $title = $input->getArgument('title');
29 | $content = $input->getArgument('content');
30 | $category = $input->getArgument('category');
31 |
32 | $publishPost = new PublishPost($id, $title, $content, $category);
33 |
34 | $this->getCommandBus()->dispatch($publishPost);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Adapter/Broadway/BroadwayPostRepository.php:
--------------------------------------------------------------------------------
1 | eventSourcingRepository = $eventSourcingRepository;
22 | }
23 |
24 | /**
25 | * @param string $id
26 | *
27 | * @return Post
28 | */
29 | public function find($id)
30 | {
31 | return $this->eventSourcingRepository->load($id);
32 | }
33 |
34 | /**
35 | * @return Post[]
36 | */
37 | public function findAll()
38 | {
39 | // TODO: Implement findAll() method.
40 | }
41 |
42 | /**
43 | * @param Post $post
44 | */
45 | public function save(Post $post)
46 | {
47 | $this->eventSourcingRepository->save($post);
48 | }
49 |
50 | protected static function getAggregateRootClass()
51 | {
52 | return Post::class;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostCategoryCount/Adapter/SuperAwesome/Eloquent/EloquentPostCategoryCountRepository.php:
--------------------------------------------------------------------------------
1 | $category,
15 | ]);
16 | } catch (\Exception $e) {
17 | return null;
18 | }
19 | }
20 |
21 | public function findAll()
22 | {
23 | return PostCategoryCount::get();
24 | }
25 |
26 | public function increment($category)
27 | {
28 | DB::transactional(function () use ($category) {
29 | $post_category_count = PostCategoryCount::firstOrNew([
30 | 'category' => $category,
31 | ]);
32 |
33 | $post_category_count->category_count++;
34 | $post_category_count->save();
35 | });
36 | }
37 |
38 | public function decrement($category)
39 | {
40 | DB::transactional(function () use ($category) {
41 | $post_category_count = PostCategoryCount::firstOrNew([
42 | 'category' => $category,
43 | ]);
44 |
45 | $post_category_count->category_count--;
46 | $post_category_count->save();
47 | });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Domain/Model/Adapter/Broadway/BroadwayModelRepository.php:
--------------------------------------------------------------------------------
1 | id = $id;
35 | $this->title = $title;
36 | $this->content = $content;
37 | $this->category = $category;
38 | }
39 |
40 | /**
41 | * @return mixed The object instance
42 | */
43 | public static function deserialize(array $data)
44 | {
45 | return new static(
46 | $data['id'],
47 | $data['title'],
48 | $data['content'],
49 | $data['category']
50 | );
51 | }
52 |
53 | /**
54 | * @return array
55 | */
56 | public function serialize(): array
57 | {
58 | return [
59 | 'id' => $this->id,
60 | 'title' => $this->title,
61 | 'content' => $this->content,
62 | 'category' => $this->category,
63 | ];
64 | }
65 |
66 | /**
67 | * @return string
68 | */
69 | public function getId(): string {
70 | return (string) $this->id;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/SuperAwesomePostRepository.php:
--------------------------------------------------------------------------------
1 | eventStore = $eventStore;
25 | $this->eventBus = $eventBus;
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | public function find($id)
32 | {
33 | $recordedEvents = $this->eventStore->getEvents($id);
34 | $post = Post::instantiateForReconstitution();
35 | $post->applyRecordedEvents($recordedEvents);
36 |
37 | return $post;
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function findAll()
44 | {
45 | throw new \RuntimeException('Not implemented. (and will never be implemented!)');
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function save(Post $post)
52 | {
53 | $recordedEvents = $post->getRecordedEvents();
54 |
55 | $this->eventStore->appendEvents(
56 | $post->getId(),
57 | $recordedEvents
58 | );
59 |
60 | $this->eventBus->publish($recordedEvents);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostTagCount/PostTagCount.php:
--------------------------------------------------------------------------------
1 | tag = $tag;
27 | $this->count = $count;
28 | }
29 |
30 | /**
31 | * @return string
32 | */
33 | public function getTag()
34 | {
35 | return $this->tag;
36 | }
37 |
38 | /**
39 | * @param string $tag
40 | */
41 | public function setTag($tag)
42 | {
43 | $this->tag = $tag;
44 | }
45 |
46 | /**
47 | * @return int
48 | */
49 | public function getCount()
50 | {
51 | return $this->count;
52 | }
53 |
54 | /**
55 | * @param int $count
56 | */
57 | public function setCount($count)
58 | {
59 | $this->count = $count;
60 | }
61 |
62 | /**
63 | * @return mixed The object instance
64 | */
65 | public static function deserialize(array $data)
66 | {
67 | return new static($data['tag'], $data['count']);
68 | }
69 |
70 | /**
71 | * @return array
72 | */
73 | public function serialize(): array
74 | {
75 | return [
76 | 'tag' => $this->tag,
77 | 'count' => $this->count,
78 | ];
79 | }
80 |
81 | /**
82 | * @return string
83 | */
84 | public function getId(): string {
85 | return (string) $this->id;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/PostScenario.php:
--------------------------------------------------------------------------------
1 | testCase = $testCase;
25 | }
26 |
27 | /**
28 | * @param array $givens
29 | *
30 | * @return self
31 | */
32 | public function given(array $givens = [])
33 | {
34 | if (! $givens) {
35 | $this->post = null;
36 |
37 | return $this;
38 | }
39 |
40 | /** @var Post|RecordsEvents|AppliesRecordedEvents $post */
41 | $post = Post::instantiateForReconstitution();
42 | $post->applyRecordedEvents($givens);
43 |
44 | $this->post = $post;
45 |
46 | return $this;
47 | }
48 |
49 | public function when($when)
50 | {
51 | if (! is_callable($when)) {
52 | return $this;
53 | }
54 |
55 | if ($this->post) {
56 | $when($this->post);
57 | } else {
58 | $this->post = $when(null);
59 | }
60 |
61 | return $this;
62 | }
63 |
64 | public function then(array $thens)
65 | {
66 | $this->testCase->assertEquals(
67 | $thens,
68 | $this->post->getRecordedEvents()
69 | );
70 |
71 | $this->post->clearRecordedEvents();
72 |
73 | return $this;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/AppKernel.php:
--------------------------------------------------------------------------------
1 | getEnvironment(), array('dev', 'test'))) {
21 | $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
22 | }
23 |
24 | return $bundles;
25 | }
26 |
27 | public function registerContainerConfiguration(LoaderInterface $loader)
28 | {
29 | $loader->load(__DIR__ . '/config/config.yml');
30 | $loader->load(__DIR__ . '/config/services.yml');
31 |
32 | $envParameters = $this->getEnvParameters();
33 | $loader->load(function (ContainerBuilder $container) use ($envParameters) {
34 | $container->getParameterBag()->add($envParameters);
35 | });
36 |
37 | if (in_array($this->getEnvironment(), array('dev', 'test'))) {
38 | $loader->load(function (ContainerBuilder $container) {
39 | $container->loadFromExtension('web_profiler', array(
40 | 'toolbar' => true,
41 | ));
42 |
43 | $container->loadFromExtension('framework', array(
44 | 'test' => true,
45 | ));
46 | });
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/SchemaInitCommand.php:
--------------------------------------------------------------------------------
1 | setName('superawesome:schema:init')
19 | ->setDescription('Creates the read model schema')
20 | ;
21 | }
22 |
23 | /**
24 | * {@inheritDoc}
25 | */
26 | protected function execute(InputInterface $input, OutputInterface $output)
27 | {
28 | $connection = $this->getDoctrineConnection('default');
29 |
30 | $error = false;
31 | try {
32 | $schemaManager = $connection->getSchemaManager();
33 | $schema = $schemaManager->createSchema();
34 |
35 | $table = PoorlyDesignedBroadwayDbalRepository::configureSchema($schema);
36 | if (null !== $table) {
37 | $schemaManager->createTable($table);
38 | $output->writeln('Created poorly designed dbal read model schema');
39 | } else {
40 | $output->writeln('Poorly designed dbal read model schema already exists');
41 | }
42 | } catch (\Exception $e) {
43 | $output->writeln('Could not create poorly designed dbal read model schema');
44 | $output->writeln(sprintf('%s', $e->getMessage()));
45 | $error = true;
46 | }
47 |
48 | return $error ? 1 : 0;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostCategoryCount/PostCategoryCount.php:
--------------------------------------------------------------------------------
1 | category = $category;
27 | $this->count = $count;
28 | }
29 |
30 | /**
31 | * @return string
32 | */
33 | public function getCategory()
34 | {
35 | return $this->category;
36 | }
37 |
38 | /**
39 | * @param string $category
40 | */
41 | public function setCategory($category)
42 | {
43 | $this->category = $category;
44 | }
45 |
46 | /**
47 | * @return int
48 | */
49 | public function getCount()
50 | {
51 | return $this->count;
52 | }
53 |
54 | /**
55 | * @param int $count
56 | */
57 | public function setCount($count)
58 | {
59 | $this->count = $count;
60 | }
61 |
62 | /**
63 | * @return string
64 | */
65 | public function getId(): string
66 | {
67 | return $this->category;
68 | }
69 |
70 | /**
71 | * @return mixed The object instance
72 | */
73 | public static function deserialize(array $data)
74 | {
75 | return new static($data['category'], $data['count']);
76 | }
77 |
78 | /**
79 | * @return array
80 | */
81 | public function serialize(): array
82 | {
83 | return [
84 | 'category' => $this->category,
85 | 'count' => $this->count,
86 | ];
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/Command/Handler/PostHandlerScenario.php:
--------------------------------------------------------------------------------
1 | testCase = $testCase;
23 | $this->eventStore = $eventStore;
24 | $this->commandHandler = $commandHandler;
25 | }
26 |
27 | /**
28 | * @param $id
29 | *
30 | * @return PostHandlerScenario
31 | */
32 | public function withId($id)
33 | {
34 | $this->id = $id;
35 |
36 | return $this;
37 | }
38 |
39 | /**
40 | * @param array $events
41 | *
42 | * @return PostHandlerScenario
43 | */
44 | public function given(array $events = [])
45 | {
46 | if (! $events) {
47 | return $this;
48 | }
49 |
50 | foreach ($events as $event) {
51 | $this->eventStore->appendEvents($this->id, [$event]);
52 | }
53 |
54 | $this->eventStore->clearRecordedEvents();
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * @param mixed $command
61 | *
62 | * @return PostHandlerScenario
63 | */
64 | public function when($command)
65 | {
66 | $this->commandHandler->handle($command);
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * @param array $events
73 | *
74 | * @return PostHandlerScenario
75 | */
76 | public function then(array $events = [])
77 | {
78 | $this->testCase->assertEquals($events, $this->eventStore->getRecordedEvents());
79 | $this->eventStore->clearRecordedEvents();
80 |
81 | return $this;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Event Sourcing and CQRS with Broadway Workshop
2 |
3 | ## Tutorial Setup
4 |
5 | Prior to arriving at the event, clone this repository so that you will not have to download it at the venue. Also, make sure to run `composer install` after you have finished cloning. If you are able to do this, you are more likely to have a successful tutorial.
6 |
7 | Keep in mind that there might be last minute changes to the repository. Check the night before the event to see if you need to pull any recent changes.
8 |
9 | To be able to do the exercises in this tutorial, you need to have the following things set-up on your machine:
10 |
11 | 1. Composer
12 | 2. PHP 7.1 (or later)
13 |
14 | It is expected that you know how to edit PHP files, and run them on the command line. All exercises will revolve around writing command line scripts.
15 |
16 | If you have questions related to any of the installation instructions below, please email me at beau@dflydev.com, or find me (simensen) on IRC's Freenode network.
17 |
18 | ### Composer
19 |
20 | Make sure you can call Composer through either just `composer`, or by calling `php /path/to/composer.phar`. I would recommend that you follow the instructions at https://getcomposer.org/download/ by running the 4 PHP commands, and then copy the installed `composer.phar` file to `/usr/local/bin/composer`:
21 |
22 | sudo cp composer.phar /usr/local/bin/composer
23 |
24 | Now verify whether Composer works by running:
25 |
26 | composer --version
27 |
28 | ### PHP 7.1, 7.2
29 |
30 | Please install the PHP package through your package manager. For Debian and Ubuntu, this package is named `php`.
31 |
32 | To verify whether your installation worked, run the following commands:
33 |
34 | php --version
35 |
36 | Running this command should output something like this (pay attention to the version numbers):
37 |
38 | PHP 7.2.4 (cli) (built: Mar 29 2018 15:32:43) ( NTS )
39 | Copyright (c) 1997-2018 The PHP Group
40 | Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
41 | with blackfire v1.18.2~mac-x64-non_zts72, https://blackfire.io, by SensioLabs
42 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Post.php:
--------------------------------------------------------------------------------
1 | id = $id;
28 | }
29 |
30 | /**
31 | * @return string
32 | */
33 | public function getId()
34 | {
35 | return $this->id;
36 | }
37 |
38 | /**
39 | * @return string
40 | */
41 | public function getTitle()
42 | {
43 | return $this->title;
44 | }
45 |
46 | /**
47 | * @return string
48 | */
49 | public function getContent()
50 | {
51 | return $this->content;
52 | }
53 |
54 | /**
55 | * @return string
56 | */
57 | public function getCategory()
58 | {
59 | return $this->category;
60 | }
61 |
62 | /**
63 | * @return array
64 | */
65 | public function getTags()
66 | {
67 | return array_keys($this->tags);
68 | }
69 |
70 | /**
71 | * Publish a post.
72 | *
73 | * @param $title
74 | * @param $content
75 | * @param $category
76 | */
77 | public function publish($title, $content, $category)
78 | {
79 | $this->title = $title;
80 | $this->content = $content;
81 | $this->category = $category;
82 | }
83 |
84 | /**
85 | * Tag a post.
86 | *
87 | * @param string $tag
88 | */
89 | public function addTag($tag)
90 | {
91 | $this->tags[$tag] = true;
92 | }
93 |
94 | /**
95 | * Untag a post.
96 | *
97 | * @param string $tag
98 | */
99 | public function removeTag($tag)
100 | {
101 | if (isset($this->tags[$tag])) {
102 | unset($this->tags[$tag]);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostTagCount/Adapter/Broadway/BroadwayPostTagCountRepository.php:
--------------------------------------------------------------------------------
1 | broadwayRepository = $broadwayRepository;
22 | }
23 |
24 | /**
25 | * @param string $tag
26 | *
27 | * @return PostTagCount
28 | */
29 | public function find($tag)
30 | {
31 | return $this->broadwayRepository->find($tag);
32 | }
33 |
34 | /**
35 | * @return PostTagCount[]
36 | */
37 | public function findAll()
38 | {
39 | return $this->broadwayRepository->findAll();
40 | }
41 |
42 | /**
43 | * @param string $tag
44 | *
45 | * @return void
46 | */
47 | public function increment($tag)
48 | {
49 | /** @var PostTagCount $postTagCount */
50 | $postTagCount = $this->broadwayRepository->find($tag);
51 |
52 | if ($postTagCount) {
53 | $postTagCount->setCount($postTagCount->getCount() + 1);
54 | } else {
55 | $postTagCount = new PostTagCount($tag, 1);
56 | }
57 |
58 | $this->broadwayRepository->save($postTagCount);
59 | }
60 |
61 | /**
62 | * @param string $tag
63 | *
64 | * @return void
65 | */
66 | public function decrement($tag)
67 | {
68 | /** @var PostTagCount $postTagCount */
69 | $postTagCount = $this->broadwayRepository->find($tag);
70 |
71 | if ($postTagCount) {
72 | $postTagCount->setCount($postTagCount->getCount() - 1);
73 | } else {
74 | $postTagCount = new PostTagCount($tag, 1);
75 | }
76 |
77 | $this->broadwayRepository->save($postTagCount);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Resources/config/read-model/post-tag-count.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | superawesome:blog:post
8 | SuperAwesome\Blog\Domain\ReadModel\PostTagCount\PostTagCount
9 |
10 |
11 |
12 |
13 | SuperAwesome\Blog\Domain\ReadModel\PostTagCount\PostTagCount
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Resources/config/read-model/published-post.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | superawesome:blog:post
8 | SuperAwesome\Blog\Domain\ReadModel\PublishedPost\PublishedPost
9 |
10 |
11 |
12 |
13 | SuperAwesome\Blog\Domain\ReadModel\PublishedPost\PublishedPost
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Resources/config/read-model/post-category-count.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | superawesome:blog:post
8 | SuperAwesome\Blog\Domain\ReadModel\PostCategoryCount\PostCategoryCount
9 |
10 |
11 |
12 |
13 | SuperAwesome\Blog\Domain\ReadModel\PostCategoryCount\PostCategoryCount
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PostCategoryCount/Adapter/Broadway/BroadwayPostCategoryCountRepository.php:
--------------------------------------------------------------------------------
1 | broadwayRepository = $broadwayRepository;
22 | }
23 |
24 | /**
25 | * @param string $category
26 | *
27 | * @return PostCategoryCount
28 | */
29 | public function find($category)
30 | {
31 | return $this->broadwayRepository->find($category);
32 | }
33 |
34 | /**
35 | * @return PostCategoryCount[]
36 | */
37 | public function findAll()
38 | {
39 | return $this->broadwayRepository->findAll();
40 | }
41 |
42 | /**
43 | * @param string $tag
44 | *
45 | * @return void
46 | */
47 | public function increment($category)
48 | {
49 | /** @var PostCategoryCount $postCategoryCount */
50 | $postCategoryCount = $this->broadwayRepository->find($category);
51 |
52 | if ($postCategoryCount) {
53 | $postCategoryCount->setCount($postCategoryCount->getCount() + 1);
54 | } else {
55 | $postCategoryCount = new PostCategoryCount($category, 1);
56 | }
57 |
58 | $this->broadwayRepository->save($postCategoryCount);
59 | }
60 |
61 | /**
62 | * @param string $tag
63 | *
64 | * @return void
65 | */
66 | public function decrement($category)
67 | {
68 | /** @var PostCategoryCount $postCategoryCount */
69 | $postCategoryCount = $this->broadwayRepository->find($category);
70 |
71 | if ($postCategoryCount) {
72 | $postCategoryCount->setCount($postCategoryCount->getCount() - 1);
73 | } else {
74 | $postCategoryCount = new PostCategoryCount($category, 1);
75 | }
76 |
77 | $this->broadwayRepository->save($postCategoryCount);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/ReadModel/PublishedPost/Adapter/SuperAwesome/Dbal/DbalPublishedPostRepository.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function find($id)
28 | {
29 | $results = array_map([$this, 'unwrapData'], $this->connection->fetchAll(
30 | 'SELECT * FROM published_posts WHERE id = ?',
31 | [$id]
32 | ));
33 |
34 | return count($results) ? reset($results) : null;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function findAll()
41 | {
42 | return array_map([$this, 'unwrapData'], $this->connection->fetchAll(
43 | 'SELECT * FROM published_posts WHERE id = ?'
44 | ));
45 | }
46 |
47 | /**
48 | * @param PublishedPost $publishedPost
49 | *
50 | * @return void
51 | */
52 | public function save(PublishedPost $publishedPost)
53 | {
54 | $data = [
55 | 'title' => $publishedPost->title,
56 | 'content' => $publishedPost->content,
57 | 'category' => $publishedPost->category,
58 | ];
59 |
60 | try {
61 | $this->connection->insert('published_posts', array_merge(
62 | $data,
63 | ['id' => $publishedPost->id]
64 | ));
65 | } catch (\Doctrine\DBAL\DBALException $e) {
66 | $this->connection->update('published_posts', $data, [
67 | 'id' => $publishedPost->id,
68 | ]);
69 | }
70 | }
71 |
72 | /**
73 | * @param array $row
74 | * @return PublishedPost
75 | */
76 | protected function unwrapData(array $row)
77 | {
78 | return new PublishedPost(
79 | $row['id'],
80 | $row['title'],
81 | $row['content'],
82 | $row['category']
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/AbstractSchemaEventStoreCommand.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | //namespace Broadway\Bundle\BroadwayBundle\Command;
13 | namespace SuperAwesome\Symfony\BlogBundle\Command;
14 |
15 | use Assert\Assertion;
16 | use Broadway\EventStore\Dbal\DBALEventStore;
17 | use Doctrine\Bundle\DoctrineBundle\Command\DoctrineCommand;
18 | use Symfony\Component\Console\Input\InputInterface;
19 | use Symfony\Component\Console\Input\InputOption;
20 | use Symfony\Component\Console\Output\OutputInterface;
21 |
22 | /**
23 | * Class AbstractSchemaEventStoreCommand
24 | */
25 | abstract class AbstractSchemaEventStoreCommand extends DoctrineCommand
26 | {
27 | /** @var \Doctrine\DBAL\Connection */
28 | protected $connection;
29 |
30 | /** @var \Exception */
31 | protected $exception;
32 |
33 | /**
34 | * {@inheritDoc}
35 | */
36 | protected function configure()
37 | {
38 | $this
39 | ->addOption(
40 | 'connection',
41 | 'c',
42 | InputOption::VALUE_OPTIONAL,
43 | 'Specifies the database connection to use.'
44 | );
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | protected function initialize(InputInterface $input, OutputInterface $output)
51 | {
52 | $databaseConnectionName = $input->getOption('connection') ?: $this->getContainer()->getParameter('broadway.event_store.dbal.connection');
53 | Assertion::string($databaseConnectionName, 'Input option "connection" must be of type `string`.');
54 |
55 | try {
56 | $this->connection = $this->getDoctrineConnection($databaseConnectionName);
57 | } catch (\Exception $exception) {
58 | $this->exception = $exception;
59 | }
60 | }
61 |
62 | /**
63 | * @return DBALEventStore
64 | *
65 | * @throws \RuntimeException
66 | */
67 | protected function getEventStore()
68 | {
69 | $eventStore = $this->getContainer()->get('broadway.event_store');
70 |
71 | if (!$eventStore instanceof DBALEventStore) {
72 | throw new \RuntimeException("'broadway.event_store' must be configured as an instance of DBALEventStore");
73 | }
74 |
75 | return $eventStore;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/SchemaEventStoreDropCommand.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | //namespace Broadway\Bundle\BroadwayBundle\Command;
13 | namespace SuperAwesome\Symfony\BlogBundle\Command;
14 |
15 | use Exception;
16 | use Symfony\Component\Console\Input\InputInterface;
17 | use Symfony\Component\Console\Output\OutputInterface;
18 |
19 | /**
20 | * Drops the event store schema.
21 | */
22 | class SchemaEventStoreDropCommand extends AbstractSchemaEventStoreCommand
23 | {
24 | /**
25 | * {@inheritDoc}
26 | */
27 | protected function configure()
28 | {
29 | parent::configure();
30 |
31 | $this
32 | ->setName('broadway:event-store:schema:drop')
33 | ->setDescription('Drops the event store schema')
34 | ->setHelp(
35 | <<%command.name% command drops the schema in the default
37 | connections database:
38 |
39 | php app/console %command.name%
40 | EOT
41 | );
42 | }
43 |
44 | /**
45 | * {@inheritDoc}
46 | */
47 | protected function execute(InputInterface $input, OutputInterface $output)
48 | {
49 | if (!$this->connection) {
50 | $output->writeln('Could not drop Broadway event-store schema');
51 | $output->writeln(sprintf('%s', $this->exception->getMessage()));
52 |
53 | return 1;
54 | }
55 |
56 | $error = false;
57 | try {
58 | $schemaManager = $this->connection->getSchemaManager();
59 | $eventStore = $this->getEventStore();
60 |
61 | $table = $eventStore->configureTable();
62 | if ($schemaManager->tablesExist([$table->getName()])) {
63 | $schemaManager->dropTable($table->getName());
64 | $output->writeln('Dropped Broadway event-store schema');
65 | } else {
66 | $output->writeln('Broadway event-store schema does not exist');
67 | }
68 | } catch (Exception $e) {
69 | $output->writeln('Could not drop Broadway event-store schema');
70 | $output->writeln(sprintf('%s', $e->getMessage()));
71 | $error = true;
72 | }
73 |
74 | return $error ? 1 : 0;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Command/SchemaEventStoreCreateCommand.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | //namespace Broadway\Bundle\BroadwayBundle\Command;
13 | namespace SuperAwesome\Symfony\BlogBundle\Command;
14 |
15 | use Exception;
16 | use Symfony\Component\Console\Input\InputInterface;
17 | use Symfony\Component\Console\Output\OutputInterface;
18 |
19 | /**
20 | * Creates the event store schema.
21 | */
22 | class SchemaEventStoreCreateCommand extends AbstractSchemaEventStoreCommand
23 | {
24 | /**
25 | * {@inheritDoc}
26 | */
27 | protected function configure()
28 | {
29 | parent::configure();
30 |
31 | $this
32 | ->setName('broadway:event-store:schema:init')
33 | ->setDescription('Creates the event store schema')
34 | ->setHelp(
35 | <<%command.name% command creates the schema in the default
37 | connections database:
38 |
39 | php app/console %command.name%
40 | EOT
41 | );
42 | }
43 |
44 | /**
45 | * {@inheritDoc}
46 | */
47 | protected function execute(InputInterface $input, OutputInterface $output)
48 | {
49 | if (!$this->connection) {
50 | $output->writeln('Could not create Broadway event-store schema');
51 | $output->writeln(sprintf('%s', $this->exception->getMessage()));
52 |
53 | return 1;
54 | }
55 |
56 | $error = false;
57 | try {
58 | $schemaManager = $this->connection->getSchemaManager();
59 | $schema = $schemaManager->createSchema();
60 | $eventStore = $this->getEventStore();
61 |
62 | $table = $eventStore->configureSchema($schema);
63 | if (null !== $table) {
64 | $schemaManager->createTable($table);
65 | $output->writeln('Created Broadway event-store schema');
66 | } else {
67 | $output->writeln('Broadway event-store schema already exists');
68 | }
69 | } catch (Exception $e) {
70 | $output->writeln('Could not create Broadway event-store schema');
71 | $output->writeln(sprintf('%s', $e->getMessage()));
72 | $error = true;
73 | }
74 |
75 | return $error ? 1 : 0;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Blog/Domain/Model/Post/Adapter/Broadway/BroadwayPostCommandHandler.php:
--------------------------------------------------------------------------------
1 | createPostHandler = $createPostHandler;
48 | $this->publishPostHandler = $publishPostHandler;
49 | $this->tagPostHandler = $tagPostHandler;
50 | $this->untagPostHandler = $untagPostHandler;
51 | }
52 |
53 | /**
54 | * @param CreatePost $command
55 | */
56 | public function handleCreatePost(CreatePost $command)
57 | {
58 | $this->createPostHandler->handle($command);
59 | }
60 |
61 | /**
62 | * @param PublishPost $command
63 | */
64 | public function handlePublishPost(PublishPost $command)
65 | {
66 | $this->publishPostHandler->handle($command);
67 | }
68 |
69 | /**
70 | * @param TagPost $command
71 | */
72 | public function handleTagPost(TagPost $command)
73 | {
74 | $this->tagPostHandler->handle($command);
75 | }
76 |
77 | /**
78 | * @param UntagPost $command
79 | */
80 | public function handleUntagPost(UntagPost $command)
81 | {
82 | $this->untagPostHandler->handle($command);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/Command/Handler/TagPostHandlerTest.php:
--------------------------------------------------------------------------------
1 | markTestIncomplete('Post cannot be tagged.');
19 |
20 | $id = 'my-id';
21 | $title = 'the title';
22 | $content = 'the content';
23 | $category = 'draft';
24 |
25 | $es = 'es';
26 | $cqrs = 'cqrs';
27 | $broadway = 'broadway';
28 |
29 | $this->scenario
30 | ->withId($id)
31 | ->given([
32 | new PostWasCreated($id),
33 | new PostWasCategorized($id, $category),
34 | new PostWasPublished($id, $title, $content, $category),
35 | ])
36 | ->when(new TagPost($id, $es))
37 | ->then([
38 | new PostWasTagged($id, $es),
39 | ])
40 | ->when(new TagPost($id, $cqrs))
41 | ->then([
42 | new PostWasTagged($id, $cqrs),
43 | ])
44 | ->when(new TagPost($id, $broadway))
45 | ->then([
46 | new PostWasTagged($id, $broadway),
47 | ])
48 | ;
49 | }
50 |
51 | /** @test */
52 | public function it_does_not_tag_again()
53 | {
54 | $this->markTestIncomplete('Post cannot be tagged.');
55 |
56 | $id = 'my-id';
57 | $title = 'the title';
58 | $content = 'the content';
59 | $category = 'draft';
60 |
61 | $es = 'es';
62 | $cqrs = 'cqrs';
63 | $broadway = 'broadway';
64 |
65 | $this->scenario
66 | ->withId($id)
67 | ->given([
68 | new PostWasCreated($id),
69 | new PostWasCategorized($id, $category),
70 | new PostWasPublished($id, $title, $content, $category),
71 | new PostWasTagged($id, $es),
72 | new PostWasTagged($id, $broadway),
73 | ])
74 | ->when(new TagPost($id, $es))
75 | ->then([
76 | ])
77 | ->when(new TagPost($id, $cqrs))
78 | ->then([
79 | new PostWasTagged($id, $cqrs),
80 | ])
81 | ->when(new TagPost($id, $broadway))
82 | ->then([
83 | ])
84 | ;
85 | }
86 |
87 | protected function createCommandHandler(PostRepository $postRepository)
88 | {
89 | return new TagPostHandler($postRepository);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/Command/Handler/UntagPostHandlerTest.php:
--------------------------------------------------------------------------------
1 | markTestIncomplete('Post::instantiateForReconstitution does not exist.');
20 |
21 | $id = 'my-id';
22 | $title = 'the title';
23 | $content = 'the content';
24 | $category = 'draft';
25 |
26 | $es = 'es';
27 | $cqrs = 'cqrs';
28 | $broadway = 'broadway';
29 |
30 | $this->scenario
31 | ->withId($id)
32 | ->given([
33 | new PostWasCreated($id),
34 | new PostWasCategorized($id, $category),
35 | new PostWasPublished($id, $title, $content, $category),
36 | new PostWasTagged($id, $es),
37 | new PostWasTagged($id, $cqrs),
38 | new PostWasTagged($id, $broadway),
39 | ])
40 | ->when(new UntagPost($id, $es))
41 | ->then([
42 | new PostWasUntagged($id, $es),
43 | ])
44 | ->when(new UntagPost($id, $cqrs))
45 | ->then([
46 | new PostWasUntagged($id, $cqrs),
47 | ])
48 | ->when(new UntagPost($id, $broadway))
49 | ->then([
50 | new PostWasUntagged($id, $broadway),
51 | ])
52 | ;
53 | }
54 |
55 | /** @test */
56 | public function it_does_not_untag_again()
57 | {
58 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
59 |
60 | $id = 'my-id';
61 | $title = 'the title';
62 | $content = 'the content';
63 | $category = 'draft';
64 |
65 | $es = 'es';
66 | $cqrs = 'cqrs';
67 | $broadway = 'broadway';
68 |
69 | $this->scenario
70 | ->withId($id)
71 | ->given([
72 | new PostWasCreated($id),
73 | new PostWasCategorized($id, $category),
74 | new PostWasPublished($id, $title, $content, $category),
75 | new PostWasTagged($id, $es),
76 | new PostWasTagged($id, $cqrs),
77 | new PostWasTagged($id, $broadway),
78 | new PostWasUntagged($id, $es),
79 | new PostWasUntagged($id, $broadway),
80 | ])
81 | ->when(new UntagPost($id, $es))
82 | ->then([
83 | ])
84 | ->when(new UntagPost($id, $cqrs))
85 | ->then([
86 | new PostWasUntagged($id, $cqrs),
87 | ])
88 | ->when(new UntagPost($id, $broadway))
89 | ->then([
90 | ])
91 | ;
92 | }
93 |
94 | protected function createCommandHandler(PostRepository $postRepository)
95 | {
96 | return new UntagPostHandler($postRepository);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Symfony/BlogBundle/Resources/config/model/post.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | SuperAwesome\Blog\Domain\Model\Post\Adapter\Broadway\BroadwayPostRepository
7 | SuperAwesome\Blog\Domain\Model\Post\Adapter\Broadway\BroadwayPostRepository
8 | SuperAwesome\Blog\Domain\Model\Post\Adapter\Broadway\BroadwayPostCommandHandler
9 |
10 | SuperAwesome\Blog\Domain\Model\Post\Command\Handler\CreatePostHandler
11 | SuperAwesome\Blog\Domain\Model\Post\Command\Handler\PublishPostHandler
12 | SuperAwesome\Blog\Domain\Model\Post\Command\Handler\TagPostHandler
13 | SuperAwesome\Blog\Domain\Model\Post\Command\Handler\UntagPostHandler
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/SuperAwesome/Common/Domain/ReadModel/Adapter/Broadway/PoorlyDesignedBroadwayDbalRepository.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
36 | $this->serializer = $serializer;
37 | $this->class = $class;
38 | }
39 |
40 | public function save(Identifiable $data)
41 | {
42 | $this->connection->delete(static::TABLE, [
43 | 'class' => $this->class,
44 | 'id' => $data->getId(),
45 | ]);
46 |
47 | $this->connection->insert(static::TABLE, [
48 | 'class' => $this->class,
49 | 'id' => $data->getId(),
50 | 'serialized' => json_encode($this->serializer->serialize($data)),
51 | ]);
52 | }
53 |
54 | /**
55 | * @param string $id
56 | *
57 | * @return Identifiable|null
58 | */
59 | public function find($id)
60 | {
61 | $statement = $this->connection->prepare(
62 | sprintf('SELECT serialized FROM %s WHERE class = :class AND id = :id', static::TABLE)
63 | );
64 |
65 | $statement->execute([
66 | 'class' => $this->class,
67 | 'id' => $id,
68 | ]);
69 |
70 | $row = $statement->fetch(\PDO::FETCH_ASSOC);
71 |
72 | if (! $row) {
73 | return null;
74 | }
75 |
76 | return $this->serializer->deserialize(json_decode($row['serialized'], true));
77 | }
78 |
79 | /**
80 | * @param array $fields
81 | *
82 | * @return Identifiable[]
83 | */
84 | public function findBy(array $fields): array
85 | {
86 | throw new \RuntimeException('Not implemented.');
87 | }
88 |
89 | /**
90 | * @return Identifiable[]
91 | */
92 | public function findAll(): array
93 | {
94 | $statement = $this->connection->prepare(
95 | sprintf('SELECT serialized FROM %s WHERE class = :class', static::TABLE)
96 | );
97 |
98 | $statement->execute([
99 | 'class' => $this->class,
100 | ]);
101 |
102 | return array_map(function ($row) {
103 | return $this->serializer->deserialize(json_decode($row['serialized'], true));
104 | }, $statement->fetchAll(\PDO::FETCH_ASSOC));
105 | }
106 |
107 | /**
108 | * @param string $id
109 | */
110 | public function remove($id)
111 | {
112 | $this->connection->delete(static::TABLE, [
113 | 'class' => $this->class,
114 | 'id' => $id,
115 | ]);
116 | }
117 |
118 | /**
119 | * @return \Doctrine\DBAL\Schema\Table|null
120 | */
121 | public static function configureSchema(Schema $schema)
122 | {
123 | if ($schema->hasTable(static::TABLE)) {
124 | return null;
125 | }
126 |
127 | return static::configureTable();
128 | }
129 |
130 | public static function configureTable()
131 | {
132 | $schema = new Schema();
133 |
134 | $table = $schema->createTable(static::TABLE);
135 |
136 | $table->addColumn('class', 'string');
137 | $table->addColumn('id', 'string');
138 | $table->addColumn('serialized', 'text');
139 |
140 | $table->setPrimaryKey(array('class', 'id'));
141 |
142 | return $table;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/Command/Handler/PublishPostHandlerTest.php:
--------------------------------------------------------------------------------
1 | markTestIncomplete('Post is not an EventSourcedAggregateRoot.');
19 |
20 | $id = 'my-id';
21 | $title = 'the title';
22 | $content = 'the content';
23 | $category = 'draft';
24 |
25 | $this->scenario
26 | ->withId($id)
27 | ->given([
28 | new PostWasCreated($id),
29 | ])
30 | ->when(new PublishPost($id, $title, $content, $category))
31 | ->then([
32 | new PostWasCategorized($id, $category),
33 | new PostWasPublished($id, $title, $content, $category),
34 | ])
35 | ;
36 | }
37 |
38 | /** @test */
39 | public function it_uncategorizes_when_publishing_with_a_different_category()
40 | {
41 | $this->markTestIncomplete('Post is not an EventSourcedAggregateRoot.');
42 |
43 | $id = 'my-id';
44 | $title = 'the title';
45 | $content = 'the content';
46 | $category = 'live';
47 |
48 | $originalTitle = 'the original title';
49 | $originalContent = 'the original content';
50 | $originalCategory = 'draft';
51 |
52 | $this->scenario
53 | ->withId($id)
54 | ->given([
55 | new PostWasCreated($id),
56 | new PostWasCategorized($id, $originalCategory),
57 | new PostWasPublished($id, $originalTitle, $originalContent, $originalCategory),
58 | ])
59 | ->when(new PublishPost($id, $title, $content, $category))
60 | ->then([
61 | new PostWasUncategorized($id, $originalCategory),
62 | new PostWasCategorized($id, $category),
63 | new PostWasPublished($id, $title, $content, $category),
64 | ])
65 | ;
66 | }
67 |
68 | /** @test */
69 | public function it_does_not_uncategorize_when_publishing_with_same_category()
70 | {
71 | $this->markTestIncomplete('Post is not an EventSourcedAggregateRoot.');
72 |
73 | $id = 'my-id';
74 | $title = 'the title';
75 | $content = 'the content';
76 | $category = 'draft';
77 |
78 | $originalTitle = 'the original title';
79 | $originalContent = 'the original content';
80 | $originalCategory = 'draft';
81 |
82 | $this->scenario
83 | ->withId($id)
84 | ->given([
85 | new PostWasCreated($id),
86 | new PostWasCategorized($id, $originalCategory),
87 | new PostWasPublished($id, $originalTitle, $originalContent, $originalCategory),
88 | ])
89 | ->when(new PublishPost($id, $title, $content, $category))
90 | ->then([
91 | new PostWasPublished($id, $title, $content, $category),
92 | ])
93 | ;
94 | }
95 |
96 | /** @test */
97 | public function it_does_not_publish_if_nothing_changed()
98 | {
99 | $this->markTestIncomplete('Post is not an EventSourcedAggregateRoot.');
100 |
101 | $id = 'my-id';
102 | $title = 'the title';
103 | $content = 'the content';
104 | $category = 'draft';
105 |
106 | $this->scenario
107 | ->withId($id)
108 | ->given([
109 | new PostWasCreated($id),
110 | new PostWasCategorized($id, $category),
111 | new PostWasPublished($id, $title, $content, $category),
112 | ])
113 | ->when(new PublishPost($id, $title, $content, $category))
114 | ->then([
115 | ])
116 | ;
117 | }
118 |
119 | protected function createCommandHandler(PostRepository $postRepository)
120 | {
121 | return new PublishPostHandler($postRepository);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/tests/SuperAwesome/Blog/Domain/Model/Post/Adapter/SuperAwesome/PostTest.php:
--------------------------------------------------------------------------------
1 | scenario = new PostScenario($this);
23 | }
24 |
25 | /** @test */
26 | public function it_can_create()
27 | {
28 | $this->markTestIncomplete('Post::create() does not exist.');
29 |
30 | $id = 'my-id';
31 |
32 | $this->scenario
33 | ->when(function () use ($id) {
34 | return Post::create($id);
35 | })
36 | ->then([
37 | new PostWasCreated($id),
38 | ])
39 | ;
40 | }
41 |
42 | /** @test */
43 | public function it_can_publish()
44 | {
45 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
46 |
47 | $id = 'my-id';
48 | $title = 'the title';
49 | $content = 'the content';
50 | $category = 'draft';
51 |
52 | $this->scenario
53 | ->given([
54 | new PostWasCreated($id),
55 | ])
56 | ->when(function (Post $post) use ($title, $content, $category) {
57 | $post->publish($title, $content, $category);
58 | })
59 | ->then([
60 | new PostWasCategorized($id, $category),
61 | new PostWasPublished($id, $title, $content, $category),
62 | ])
63 | ;
64 | }
65 |
66 | /** @test */
67 | public function it_uncategorizes_when_publishing_with_a_different_category()
68 | {
69 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
70 |
71 | $id = 'my-id';
72 | $title = 'the title';
73 | $content = 'the content';
74 | $category = 'live';
75 |
76 | $originalTitle = 'the original title';
77 | $originalContent = 'the original content';
78 | $originalCategory = 'draft';
79 |
80 | $this->scenario
81 | ->given([
82 | new PostWasCreated($id),
83 | new PostWasCategorized($id, $originalCategory),
84 | new PostWasPublished($id, $originalTitle, $originalContent, $originalCategory),
85 | ])
86 | ->when(function (Post $post) use ($title, $content, $category) {
87 | $post->publish($title, $content, $category);
88 | })
89 | ->then([
90 | new PostWasUncategorized($id, $originalCategory),
91 | new PostWasCategorized($id, $category),
92 | new PostWasPublished($id, $title, $content, $category),
93 | ])
94 | ;
95 | }
96 |
97 | /** @test */
98 | public function it_does_not_uncategorize_when_publishing_with_same_category()
99 | {
100 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
101 |
102 | $id = 'my-id';
103 | $title = 'the title';
104 | $content = 'the content';
105 | $category = 'draft';
106 |
107 | $originalTitle = 'the original title';
108 | $originalContent = 'the original content';
109 | $originalCategory = 'draft';
110 |
111 | $this->scenario
112 | ->given([
113 | new PostWasCreated($id),
114 | new PostWasCategorized($id, $originalCategory),
115 | new PostWasPublished($id, $originalTitle, $originalContent, $originalCategory),
116 | ])
117 | ->when(function (Post $post) use ($title, $content, $category) {
118 | $post->publish($title, $content, $category);
119 | })
120 | ->then([
121 | new PostWasPublished($id, $title, $content, $category),
122 | ])
123 | ;
124 | }
125 |
126 | /** @test */
127 | public function it_does_not_publish_if_nothing_changed()
128 | {
129 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
130 |
131 | $id = 'my-id';
132 | $title = 'the title';
133 | $content = 'the content';
134 | $category = 'draft';
135 |
136 | $this->scenario
137 | ->given([
138 | new PostWasCreated($id),
139 | new PostWasCategorized($id, $category),
140 | new PostWasPublished($id, $title, $content, $category),
141 | ])
142 | ->when(function (Post $post) use ($title, $content, $category) {
143 | $post->publish($title, $content, $category);
144 | })
145 | ->then([
146 | ])
147 | ;
148 | }
149 |
150 | /** @test */
151 | public function it_can_tag()
152 | {
153 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
154 |
155 | $id = 'my-id';
156 | $title = 'the title';
157 | $content = 'the content';
158 | $category = 'draft';
159 |
160 | $es = 'es';
161 | $cqrs = 'cqrs';
162 | $broadway = 'broadway';
163 |
164 | $this->scenario
165 | ->given([
166 | new PostWasCreated($id),
167 | new PostWasCategorized($id, $category),
168 | new PostWasPublished($id, $title, $content, $category),
169 | ])
170 | ->when(function (Post $post) use ($es, $cqrs, $broadway) {
171 | $post->addTag($es);
172 | $post->addTag($cqrs);
173 | $post->addTag($broadway);
174 | })
175 | ->then([
176 | new PostWasTagged($id, $es),
177 | new PostWasTagged($id, $cqrs),
178 | new PostWasTagged($id, $broadway),
179 | ])
180 | ;
181 | }
182 |
183 | /** @test */
184 | public function it_does_not_tag_again()
185 | {
186 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
187 |
188 | $id = 'my-id';
189 | $title = 'the title';
190 | $content = 'the content';
191 | $category = 'draft';
192 |
193 | $es = 'es';
194 | $cqrs = 'cqrs';
195 | $broadway = 'broadway';
196 |
197 | $this->scenario
198 | ->given([
199 | new PostWasCreated($id),
200 | new PostWasCategorized($id, $category),
201 | new PostWasPublished($id, $title, $content, $category),
202 | new PostWasTagged($id, $es),
203 | new PostWasTagged($id, $broadway),
204 | ])
205 | ->when(function (Post $post) use ($es, $cqrs, $broadway) {
206 | $post->addTag($es);
207 | $post->addTag($cqrs);
208 | $post->addTag($broadway);
209 | })
210 | ->then([
211 | new PostWasTagged($id, $cqrs),
212 | ])
213 | ;
214 | }
215 |
216 | /** @test */
217 | public function it_can_untag()
218 | {
219 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
220 |
221 | $id = 'my-id';
222 | $title = 'the title';
223 | $content = 'the content';
224 | $category = 'draft';
225 |
226 | $es = 'es';
227 | $cqrs = 'cqrs';
228 | $broadway = 'broadway';
229 |
230 | $this->scenario
231 | ->given([
232 | new PostWasCreated($id),
233 | new PostWasCategorized($id, $category),
234 | new PostWasPublished($id, $title, $content, $category),
235 | new PostWasTagged($id, $es),
236 | new PostWasTagged($id, $cqrs),
237 | new PostWasTagged($id, $broadway),
238 | ])
239 | ->when(function (Post $post) use ($es, $cqrs, $broadway) {
240 | $post->removeTag($es);
241 | $post->removeTag($cqrs);
242 | $post->removeTag($broadway);
243 | })
244 | ->then([
245 | new PostWasUntagged($id, $es),
246 | new PostWasUntagged($id, $cqrs),
247 | new PostWasUntagged($id, $broadway),
248 | ])
249 | ;
250 | }
251 |
252 | /** @test */
253 | public function it_does_not_untag_again()
254 | {
255 | $this->markTestIncomplete('Post::instantiateForReconstitution does not exist.');
256 |
257 | $id = 'my-id';
258 | $title = 'the title';
259 | $content = 'the content';
260 | $category = 'draft';
261 |
262 | $es = 'es';
263 | $cqrs = 'cqrs';
264 | $broadway = 'broadway';
265 |
266 | $this->scenario
267 | ->given([
268 | new PostWasCreated($id),
269 | new PostWasCategorized($id, $category),
270 | new PostWasPublished($id, $title, $content, $category),
271 | new PostWasTagged($id, $es),
272 | new PostWasTagged($id, $cqrs),
273 | new PostWasTagged($id, $broadway),
274 | new PostWasUntagged($id, $es),
275 | new PostWasUntagged($id, $broadway),
276 | ])
277 | ->when(function (Post $post) use ($es, $cqrs, $broadway) {
278 | $post->removeTag($es);
279 | $post->removeTag($cqrs);
280 | $post->removeTag($broadway);
281 | })
282 | ->then([
283 | new PostWasUntagged($id, $cqrs),
284 | ])
285 | ;
286 | }
287 | }
288 |
--------------------------------------------------------------------------------