├── .env.dist
├── .gitignore
├── LICENSE
├── LICENSE.txt
├── README.md
├── bin
├── console
└── phpunit
├── composer.json
├── composer.lock
├── config
├── bundles.php
├── graphql
│ ├── schema
│ │ ├── Mutation.yml
│ │ └── Query.yml
│ └── types
│ │ ├── Book.graphql
│ │ ├── Category.graphql
│ │ └── Reader.graphql
├── packages
│ ├── dev
│ │ └── routing.yaml
│ ├── doctrine.yaml
│ ├── doctrine_migrations.yaml
│ ├── framework.yaml
│ ├── graphql.yaml
│ ├── league_tactician.yaml
│ ├── prod
│ │ └── doctrine.yaml
│ ├── routing.yaml
│ ├── test
│ │ └── framework.yaml
│ └── twig.yaml
├── routes.yaml
├── routes
│ ├── annotations.yaml
│ ├── dev
│ │ ├── graphiql.yaml
│ │ └── twig.yaml
│ └── graphql.yaml
├── services.yaml
└── services_test.yaml
├── phpunit.xml.dist
├── public
└── index.php
├── src
├── Entity
│ ├── Book.php
│ └── Reader.php
├── Event
│ ├── AbstractEvent.php
│ ├── EventRecorder.php
│ └── ReaderBookRegisteredEvent.php
├── EventSubscriber
│ └── ReaderSubscriber.php
├── Exception
│ ├── BookNotFoundException.php
│ ├── ReaderAlreadyHasBookException.php
│ ├── ReaderNotFoundException.php
│ └── UnknownCategoryException.php
├── Kernel.php
├── Middleware
│ ├── GraphQLMiddleware.php
│ ├── QueueMiddleware.php
│ └── ReleaseRecordedEventsMiddleware.php
├── Migrations
│ ├── .gitignore
│ ├── Version20180221021751.php
│ ├── Version20180221022807.php
│ ├── Version20180224062434.php
│ └── Version20180224074548.php
├── Queue
│ └── AbstractQueueableCommand.php
├── Repository
│ ├── AbstractRepository.php
│ ├── BookRepository.php
│ └── ReaderRepository.php
└── Service
│ ├── CatalogBook.php
│ ├── CatalogBookHandler.php
│ ├── HandlerInterface.php
│ ├── NotifyTradeOpportunities.php
│ ├── NotifyTradeOpportunitiesHandler.php
│ ├── RegisterReader.php
│ ├── RegisterReaderBook.php
│ ├── RegisterReaderBookHandler.php
│ ├── RegisterReaderHandler.php
│ ├── RegisterReaderWish.php
│ └── RegisterReaderWishHandler.php
├── symfony.lock
├── templates
└── base.html.twig
└── tests
├── .gitignore
├── Integration
├── AbstractIntegrationTestCase.php
└── Service
│ ├── CatalogBookHandlerTest.php
│ ├── RegisterReaderBookHandlerTest.php
│ ├── RegisterReaderHandlerTest.php
│ └── RegisterReaderWishHandlerTest.php
└── Unit
├── AbstractUnitTestCase.php
├── Entity
└── ReaderTest.php
├── Event
└── EventRecorderTest.php
├── Middleware
└── ReleaseRecordedEventsMiddlewareTest.php
└── Service
├── CatalogBookHandlerTest.php
├── RegisterReaderBookHandlerTest.php
├── RegisterReaderHandlerTest.php
└── RegisterReaderWishHandlerTest.php
/.env.dist:
--------------------------------------------------------------------------------
1 | # This file is a "template" of which env vars need to be defined for your application
2 | # Copy this file to .env file for development, create environment variables when deploying to production
3 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
4 |
5 | ###> symfony/framework-bundle ###
6 | APP_ENV=dev
7 | APP_SECRET=eb6672dc009c15c02b0da8f4a15a781d
8 | #TRUSTED_PROXIES=127.0.0.1,127.0.0.2
9 | #TRUSTED_HOSTS=localhost,example.com
10 | ###< symfony/framework-bundle ###
11 |
12 | ###> doctrine/doctrine-bundle ###
13 | # Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
14 | # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
15 | # Configure your db driver and server_version in config/packages/doctrine.yaml
16 | DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
17 | ###< doctrine/doctrine-bundle ###
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | ###> symfony/framework-bundle ###
4 | .env
5 | /public/bundles/
6 | /var/
7 | /vendor/
8 | ###< symfony/framework-bundle ###
9 |
10 | ###> symfony/phpunit-bridge ###
11 | .phpunit
12 | /phpunit.xml
13 | ###< symfony/phpunit-bridge ###
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Bruno Neves
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Bruno Neves
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Expressive Architecture
2 |
3 | This repository was created to show some concepts from "Projetando uma arquitetura expressiva" talk. Slides [here](https://www.slideshare.net/brunonm/projetando-uma-arquitetura-expressiva).
4 |
5 | ## That Book
6 |
7 | Platform to find opportunities for book trade among readers.
8 |
9 | ### Features
10 |
11 | - Symfony 4
12 | - GraphQL
13 | - Command Bus by Tactician
14 | - DDDish
15 |
16 | ### Setup
17 |
18 | ```bash
19 | $ composer install
20 | $ php bin/console doctrine:database:create
21 | $ php bin/console doctrine:migrations:migrate
22 | ```
23 |
24 | ### Tests
25 |
26 | ```bash
27 | $ bin/phpunit
28 | ```
29 |
30 | ### License
31 |
32 | Code licensed under MIT
33 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | load(__DIR__.'/../.env');
23 | }
24 |
25 | $input = new ArgvInput();
26 | $env = $input->getParameterOption(['--env', '-e'], $_SERVER['APP_ENV'] ?? 'dev', true);
27 | $debug = ($_SERVER['APP_DEBUG'] ?? ('prod' !== $env)) && !$input->hasParameterOption('--no-debug', true);
28 |
29 | if ($debug) {
30 | umask(0000);
31 |
32 | if (class_exists(Debug::class)) {
33 | Debug::enable();
34 | }
35 | }
36 |
37 | $kernel = new Kernel($env, $debug);
38 | $application = new Application($kernel);
39 | $application->run($input);
40 |
--------------------------------------------------------------------------------
/bin/phpunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | ['all' => true],
5 | Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
6 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
7 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
8 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
9 | League\Tactician\Bundle\TacticianBundle::class => ['all' => true],
10 | Overblog\GraphQLBundle\OverblogGraphQLBundle::class => ['all' => true],
11 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
12 | Overblog\GraphiQLBundle\OverblogGraphiQLBundle::class => ['dev' => true],
13 | ];
14 |
--------------------------------------------------------------------------------
/config/graphql/schema/Mutation.yml:
--------------------------------------------------------------------------------
1 | Mutation:
2 | type: object
3 | config:
4 | fields:
5 | registerReaderBook:
6 | type: Boolean
7 | args:
8 | bookId:
9 | type: String!
10 | readerId:
11 | type: String!
12 | resolve: >
13 | @=service("tactician.commandbus.mutation")
14 | .handle(
15 | newObject("ThatBook\\Service\\RegisterReaderBook", [
16 | args["bookId"],
17 | args["readerId"]
18 | ])
19 | )
20 |
21 | registerReaderWish:
22 | type: Boolean
23 | args:
24 | bookId:
25 | type: String!
26 | readerId:
27 | type: String!
28 | resolve: >
29 | @=service("tactician.commandbus.mutation")
30 | .handle(
31 | newObject("ThatBook\\Service\\RegisterReaderWish", [
32 | args["bookId"],
33 | args["readerId"]
34 | ])
35 | )
36 |
37 | catalogBook:
38 | type: Book
39 | args:
40 | title:
41 | type: String!
42 | publisher:
43 | type: String!
44 | category:
45 | type: Category!
46 | resolve: >
47 | @=service("tactician.commandbus.mutation")
48 | .handle(
49 | newObject("ThatBook\\Service\\CatalogBook", [
50 | args["title"],
51 | args["publisher"],
52 | args["category"]
53 | ])
54 | )
55 |
56 | registerReader:
57 | type: Reader
58 | args:
59 | name:
60 | type: String!
61 | resolve: >
62 | @=service("tactician.commandbus.mutation")
63 | .handle(
64 | newObject("ThatBook\\Service\\RegisterReader", [
65 | args["name"]
66 | ])
67 | )
--------------------------------------------------------------------------------
/config/graphql/schema/Query.yml:
--------------------------------------------------------------------------------
1 | Query:
2 | type: object
3 | config:
4 | fields:
5 | books:
6 | type: "[Book]"
7 | resolve: '@=service("ThatBook\\Repository\\BookRepository").findAll()'
8 | readers:
9 | type: "[Reader]"
10 | resolve: '@=service("ThatBook\\Repository\\ReaderRepository").findAll()'
11 |
--------------------------------------------------------------------------------
/config/graphql/types/Book.graphql:
--------------------------------------------------------------------------------
1 | type Book {
2 | id: ID!
3 | title: String!
4 | publisher: String!
5 | category: Category!
6 | }
--------------------------------------------------------------------------------
/config/graphql/types/Category.graphql:
--------------------------------------------------------------------------------
1 | enum Category {
2 | ACTION
3 | ADVENTURE
4 | BIOGRAPHY
5 | CHILDREN
6 | COMEDY
7 | FICTION
8 | HISTORY
9 | ROMANCE
10 | THRILLER
11 | HORROR
12 | }
--------------------------------------------------------------------------------
/config/graphql/types/Reader.graphql:
--------------------------------------------------------------------------------
1 | type Reader {
2 | id: ID!
3 | name: String!
4 | books: [Book]
5 | wishlist: [Book]
6 | }
--------------------------------------------------------------------------------
/config/packages/dev/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: true
4 |
--------------------------------------------------------------------------------
/config/packages/doctrine.yaml:
--------------------------------------------------------------------------------
1 | parameters:
2 | # Adds a fallback DATABASE_URL if the env var is not set.
3 | # This allows you to run cache:warmup even if your
4 | # environment variables are not available yet.
5 | # You should not need to change this value.
6 | env(DATABASE_URL): ''
7 |
8 | doctrine:
9 | dbal:
10 | # configure these for your database server
11 | driver: 'pdo_mysql'
12 | server_version: '5.7'
13 | charset: utf8mb4
14 |
15 | # With Symfony 3.3, remove the `resolve:` prefix
16 | url: '%env(resolve:DATABASE_URL)%'
17 | orm:
18 | auto_generate_proxy_classes: '%kernel.debug%'
19 | naming_strategy: doctrine.orm.naming_strategy.underscore
20 | auto_mapping: true
21 | mappings:
22 | ThatBook:
23 | is_bundle: false
24 | type: annotation
25 | dir: '%kernel.project_dir%/src/Entity'
26 | prefix: 'ThatBook\Entity'
27 | alias: ThatBook
28 |
--------------------------------------------------------------------------------
/config/packages/doctrine_migrations.yaml:
--------------------------------------------------------------------------------
1 | doctrine_migrations:
2 | dir_name: '%kernel.project_dir%/src/Migrations'
3 | # namespace is arbitrary but should be different from App\Migrations
4 | # as migrations classes should NOT be autoloaded
5 | namespace: DoctrineMigrations
6 |
--------------------------------------------------------------------------------
/config/packages/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: '%env(APP_SECRET)%'
3 | #default_locale: en
4 | #csrf_protection: true
5 | #http_method_override: true
6 |
7 | # Enables session support. Note that the session will ONLY be started if you read or write from it.
8 | # Remove or comment this section to explicitly disable session support.
9 | session:
10 | handler_id: ~
11 |
12 | #esi: true
13 | #fragments: true
14 | php_errors:
15 | log: true
16 |
17 | cache:
18 | # Put the unique name of your app here: the prefix seed
19 | # is used to compute stable namespaces for cache keys.
20 | #prefix_seed: your_vendor_name/app_name
21 |
22 | # The app cache caches to the filesystem by default.
23 | # Other options include:
24 |
25 | # Redis
26 | #app: cache.adapter.redis
27 | #default_redis_provider: redis://localhost
28 |
29 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
30 | #app: cache.adapter.apcu
31 |
--------------------------------------------------------------------------------
/config/packages/graphql.yaml:
--------------------------------------------------------------------------------
1 | overblog_graphql:
2 | definitions:
3 | schema:
4 | query: Query
5 | mutation: Mutation
6 | mappings:
7 | auto_discover: false
8 | types:
9 | - type: graphql
10 | dir: "%kernel.project_dir%/config/graphql/types"
11 | suffix: ~
12 | - type: yaml
13 | dir: "%kernel.project_dir%/config/graphql/schema"
14 | suffix: ~
15 |
--------------------------------------------------------------------------------
/config/packages/league_tactician.yaml:
--------------------------------------------------------------------------------
1 | # Library documentation: http://tactician.thephpleague.com/
2 | # Bundle documentation: https://github.com/thephpleague/tactician-bundle/blob/v1.0/README.md
3 | tactician:
4 | commandbus:
5 | default:
6 | middleware:
7 | - ThatBook\Middleware\QueueMiddleware
8 | - tactician.middleware.locking
9 | - ThatBook\Middleware\ReleaseRecordedEventsMiddleware
10 | - tactician.middleware.doctrine
11 | - tactician.middleware.command_handler
12 | mutation:
13 | middleware:
14 | - ThatBook\Middleware\QueueMiddleware
15 | - ThatBook\Middleware\GraphQLMiddleware
16 | - tactician.middleware.locking
17 | - ThatBook\Middleware\ReleaseRecordedEventsMiddleware
18 | - tactician.middleware.doctrine
19 | - tactician.middleware.command_handler
--------------------------------------------------------------------------------
/config/packages/prod/doctrine.yaml:
--------------------------------------------------------------------------------
1 | doctrine:
2 | orm:
3 | metadata_cache_driver:
4 | type: service
5 | id: doctrine.system_cache_provider
6 | query_cache_driver:
7 | type: service
8 | id: doctrine.system_cache_provider
9 | result_cache_driver:
10 | type: service
11 | id: doctrine.result_cache_provider
12 |
13 | services:
14 | doctrine.result_cache_provider:
15 | class: Symfony\Component\Cache\DoctrineProvider
16 | public: false
17 | arguments:
18 | - '@doctrine.result_cache_pool'
19 | doctrine.system_cache_provider:
20 | class: Symfony\Component\Cache\DoctrineProvider
21 | public: false
22 | arguments:
23 | - '@doctrine.system_cache_pool'
24 |
25 | framework:
26 | cache:
27 | pools:
28 | doctrine.result_cache_pool:
29 | adapter: cache.app
30 | doctrine.system_cache_pool:
31 | adapter: cache.system
32 |
--------------------------------------------------------------------------------
/config/packages/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: ~
4 |
--------------------------------------------------------------------------------
/config/packages/test/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: true
3 | session:
4 | storage_id: session.storage.mock_file
5 |
--------------------------------------------------------------------------------
/config/packages/twig.yaml:
--------------------------------------------------------------------------------
1 | twig:
2 | paths: ['%kernel.project_dir%/templates']
3 | debug: '%kernel.debug%'
4 | strict_variables: '%kernel.debug%'
5 |
--------------------------------------------------------------------------------
/config/routes.yaml:
--------------------------------------------------------------------------------
1 | #index:
2 | # path: /
3 | # defaults: { _controller: 'App\Controller\DefaultController::index' }
4 |
--------------------------------------------------------------------------------
/config/routes/annotations.yaml:
--------------------------------------------------------------------------------
1 | #controllers:
2 | # resource: ../../src/Controller/
3 | # type: annotation
4 |
--------------------------------------------------------------------------------
/config/routes/dev/graphiql.yaml:
--------------------------------------------------------------------------------
1 | overblog_graphiql:
2 | resource: "@OverblogGraphiQLBundle/Resources/config/routing.xml"
3 |
--------------------------------------------------------------------------------
/config/routes/dev/twig.yaml:
--------------------------------------------------------------------------------
1 | _errors:
2 | resource: '@TwigBundle/Resources/config/routing/errors.xml'
3 | prefix: /_error
4 |
--------------------------------------------------------------------------------
/config/routes/graphql.yaml:
--------------------------------------------------------------------------------
1 | overblog_graphql_endpoint:
2 | resource: "@OverblogGraphQLBundle/Resources/config/routing/graphql.yml"
3 |
--------------------------------------------------------------------------------
/config/services.yaml:
--------------------------------------------------------------------------------
1 | # Put parameters here that don't need to change on each machine where the app is deployed
2 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
3 | parameters:
4 |
5 | services:
6 | # default configuration for services in *this* file
7 | _defaults:
8 | autowire: true # Automatically injects dependencies in your services.
9 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
10 | public: false # Allows optimizing the container by removing unused services; this also means
11 | # fetching services directly from the container via $container->get() won't work.
12 | # The best practice is to be explicit about your dependencies anyway.
13 |
14 | # makes classes in src/ available to be used as services
15 | # this creates a service per class whose id is the fully-qualified class name
16 | ThatBook\:
17 | resource: '../src/*'
18 | exclude: '../src/{Entity,Event,Repository,Migrations,Tests,Kernel.php}'
19 |
20 | ThatBook\Repository\:
21 | resource: '../src/Repository'
22 | public: true
23 |
24 | event_recorder:
25 | class: ThatBook\Event\EventRecorder
26 | autowire: false
27 |
28 | ThatBook\Event\EventRecorder: '@event_recorder'
29 |
30 | League\Tactician\CommandBus: '@tactician.commandbus.default'
--------------------------------------------------------------------------------
/config/services_test.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | _defaults:
3 | public: true
4 |
5 | # If you need to access services in a test, create an alias
6 | # and then fetch that alias from the container. As a convention,
7 | # aliases are prefixed with test. For example:
8 | #
9 | # test.App\Service\MyService: '@App\Service\MyService'
10 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | tests/
38 |
39 |
40 |
41 |
42 |
43 | ./src/
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | load(__DIR__.'/../.env');
16 | }
17 |
18 | $env = $_SERVER['APP_ENV'] ?? 'dev';
19 | $debug = $_SERVER['APP_DEBUG'] ?? ('prod' !== $env);
20 |
21 | if ($debug) {
22 | umask(0000);
23 |
24 | Debug::enable();
25 | }
26 |
27 | if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {
28 | Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
29 | }
30 |
31 | if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {
32 | Request::setTrustedHosts(explode(',', $trustedHosts));
33 | }
34 |
35 | $kernel = new Kernel($env, $debug);
36 | $request = Request::createFromGlobals();
37 | $response = $kernel->handle($request);
38 | $response->send();
39 | $kernel->terminate($request, $response);
40 |
--------------------------------------------------------------------------------
/src/Entity/Book.php:
--------------------------------------------------------------------------------
1 | title = $title;
60 | $this->publisher = $publisher;
61 | $this->category = $category;
62 | }
63 |
64 | public function getId(): string
65 | {
66 | return $this->id;
67 | }
68 |
69 | public function getTitle(): string
70 | {
71 | return $this->title;
72 | }
73 |
74 | public function getPublisher(): string
75 | {
76 | return $this->publisher;
77 | }
78 |
79 | public function getCategory(): string
80 | {
81 | return $this->category;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Entity/Reader.php:
--------------------------------------------------------------------------------
1 | name = $name;
55 | $this->books = new ArrayCollection();
56 | $this->wishlist = new ArrayCollection();
57 | }
58 |
59 | public function getId(): string
60 | {
61 | return $this->id;
62 | }
63 |
64 | public function getName(): string
65 | {
66 | return $this->name;
67 | }
68 |
69 | public function registerBook(Book $book): self
70 | {
71 | if (!$this->books->contains($book)) {
72 | $this->books[] = $book;
73 | }
74 |
75 | if ($this->wishlist->contains($book)) {
76 | $this->wishlist->removeElement($book);
77 | }
78 |
79 | return $this;
80 | }
81 |
82 | public function registerWish(Book $book): self
83 | {
84 | if ($this->books->contains($book)) {
85 | throw new ReaderAlreadyHasBookException();
86 | }
87 |
88 | if (!$this->wishlist->contains($book)) {
89 | $this->wishlist[] = $book;
90 | }
91 |
92 | return $this;
93 | }
94 |
95 | public function getBooks(): Collection
96 | {
97 | return $this->books;
98 | }
99 |
100 | public function getWishlist(): Collection
101 | {
102 | return $this->wishlist;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Event/AbstractEvent.php:
--------------------------------------------------------------------------------
1 | recordedEvents;
16 | $this->eraseEvents();
17 | return $events;
18 | }
19 |
20 | public function eraseEvents()
21 | {
22 | $this->recordedEvents = [];
23 | }
24 |
25 | public function record(AbstractEvent $event)
26 | {
27 | $this->recordedEvents[] = $event;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Event/ReaderBookRegisteredEvent.php:
--------------------------------------------------------------------------------
1 | book = $book;
29 | $this->reader = $reader;
30 | $this->createdAt = new \DateTime();
31 | }
32 |
33 | public function getBook(): Book
34 | {
35 | return $this->book;
36 | }
37 |
38 | public function getReader(): Reader
39 | {
40 | return $this->reader;
41 | }
42 |
43 | public function getCreatedAt(): \DateTime
44 | {
45 | return $this->createdAt;
46 | }
47 |
48 | public static function getName(): string
49 | {
50 | return 'reader_book.registered';
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/EventSubscriber/ReaderSubscriber.php:
--------------------------------------------------------------------------------
1 | serviceBus = $serviceBus;
21 | }
22 |
23 | public static function getSubscribedEvents(): array
24 | {
25 | return [
26 | ReaderBookRegisteredEvent::getName() => 'whenReaderBookRegistered'
27 | ];
28 | }
29 |
30 | public function whenReaderBookRegistered(ReaderBookRegisteredEvent $event)
31 | {
32 | $command = new NotifyTradeOpportunities();
33 | $this->serviceBus->handle($command);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Exception/BookNotFoundException.php:
--------------------------------------------------------------------------------
1 | getProjectDir().'/var/cache/'.$this->environment;
21 | }
22 |
23 | public function getLogDir()
24 | {
25 | return $this->getProjectDir().'/var/log';
26 | }
27 |
28 | public function registerBundles()
29 | {
30 | $contents = require $this->getProjectDir().'/config/bundles.php';
31 | foreach ($contents as $class => $envs) {
32 | if (isset($envs['all']) || isset($envs[$this->environment])) {
33 | yield new $class();
34 | }
35 | }
36 | }
37 |
38 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
39 | {
40 | $container->setParameter('container.autowiring.strict_mode', true);
41 | $container->setParameter('container.dumper.inline_class_loader', true);
42 | $confDir = $this->getProjectDir().'/config';
43 |
44 | $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
45 | $loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
46 | $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
47 | $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
48 |
49 | $handlerDefinition = $container->registerForAutoconfiguration(HandlerInterface::class);
50 | $handlerDefinition->addTag('tactician.handler', ['typehints' => true]);
51 | }
52 |
53 | protected function configureRoutes(RouteCollectionBuilder $routes)
54 | {
55 | $confDir = $this->getProjectDir().'/config';
56 |
57 | $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
58 | $routes->import($confDir.'/{routes}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
59 | $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Middleware/GraphQLMiddleware.php:
--------------------------------------------------------------------------------
1 | producer->publish(
26 | // $command->publisherServiceName(),
27 | // $command->toMessage(),
28 | // $command->routingKey()
29 | // );
30 |
31 | } catch (\Exception | \Throwable $exception) {
32 | throw $exception;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Middleware/ReleaseRecordedEventsMiddleware.php:
--------------------------------------------------------------------------------
1 | eventRecorder = $eventRecorder;
29 | $this->eventDispatcher = $eventDispatcher;
30 | }
31 |
32 | /**
33 | * @inheritdoc
34 | */
35 | public function execute($command, callable $next)
36 | {
37 | try {
38 | $result = $next($command);
39 | } catch (\Exception $exception) {
40 | $this->eventRecorder->eraseEvents();
41 | throw $exception;
42 | }
43 |
44 | $recordedEvents = $this->eventRecorder->releaseEvents();
45 |
46 | foreach ($recordedEvents as $event) {
47 | $this->eventDispatcher->dispatch($event->getName(), $event);
48 | }
49 |
50 | return $result;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Migrations/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brunonm/expressive-architecture/49585506667e1b2041ca6b10ad09338a476367c3/src/Migrations/.gitignore
--------------------------------------------------------------------------------
/src/Migrations/Version20180221021751.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('CREATE TABLE category (id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', title VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
19 | $this->addSql('CREATE TABLE book (id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', category_id CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', title VARCHAR(255) NOT NULL, publisher VARCHAR(255) NOT NULL, INDEX IDX_CBE5A33112469DE2 (category_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
20 | $this->addSql('CREATE TABLE reader (id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
21 | $this->addSql('CREATE TABLE reader_book (reader_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', book_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', INDEX IDX_2A3845F31717D737 (reader_id), INDEX IDX_2A3845F316A2B381 (book_id), PRIMARY KEY(reader_id, book_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
22 | $this->addSql('CREATE TABLE wishlist (reader_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', book_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', INDEX IDX_9CE12A311717D737 (reader_id), INDEX IDX_9CE12A3116A2B381 (book_id), PRIMARY KEY(reader_id, book_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
23 | $this->addSql('CREATE TABLE trade (id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', old_reader_id CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', new_reader_id CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', book_id CHAR(36) DEFAULT NULL COMMENT \'(DC2Type:guid)\', created_at DATETIME NOT NULL, INDEX IDX_7E1A436652A39386 (old_reader_id), INDEX IDX_7E1A43662588D258 (new_reader_id), INDEX IDX_7E1A436616A2B381 (book_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
24 | $this->addSql('ALTER TABLE book ADD CONSTRAINT FK_CBE5A33112469DE2 FOREIGN KEY (category_id) REFERENCES category (id)');
25 | $this->addSql('ALTER TABLE reader_book ADD CONSTRAINT FK_2A3845F31717D737 FOREIGN KEY (reader_id) REFERENCES reader (id)');
26 | $this->addSql('ALTER TABLE reader_book ADD CONSTRAINT FK_2A3845F316A2B381 FOREIGN KEY (book_id) REFERENCES book (id)');
27 | $this->addSql('ALTER TABLE wishlist ADD CONSTRAINT FK_9CE12A311717D737 FOREIGN KEY (reader_id) REFERENCES reader (id)');
28 | $this->addSql('ALTER TABLE wishlist ADD CONSTRAINT FK_9CE12A3116A2B381 FOREIGN KEY (book_id) REFERENCES book (id)');
29 | $this->addSql('ALTER TABLE trade ADD CONSTRAINT FK_7E1A436652A39386 FOREIGN KEY (old_reader_id) REFERENCES reader (id)');
30 | $this->addSql('ALTER TABLE trade ADD CONSTRAINT FK_7E1A43662588D258 FOREIGN KEY (new_reader_id) REFERENCES reader (id)');
31 | $this->addSql('ALTER TABLE trade ADD CONSTRAINT FK_7E1A436616A2B381 FOREIGN KEY (book_id) REFERENCES book (id)');
32 | }
33 |
34 | public function down(Schema $schema)
35 | {
36 | // this down() migration is auto-generated, please modify it to your needs
37 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
38 |
39 | $this->addSql('ALTER TABLE book DROP FOREIGN KEY FK_CBE5A33112469DE2');
40 | $this->addSql('ALTER TABLE reader_book DROP FOREIGN KEY FK_2A3845F316A2B381');
41 | $this->addSql('ALTER TABLE wishlist DROP FOREIGN KEY FK_9CE12A3116A2B381');
42 | $this->addSql('ALTER TABLE trade DROP FOREIGN KEY FK_7E1A436616A2B381');
43 | $this->addSql('ALTER TABLE reader_book DROP FOREIGN KEY FK_2A3845F31717D737');
44 | $this->addSql('ALTER TABLE wishlist DROP FOREIGN KEY FK_9CE12A311717D737');
45 | $this->addSql('ALTER TABLE trade DROP FOREIGN KEY FK_7E1A436652A39386');
46 | $this->addSql('ALTER TABLE trade DROP FOREIGN KEY FK_7E1A43662588D258');
47 | $this->addSql('DROP TABLE category');
48 | $this->addSql('DROP TABLE book');
49 | $this->addSql('DROP TABLE reader');
50 | $this->addSql('DROP TABLE reader_book');
51 | $this->addSql('DROP TABLE wishlist');
52 | $this->addSql('DROP TABLE trade');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Migrations/Version20180221022807.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('ALTER TABLE book CHANGE category_id category_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\'');
19 | $this->addSql('ALTER TABLE trade CHANGE old_reader_id old_reader_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', CHANGE new_reader_id new_reader_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', CHANGE book_id book_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\'');
20 | }
21 |
22 | public function down(Schema $schema)
23 | {
24 | // this down() migration is auto-generated, please modify it to your needs
25 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
26 |
27 | $this->addSql('ALTER TABLE book CHANGE category_id category_id CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\'');
28 | $this->addSql('ALTER TABLE trade CHANGE old_reader_id old_reader_id CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', CHANGE new_reader_id new_reader_id CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', CHANGE book_id book_id CHAR(36) DEFAULT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\'');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Migrations/Version20180224062434.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('DROP TABLE trade');
19 | }
20 |
21 | public function down(Schema $schema)
22 | {
23 | // this down() migration is auto-generated, please modify it to your needs
24 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
25 |
26 | $this->addSql('CREATE TABLE trade (id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', old_reader_id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', new_reader_id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', book_id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', created_at DATETIME NOT NULL, INDEX IDX_7E1A436652A39386 (old_reader_id), INDEX IDX_7E1A43662588D258 (new_reader_id), INDEX IDX_7E1A436616A2B381 (book_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
27 | $this->addSql('ALTER TABLE trade ADD CONSTRAINT FK_7E1A436616A2B381 FOREIGN KEY (book_id) REFERENCES book (id)');
28 | $this->addSql('ALTER TABLE trade ADD CONSTRAINT FK_7E1A43662588D258 FOREIGN KEY (new_reader_id) REFERENCES reader (id)');
29 | $this->addSql('ALTER TABLE trade ADD CONSTRAINT FK_7E1A436652A39386 FOREIGN KEY (old_reader_id) REFERENCES reader (id)');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Migrations/Version20180224074548.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
17 |
18 | $this->addSql('ALTER TABLE book DROP FOREIGN KEY FK_CBE5A33112469DE2');
19 | $this->addSql('DROP TABLE category');
20 | $this->addSql('DROP INDEX IDX_CBE5A33112469DE2 ON book');
21 | $this->addSql('ALTER TABLE book ADD category VARCHAR(255) NOT NULL, DROP category_id');
22 | }
23 |
24 | public function down(Schema $schema)
25 | {
26 | // this down() migration is auto-generated, please modify it to your needs
27 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
28 |
29 | $this->addSql('CREATE TABLE category (id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', title VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
30 | $this->addSql('ALTER TABLE book ADD category_id CHAR(36) NOT NULL COLLATE utf8_unicode_ci COMMENT \'(DC2Type:guid)\', DROP category');
31 | $this->addSql('ALTER TABLE book ADD CONSTRAINT FK_CBE5A33112469DE2 FOREIGN KEY (category_id) REFERENCES category (id)');
32 | $this->addSql('CREATE INDEX IDX_CBE5A33112469DE2 ON book (category_id)');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Queue/AbstractQueueableCommand.php:
--------------------------------------------------------------------------------
1 | _em->persist($entity);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Repository/BookRepository.php:
--------------------------------------------------------------------------------
1 | title = $title;
26 | $this->publisher = $publisher;
27 | $this->category = $category;
28 | }
29 |
30 | public function getTitle(): string
31 | {
32 | return $this->title;
33 | }
34 |
35 | public function getPublisher(): string
36 | {
37 | return $this->publisher;
38 | }
39 |
40 | public function getCategory(): string
41 | {
42 | return $this->category;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Service/CatalogBookHandler.php:
--------------------------------------------------------------------------------
1 | bookRepository = $bookRepository;
21 | }
22 |
23 | public function handle(CatalogBook $command): Book
24 | {
25 | $book = new Book($command->getTitle(), $command->getPublisher(), $command->getCategory());
26 | $this->bookRepository->store($book);
27 | return $book;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Service/HandlerInterface.php:
--------------------------------------------------------------------------------
1 | readerRepository = $readerRepository;
20 | }
21 |
22 | public function handle(NotifyTradeOpportunities $command)
23 | {
24 | // find trade opportunities
25 | // send email
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Service/RegisterReader.php:
--------------------------------------------------------------------------------
1 | name = $name;
16 | }
17 |
18 | public function getName(): string
19 | {
20 | return $this->name;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Service/RegisterReaderBook.php:
--------------------------------------------------------------------------------
1 | bookId = $bookId;
21 | $this->readerId = $readerId;
22 | }
23 |
24 | public function getBookId(): string
25 | {
26 | return $this->bookId;
27 | }
28 |
29 | public function getReaderId(): string
30 | {
31 | return $this->readerId;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Service/RegisterReaderBookHandler.php:
--------------------------------------------------------------------------------
1 | readerRepository = $readerRepository;
38 | $this->bookRepository = $bookRepository;
39 | $this->eventRecorder = $eventRecorder;
40 | }
41 |
42 | public function handle(RegisterReaderBook $command)
43 | {
44 | if (!$reader = $this->readerRepository->find($command->getReaderId())) {
45 | throw new ReaderNotFoundException();
46 | }
47 |
48 | if (!$book = $this->bookRepository->find($command->getBookId())) {
49 | throw new BookNotFoundException();
50 | }
51 |
52 | $reader->registerBook($book);
53 |
54 | $this->readerRepository->store($reader);
55 |
56 | $this->eventRecorder->record(new ReaderBookRegisteredEvent($book, $reader));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Service/RegisterReaderHandler.php:
--------------------------------------------------------------------------------
1 | readerRepository = $readerRepository;
21 | }
22 |
23 | public function handle(RegisterReader $command): Reader
24 | {
25 | $reader = new Reader($command->getName());
26 | $this->readerRepository->store($reader);
27 | return $reader;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Service/RegisterReaderWish.php:
--------------------------------------------------------------------------------
1 | bookId = $bookId;
21 | $this->readerId = $readerId;
22 | }
23 |
24 | public function getBookId(): string
25 | {
26 | return $this->bookId;
27 | }
28 |
29 | public function getReaderId(): string
30 | {
31 | return $this->readerId;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Service/RegisterReaderWishHandler.php:
--------------------------------------------------------------------------------
1 | readerRepository = $readerRepository;
28 | $this->bookRepository = $bookRepository;
29 | }
30 |
31 | public function handle(RegisterReaderWish $command)
32 | {
33 | if (!$reader = $this->readerRepository->find($command->getReaderId())) {
34 | throw new ReaderNotFoundException();
35 | }
36 |
37 | if (!$book = $this->bookRepository->find($command->getBookId())) {
38 | throw new BookNotFoundException();
39 | }
40 |
41 | $reader->registerWish($book);
42 |
43 | $this->readerRepository->store($reader);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/symfony.lock:
--------------------------------------------------------------------------------
1 | {
2 | "doctrine/annotations": {
3 | "version": "1.0",
4 | "recipe": {
5 | "repo": "github.com/symfony/recipes",
6 | "branch": "master",
7 | "version": "1.0",
8 | "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672"
9 | }
10 | },
11 | "doctrine/cache": {
12 | "version": "v1.7.1"
13 | },
14 | "doctrine/collections": {
15 | "version": "v1.5.0"
16 | },
17 | "doctrine/common": {
18 | "version": "v2.8.1"
19 | },
20 | "doctrine/dbal": {
21 | "version": "v2.6.3"
22 | },
23 | "doctrine/doctrine-bundle": {
24 | "version": "1.6",
25 | "recipe": {
26 | "repo": "github.com/symfony/recipes",
27 | "branch": "master",
28 | "version": "1.6",
29 | "ref": "c407ab0b5e5a39b242a52d323a5e84e6d3b7e4c0"
30 | }
31 | },
32 | "doctrine/doctrine-cache-bundle": {
33 | "version": "1.3.2"
34 | },
35 | "doctrine/doctrine-migrations-bundle": {
36 | "version": "1.2",
37 | "recipe": {
38 | "repo": "github.com/symfony/recipes",
39 | "branch": "master",
40 | "version": "1.2",
41 | "ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1"
42 | }
43 | },
44 | "doctrine/inflector": {
45 | "version": "v1.3.0"
46 | },
47 | "doctrine/instantiator": {
48 | "version": "1.1.0"
49 | },
50 | "doctrine/lexer": {
51 | "version": "v1.0.1"
52 | },
53 | "doctrine/migrations": {
54 | "version": "v1.6.2"
55 | },
56 | "doctrine/orm": {
57 | "version": "v2.6.0"
58 | },
59 | "hamcrest/hamcrest-php": {
60 | "version": "v2.0.0"
61 | },
62 | "jdorn/sql-formatter": {
63 | "version": "v1.2.17"
64 | },
65 | "league/tactician": {
66 | "version": "v1.0.3"
67 | },
68 | "league/tactician-bundle": {
69 | "version": "1.0",
70 | "recipe": {
71 | "repo": "github.com/symfony/recipes-contrib",
72 | "branch": "master",
73 | "version": "1.0",
74 | "ref": "222c3d39d38378bc6a9790a0b5baf841ba6679b9"
75 | }
76 | },
77 | "league/tactician-container": {
78 | "version": "2.0.0"
79 | },
80 | "league/tactician-doctrine": {
81 | "version": "v1.1"
82 | },
83 | "mockery/mockery": {
84 | "version": "1.0"
85 | },
86 | "ocramius/package-versions": {
87 | "version": "1.3.0"
88 | },
89 | "ocramius/proxy-manager": {
90 | "version": "2.2.0"
91 | },
92 | "overblog/graphiql-bundle": {
93 | "version": "0.1",
94 | "recipe": {
95 | "repo": "github.com/symfony/recipes-contrib",
96 | "branch": "master",
97 | "version": "0.1",
98 | "ref": "fe8d172f2480efc598f5a8be0e732656d3594cec"
99 | }
100 | },
101 | "overblog/graphql-bundle": {
102 | "version": "0.11",
103 | "recipe": {
104 | "repo": "github.com/symfony/recipes-contrib",
105 | "branch": "master",
106 | "version": "0.11",
107 | "ref": "cb22b949fa6a4b01f6955e78a58444605f062616"
108 | }
109 | },
110 | "overblog/graphql-php-generator": {
111 | "version": "v0.7.2"
112 | },
113 | "psr/cache": {
114 | "version": "1.0.1"
115 | },
116 | "psr/container": {
117 | "version": "1.0.0"
118 | },
119 | "psr/log": {
120 | "version": "1.0.2"
121 | },
122 | "psr/simple-cache": {
123 | "version": "1.0.0"
124 | },
125 | "symfony/cache": {
126 | "version": "v4.0.4"
127 | },
128 | "symfony/config": {
129 | "version": "v4.0.4"
130 | },
131 | "symfony/console": {
132 | "version": "3.3",
133 | "recipe": {
134 | "repo": "github.com/symfony/recipes",
135 | "branch": "master",
136 | "version": "3.3",
137 | "ref": "c646e4b71af082e94b5014daca36ef6812bad076"
138 | }
139 | },
140 | "symfony/debug": {
141 | "version": "v4.0.4"
142 | },
143 | "symfony/dependency-injection": {
144 | "version": "v4.0.4"
145 | },
146 | "symfony/doctrine-bridge": {
147 | "version": "v4.0.4"
148 | },
149 | "symfony/dotenv": {
150 | "version": "v4.0.4"
151 | },
152 | "symfony/event-dispatcher": {
153 | "version": "v4.0.4"
154 | },
155 | "symfony/expression-language": {
156 | "version": "v4.0.5"
157 | },
158 | "symfony/filesystem": {
159 | "version": "v4.0.4"
160 | },
161 | "symfony/finder": {
162 | "version": "v4.0.4"
163 | },
164 | "symfony/flex": {
165 | "version": "1.0",
166 | "recipe": {
167 | "repo": "github.com/symfony/recipes",
168 | "branch": "master",
169 | "version": "1.0",
170 | "ref": "cc1afd81841db36fbef982fe56b48ade6716fac4"
171 | }
172 | },
173 | "symfony/framework-bundle": {
174 | "version": "3.3",
175 | "recipe": {
176 | "repo": "github.com/symfony/recipes",
177 | "branch": "master",
178 | "version": "3.3",
179 | "ref": "b9f462a47f7fd28d56c61f59c027fd7ad8e1aac8"
180 | }
181 | },
182 | "symfony/http-foundation": {
183 | "version": "v4.0.4"
184 | },
185 | "symfony/http-kernel": {
186 | "version": "v4.0.4"
187 | },
188 | "symfony/inflector": {
189 | "version": "v4.0.5"
190 | },
191 | "symfony/lts": {
192 | "version": "4-dev"
193 | },
194 | "symfony/maker-bundle": {
195 | "version": "1.0",
196 | "recipe": {
197 | "repo": "github.com/symfony/recipes",
198 | "branch": "master",
199 | "version": "1.0",
200 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
201 | }
202 | },
203 | "symfony/options-resolver": {
204 | "version": "v4.0.5"
205 | },
206 | "symfony/orm-pack": {
207 | "version": "v1.0.5"
208 | },
209 | "symfony/phpunit-bridge": {
210 | "version": "3.3",
211 | "recipe": {
212 | "repo": "github.com/symfony/recipes",
213 | "branch": "master",
214 | "version": "3.3",
215 | "ref": "179470cb6492db92dffee208cfdb436f175c93b4"
216 | }
217 | },
218 | "symfony/polyfill-mbstring": {
219 | "version": "v1.7.0"
220 | },
221 | "symfony/property-access": {
222 | "version": "v4.0.5"
223 | },
224 | "symfony/routing": {
225 | "version": "4.0",
226 | "recipe": {
227 | "repo": "github.com/symfony/recipes",
228 | "branch": "master",
229 | "version": "4.0",
230 | "ref": "cda8b550123383d25827705d05a42acf6819fe4e"
231 | }
232 | },
233 | "symfony/twig-bridge": {
234 | "version": "v4.0.5"
235 | },
236 | "symfony/twig-bundle": {
237 | "version": "3.3",
238 | "recipe": {
239 | "repo": "github.com/symfony/recipes",
240 | "branch": "master",
241 | "version": "3.3",
242 | "ref": "f75ac166398e107796ca94cc57fa1edaa06ec47f"
243 | }
244 | },
245 | "symfony/yaml": {
246 | "version": "v4.0.4"
247 | },
248 | "twig/twig": {
249 | "version": "v2.4.4"
250 | },
251 | "webonyx/graphql-php": {
252 | "version": "v0.11.5"
253 | },
254 | "zendframework/zend-code": {
255 | "version": "3.3.0"
256 | },
257 | "zendframework/zend-eventmanager": {
258 | "version": "3.2.0"
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/templates/base.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block title %}Welcome!{% endblock %}
6 | {% block stylesheets %}{% endblock %}
7 |
8 |
9 | {% block body %}{% endblock %}
10 | {% block javascripts %}{% endblock %}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brunonm/expressive-architecture/49585506667e1b2041ca6b10ad09338a476367c3/tests/.gitignore
--------------------------------------------------------------------------------
/tests/Integration/AbstractIntegrationTestCase.php:
--------------------------------------------------------------------------------
1 | container = static::$kernel->getContainer();
38 | $this->em = $this->container->get('doctrine.orm.entity_manager');
39 | $this->databaseFile = $this->em->getConnection()->getDatabase();
40 | $this->databaseFileBackup = $this->databaseFile . '.bak';
41 | $this->createDatabase();
42 | }
43 |
44 | protected function getServiceBus(): CommandBus
45 | {
46 | return $this->container->get('tactician.commandbus.default');
47 | }
48 |
49 | protected function createDatabase()
50 | {
51 | if (!file_exists($this->databaseFile)) {
52 |
53 | $application = new Application(static::$kernel);
54 | $application->setAutoExit(false);
55 |
56 | $schemaCreate = [
57 | 'command' => 'doctrine:schema:create',
58 | '--quiet' => true,
59 | '--env' => 'test'
60 | ];
61 |
62 | $application->run(new ArrayInput($schemaCreate));
63 |
64 | $this->backupDatabase();
65 | }
66 | }
67 |
68 | protected function backupDatabase()
69 | {
70 | copy($this->databaseFile, $this->databaseFileBackup);
71 | }
72 |
73 | protected function restoreDatabase()
74 | {
75 | unlink($this->databaseFile);
76 | copy($this->databaseFileBackup, $this->databaseFile);
77 | }
78 |
79 | protected function tearDown()
80 | {
81 | parent::tearDown();
82 | $this->em->close();
83 | $this->em = null;
84 | $this->restoreDatabase();
85 | }
86 | }
--------------------------------------------------------------------------------
/tests/Integration/Service/CatalogBookHandlerTest.php:
--------------------------------------------------------------------------------
1 | getServiceBus()->handle(new CatalogBook('Pequeni princípe', 'Tenante', 'CHILDREN'));
15 |
16 | $books = $this->em->getRepository(Book::class)->findAll();
17 | $this->assertCount(1, $books);
18 | $this->assertEquals('Pequeni princípe', $books[0]->getTitle());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Integration/Service/RegisterReaderBookHandlerTest.php:
--------------------------------------------------------------------------------
1 | em->persist($reader);
17 |
18 | $book = new Book('Pequeni princípe', 'Tenante', 'CHILDREN');
19 | $this->em->persist($book);
20 |
21 | $this->em->flush();
22 |
23 | $this->getServiceBus()->handle(new RegisterReaderBook($book->getId(), $reader->getId()));
24 |
25 | $this->assertCount(1, $reader->getBooks());
26 | $this->assertEquals($book, $reader->getBooks()->first());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Integration/Service/RegisterReaderHandlerTest.php:
--------------------------------------------------------------------------------
1 | getServiceBus()->handle(new RegisterReader('Bruno'));
15 |
16 | $repo = $this->em->getRepository(Reader::class);
17 |
18 | $this->assertCount(1, $repo->findAll());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Integration/Service/RegisterReaderWishHandlerTest.php:
--------------------------------------------------------------------------------
1 | em->persist($reader);
17 |
18 | $book = new Book('Pequeni princípe', 'Tenante', 'CHILDREN');
19 | $this->em->persist($book);
20 |
21 | $this->em->flush();
22 |
23 | $this->getServiceBus()->handle(new RegisterReaderWish($book->getId(), $reader->getId()));
24 |
25 | $this->assertCount(1, $reader->getWishlist());
26 | $this->assertEquals($book, $reader->getWishlist()->first());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Unit/AbstractUnitTestCase.php:
--------------------------------------------------------------------------------
1 | registerBook($book);
19 |
20 | $this->assertCount(1, $reader->getBooks());
21 | }
22 |
23 | public function testShouldNotRegisterReaderBookTwice()
24 | {
25 | $book = M::mock(Book::class);
26 |
27 | $reader = new Reader('Bruno');
28 | $reader->registerBook($book);
29 | $reader->registerBook($book);
30 |
31 | $this->assertCount(1, $reader->getBooks());
32 | }
33 |
34 | public function testShouldRemoveWishWhenRegisterTheBook()
35 | {
36 | $book = M::mock(Book::class);
37 |
38 | $reader = new Reader('Bruno');
39 | $reader->registerWish($book);
40 | $reader->registerBook($book);
41 |
42 | $this->assertCount(0, $reader->getWishlist());
43 | }
44 |
45 | public function testShouldRegisterAReaderWish()
46 | {
47 | $book = M::mock(Book::class);
48 |
49 | $reader = new Reader('Bruno');
50 | $reader->registerBook($book);
51 |
52 | $this->assertCount(1, $reader->getBooks());
53 | }
54 |
55 | /**
56 | * @expectedException \ThatBook\Exception\ReaderAlreadyHasBookException
57 | */
58 | public function testShouldThrowExceptionIfReaderAlreadyHasBookWhenRegisterWish()
59 | {
60 | $book = M::mock(Book::class);
61 |
62 | $reader = new Reader('Bruno');
63 | $reader->registerBook($book);
64 | $reader->registerWish($book);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Unit/Event/EventRecorderTest.php:
--------------------------------------------------------------------------------
1 | record($event);
20 | $this->assertCount(1, $recorder->releaseEvents());
21 | }
22 |
23 | public function testShouldEraseRecordedEvents()
24 | {
25 | $event = new ReaderBookRegisteredEvent(M::mock(Book::class), M::mock(Reader::class));
26 | $recorder = new EventRecorder();
27 | $recorder->record($event);
28 | $recorder->eraseEvents();
29 | $this->assertCount(0, $recorder->releaseEvents());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Unit/Middleware/ReleaseRecordedEventsMiddlewareTest.php:
--------------------------------------------------------------------------------
1 | makePartial();
18 |
19 | $recorderMock = M::mock(EventRecorder::class);
20 | $recorderMock->shouldReceive('releaseEvents')->andReturn([$eventMock]);
21 |
22 | $dispatcherMock = M::mock(EventDispatcherInterface::class);
23 | $dispatcherMock->shouldReceive('dispatch')->once();
24 |
25 | $middleware = new ReleaseRecordedEventsMiddleware($recorderMock, $dispatcherMock);
26 |
27 | $middleware->execute('command', function () {});
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Unit/Service/CatalogBookHandlerTest.php:
--------------------------------------------------------------------------------
1 | bookRepoMock = M::mock(BookRepository::class);
21 | $this->handler = new CatalogBookHandler($this->bookRepoMock);
22 | }
23 |
24 | public function testShouldCatalogABook()
25 | {
26 | $this->bookRepoMock->shouldReceive('store')->once();
27 |
28 | $command = new CatalogBook('Contra', 'Alta books', 'ACTION');
29 |
30 | $this->handler->handle($command);
31 | }
32 |
33 | /**
34 | * @expectedException ThatBook\Exception\UnknownCategoryException
35 | */
36 | public function testShouldThrowExceptionIfCategoryNotFound()
37 | {
38 | $command = new CatalogBook('Contra', 'Alta books', 'XPTO');
39 |
40 | $this->handler->handle($command);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Unit/Service/RegisterReaderBookHandlerTest.php:
--------------------------------------------------------------------------------
1 | readerRepoMock = M::mock(ReaderRepository::class);
29 | $this->bookRepoMock = M::mock(BookRepository::class);
30 | $this->recorderMock = M::mock(EventRecorder::class)->makePartial();
31 |
32 | $this->handler = new RegisterReaderBookHandler(
33 | $this->readerRepoMock,
34 | $this->bookRepoMock,
35 | $this->recorderMock
36 | );
37 | }
38 |
39 | public function testShouldRegisterABook()
40 | {
41 | $readerMock = M::mock(Reader::class);
42 | $readerMock->shouldReceive('registerBook')->once();
43 |
44 | $this->readerRepoMock
45 | ->shouldReceive('find')
46 | ->andReturn($readerMock);
47 |
48 | $this->readerRepoMock
49 | ->shouldReceive('store')
50 | ->once();
51 |
52 | $this->bookRepoMock
53 | ->shouldReceive('find')
54 | ->andReturn(M::mock(Book::class));
55 |
56 | $command = new RegisterReaderBook('bookId', 'readerId');
57 |
58 | $this->handler->handle($command);
59 | }
60 |
61 | public function testShouldRecordAnEvent()
62 | {
63 | $readerMock = M::mock(Reader::class);
64 | $readerMock->shouldReceive('registerBook');
65 |
66 | $this->readerRepoMock
67 | ->shouldReceive('find')
68 | ->andReturn($readerMock);
69 |
70 | $this->readerRepoMock->shouldReceive('store');
71 |
72 | $this->bookRepoMock
73 | ->shouldReceive('find')
74 | ->andReturn(M::mock(Book::class));
75 |
76 | $this->recorderMock->shouldReceive('record')->once();
77 |
78 | $command = new RegisterReaderBook('bookId', 'readerId');
79 |
80 | $this->handler->handle($command);
81 | }
82 |
83 | /**
84 | * @expectedException ThatBook\Exception\BookNotFoundException
85 | */
86 | public function testShouldThrowExceptionIfBookNotFound()
87 | {
88 | $this->readerRepoMock->shouldReceive('find')->andReturn(M::mock(Reader::class));
89 | $this->bookRepoMock->shouldReceive('find')->andReturn(null);
90 | $command = new RegisterReaderBook('bookId', 'readerId');
91 | $this->handler->handle($command);
92 | }
93 |
94 | /**
95 | * @expectedException ThatBook\Exception\ReaderNotFoundException
96 | */
97 | public function testShouldThrowExceptionIfReaderNotFound()
98 | {
99 | $this->readerRepoMock->shouldReceive('find')->andReturn(null);
100 | $this->bookRepoMock->shouldReceive('find')->andReturn(M::mock(Book::class));
101 | $command = new RegisterReaderBook('bookId', 'readerId');
102 | $this->handler->handle($command);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/Unit/Service/RegisterReaderHandlerTest.php:
--------------------------------------------------------------------------------
1 | readerRepoMock = M::mock(ReaderRepository::class);
21 | $this->handler = new RegisterReaderHandler($this->readerRepoMock);
22 | }
23 |
24 | public function testShouldRCatalogAReader()
25 | {
26 | $this->readerRepoMock->shouldReceive('store')->once();
27 |
28 | $command = new RegisterReader('Alice');
29 |
30 | $this->handler->handle($command);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Unit/Service/RegisterReaderWishHandlerTest.php:
--------------------------------------------------------------------------------
1 | readerRepoMock = M::mock(ReaderRepository::class);
26 | $this->bookRepoMock = M::mock(BookRepository::class);
27 | $this->handler = new RegisterReaderWishHandler($this->readerRepoMock, $this->bookRepoMock);
28 | }
29 |
30 | public function testShouldRegisterAWish()
31 | {
32 | $readerMock = M::mock(Reader::class);
33 | $readerMock->shouldReceive('registerWish')->once();
34 |
35 | $this->readerRepoMock
36 | ->shouldReceive('find')
37 | ->andReturn($readerMock);
38 |
39 | $this->readerRepoMock
40 | ->shouldReceive('store')
41 | ->once();
42 |
43 | $this->bookRepoMock
44 | ->shouldReceive('find')
45 | ->andReturn(M::mock(Book::class));
46 |
47 | $command = new RegisterReaderWish('bookId', 'readerId');
48 |
49 | $this->handler->handle($command);
50 | }
51 |
52 | /**
53 | * @expectedException ThatBook\Exception\BookNotFoundException
54 | */
55 | public function testShouldThrowExceptionIfBookNotFound()
56 | {
57 | $this->readerRepoMock->shouldReceive('find')->andReturn(M::mock(Reader::class));
58 | $this->bookRepoMock->shouldReceive('find')->andReturn(null);
59 | $command = new RegisterReaderWish('bookId', 'readerId');
60 | $this->handler->handle($command);
61 | }
62 |
63 | /**
64 | * @expectedException ThatBook\Exception\ReaderNotFoundException
65 | */
66 | public function testShouldThrowExceptionIfReaderNotFound()
67 | {
68 | $this->readerRepoMock->shouldReceive('find')->andReturn(null);
69 | $this->bookRepoMock->shouldReceive('find')->andReturn(M::mock(Book::class));
70 | $command = new RegisterReaderWish('bookId', 'readerId');
71 | $this->handler->handle($command);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------