├── .coveralls.yml ├── .docheader ├── .docker └── wait_for_sql.sh ├── .env.dist ├── .env.test ├── .gitignore ├── .php_cs ├── .php_cs.dist ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README-GIT.md ├── README.md ├── Vagrantfile ├── bin ├── console └── phpunit ├── composer.json ├── composer.lock ├── config ├── bootstrap.php ├── bundles.php ├── event_store_http_api.yaml ├── packages │ ├── cache.yaml │ ├── dev │ │ ├── debug.yaml │ │ ├── easy_log_handler.yaml │ │ ├── monolog.php │ │ ├── monolog.yaml │ │ ├── routing.yaml │ │ ├── swiftmailer.yaml │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── framework.yaml │ ├── prod │ │ ├── doctrine.yaml │ │ ├── monolog.php │ │ └── monolog.yaml │ ├── prooph_event_store.yaml │ ├── prooph_service_bus.yaml │ ├── routing.yaml │ ├── sensio_framework_extra.yaml │ ├── swiftmailer.yaml │ ├── test │ │ ├── framework.yaml │ │ ├── monolog.yaml │ │ ├── routing.yaml │ │ ├── swiftmailer.yaml │ │ └── web_profiler.yaml │ └── twig.yaml ├── routes.yaml ├── routes │ ├── annotations.yaml │ ├── dev │ │ ├── twig.yaml │ │ └── web_profiler.yaml │ └── event_store_http_api.yaml ├── services.yaml └── services_test.yaml ├── docker-compose.yml ├── docs ├── installation │ ├── debugging.md │ ├── docker.md │ ├── index.md │ ├── manual.md │ ├── snapshots.md │ └── vagrant.md └── mgmt-ui │ └── message_flow.json ├── phpunit.xml.dist ├── public ├── assets │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.datetimepicker.css │ │ ├── bootstrap.min.css │ │ ├── bootstrap.paper.min.css │ │ ├── bootstrap.slate.min.css │ │ ├── qunit.css │ │ └── style.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ └── js │ │ ├── bootstrap-datetimepicker.js │ │ ├── bootstrap.min.js │ │ ├── jquery.min.js │ │ ├── jquery.plugins.js │ │ ├── lodash.js │ │ ├── moment.min.js │ │ ├── notify.min.js │ │ ├── prooph.riot.app.js │ │ ├── riot.min.js │ │ ├── underscore.string.js │ │ ├── uuid.js │ │ └── validate.min.js ├── index.php └── mgmt-ui │ ├── asset-manifest.json │ ├── css │ ├── main.03eb1f0c.css │ └── main.03eb1f0c.css.map │ ├── font │ ├── icons.674f50d287a8c48dc19ba404d20fe713.eot │ ├── icons.af7ae505a9eed503f8b8e6982036873e.woff2 │ ├── icons.b06871f281fee6b241d60582ae9369b9.ttf │ └── icons.fee66e712a8a08eef5805a46892932ad.woff │ ├── ico │ ├── apple-touch-icon-114-precomposed.png │ ├── apple-touch-icon-144-precomposed.png │ ├── apple-touch-icon-57-precomposed.png │ ├── apple-touch-icon-72-precomposed.png │ └── prooph-logo@0.5x.png │ ├── img │ ├── flags.9c74e172.png │ ├── icons.912ec66d.svg │ └── prooph_sticker.9e41e468.png │ ├── index.html │ ├── js │ ├── main.58996ad3.js │ └── main.58996ad3.js.map │ ├── manifest.json │ └── service-worker.js ├── src ├── Command │ └── CreateEventStreamCommand.php ├── Controller │ ├── .gitignore │ ├── ApiCommandController.php │ ├── DefaultController.php │ ├── UserListController.php │ ├── UserRegistrationController.php │ ├── UserTodoFormController.php │ └── UserTodoListController.php ├── Entity │ └── .gitignore ├── Kernel.php ├── ProophessorDo │ ├── Infrastructure │ │ ├── EventStoreHttpApi │ │ │ ├── JsonTransformer.php │ │ │ └── SymfonyUrlHelper.php │ │ ├── Repository │ │ │ ├── EventStoreTodoList.php │ │ │ └── EventStoreUserCollection.php │ │ └── Service │ │ │ └── ChecksUniqueUsersEmailAddressFromReadModel.php │ ├── Model │ │ ├── Entity.php │ │ ├── Enum.php │ │ ├── Todo │ │ │ ├── Command │ │ │ │ ├── AddDeadlineToTodo.php │ │ │ │ ├── AddReminderToTodo.php │ │ │ │ ├── MarkTodoAsDone.php │ │ │ │ ├── MarkTodoAsExpired.php │ │ │ │ ├── NotifyUserOfExpiredTodo.php │ │ │ │ ├── PostTodo.php │ │ │ │ ├── RemindTodoAssignee.php │ │ │ │ ├── ReopenTodo.php │ │ │ │ └── SendTodoReminderMail.php │ │ │ ├── Event │ │ │ │ ├── DeadlineWasAddedToTodo.php │ │ │ │ ├── ReminderWasAddedToTodo.php │ │ │ │ ├── TodoAssigneeWasReminded.php │ │ │ │ ├── TodoWasMarkedAsDone.php │ │ │ │ ├── TodoWasMarkedAsExpired.php │ │ │ │ ├── TodoWasPosted.php │ │ │ │ ├── TodoWasReopened.php │ │ │ │ └── TodoWasUnmarkedAsExpired.php │ │ │ ├── Exception │ │ │ │ ├── CannotReopenTodo.php │ │ │ │ ├── InvalidDeadline.php │ │ │ │ ├── InvalidReminder.php │ │ │ │ ├── InvalidText.php │ │ │ │ ├── TodoAlreadyDone.php │ │ │ │ ├── TodoNotExpired.php │ │ │ │ ├── TodoNotFound.php │ │ │ │ └── TodoNotOpen.php │ │ │ ├── Handler │ │ │ │ ├── AddDeadlineToTodoHandler.php │ │ │ │ ├── AddReminderToTodoHandler.php │ │ │ │ ├── GetTodoByIdHandler.php │ │ │ │ ├── GetTodosByAssigneeIdHandler.php │ │ │ │ ├── MarkTodoAsDoneHandler.php │ │ │ │ ├── MarkTodoAsExpiredHandler.php │ │ │ │ ├── NotifyUserOfExpiredTodoHandler.php │ │ │ │ ├── PostTodoHandler.php │ │ │ │ ├── RemindTodoAssigneeHandler.php │ │ │ │ ├── ReopenTodoHandler.php │ │ │ │ └── SendTodoReminderMailHandler.php │ │ │ ├── Query │ │ │ │ ├── GetTodoById.php │ │ │ │ └── GetTodosByAssigneeId.php │ │ │ ├── Todo.php │ │ │ ├── TodoDeadline.php │ │ │ ├── TodoId.php │ │ │ ├── TodoList.php │ │ │ ├── TodoReminder.php │ │ │ ├── TodoReminderStatus.php │ │ │ ├── TodoStatus.php │ │ │ └── TodoText.php │ │ ├── User │ │ │ ├── Command │ │ │ │ └── RegisterUser.php │ │ │ ├── EmailAddress.php │ │ │ ├── Event │ │ │ │ ├── UserWasRegistered.php │ │ │ │ └── UserWasRegisteredAgain.php │ │ │ ├── Exception │ │ │ │ ├── InvalidName.php │ │ │ │ ├── UserAlreadyExists.php │ │ │ │ └── UserNotFound.php │ │ │ ├── Handler │ │ │ │ ├── GetAllUsersHandler.php │ │ │ │ ├── GetUserByIdHandler.php │ │ │ │ └── RegisterUserHandler.php │ │ │ ├── Query │ │ │ │ ├── GetAllUsers.php │ │ │ │ └── GetUserById.php │ │ │ ├── Service │ │ │ │ └── ChecksUniqueUsersEmailAddress.php │ │ │ ├── User.php │ │ │ ├── UserCollection.php │ │ │ ├── UserId.php │ │ │ └── UserName.php │ │ └── ValueObject.php │ ├── ProcessManager │ │ ├── SendTodoDeadlineExpiredMailProcessManager.php │ │ └── SendTodoReminderMailProcessManager.php │ └── Projection │ │ ├── Table.php │ │ ├── Todo │ │ ├── TodoFinder.php │ │ ├── TodoProjection.php │ │ ├── TodoReadModel.php │ │ ├── TodoReminderFinder.php │ │ ├── TodoReminderProjection.php │ │ └── TodoReminderReadModel.php │ │ └── User │ │ ├── UserFinder.php │ │ ├── UserProjection.php │ │ └── UserReadModel.php ├── Repository │ └── .gitignore └── Twig │ └── RiotTag.php ├── symfony.lock ├── templates ├── base.html.twig └── default │ ├── base.html.twig │ ├── index.html.twig │ ├── riot-user-form.html.twig │ ├── riot-user-todo-form.html.twig │ ├── riot-user-todo-list.html.twig │ ├── riot-user-todo.html.twig │ ├── user-list.html.twig │ ├── user-registration-form.html.twig │ ├── user-todo-form.html.twig │ └── user-todo-list.html.twig └── tests └── .gitignore /.coveralls.yml: -------------------------------------------------------------------------------- 1 | # for php-coveralls 2 | service_name: travis-ci 3 | coverage_clover: build/logs/clover.xml 4 | -------------------------------------------------------------------------------- /.docheader: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of prooph/proophessor-do. 3 | * (c) 2014-%year% prooph software GmbH 4 | * (c) 2015-%year% Sascha-Oliver Prolic 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | -------------------------------------------------------------------------------- /.docker/wait_for_sql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo ">> Waiting for MySql to start" 5 | WAIT=0 6 | while ! nc -z mysql 3306; do 7 | sleep 1 8 | echo " MySql not ready yet" 9 | WAIT=$(($WAIT + 1)) 10 | if [ "$WAIT" -gt 20 ]; then 11 | echo "Error: Timeout when waiting for MySql socket" 12 | exit 1 13 | fi 14 | done 15 | 16 | echo ">> MySql socket available, resuming command execution" 17 | 18 | "$@" 19 | -------------------------------------------------------------------------------- /.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_DEBUG=1 8 | APP_SECRET=24a3e57c9db4197a8d32ff3bd5c28371 9 | ###< symfony/framework-bundle ### 10 | 11 | ###> symfony/swiftmailer-bundle ### 12 | # For Gmail as a transport, use: "gmail://username:password@localhost" 13 | # For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode=" 14 | # Delivery is disabled by default via "null://localhost" 15 | MAILER_URL=null://localhost 16 | ###< symfony/swiftmailer-bundle ### 17 | 18 | ###> doctrine/doctrine-bundle ### 19 | # Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url 20 | # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" 21 | # Set "serverVersion" to your server version to avoid edge-case exceptions and extra database calls 22 | DATABASE_URL="mysql://dev@mysql:3306/todo?charset=utf8mb4&password=dev" 23 | ###< doctrine/doctrine-bundle ### 24 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='s$cretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | .env 4 | /public/bundles/ 5 | /var/ 6 | /vendor/ 7 | ###< symfony/framework-bundle ### 8 | .idea 9 | .php_cs.cache 10 | .project 11 | .settings 12 | ###> phpunit/phpunit ### 13 | /phpunit.xml 14 | ###< phpunit/phpunit ### 15 | 16 | ###> friendsofphp/php-cs-fixer ### 17 | .php_cs 18 | .php_cs.cache 19 | ###< friendsofphp/php-cs-fixer ### 20 | 21 | ###> symfony/phpunit-bridge ### 22 | .phpunit 23 | /phpunit.xml 24 | ###< symfony/phpunit-bridge ### 25 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | getFinder()->in(__DIR__); 5 | 6 | $cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__; 7 | 8 | $config->setCacheFile($cacheDir . '/.php_cs.cache'); 9 | 10 | return $config; 11 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | setRules([ 5 | '@Symfony' => true, 6 | 'array_syntax' => ['syntax' => 'short'], 7 | ]) 8 | ; 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | fast_finish: true 5 | include: 6 | - php: 7.1 7 | env: 8 | - DEPENDENCIES="" 9 | - EXECUTE_CS_CHECK=true 10 | - TEST_COVERAGE=true 11 | - php: 7.1 12 | env: 13 | - DEPENDENCIES="--prefer-lowest --prefer-stable" 14 | 15 | cache: 16 | directories: 17 | - $HOME/.composer/cache 18 | - $HOME/.php-cs-fixer 19 | - $HOME/.local 20 | 21 | before_script: 22 | - mkdir -p "$HOME/.php-cs-fixer" 23 | - phpenv config-rm xdebug.ini 24 | - composer self-update 25 | - composer update --prefer-source $DEPENDENCIES 26 | 27 | script: 28 | - if [[ $TEST_COVERAGE == 'true' ]]; then php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-clover ./build/logs/clover.xml; else ./vendor/bin/phpunit; fi 29 | - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run; fi 30 | 31 | after_success: 32 | - if [[ $TEST_COVERAGE == 'true' ]]; then php vendor/bin/coveralls -v; fi 33 | 34 | notifications: 35 | webhooks: 36 | urls: 37 | - https://webhooks.gitter.im/e/61c75218816eebde4486 38 | on_success: change # options: [always|never|change] default: always 39 | on_failure: always # options: [always|never|change] default: always 40 | on_start: never # options: [always|never|change] default: always 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## TBA 6 | 7 | ### Added 8 | 9 | * Nothing 10 | 11 | ### Deprecated 12 | 13 | * Nothing 14 | 15 | ### Removed 16 | 17 | * Nothing 18 | 19 | ### Fixed 20 | 21 | * Nothing 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Visit [github.com/prooph/service-bus-symfony-bundle/](https://github.com/prooph/service-bus-symfony-bundle/ "Project Website") for the project website. 4 | 5 | - Make sure you have execute `composer install` 6 | - Be sure you are in the root directory 7 | 8 | ## Resources 9 | 10 | If you wish to contribute to `service-bus-symfony-bundle`, please be sure to read to the following resources: 11 | 12 | - Coding Standards: [PSR-0/1/2/4](https://github.com/php-fig/fig-standards/tree/master/accepted) 13 | - Git Guide: [README-GIT.md](https://github.com/prooph/service-bus-symfony-bundle/blob/master/README-GIT.md) 14 | 15 | If you are working on new features, or refactoring an existing component, please create an issue first, so we can discuss 16 | it. 17 | 18 | ## Running tests 19 | 20 | To run tests execute *phpunit*: 21 | 22 | ```sh 23 | $ ./vendor/bin/phpunit 24 | ``` 25 | 26 | ## Running PHPCodeSniffer 27 | 28 | To check coding standards execute *phpcs*: 29 | 30 | ```sh 31 | $ ./vendor/bin/phpcs 32 | ``` 33 | 34 | To auto fix coding standard issues execute: 35 | 36 | ```sh 37 | $ ./vendor/bin/phpcbf 38 | ``` 39 | 40 | ## Generate documentation 41 | 42 | To generate the documentation execute *bookdown*: 43 | 44 | ```sh 45 | $ ./vendor/bin/bookdown doc/bookdown.json 46 | ``` 47 | 48 | ## Composer shortcuts 49 | 50 | For every program above there are shortcuts defined in the `composer.json` file. 51 | 52 | * `check`: Executes PHPCodeSniffer and PHPUnit 53 | * `cs`: Executes PHPCodeSniffer 54 | * `cs-fix`: Executes PHPCodeSniffer and auto fixes issues 55 | * `test`: Executes PHPUnit 56 | * `test-coverage`: Executes PHPUnit with code coverage 57 | * `docs`: Generates awesome Bookdown.io docs 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | New BSD License 2 | =============== 3 | 4 | Copyright (c) 2016, prooph software GmbH and Sascha-Oliver Prolic 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the names of the copyright holders nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef APP_ENV 2 | include .env 3 | endif 4 | 5 | 6 | ###> symfony/framework-bundle ### 7 | CONSOLE := $(shell which bin/console) 8 | sf_console: 9 | ifndef CONSOLE 10 | @printf "Run \033[32mcomposer require cli\033[39m to install the Symfony console.\n" 11 | endif 12 | 13 | cache-clear: 14 | ifdef CONSOLE 15 | @$(CONSOLE) cache:clear --no-warmup 16 | else 17 | @rm -rf var/cache/* 18 | endif 19 | .PHONY: cache-clear 20 | 21 | cache-warmup: cache-clear 22 | ifdef CONSOLE 23 | @$(CONSOLE) cache:warmup 24 | else 25 | @printf "Cannot warm up the cache (needs symfony/console).\n" 26 | endif 27 | .PHONY: cache-warmup 28 | 29 | serve_as_sf: sf_console 30 | ifndef CONSOLE 31 | @${MAKE} serve_as_php 32 | endif 33 | @$(CONSOLE) | grep server:start > /dev/null || ${MAKE} serve_as_php 34 | @$(CONSOLE) server:start 35 | 36 | @printf "Quit the server with \033[32;49mbin/console server:stop\033[39m\n" 37 | 38 | serve_as_php: 39 | @printf "\033[32;49mServer listening on http://127.0.0.1:8000\033[39m\n" 40 | @printf "Quit the server with CTRL-C.\n" 41 | @printf "Run \033[32mcomposer require symfony/web-server-bundle\033[39m for a better web server.\n" 42 | php -S 127.0.0.1:8000 -t public 43 | 44 | serve: 45 | @${MAKE} serve_as_sf 46 | .PHONY: sf_console serve serve_as_sf serve_as_php 47 | ###< symfony/framework-bundle ### 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proophessor Do Symfony 2 | **prooph components in action** 3 | 4 | [![Build Status](https://travis-ci.org/prooph/proophessor-do-symfony.svg)](https://travis-ci.org/prooph/proophessor-do-symfony) 5 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/prooph/improoph) 6 | 7 | Proophessor Do Symfony (short *Do*) demonstrates the power of prooph components in conjunction with Symfony (with Flex enabled). 8 | 9 | > This is a clone of [proophessor-do](https://github.com/prooph/proophessor-do) to demonstrate the *Symfony way* with prooph components. 10 | 11 | ## Business Domain 12 | 13 | The business logic implemented in this educational project is very simple and should be known by everybody in one way or the other. 14 | It is about managing todo lists for users whereby a todo can have a deadline and the assigned user can add a reminder to get notified when 15 | time has passed. 16 | 17 | ## Installation 18 | 19 | Please refer to the [installation instructions](https://github.com/prooph/proophessor-do-symfony/blob/master/docs/installation/index.md). 20 | 21 | If you have problems with cache files run `sudo chmod -R 777 var` 22 | 23 | 24 | ## Running the app with Docker 25 | 26 | ```php 27 | docker-compose up -d 28 | ``` 29 | ## Management UI 30 | 31 | Information about the Management UI can be found in the original proophessor-do repo: 32 | 33 | [Management UI Documentation](https://github.com/prooph/proophessor-do/blob/master/docs/mgmt-ui/index.md) 34 | 35 | ![Model Exploration](https://github.com/prooph/proophessor/blob/master/assets/prooph_do_exploration.gif) 36 | 37 | ## Learning by doing! 38 | 39 | Check [proophessor-do](https://github.com/prooph/proophessor-do) for open tasks and more information. 40 | 41 | We want to merge the different framework variants of our learning application to make it easier for people to work on a task 42 | using their favorite web framework. Check out the corresponding [issue](https://github.com/prooph/proophessor-do-symfony/issues/6) 43 | 44 | 45 | ## Support 46 | 47 | - Ask questions on Stack Overflow tagged with [#prooph](https://stackoverflow.com/questions/tagged/prooph). 48 | - File issues at [https://github.com/prooph/proophessor-do/issues](https://github.com/prooph/proophessor-do-symfony/issues). 49 | - Say hello in the [prooph gitter](https://gitter.im/prooph/improoph) chat. 50 | 51 | Happy messaging! 52 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # global configuration 5 | VAGRANTFILE_API_VERSION = "2" 6 | VAGRANT_BOX = "ubuntu/xenial64" 7 | VAGRANT_BOX_MEMORY = 2048 8 | VIRTUAL_BOX_NAME = "proophessordo" 9 | 10 | # only change these lines if you know what you do 11 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 12 | config.vm.box = VAGRANT_BOX 13 | config.vm.hostname = VIRTUAL_BOX_NAME + ".dev" 14 | 15 | # configure vhost ports, more vhosts => more port forwarding definitions 16 | config.vm.network :forwarded_port, guest: 8080, host: 8080, host_ip: "127.0.0.1" 17 | config.vm.network :forwarded_port, guest: 443, host: 443, host_ip: "127.0.0.1" 18 | config.vm.network :forwarded_port, guest: 3306, host: 3306, host_ip: "127.0.0.1" 19 | 20 | # for z-ray docker image 21 | config.vm.network :forwarded_port, guest: 10081, host: 10081, host_ip: "127.0.0.1" 22 | config.vm.network :forwarded_port, guest: 10082, host: 10082, host_ip: "127.0.0.1" 23 | 24 | config.vm.synced_folder ".", "/vagrant" 25 | 26 | # forward ssh requests for public keys 27 | config.ssh.forward_agent = true 28 | 29 | # ensure box name 30 | config.vm.define VIRTUAL_BOX_NAME do |t| 31 | end 32 | 33 | # configure virtual box 34 | config.vm.provider :virtualbox do |vb| 35 | vb.name = VIRTUAL_BOX_NAME 36 | vb.customize ["modifyvm", :id, "--memory", VAGRANT_BOX_MEMORY] 37 | end 38 | 39 | config.vm.provision :docker 40 | config.vm.provision :docker_compose, yml: "/vagrant/docker-compose.yml", rebuild: false, run: "always" 41 | config.vm.provision "shell", inline: "cd /vagrant && docker run --rm --volume $(pwd):/app prooph/composer:7.1 install -o --prefer-dist" 42 | config.vm.provision "shell", inline: "cd /vagrant && docker-compose run --rm php php bin/console event-store:event-stream:create" 43 | end 44 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | load(__DIR__.'/../.env'); 21 | } 22 | 23 | $input = new ArgvInput(); 24 | $env = $input->getParameterOption(['--env', '-e'], $_SERVER['APP_ENV'] ?? 'dev'); 25 | $debug = ($_SERVER['APP_DEBUG'] ?? true) !== '0' && !$input->hasParameterOption(['--no-debug', '']); 26 | 27 | if ($debug && class_exists(Debug::class)) { 28 | Debug::enable(); 29 | } 30 | 31 | $kernel = new Kernel($env, $debug); 32 | $application = new Application($kernel); 33 | $application->run($input); 34 | -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =1.2) 9 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) { 10 | $_SERVER += $env; 11 | $_ENV += $env; 12 | } elseif (!class_exists(Dotenv::class)) { 13 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); 14 | } else { 15 | // load all the .env files 16 | (new Dotenv())->loadEnv(dirname(__DIR__).'/.env'); 17 | } 18 | 19 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $_SERVER['APP_ENV'] ?: $_ENV['APP_ENV'] ?: 'dev'; 20 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 21 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 22 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 6 | Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true], 7 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 8 | Prooph\Bundle\ServiceBus\ProophServiceBusBundle::class => ['all' => true], 9 | Prooph\Bundle\EventStore\ProophEventStoreBundle::class => ['all' => true], 10 | Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true], 11 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 12 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 13 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], 14 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 15 | ]; 16 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Put the unique name of your app here: the prefix seed 4 | # is used to compute stable namespaces for cache keys. 5 | #prefix_seed: your_vendor_name/app_name 6 | 7 | # The app cache caches to the filesystem by default. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: ~ 20 | -------------------------------------------------------------------------------- /config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /config/packages/dev/easy_log_handler.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | EasyCorp\EasyLog\EasyLogHandler: 3 | public: false 4 | arguments: ['%kernel.logs_dir%/%kernel.environment%.log'] 5 | 6 | #// FIXME: How to add this configuration automatically without messing up with the monolog configuration? 7 | #monolog: 8 | # handlers: 9 | # buffered: 10 | # type: buffer 11 | # handler: easylog 12 | # channels: ['!event'] 13 | # level: debug 14 | # easylog: 15 | # type: service 16 | # id: EasyCorp\EasyLog\EasyLogHandler 17 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'type' => 'stream', 11 | 'path' => '%kernel.logs_dir%/%kernel.environment%.log', 12 | 'level' => 'debug', 13 | 'channels' => ['!event'], 14 | ], 15 | // uncomment to get logging in your browser 16 | // you may have to allow bigger header sizes in your Web server configuration 17 | //'firephp' => [ 18 | // 'type' => 'firephp', 19 | // 'level' => 'info', 20 | //], 21 | //'chromephp' => [ 22 | // 'type' => 'chromephp', 23 | // 'level' => 'info', 24 | //], 25 | ]; 26 | 27 | $container->addResource(new ClassExistenceResource(Application::class)); 28 | if (\class_exists(Application::class)) { 29 | $handlers['console'] = [ 30 | 'type' => 'console', 31 | 'process_psr_3_messages' => false, 32 | 'channels' => ['!event', '!doctrine', '!console'], 33 | ]; 34 | } 35 | 36 | $container->loadFromExtension('monolog', [ 37 | 'handlers' => $handlers, 38 | ]); 39 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | # uncomment to get logging in your browser 9 | # you may have to allow bigger header sizes in your Web server configuration 10 | #firephp: 11 | # type: firephp 12 | # level: info 13 | #chromephp: 14 | # type: chromephp 15 | # level: info 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine", "!console"] 20 | -------------------------------------------------------------------------------- /config/packages/dev/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /config/packages/dev/swiftmailer.yaml: -------------------------------------------------------------------------------- 1 | # See https://symfony.com/doc/current/email/dev_environment.html 2 | swiftmailer: 3 | # send all emails to a specific address 4 | #delivery_addresses: ['me@example.com'] 5 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | url: '%env(DATABASE_URL)%' -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: '%env(APP_SECRET)%' 3 | #default_locale: en 4 | #csrf_protection: ~ 5 | #http_method_override: true 6 | #trusted_hosts: ~ 7 | # https://symfony.com/doc/current/reference/configuration/framework.html#handler-id 8 | #session: 9 | # handler_id: session.handler.native_file 10 | # save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%' 11 | #esi: ~ 12 | #fragments: ~ 13 | php_errors: 14 | log: true 15 | 16 | templating: 17 | engines: ['twig'] 18 | -------------------------------------------------------------------------------- /config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | #metadata_cache_driver: apcu 4 | #result_cache_driver: apcu 5 | #query_cache_driver: apcu 6 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'type' => 'fingers_crossed', 11 | 'action_level' => 'error', 12 | 'handler' => 'nested', 13 | ], 14 | 'nested' => [ 15 | 'type' => 'stream', 16 | 'path' => '%kernel.logs_dir%/%kernel.environment%.log', 17 | 'level' => 'debug', 18 | ], 19 | ]; 20 | 21 | $container->addResource(new ClassExistenceResource(Application::class)); 22 | if (\class_exists(Application::class)) { 23 | $handlers['console'] = [ 24 | 'type' => 'console', 25 | 'process_psr_3_messages' => false, 26 | 'channels' => ['!event', '!doctrine'], 27 | ]; 28 | } 29 | 30 | $container->loadFromExtension('monolog', [ 31 | 'handlers' => $handlers, 32 | ]); 33 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_404s: 8 | # regex: exclude all 404 errors from the logs 9 | - ^/ 10 | nested: 11 | type: stream 12 | path: "%kernel.logs_dir%/%kernel.environment%.log" 13 | level: debug 14 | console: 15 | type: console 16 | process_psr_3_messages: false 17 | channels: ["!event", "!doctrine"] 18 | -------------------------------------------------------------------------------- /config/packages/prooph_event_store.yaml: -------------------------------------------------------------------------------- 1 | prooph_event_store: 2 | stores: 3 | todo_store: 4 | event_store: Prooph\EventStore\Pdo\MySqlEventStore 5 | repositories: 6 | todo_list: 7 | repository_class: Prooph\ProophessorDo\Infrastructure\Repository\EventStoreTodoList 8 | aggregate_type: Prooph\ProophessorDo\Model\Todo\Todo 9 | aggregate_translator: prooph_event_sourcing.aggregate_translator 10 | 11 | user_collection: 12 | repository_class: Prooph\ProophessorDo\Infrastructure\Repository\EventStoreUserCollection 13 | aggregate_type: Prooph\ProophessorDo\Model\User\User 14 | aggregate_translator: prooph_event_sourcing.aggregate_translator 15 | projection_managers: 16 | todo_projection_manager: 17 | event_store: Prooph\EventStore\Pdo\MySqlEventStore # event store 18 | connection: 'doctrine.pdo.connection' 19 | projections: 20 | user_projection: 21 | read_model: Prooph\ProophessorDo\Projection\User\UserReadModel 22 | projection: Prooph\ProophessorDo\Projection\User\UserProjection 23 | todo_projection: 24 | read_model: Prooph\ProophessorDo\Projection\Todo\TodoReadModel 25 | projection: Prooph\ProophessorDo\Projection\Todo\TodoProjection 26 | todo_reminder_projection: 27 | read_model: Prooph\ProophessorDo\Projection\Todo\TodoReminderReadModel 28 | projection: Prooph\ProophessorDo\Projection\Todo\TodoReminderProjection 29 | 30 | 31 | services: 32 | Prooph\EventStore\Pdo\MySqlEventStore: 33 | arguments: ['@prooph_event_store.message_factory', '@doctrine.pdo.connection', '@prooph_event_store.single_stream_strategy'] 34 | 35 | doctrine.pdo.connection: 36 | class: PDO 37 | factory: ['@database_connection', getWrappedConnection] 38 | 39 | prooph_event_store.single_stream_strategy: 40 | class: Prooph\EventStore\Pdo\PersistenceStrategy\MySqlSingleStreamStrategy 41 | 42 | prooph_event_sourcing.aggregate_translator: 43 | class: Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator -------------------------------------------------------------------------------- /config/packages/prooph_service_bus.yaml: -------------------------------------------------------------------------------- 1 | prooph_service_bus: 2 | command_buses: 3 | todo_command_bus: 4 | router: 5 | type: 'prooph_service_bus.command_bus_router' 6 | 7 | event_buses: 8 | todo_event_bus: 9 | plugins: 10 | - 'prooph_service_bus.on_event_invoke_strategy' 11 | router: 12 | type: 'prooph_service_bus.event_bus_router' 13 | routes: 14 | 'Prooph\ProophessorDo\Model\Todo\Event\TodoAssigneeWasReminded': 15 | - 'Prooph\ProophessorDo\ProcessManager\SendTodoReminderMailProcessManager' 16 | 17 | 'Prooph\ProophessorDo\Model\Todo\Event\TodoWasMarkedAsExpired': 18 | - 'Prooph\ProophessorDo\ProcessManager\SendTodoDeadlineExpiredMailProcessManager' 19 | 20 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: ~ 4 | -------------------------------------------------------------------------------- /config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /config/packages/swiftmailer.yaml: -------------------------------------------------------------------------------- 1 | swiftmailer: 2 | url: '%env(MAILER_URL)%' 3 | spool: { type: 'memory' } 4 | -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: ~ 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | -------------------------------------------------------------------------------- /config/packages/test/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /config/packages/test/swiftmailer.yaml: -------------------------------------------------------------------------------- 1 | swiftmailer: 2 | disable_delivery: true 3 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /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 | event_store_http_api: 2 | resource: "routes/event_store_http_api.yaml" 3 | 4 | page::home: 5 | path: / 6 | defaults: { _controller: App\Controller\DefaultController::indexAction } 7 | 8 | page::user-list: 9 | path: /user-list 10 | defaults: { _controller: App\Controller\UserListController:listAction } 11 | 12 | page::user-registration-form: 13 | path: /user-registration 14 | defaults: { _controller: App\Controller\UserRegistrationController:registerAction } 15 | 16 | page::user-todo-list: 17 | path: /user-todo-list/{userId} 18 | defaults: { _controller: App\Controller\UserTodoListController:listAction } 19 | 20 | page::user-todo-form: 21 | path: /user-todo-list/{userId}/new-todo 22 | defaults: { _controller: App\Controller\UserTodoFormController:formAction } 23 | 24 | command::register-user: 25 | path: /api/commands/register-user 26 | defaults: { _controller: App\Controller\ApiCommandController:postAction, prooph_command_name: 'Prooph\ProophessorDo\Model\User\Command\RegisterUser' } 27 | 28 | command::post-todo: 29 | path: /api/commands/post-todo 30 | defaults: { _controller: App\Controller\ApiCommandController:postAction, prooph_command_name: 'Prooph\ProophessorDo\Model\Todo\Command\PostTodo' } 31 | 32 | command::mark-todo-as-done: 33 | path: /api/commands/mark-todo-as-done 34 | defaults: { _controller: App\Controller\ApiCommandController:postAction, prooph_command_name: 'Prooph\ProophessorDo\Model\Todo\Command\MarkTodoAsDone' } 35 | 36 | command::reopen-todo: 37 | path: /api/commands/reopen-todo 38 | defaults: { _controller: App\Controller\ApiCommandController:postAction, prooph_command_name: 'Prooph\ProophessorDo\Model\Todo\Command\ReopenTodo' } 39 | 40 | command::add-deadline-to-todo: 41 | path: /api/commands/add-deadline-to-todo 42 | defaults: { _controller: App\Controller\ApiCommandController:postAction, prooph_command_name: 'Prooph\ProophessorDo\Model\Todo\Command\AddDeadlineToTodo' } 43 | 44 | command::add-reminder-to-todo: 45 | path: /api/commands/add-reminder-to-todo 46 | defaults: { _controller: App\Controller\ApiCommandController:postAction, prooph_command_name: 'Prooph\ProophessorDo\Model\Todo\Command\AddReminderToTodo' } 47 | 48 | command::mark-todo-as-expired: 49 | path: /api/commands/mark-todo-as-expired 50 | defaults: { _controller: App\Controller\ApiCommandController:postAction, prooph_command_name: 'Prooph\ProophessorDo\Model\Todo\Command\MarkTodoAsExpired' } 51 | 52 | -------------------------------------------------------------------------------- /config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: annotation 4 | -------------------------------------------------------------------------------- /config/routes/dev/twig.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@TwigBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | 4 | mysql: 5 | image: mysql:5.7 6 | ports: 7 | - 3306:3306 8 | environment: 9 | - MYSQL_ROOT_PASSWORD=dev 10 | - MYSQL_USER=dev 11 | - MYSQL_PASSWORD=dev 12 | - MYSQL_DATABASE=todo 13 | volumes: 14 | - ./vendor/prooph/pdo-event-store/scripts/mysql:/docker-entrypoint-initdb.d 15 | 16 | nginx: 17 | image: prooph/nginx:www 18 | ports: 19 | - 8080:80 20 | - 443:443 21 | - 10081:10081 22 | - 10082:10082 23 | links: 24 | - php:php 25 | volumes: 26 | - .:/var/www 27 | 28 | php: 29 | image: prooph/php:7.1-fpm 30 | volumes: 31 | - .:/var/www 32 | depends_on: 33 | - mysql 34 | environment: 35 | - PROOPH_ENV=development 36 | 37 | projection_todo: 38 | image: prooph/php:7.1-cli 39 | entrypoint: 40 | - /app/.docker/wait_for_sql.sh 41 | volumes: 42 | - .:/app 43 | depends_on: 44 | - mysql 45 | command: php bin/console event-store:projection:run todo_projection 46 | 47 | projection_todo_reminder: 48 | image: prooph/php:7.1-cli 49 | entrypoint: /app/.docker/wait_for_sql.sh 50 | volumes: 51 | - .:/app 52 | depends_on: 53 | - mysql 54 | command: php bin/console event-store:projection:run todo_reminder_projection 55 | 56 | projection_user: 57 | image: prooph/php:7.1-cli 58 | entrypoint: /app/.docker/wait_for_sql.sh 59 | volumes: 60 | - .:/app 61 | depends_on: 62 | - mysql 63 | command: php bin/console event-store:projection:run user_projection 64 | 65 | # Uncomment when using snapshotters 66 | # @TODO: Implement snapshot projections, see proophessor-do 67 | # snapshotter_todo: 68 | # image: prooph/php:7.1-cli 69 | # entrypoint: /app/.docker/wait_for_sql.sh 70 | # volumes: 71 | # - .:/app 72 | # depends_on: 73 | # - mysql 74 | # - mongodb 75 | # command: php /app/bin/todo_snapshotter.php 76 | # 77 | # snapshotter_user: 78 | # image: prooph/php:7.1-cli 79 | # entrypoint: /app/.docker/wait_for_sql.sh 80 | # volumes: 81 | # - .:/app 82 | # depends_on: 83 | # - mysql 84 | # - mongodb 85 | # command: php /app/bin/user_snapshotter.php 86 | -------------------------------------------------------------------------------- /docs/installation/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging with xDebug 2 | 3 | Xdebug is a good way to step through the code an learn about the internal structure. 4 | 5 | When using the **docker** or **vagrant** installation method you can 6 | enable xdebug by replacing 7 | 8 | ```yaml 9 | image: prooph/php:7.1-fpm 10 | ``` 11 | with 12 | ```yaml 13 | image: prooph/php:7.1-fpm-xdebug 14 | ``` 15 | in the `docker-compose.yml` file. 16 | 17 | **Note**: Port 10000 is using instead of the default Port 9000 with this image. You need to 18 | configure your IDE to this port. 19 | 20 | -------------------------------------------------------------------------------- /docs/installation/docker.md: -------------------------------------------------------------------------------- 1 | # Installation using Docker 2 | 3 | First ensure [Docker](https://docs.docker.com/engine/installation/ubuntulinux/) and [Docker Compose](https://docs.docker.com/compose/install/) 4 | are installed. It's recommended to use the latest version of Docker and Docker Compose. Docker will download all 5 | dependencies and starts the containers. 6 | 7 | ### Step 1 - Get the source code 8 | The application runs as a Docker volume. First download the source to a local directory: 9 | `git clone https://github.com/prooph/proophessor-do-symfony.git` 10 | 11 | If you are using a Linux distribution running SELinux then set the security context to allow Docker access to the volume: 12 | `chcon -Rt svirt_sandbox_file_t proophessor-do-symfony/` 13 | 14 | All the Docker commands are run from the source directory, so also do: 15 | `cd proophessor-do-symfony/` 16 | 17 | ### Step 2 - Install dependencies 18 | 19 | To ensure you have the latest Docker images for the default application execute: 20 | 21 | ```bash 22 | $ docker pull prooph/php:7.1-fpm && docker pull prooph/composer:7.1 && docker pull prooph/nginx:www 23 | ``` 24 | 25 | Install PHP dependencies via Composer 26 | 27 | #### Note for Windows: Replace `$(pwd)` with `%cd%` in all commands 28 | 29 | ```bash 30 | $ docker run --rm -it --volume $(pwd):/app prooph/composer:7.1 install -o --prefer-dist 31 | 32 | # respectively Windows: 33 | $ docker run --rm -it --volume %cd%:/app prooph/composer:7.1 install -o --prefer-dist 34 | ``` 35 | 36 | ### Step 3 - Configure 37 | 38 | #### 3.1 Email sending (mandatory): 39 | 40 | tbd 41 | 42 | #### 3.2 Read model (mandatory): 43 | 44 | Copy `.env.dist` to `.env`. The file already contains the correct DATABASE_URL to connect to the MySql container. 45 | 46 | #### 3.3 Start your Docker Containers 47 | 48 | ```bash 49 | $ docker-compose up -d 50 | ``` 51 | 52 | #### 3.4 Create the initial event stream with the already started container: 53 | 54 | ```bash 55 | $ docker-compose run --rm php php bin/console event-store:event-stream:create 56 | ``` 57 | 58 | ### Step 4 - That's it! 59 | Now open [http://localhost:8080](http://localhost:8080/) and have fun. 60 | -------------------------------------------------------------------------------- /docs/installation/index.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | *proophessor-do-symfony* offers you three options to install the demo application. We support *Vagrant*, *Docker* and have *manual* 4 | instructions. 5 | 6 | > Docker is the recommended and fastest installation method 7 | 8 | - [Install using Docker](docker.md) 9 | - [Install using Vagrant](vagrant.md) 10 | - [Manual Installation](manual.md) 11 | - Optional Feature: [Debugging with Xdebug](debugging.md) 12 | 13 | ## TODO 14 | - Optional Feature: [Install Snapshots](snapshots.md) 15 | 16 | -------------------------------------------------------------------------------- /docs/installation/manual.md: -------------------------------------------------------------------------------- 1 | # Manual installation 2 | 3 | This is the hard way. Please ensure that you not want to use Docker. ;-) 4 | 5 | ### Requirements: 6 | - PHP >= v7.1 7 | - MySql >= v5.7.9 (For JSON support) 8 | 9 | ### Step 1 - Get source code 10 | 11 | `git clone https://github.com/prooph/proophessor-do-symfony.git` into the document root of a local web server. 12 | 13 | ### Step 2 - Configure Database 14 | 15 | Before you can start you have to configure your database connection. 16 | As this is an example application for a CQRS-driven application we are using two different persistence layers. 17 | One is responsible for persisting the **write model**. This task is taken by prooph/event-store. 18 | And the other one is responsible for persisting the **read model** aka **projections**. 19 | 20 | ### Step 3 - Configuration 21 | 22 | #### 3.1 Email sending (mandatory): 23 | 24 | tbd 25 | 26 | #### 3.2 Read model (mandatory): 27 | 28 | - Copy `.env.dist` to `.env` and make your adjustments to the `DATABASE_URL`. 29 | - Execute `CREATE DATABASE todo;` on your MySQL instance. 30 | 31 | #### 3.3 Event Store 32 | 33 | - Execute the scripts located at: 34 | - `vendor/prooph/pdo-event-store/scripts/mysql/01_event_streams_table.sql` 35 | - `vendor/prooph/pdo-event-store/scripts/mysql/02_projections_table.sql` 36 | - Create empty stream: Run `php bin/console event-store:event-stream:create` 37 | 38 | ### Step 4 - Start the backend scripts 39 | 40 | #### 4.1 - Start the projetions in different terminal windows 41 | 42 | `php bin/console event-store:projection:run todo_projection` 43 | 44 | `php bin/console event-store:projection:run todo_reminder_projection` 45 | 46 | `php bin/console event-store:projection:run user_projection` 47 | 48 | #### 4.2 Start snapshotters (only if you decided to use 3.4) 49 | 50 | tbd 51 | 52 | ### Step 5 - View It 53 | 54 | Open a terminal and navigate to the project root. Then start the PHP built-in web server with `php -S 0.0.0.0:8080 -t public` 55 | and open [http://localhost:8080](http://localhost:8080/) in a browser. 56 | 57 | *Note: You can also set the environmental variable `PROOPH_ENV` to `development`. That will forward exception messages to the client in case of an error. 58 | When using the built-in web server you can set the variable like so: `PROOPH_ENV=development php -S 0.0.0.0:8080 -t public`* 59 | -------------------------------------------------------------------------------- /docs/installation/snapshots.md: -------------------------------------------------------------------------------- 1 | # Snapshot Store Installation 2 | 3 | **Note: Snapshots are work in progress for proophessor-do-symfony. This guide is taken from the original proophessor-do and requires updates!** 4 | 5 | Snapshots are an optional feature. 6 | 7 | At the moment prooph offers snapshot store implementations for: 8 | 9 | - [PDO](https://github.com/prooph/pdo-snapshot-store) (Mysql/Postgres) 10 | - [MongoDB](https://github.com/prooph/mongodb-snapshot-store) 11 | - [ArangoDB](https://github.com/prooph/arangodb-snapshot-store) 12 | 13 | 14 | ### Snapshotter 15 | 16 | As a general purpose helper we use the *prooph/snapshotter* which can be installed using 17 | 18 | ```bash 19 | $ composer require prooph/snapshotter 20 | ``` 21 | 22 | ### PDO Snapshot Store 23 | 24 | *PDO Snapshot Store uses the same PDO connection as for given EventStore* 25 | 26 | - Copy `config/autoload/pdo_snapshot_store.local.php.dist` to `config/autoload/pdo_snapshot_store.local.php` and make your adjustments. 27 | - Install the needed package: 28 | 29 | ```bash 30 | $ composer require prooph/pdo-snapshot-store 31 | ``` 32 | 33 | - Execute the following script: 34 | 35 | for MySQL: `vendor/prooph/pdo-snapshot-store/scripts/mysql_snapshot_table.sql` 36 | 37 | for Postgres: `vendor/prooph/pdo-snapshot-store/scripts/postgres_snapshot_table.sql` 38 | 39 | ### MongoDB Snapshot Store 40 | 41 | - Copy `config/autoload/mongo_snapshot_store.local.php.dist` to `config/autoload/mongo_snapshot_store.local.php` and make your adjustments. 42 | - Install the needed package: 43 | 44 | ```bash 45 | $ composer require prooph/mongodb-snapshot-store 46 | ``` 47 | 48 | ## Snapshot tutorial 49 | 50 | Now as you picked and configured you snapshot store, start playing with the new feature. 51 | 52 | Please jump to the [Snapshot Tutorial](../tutorial/take_snapshots.md) ! -------------------------------------------------------------------------------- /docs/installation/vagrant.md: -------------------------------------------------------------------------------- 1 | # Installation using Vagrant 2 | Please install the following software if not already installed: 3 | 4 | * [Vagrant (tested with 1.9.3)](http://www.vagrantup.com/downloads.html) 5 | * [Virtualbox >= 5.0](https://www.virtualbox.org/wiki/Downloads) 6 | * [Vagrant docker-compose plugin](https://github.com/leighmcculloch/vagrant-docker-compose) (`vagrant plugin install vagrant-docker-compose`) 7 | 8 | 9 | 1.) Please follow the "Configuration" section from [docker Instructions](docker.md#step-3-configuration) 10 | 2.) Then run: 11 | ```bash 12 | $: vagrant up 13 | ``` 14 | 15 | All dependencies will be downloaded. This may take a while ... 16 | 17 | Now open [http://localhost:8080](http://localhost:8080/) and have fun. 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 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 | 38 | 39 | 40 | 41 | 42 | tests/ 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /public/assets/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 80px; 3 | padding-bottom: 40px; 4 | } 5 | 6 | .text-btn-align {padding: 6px; vertical-align: middle;} 7 | 8 | .btn-h3 {margin-top: 16px} 9 | 10 | .form-control-with-desc {margin-top: 20px} 11 | 12 | .form-control-separator { 13 | font-size: 30px; 14 | } 15 | 16 | .form-inline .form-control-inline-2 { 17 | width: 50px; 18 | max-width: 50px; 19 | } 20 | 21 | .form-inline .form-control-inline-2 { 22 | width: 100px; 23 | max-width: 100px; 24 | } 25 | 26 | .form-inline .form-control-inline-3 { 27 | width: 150px; 28 | max-width: 150px; 29 | } 30 | 31 | .form-inline .form-control-inline-4 { 32 | width: 200px; 33 | max-width: 200px; 34 | } 35 | 36 | .form-inline .form-control-inline-5 { 37 | width: 250px; 38 | max-width: 250px; 39 | } 40 | 41 | .form-inline .form-control-inline-6 { 42 | width: 300px; 43 | max-width: 300px; 44 | } 45 | 46 | .form-inline .form-control-inline-10 { 47 | width: 500px; 48 | max-width: 600px; 49 | } 50 | 51 | .form-inline .form-control-inline-12 { 52 | width: 600px; 53 | max-width: 600px; 54 | } 55 | 56 | 57 | /* Sidebar Styles */ 58 | 59 | #sidebar-left { 60 | margin-top: 40px; 61 | } 62 | 63 | #sidebar-left.affix { 64 | top : 60px; 65 | margin-top: 0px; 66 | } 67 | 68 | .sidebar-nav { 69 | margin: 0; 70 | padding: 0; 71 | list-style: none; 72 | } 73 | 74 | .sidebar-nav li { 75 | text-indent: 40px; 76 | line-height: 40px; 77 | } 78 | 79 | .sidebar-nav li a { 80 | display: block; 81 | text-decoration: none; 82 | color: #999999; 83 | } 84 | 85 | .sidebar-nav li a.active { 86 | text-decoration: underline; 87 | } 88 | 89 | .sidebar-nav li a:hover { 90 | text-decoration: none; 91 | color: #269abc; 92 | } 93 | 94 | .sidebar-nav li a:active, 95 | .sidebar-nav li a:focus { 96 | text-decoration: none; 97 | } 98 | 99 | .glyphicon-unchecked,.glyphicon-check { 100 | cursor: pointer; 101 | } 102 | -------------------------------------------------------------------------------- /public/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/assets/js/jquery.plugins.js: -------------------------------------------------------------------------------- 1 | //jQuery Additions 2 | (function( $ ) { 3 | 4 | $.postJSON = function(url, data, settings) { 5 | if (typeof settings == 'undefined') settings = {}; 6 | return $.ajax(url, $.extend({ 7 | contentType : 'application/json; charset=UTF-8', 8 | type: "POST", 9 | data : JSON.stringify(data), 10 | dataType : 'json', 11 | dataFilter : function (data, dataType) { 12 | if (! data && dataType == "json") return "{}"; 13 | return data; 14 | } 15 | }, settings)) 16 | } 17 | 18 | $.putJSON = function(url, data, settings) { 19 | if (typeof settings == 'undefined') settings = {}; 20 | return $.ajax(url, $.extend({ 21 | contentType : 'application/json; charset=UTF-8', 22 | type: "PUT", 23 | data : JSON.stringify(data), 24 | dataType : 'json', 25 | dataFilter : function (data, dataType) { 26 | if (! data && dataType == "json") return "{}"; 27 | return data; 28 | } 29 | }, settings)) 30 | } 31 | 32 | $.failNotify = function(xhr) { 33 | if (typeof xhr.responseJSON == 'undefined') { 34 | xhr.responseJSON = {status : 500, title : 'Unknown Server Error', detail : 'Unknown Server response received'} 35 | } 36 | 37 | $.notify('['+ xhr.status +'] '+xhr.responseJSON.message, { 38 | className : 'error', 39 | clickToHide: true, 40 | autoHide: false 41 | }); 42 | } 43 | 44 | $.appErrorNotify = function(msg) { 45 | $.notify(msg, { 46 | className : 'error', 47 | clickToHide: true, 48 | autoHide: false 49 | }); 50 | } 51 | 52 | })( jQuery ); 53 | 54 | 55 | //Settings 56 | $(function() { 57 | $.notify.defaults({ 58 | globalPosition: 'bottom left' 59 | }); 60 | }); 61 | 62 | -------------------------------------------------------------------------------- /public/assets/js/lodash.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/assets/js/lodash.js -------------------------------------------------------------------------------- /public/assets/js/underscore.string.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/assets/js/underscore.string.js -------------------------------------------------------------------------------- /public/assets/js/uuid.js: -------------------------------------------------------------------------------- 1 | /** Generates UUID v4 2 | * 3 | * @node There is a bug in Chrome's Math.random() according to http://devoluk.com/google-chrome-math-random-issue.html 4 | * For that reason we use Date.now() as well. 5 | * 6 | * Taken from https://gist.github.com/duzun/d1bfb5406a362e06eccd 7 | */ 8 | function UUID() { 9 | function s(n) { return h((Math.random() * (1<<(n<<2)))^Date.now()).slice(-n); } 10 | function h(n) { return (n|0).toString(16); } 11 | return [ 12 | s(4) + s(4), s(4), 13 | '4' + s(3), // UUID version 4 14 | h(8|(Math.random()*4)) + s(3), // {8|9|A|B}xxx 15 | // s(4) + s(4) + s(4), 16 | Date.now().toString(16).slice(-10) + s(2) // Use timestamp to avoid collisions 17 | ].join('-'); 18 | } 19 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | load(__DIR__.'/../.env'); 15 | } 16 | 17 | if ($_SERVER['APP_DEBUG'] ?? false) { 18 | // WARNING: You should setup permissions the proper way! 19 | // REMOVE the following PHP line and read 20 | // https://symfony.com/doc/current/book/installation.html#checking-symfony-application-configuration-and-setup 21 | \umask(0000); 22 | 23 | Debug::enable(); 24 | } 25 | 26 | // Request::setTrustedProxies(['0.0.0.0/0'], Request::HEADER_FORWARDED); 27 | 28 | $kernel = new Kernel($_SERVER['APP_ENV'] ?? 'dev', (bool)($_SERVER['APP_DEBUG'] ?? false)); 29 | $request = Request::createFromGlobals(); 30 | $response = $kernel->handle($request); 31 | $response->send(); 32 | $kernel->terminate($request, $response); 33 | -------------------------------------------------------------------------------- /public/mgmt-ui/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "font/icons.eot": "font/icons.674f50d287a8c48dc19ba404d20fe713.eot", 3 | "font/icons.ttf": "font/icons.b06871f281fee6b241d60582ae9369b9.ttf", 4 | "font/icons.woff": "font/icons.fee66e712a8a08eef5805a46892932ad.woff", 5 | "font/icons.woff2": "font/icons.af7ae505a9eed503f8b8e6982036873e.woff2", 6 | "img/flags.png": "img/flags.9c74e172.png", 7 | "img/icons.svg": "img/icons.912ec66d.svg", 8 | "img/prooph_sticker.png": "img/prooph_sticker.9e41e468.png", 9 | "main.css": "css/main.03eb1f0c.css", 10 | "main.css.map": "css/main.03eb1f0c.css.map", 11 | "main.js": "js/main.58996ad3.js", 12 | "main.js.map": "js/main.58996ad3.js.map" 13 | } -------------------------------------------------------------------------------- /public/mgmt-ui/font/icons.674f50d287a8c48dc19ba404d20fe713.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/font/icons.674f50d287a8c48dc19ba404d20fe713.eot -------------------------------------------------------------------------------- /public/mgmt-ui/font/icons.af7ae505a9eed503f8b8e6982036873e.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/font/icons.af7ae505a9eed503f8b8e6982036873e.woff2 -------------------------------------------------------------------------------- /public/mgmt-ui/font/icons.b06871f281fee6b241d60582ae9369b9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/font/icons.b06871f281fee6b241d60582ae9369b9.ttf -------------------------------------------------------------------------------- /public/mgmt-ui/font/icons.fee66e712a8a08eef5805a46892932ad.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/font/icons.fee66e712a8a08eef5805a46892932ad.woff -------------------------------------------------------------------------------- /public/mgmt-ui/ico/apple-touch-icon-114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/ico/apple-touch-icon-114-precomposed.png -------------------------------------------------------------------------------- /public/mgmt-ui/ico/apple-touch-icon-144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/ico/apple-touch-icon-144-precomposed.png -------------------------------------------------------------------------------- /public/mgmt-ui/ico/apple-touch-icon-57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/ico/apple-touch-icon-57-precomposed.png -------------------------------------------------------------------------------- /public/mgmt-ui/ico/apple-touch-icon-72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/ico/apple-touch-icon-72-precomposed.png -------------------------------------------------------------------------------- /public/mgmt-ui/ico/prooph-logo@0.5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/ico/prooph-logo@0.5x.png -------------------------------------------------------------------------------- /public/mgmt-ui/img/flags.9c74e172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/img/flags.9c74e172.png -------------------------------------------------------------------------------- /public/mgmt-ui/img/prooph_sticker.9e41e468.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/public/mgmt-ui/img/prooph_sticker.9e41e468.png -------------------------------------------------------------------------------- /public/mgmt-ui/index.html: -------------------------------------------------------------------------------- 1 | prooph event store mgmt
-------------------------------------------------------------------------------- /public/mgmt-ui/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React + Redux + Semantic UI Skeleton", 3 | "name": "React + Redux + Semantic UI Skeleton", 4 | "start_url": "./index.html", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /src/Command/CreateEventStreamCommand.php: -------------------------------------------------------------------------------- 1 | setName('event-store:event-stream:create') 26 | ->setDescription('Create event_stream.') 27 | ->setHelp('This command creates the event_stream'); 28 | } 29 | 30 | protected function execute(InputInterface $input, OutputInterface $output) 31 | { 32 | /** @var EventStore $eventStore */ 33 | $eventStore = $this->getContainer()->get('prooph_event_store.todo_store'); 34 | 35 | $eventStore->create(new Stream(new StreamName('event_stream'), new \ArrayIterator([]))); 36 | $output->writeln('Event stream was created successfully.'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/src/Controller/.gitignore -------------------------------------------------------------------------------- /src/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | templateEngine = $engine; 28 | } 29 | 30 | public function indexAction(Request $request): Response 31 | { 32 | return $this 33 | ->templateEngine 34 | ->renderResponse( 35 | 'default/index.html.twig', 36 | ['sidebar_right' => ''] 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Controller/UserListController.php: -------------------------------------------------------------------------------- 1 | templateEngine = $templateEngine; 37 | $this->userFinder = $userFinder; 38 | } 39 | 40 | public function listAction(Request $request): Response 41 | { 42 | $users = $this->userFinder->findAll(); 43 | 44 | return $this 45 | ->templateEngine 46 | ->renderResponse( 47 | 'default/user-list.html.twig', 48 | ['users' => $users] 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Controller/UserRegistrationController.php: -------------------------------------------------------------------------------- 1 | templateEngine = $templateEngine; 31 | } 32 | 33 | public function registerAction(Request $request): Response 34 | { 35 | return $this 36 | ->templateEngine 37 | ->renderResponse( 38 | 'default/user-registration-form.html.twig' 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Controller/UserTodoFormController.php: -------------------------------------------------------------------------------- 1 | templateEngine = $templateEngine; 37 | $this->userFinder = $userFinder; 38 | } 39 | 40 | public function formAction(Request $request): Response 41 | { 42 | $userId = $request->get('userId'); 43 | 44 | $invalidUser = true; 45 | $user = null; 46 | 47 | if ($userId) { 48 | $user = $this->userFinder->findById($userId); 49 | 50 | if ($user) { 51 | $invalidUser = false; 52 | } 53 | } 54 | 55 | return $this 56 | ->templateEngine 57 | ->renderResponse( 58 | 'default/user-todo-form.html.twig', 59 | ['invalidUser' => $invalidUser, 'user' => $user] 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Controller/UserTodoListController.php: -------------------------------------------------------------------------------- 1 | templateEngine = $templateEngine; 45 | $this->userFinder = $userFinder; 46 | $this->todoFinder = $todoFinder; 47 | } 48 | 49 | public function listAction(Request $request): Response 50 | { 51 | $userId = $request->get('userId'); 52 | $user = $this->userFinder->findById($userId); 53 | $todos = $this->todoFinder->findByAssigneeId($userId); 54 | 55 | return $this 56 | ->templateEngine 57 | ->renderResponse( 58 | 'default/user-todo-list.html.twig', 59 | [ 60 | 'user' => $user, 61 | 'todos' => $todos, 62 | ] 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/src/Entity/.gitignore -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | environment; 22 | } 23 | 24 | public function getLogDir(): string 25 | { 26 | return \dirname(__DIR__).'/var/log'; 27 | } 28 | 29 | public function registerBundles() 30 | { 31 | $contents = require \dirname(__DIR__).'/config/bundles.php'; 32 | foreach ($contents as $class => $envs) { 33 | if (isset($envs['all']) || isset($envs[$this->environment])) { 34 | yield new $class(); 35 | } 36 | } 37 | } 38 | 39 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader) 40 | { 41 | $confDir = \dirname(__DIR__).'/config'; 42 | $loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob'); 43 | if (\is_dir($confDir.'/packages/'.$this->environment)) { 44 | $loader->load($confDir.'/packages/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob'); 45 | } 46 | $loader->load($confDir.'/services'.self::CONFIG_EXTS, 'glob'); 47 | $loader->load($confDir.'/services_'.$this->environment.self::CONFIG_EXTS, 'glob'); 48 | } 49 | 50 | protected function configureRoutes(RouteCollectionBuilder $routes) 51 | { 52 | $confDir = \dirname(__DIR__).'/config'; 53 | if (\is_dir($confDir.'/routes/')) { 54 | $routes->import($confDir.'/routes/*'.self::CONFIG_EXTS, '/', 'glob'); 55 | } 56 | if (\is_dir($confDir.'/routes/'.$this->environment)) { 57 | $routes->import($confDir.'/routes/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob'); 58 | } 59 | $routes->import($confDir.'/routes'.self::CONFIG_EXTS, '/', 'glob'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ProophessorDo/Infrastructure/EventStoreHttpApi/JsonTransformer.php: -------------------------------------------------------------------------------- 1 | urlGenerator = $urlGenerator; 20 | } 21 | 22 | public function generate(string $urlId, array $params = []): string 23 | { 24 | return $this->urlGenerator->generate($urlId, $params); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ProophessorDo/Infrastructure/Repository/EventStoreTodoList.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Infrastructure\Repository; 14 | 15 | use Prooph\EventSourcing\Aggregate\AggregateRepository; 16 | use Prooph\ProophessorDo\Model\Todo\Todo; 17 | use Prooph\ProophessorDo\Model\Todo\TodoId; 18 | use Prooph\ProophessorDo\Model\Todo\TodoList; 19 | 20 | final class EventStoreTodoList extends AggregateRepository implements TodoList 21 | { 22 | public function save(Todo $todo): void 23 | { 24 | $this->saveAggregateRoot($todo); 25 | } 26 | 27 | public function get(TodoId $todoId): ?Todo 28 | { 29 | return $this->getAggregateRoot($todoId->toString()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ProophessorDo/Infrastructure/Repository/EventStoreUserCollection.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Infrastructure\Repository; 14 | 15 | use Prooph\EventSourcing\Aggregate\AggregateRepository; 16 | use Prooph\ProophessorDo\Model\User\User; 17 | use Prooph\ProophessorDo\Model\User\UserCollection; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class EventStoreUserCollection extends AggregateRepository implements UserCollection 21 | { 22 | public function save(User $user): void 23 | { 24 | $this->saveAggregateRoot($user); 25 | } 26 | 27 | public function get(UserId $userId): ?User 28 | { 29 | return $this->getAggregateRoot($userId->toString()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ProophessorDo/Infrastructure/Service/ChecksUniqueUsersEmailAddressFromReadModel.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Infrastructure\Service; 14 | 15 | use Prooph\ProophessorDo\Model\User\EmailAddress; 16 | use Prooph\ProophessorDo\Model\User\Service\ChecksUniqueUsersEmailAddress; 17 | use Prooph\ProophessorDo\Model\User\UserId; 18 | use Prooph\ProophessorDo\Projection\User\UserFinder; 19 | 20 | final class ChecksUniqueUsersEmailAddressFromReadModel implements ChecksUniqueUsersEmailAddress 21 | { 22 | /** 23 | * @var UserFinder 24 | */ 25 | private $userFinder; 26 | 27 | public function __construct(UserFinder $userFinder) 28 | { 29 | $this->userFinder = $userFinder; 30 | } 31 | 32 | public function __invoke(EmailAddress $emailAddress): ?UserId 33 | { 34 | if ($user = $this->userFinder->findOneByEmailAddress($emailAddress->toString())) { 35 | return UserId::fromString($user->id); 36 | } 37 | 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Entity.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model; 14 | 15 | interface Entity 16 | { 17 | public function sameIdentityAs(Entity $other): bool; 18 | } 19 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Enum.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model; 14 | 15 | use MabeEnum\Enum as MabeEnum; 16 | use MabeEnum\EnumSerializableTrait; 17 | use Serializable; 18 | 19 | abstract class Enum extends MabeEnum implements Serializable, ValueObject 20 | { 21 | use EnumSerializableTrait; 22 | 23 | public function sameValueAs(ValueObject $object): bool 24 | { 25 | return $this->is($object); 26 | } 27 | 28 | public function toString(): string 29 | { 30 | return $this->getName(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/AddDeadlineToTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Prooph\Common\Messaging; 16 | use Prooph\ProophessorDo\Model\Todo\TodoDeadline; 17 | use Prooph\ProophessorDo\Model\Todo\TodoId; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class AddDeadlineToTodo extends Messaging\Command implements Messaging\PayloadConstructable 21 | { 22 | use Messaging\PayloadTrait; 23 | 24 | public function userId(): UserId 25 | { 26 | return UserId::fromString($this->payload['user_id']); 27 | } 28 | 29 | public function todoId(): TodoId 30 | { 31 | return TodoId::fromString($this->payload['todo_id']); 32 | } 33 | 34 | public function deadline(): TodoDeadline 35 | { 36 | return TodoDeadline::fromString($this->payload['deadline']); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/AddReminderToTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Prooph\Common\Messaging\Command; 16 | use Prooph\Common\Messaging\PayloadConstructable; 17 | use Prooph\Common\Messaging\PayloadTrait; 18 | use Prooph\ProophessorDo\Model\Todo\TodoId; 19 | use Prooph\ProophessorDo\Model\Todo\TodoReminder; 20 | use Prooph\ProophessorDo\Model\Todo\TodoReminderStatus; 21 | use Prooph\ProophessorDo\Model\User\UserId; 22 | 23 | final class AddReminderToTodo extends Command implements PayloadConstructable 24 | { 25 | use PayloadTrait; 26 | 27 | public function userId(): UserId 28 | { 29 | return UserId::fromString($this->payload['user_id']); 30 | } 31 | 32 | public function todoId(): TodoId 33 | { 34 | return TodoId::fromString($this->payload['todo_id']); 35 | } 36 | 37 | public function reminder(): TodoReminder 38 | { 39 | return TodoReminder::from($this->payload['reminder'], TodoReminderStatus::OPEN()->getName()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/MarkTodoAsDone.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | 21 | final class MarkTodoAsDone extends Command implements PayloadConstructable 22 | { 23 | use PayloadTrait; 24 | 25 | public static function with(string $todoId): MarkTodoAsDone 26 | { 27 | return new self([ 28 | 'todo_id' => $todoId, 29 | ]); 30 | } 31 | 32 | public function todoId(): TodoId 33 | { 34 | return TodoId::fromString($this->payload['todo_id']); 35 | } 36 | 37 | protected function setPayload(array $payload): void 38 | { 39 | Assertion::keyExists($payload, 'todo_id'); 40 | Assertion::uuid($payload['todo_id']); 41 | 42 | $this->payload = $payload; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/MarkTodoAsExpired.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | 21 | final class MarkTodoAsExpired extends Command implements PayloadConstructable 22 | { 23 | use PayloadTrait; 24 | 25 | public static function forTodo(string $todoId): MarkTodoAsExpired 26 | { 27 | Assertion::uuid($todoId); 28 | 29 | return new self([ 30 | 'todo_id' => $todoId, 31 | ]); 32 | } 33 | 34 | public function todoId(): TodoId 35 | { 36 | return TodoId::fromString($this->payload['todo_id']); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/NotifyUserOfExpiredTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | 21 | final class NotifyUserOfExpiredTodo extends Command implements PayloadConstructable 22 | { 23 | use PayloadTrait; 24 | 25 | public static function with(TodoId $todoId): NotifyUserOfExpiredTodo 26 | { 27 | return new self([ 28 | 'todo_id' => $todoId->toString(), 29 | ]); 30 | } 31 | 32 | public function todoId(): TodoId 33 | { 34 | return TodoId::fromString($this->payload['todo_id']); 35 | } 36 | 37 | protected function setPayload(array $payload): void 38 | { 39 | Assertion::keyExists($payload, 'todo_id'); 40 | Assertion::uuid($payload['todo_id']); 41 | 42 | $this->payload = $payload; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/PostTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | use Prooph\ProophessorDo\Model\Todo\TodoText; 21 | use Prooph\ProophessorDo\Model\User\UserId; 22 | 23 | final class PostTodo extends Command implements PayloadConstructable 24 | { 25 | use PayloadTrait; 26 | 27 | public static function forUser(string $assigneeId, string $text, string $todoId): PostTodo 28 | { 29 | return new self([ 30 | 'assignee_id' => $assigneeId, 31 | 'todo_id' => $todoId, 32 | 'text' => $text, 33 | ]); 34 | } 35 | 36 | public function todoId(): TodoId 37 | { 38 | return TodoId::fromString($this->payload['todo_id']); 39 | } 40 | 41 | public function assigneeId(): UserId 42 | { 43 | return UserId::fromString($this->payload['assignee_id']); 44 | } 45 | 46 | public function text(): TodoText 47 | { 48 | return TodoText::fromString($this->payload['text']); 49 | } 50 | 51 | protected function setPayload(array $payload): void 52 | { 53 | Assertion::keyExists($payload, 'assignee_id'); 54 | Assertion::uuid($payload['assignee_id']); 55 | Assertion::keyExists($payload, 'todo_id'); 56 | Assertion::uuid($payload['todo_id']); 57 | Assertion::keyExists($payload, 'text'); 58 | Assertion::string($payload['text']); 59 | 60 | $this->payload = $payload; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/RemindTodoAssignee.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | use Prooph\ProophessorDo\Model\Todo\TodoReminder; 21 | use Prooph\ProophessorDo\Model\Todo\TodoReminderStatus; 22 | 23 | final class RemindTodoAssignee extends Command implements PayloadConstructable 24 | { 25 | use PayloadTrait; 26 | 27 | public static function forTodo(TodoId $todoId, TodoReminder $todoReminder): RemindTodoAssignee 28 | { 29 | return new self([ 30 | 'todo_id' => $todoId->toString(), 31 | 'reminder' => $todoReminder->toString(), 32 | 'reminder_status' => $todoReminder->status()->toString(), 33 | ]); 34 | } 35 | 36 | public function todoId(): TodoId 37 | { 38 | return TodoId::fromString($this->payload['todo_id']); 39 | } 40 | 41 | public function reminder(): TodoReminder 42 | { 43 | return TodoReminder::from($this->payload['reminder'], $this->payload['reminder_status']); 44 | } 45 | 46 | protected function setPayload(array $payload): void 47 | { 48 | Assertion::keyExists($payload, 'todo_id'); 49 | Assertion::uuid($payload['todo_id']); 50 | Assertion::keyExists($payload, 'reminder'); 51 | Assertion::string($payload['reminder']); // @todo: check for date format 52 | Assertion::keyExists($payload, 'reminder_status'); 53 | Assertion::true(\defined(TodoReminderStatus::class . '::' . $payload['reminder_status'])); 54 | 55 | $this->payload = $payload; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/ReopenTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | 21 | final class ReopenTodo extends Command implements PayloadConstructable 22 | { 23 | use PayloadTrait; 24 | 25 | public static function with(TodoId $todoId): ReopenTodo 26 | { 27 | return new self([ 28 | 'todo_id' => $todoId->toString(), 29 | ]); 30 | } 31 | 32 | public function todoId(): TodoId 33 | { 34 | return TodoId::fromString($this->payload['todo_id']); 35 | } 36 | 37 | protected function setPayload(array $payload): void 38 | { 39 | Assertion::keyExists($payload, 'todo_id'); 40 | Assertion::uuid($payload['todo_id']); 41 | 42 | $this->payload = $payload; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Command/SendTodoReminderMail.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | use Prooph\ProophessorDo\Model\User\UserId; 21 | 22 | final class SendTodoReminderMail extends Command implements PayloadConstructable 23 | { 24 | use PayloadTrait; 25 | 26 | public static function with(UserId $userId, TodoId $todoId): SendTodoReminderMail 27 | { 28 | return new self([ 29 | 'user_id' => $userId->toString(), 30 | 'todo_id' => $todoId->toString(), 31 | ]); 32 | } 33 | 34 | public function userId(): UserId 35 | { 36 | return UserId::fromString($this->payload['user_id']); 37 | } 38 | 39 | public function todoId(): TodoId 40 | { 41 | return TodoId::fromString($this->payload['todo_id']); 42 | } 43 | 44 | protected function setPayload(array $payload): void 45 | { 46 | Assertion::keyExists($payload, 'user_id'); 47 | Assertion::uuid($payload['user_id']); 48 | Assertion::keyExists($payload, 'todo_id'); 49 | Assertion::uuid($payload['todo_id']); 50 | 51 | $this->payload = $payload; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/DeadlineWasAddedToTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoDeadline; 17 | use Prooph\ProophessorDo\Model\Todo\TodoId; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class DeadlineWasAddedToTodo extends AggregateChanged 21 | { 22 | /** 23 | * @var TodoId 24 | */ 25 | private $todoId; 26 | 27 | /** 28 | * @var UserId 29 | */ 30 | private $userId; 31 | 32 | /** 33 | * @var TodoDeadline 34 | */ 35 | private $deadline; 36 | 37 | public static function byUserToDate(TodoId $todoId, UserId $userId, TodoDeadline $deadline): DeadlineWasAddedToTodo 38 | { 39 | /** @var self $event */ 40 | $event = self::occur($todoId->toString(), [ 41 | 'todo_id' => $todoId->toString(), 42 | 'user_id' => $userId->toString(), 43 | 'deadline' => $deadline->toString(), 44 | ]); 45 | 46 | $event->todoId = $todoId; 47 | $event->userId = $userId; 48 | $event->deadline = $deadline; 49 | 50 | return $event; 51 | } 52 | 53 | public function todoId(): TodoId 54 | { 55 | if (! $this->todoId) { 56 | $this->todoId = TodoId::fromString($this->payload['todo_id']); 57 | } 58 | 59 | return $this->todoId; 60 | } 61 | 62 | public function userId(): UserId 63 | { 64 | if (! $this->userId) { 65 | $this->userId = UserId::fromString($this->payload['user_id']); 66 | } 67 | 68 | return $this->userId; 69 | } 70 | 71 | public function deadline(): TodoDeadline 72 | { 73 | if (! $this->deadline) { 74 | $this->deadline = TodoDeadline::fromString($this->payload['deadline']); 75 | } 76 | 77 | return $this->deadline; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/ReminderWasAddedToTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoId; 17 | use Prooph\ProophessorDo\Model\Todo\TodoReminder; 18 | use Prooph\ProophessorDo\Model\Todo\TodoReminderStatus; 19 | use Prooph\ProophessorDo\Model\User\UserId; 20 | 21 | final class ReminderWasAddedToTodo extends AggregateChanged 22 | { 23 | /** 24 | * @var TodoId 25 | */ 26 | private $todoId; 27 | 28 | /** 29 | * @var UserId 30 | */ 31 | private $userId; 32 | 33 | /** 34 | * @var TodoReminder 35 | */ 36 | private $reminder; 37 | 38 | public static function byUserToDate(TodoId $todoId, UserId $userId, TodoReminder $reminder): ReminderWasAddedToTodo 39 | { 40 | /** @var self $event */ 41 | $event = self::occur($todoId->toString(), [ 42 | 'todo_id' => $todoId->toString(), 43 | 'user_id' => $userId->toString(), 44 | 'reminder' => $reminder->toString(), 45 | ]); 46 | 47 | $event->todoId = $todoId; 48 | $event->userId = $userId; 49 | $event->reminder = $reminder; 50 | 51 | return $event; 52 | } 53 | 54 | public function todoId(): TodoId 55 | { 56 | if (! $this->todoId) { 57 | $this->todoId = TodoId::fromString($this->payload['todo_id']); 58 | } 59 | 60 | return $this->todoId; 61 | } 62 | 63 | public function userId(): UserId 64 | { 65 | if (! $this->userId) { 66 | $this->userId = UserId::fromString($this->payload['user_id']); 67 | } 68 | 69 | return $this->userId; 70 | } 71 | 72 | public function reminder(): TodoReminder 73 | { 74 | if (! $this->reminder) { 75 | $this->reminder = TodoReminder::from($this->payload['reminder'], TodoReminderStatus::OPEN()->getName()); 76 | } 77 | 78 | return $this->reminder; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/TodoAssigneeWasReminded.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoId; 17 | use Prooph\ProophessorDo\Model\Todo\TodoReminder; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class TodoAssigneeWasReminded extends AggregateChanged 21 | { 22 | /** 23 | * @var TodoId 24 | */ 25 | private $todoId; 26 | 27 | /** 28 | * @var UserId 29 | */ 30 | private $userId; 31 | 32 | /** 33 | * @var TodoReminder 34 | */ 35 | private $reminder; 36 | 37 | public static function forAssignee(TodoId $todoId, UserId $userId, TodoReminder $reminder): TodoAssigneeWasReminded 38 | { 39 | /** @var self $event */ 40 | $event = self::occur($todoId->toString(), [ 41 | 'user_id' => $userId->toString(), 42 | 'reminder' => $reminder->toString(), 43 | 'reminder_status' => $reminder->status()->toString(), 44 | ]); 45 | 46 | $event->userId = $userId; 47 | $event->reminder = $reminder; 48 | 49 | return $event; 50 | } 51 | 52 | public function todoId(): TodoId 53 | { 54 | if (! $this->todoId) { 55 | $this->todoId = TodoId::fromString($this->aggregateId()); 56 | } 57 | 58 | return $this->todoId; 59 | } 60 | 61 | public function userId(): UserId 62 | { 63 | if (! $this->userId) { 64 | $this->userId = UserId::fromString($this->payload['user_id']); 65 | } 66 | 67 | return $this->userId; 68 | } 69 | 70 | public function reminder(): TodoReminder 71 | { 72 | if (! $this->reminder) { 73 | $this->reminder = TodoReminder::from($this->payload['reminder'], $this->payload['reminder_status']); 74 | } 75 | 76 | return $this->reminder; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/TodoWasMarkedAsDone.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoId; 17 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class TodoWasMarkedAsDone extends AggregateChanged 21 | { 22 | /** 23 | * @var TodoId 24 | */ 25 | private $todoId; 26 | 27 | /** 28 | * @var TodoStatus 29 | */ 30 | private $oldStatus; 31 | 32 | /** 33 | * @var TodoStatus 34 | */ 35 | private $newStatus; 36 | 37 | /** 38 | * @var UserId 39 | */ 40 | private $assigneeId; 41 | 42 | public static function fromStatus(TodoId $todoId, TodoStatus $oldStatus, TodoStatus $newStatus, UserId $assigneeId): TodoWasMarkedAsDone 43 | { 44 | /** @var self $event */ 45 | $event = self::occur($todoId->toString(), [ 46 | 'old_status' => $oldStatus->toString(), 47 | 'new_status' => $newStatus->toString(), 48 | 'assignee_id' => $assigneeId->toString(), 49 | ]); 50 | 51 | $event->todoId = $todoId; 52 | $event->oldStatus = $oldStatus; 53 | $event->newStatus = $newStatus; 54 | $event->assigneeId = $assigneeId; 55 | 56 | return $event; 57 | } 58 | 59 | public function todoId(): TodoId 60 | { 61 | if (null === $this->todoId) { 62 | $this->todoId = TodoId::fromString($this->aggregateId()); 63 | } 64 | 65 | return $this->todoId; 66 | } 67 | 68 | public function oldStatus(): TodoStatus 69 | { 70 | if (null === $this->oldStatus) { 71 | $this->oldStatus = TodoStatus::byName($this->payload['old_status']); 72 | } 73 | 74 | return $this->oldStatus; 75 | } 76 | 77 | public function newStatus(): TodoStatus 78 | { 79 | if (null === $this->newStatus) { 80 | $this->newStatus = TodoStatus::byName($this->payload['new_status']); 81 | } 82 | 83 | return $this->newStatus; 84 | } 85 | 86 | public function assigneeId(): UserId 87 | { 88 | if (null === $this->assigneeId) { 89 | $this->assigneeId = UserId::fromString($this->payload['assignee_id']); 90 | } 91 | 92 | return $this->assigneeId; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/TodoWasMarkedAsExpired.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoId; 17 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class TodoWasMarkedAsExpired extends AggregateChanged 21 | { 22 | /** 23 | * @var TodoId 24 | */ 25 | private $todoId; 26 | 27 | /** 28 | * @var TodoStatus 29 | */ 30 | private $oldStatus; 31 | 32 | /** 33 | * @var TodoStatus 34 | */ 35 | private $newStatus; 36 | 37 | /** 38 | * @var UserId 39 | */ 40 | private $assigneeId; 41 | 42 | public static function fromStatus(TodoId $todoId, TodoStatus $oldStatus, TodoStatus $newStatus, UserId $assigneeId): TodoWasMarkedAsExpired 43 | { 44 | /** @var self $event */ 45 | $event = self::occur($todoId->toString(), [ 46 | 'old_status' => $oldStatus->toString(), 47 | 'new_status' => $newStatus->toString(), 48 | 'assignee_id' => $assigneeId->toString(), 49 | ]); 50 | 51 | $event->todoId = $todoId; 52 | $event->oldStatus = $oldStatus; 53 | $event->newStatus = $newStatus; 54 | $event->assigneeId = $assigneeId; 55 | 56 | return $event; 57 | } 58 | 59 | public function todoId(): TodoId 60 | { 61 | if (null === $this->todoId) { 62 | $this->todoId = TodoId::fromString($this->aggregateId()); 63 | } 64 | 65 | return $this->todoId; 66 | } 67 | 68 | public function oldStatus(): TodoStatus 69 | { 70 | if (null === $this->oldStatus) { 71 | $this->oldStatus = TodoStatus::byName($this->payload['old_status']); 72 | } 73 | 74 | return $this->oldStatus; 75 | } 76 | 77 | public function newStatus(): TodoStatus 78 | { 79 | if (null === $this->newStatus) { 80 | $this->newStatus = TodoStatus::byName($this->payload['new_status']); 81 | } 82 | 83 | return $this->newStatus; 84 | } 85 | 86 | public function assigneeId(): UserId 87 | { 88 | if (null === $this->assigneeId) { 89 | $this->assigneeId = UserId::fromString($this->payload['assignee_id']); 90 | } 91 | 92 | return $this->assigneeId; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/TodoWasPosted.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoId; 17 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 18 | use Prooph\ProophessorDo\Model\Todo\TodoText; 19 | use Prooph\ProophessorDo\Model\User\UserId; 20 | 21 | final class TodoWasPosted extends AggregateChanged 22 | { 23 | /** 24 | * @var UserId 25 | */ 26 | private $assigneeId; 27 | 28 | /** 29 | * @var TodoId 30 | */ 31 | private $todoId; 32 | 33 | /** 34 | * @var TodoStatus 35 | */ 36 | private $todoStatus; 37 | 38 | public static function byUser(UserId $assigneeId, TodoText $text, TodoId $todoId, TodoStatus $todoStatus): TodoWasPosted 39 | { 40 | /** @var self $event */ 41 | $event = self::occur($todoId->toString(), [ 42 | 'assignee_id' => $assigneeId->toString(), 43 | 'text' => $text->toString(), 44 | 'status' => $todoStatus->toString(), 45 | ]); 46 | 47 | $event->todoId = $todoId; 48 | $event->assigneeId = $assigneeId; 49 | $event->todoStatus = $todoStatus; 50 | 51 | return $event; 52 | } 53 | 54 | public function todoId(): TodoId 55 | { 56 | if (null === $this->todoId) { 57 | $this->todoId = TodoId::fromString($this->aggregateId()); 58 | } 59 | 60 | return $this->todoId; 61 | } 62 | 63 | public function assigneeId(): UserId 64 | { 65 | if (null === $this->assigneeId) { 66 | $this->assigneeId = UserId::fromString($this->payload['assignee_id']); 67 | } 68 | 69 | return $this->assigneeId; 70 | } 71 | 72 | public function text(): TodoText 73 | { 74 | return TodoText::fromString($this->payload['text']); 75 | } 76 | 77 | public function todoStatus(): TodoStatus 78 | { 79 | if (null === $this->todoStatus) { 80 | $this->todoStatus = TodoStatus::byName($this->payload['status']); 81 | } 82 | 83 | return $this->todoStatus; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/TodoWasReopened.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoId; 17 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class TodoWasReopened extends AggregateChanged 21 | { 22 | /** 23 | * @var TodoId 24 | */ 25 | private $todoId; 26 | 27 | /** 28 | * @var TodoStatus 29 | */ 30 | private $status; 31 | 32 | /** 33 | * @var UserId 34 | */ 35 | private $assigneeId; 36 | 37 | public static function withStatus(TodoId $todoId, TodoStatus $status, UserId $assigneeId): TodoWasReopened 38 | { 39 | /** @var self $event */ 40 | $event = self::occur($todoId->toString(), [ 41 | 'status' => $status->toString(), 42 | 'assignee_id' => $assigneeId->toString(), 43 | ]); 44 | 45 | $event->todoId = $todoId; 46 | $event->status = $status; 47 | $event->assigneeId = $assigneeId; 48 | 49 | return $event; 50 | } 51 | 52 | public function todoId(): TodoId 53 | { 54 | if (null === $this->todoId) { 55 | $this->todoId = TodoId::fromString($this->aggregateId()); 56 | } 57 | 58 | return $this->todoId; 59 | } 60 | 61 | public function status(): TodoStatus 62 | { 63 | if (null === $this->status) { 64 | $this->status = TodoStatus::byName($this->payload['status']); 65 | } 66 | 67 | return $this->status; 68 | } 69 | 70 | public function assigneeId(): UserId 71 | { 72 | if (null === $this->assigneeId) { 73 | $this->assigneeId = UserId::fromString($this->payload['assignee_id']); 74 | } 75 | 76 | return $this->assigneeId; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Event/TodoWasUnmarkedAsExpired.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\Todo\TodoId; 17 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 18 | use Prooph\ProophessorDo\Model\User\UserId; 19 | 20 | final class TodoWasUnmarkedAsExpired extends AggregateChanged 21 | { 22 | /** 23 | * @var TodoId 24 | */ 25 | private $todoId; 26 | 27 | /** 28 | * @var TodoStatus 29 | */ 30 | private $oldStatus; 31 | 32 | /** 33 | * @var TodoStatus 34 | */ 35 | private $newStatus; 36 | 37 | /** 38 | * @var UserId 39 | */ 40 | private $assigneeId; 41 | 42 | public static function fromStatus(TodoId $todoId, TodoStatus $oldStatus, TodoStatus $newStatus, UserId $assigneeId): TodoWasUnmarkedAsExpired 43 | { 44 | /** @var self $event */ 45 | $event = self::occur($todoId->toString(), [ 46 | 'old_status' => $oldStatus->toString(), 47 | 'new_status' => $newStatus->toString(), 48 | 'assignee_id' => $assigneeId->toString(), 49 | ]); 50 | 51 | $event->todoId = $todoId; 52 | $event->oldStatus = $oldStatus; 53 | $event->newStatus = $newStatus; 54 | $event->assigneeId = $assigneeId; 55 | 56 | return $event; 57 | } 58 | 59 | public function todoId(): TodoId 60 | { 61 | if (null === $this->todoId) { 62 | $this->todoId = TodoId::fromString($this->aggregateId()); 63 | } 64 | 65 | return $this->todoId; 66 | } 67 | 68 | public function oldStatus(): TodoStatus 69 | { 70 | if (null === $this->oldStatus) { 71 | $this->oldStatus = TodoStatus::byName($this->payload['old_status']); 72 | } 73 | 74 | return $this->oldStatus; 75 | } 76 | 77 | public function newStatus(): TodoStatus 78 | { 79 | if (null === $this->newStatus) { 80 | $this->newStatus = TodoStatus::byName($this->payload['new_status']); 81 | } 82 | 83 | return $this->newStatus; 84 | } 85 | 86 | public function assigneeId(): UserId 87 | { 88 | if (null === $this->assigneeId) { 89 | $this->assigneeId = UserId::fromString($this->payload['assignee_id']); 90 | } 91 | 92 | return $this->assigneeId; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/CannotReopenTodo.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Todo; 16 | 17 | final class CannotReopenTodo extends \RuntimeException 18 | { 19 | public static function notMarkedDone(Todo $todo): CannotReopenTodo 20 | { 21 | return new self(\sprintf( 22 | 'Tried to reopen status of Todo %s. But Todo is not marked as done!', 23 | $todo->todoId()->toString() 24 | )); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/InvalidDeadline.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\TodoDeadline; 16 | use Prooph\ProophessorDo\Model\User\UserId; 17 | 18 | final class InvalidDeadline extends \Exception 19 | { 20 | public static function userIsNotAssignee(UserId $user, UserId $assigneeId): InvalidDeadline 21 | { 22 | return new self(\sprintf( 23 | 'User %s tried to add a deadline to the todo owned by %s', 24 | $user->toString(), 25 | $assigneeId->toString() 26 | )); 27 | } 28 | 29 | public static function deadlineInThePast(TodoDeadline $deadline): InvalidDeadline 30 | { 31 | return new self(\sprintf( 32 | 'Provided deadline %s is in the past from %s', 33 | $deadline->toString(), 34 | $deadline->createdOn() 35 | )); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/InvalidReminder.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\TodoReminder; 16 | use Prooph\ProophessorDo\Model\User\UserId; 17 | 18 | final class InvalidReminder extends \Exception 19 | { 20 | public static function userIsNotAssignee(UserId $user, UserId $assigneeId): InvalidReminder 21 | { 22 | return new self(\sprintf( 23 | 'User %s tried to add a reminder to the todo owned by %s', 24 | $user->toString(), 25 | $assigneeId->toString() 26 | )); 27 | } 28 | 29 | public static function reminderInThePast(TodoReminder $reminder): InvalidReminder 30 | { 31 | return new self(\sprintf( 32 | 'Provided reminder %s is in the past', 33 | $reminder->toString() 34 | )); 35 | } 36 | 37 | public static function reminderInTheFuture(TodoReminder $reminder): InvalidReminder 38 | { 39 | return new self(\sprintf( 40 | 'Provided reminder %s is in the future', 41 | $reminder->toString() 42 | )); 43 | } 44 | 45 | public static function alreadyReminded(): InvalidReminder 46 | { 47 | return new self('The assignee was already reminded.'); 48 | } 49 | 50 | public static function reminderNotCurrent(TodoReminder $expected, TodoReminder $actual): InvalidReminder 51 | { 52 | return new self(\sprintf( 53 | 'Notification for reminder %s can not be send, because %s is the current one.', 54 | $actual->toString(), 55 | $expected->toString() 56 | )); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/InvalidText.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | final class InvalidText extends \InvalidArgumentException 16 | { 17 | public static function reason(string $msg): InvalidText 18 | { 19 | return new self('The todo text is invalid: ' . $msg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/TodoAlreadyDone.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Todo; 16 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 17 | 18 | final class TodoAlreadyDone extends \RuntimeException 19 | { 20 | public static function triedStatus(TodoStatus $status, Todo $todo): TodoAlreadyDone 21 | { 22 | return new self(\sprintf( 23 | 'Tried to change status of Todo %s to %s. But Todo is already marked as done!', 24 | $todo->todoId()->toString(), 25 | $status->toString() 26 | )); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/TodoNotExpired.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Todo; 16 | use Prooph\ProophessorDo\Model\Todo\TodoDeadline; 17 | 18 | final class TodoNotExpired extends \RuntimeException 19 | { 20 | public static function withDeadline(TodoDeadline $deadline, Todo $todo): TodoNotExpired 21 | { 22 | return new self(\sprintf( 23 | 'Tried to mark a non-expired Todo as expired. Todo will expire after the deadline %s.', 24 | $deadline->toString() 25 | )); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/TodoNotFound.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\TodoId; 16 | 17 | final class TodoNotFound extends \InvalidArgumentException 18 | { 19 | public static function withTodoId(TodoId $todoId): TodoNotFound 20 | { 21 | return new self(\sprintf('Todo with id %s cannot be found.', $todoId->toString())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Exception/TodoNotOpen.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Todo; 16 | use Prooph\ProophessorDo\Model\Todo\TodoDeadline; 17 | use Prooph\ProophessorDo\Model\Todo\TodoReminder; 18 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 19 | 20 | final class TodoNotOpen extends \RuntimeException 21 | { 22 | public static function triedStatus(TodoStatus $status, Todo $todo): TodoNotOpen 23 | { 24 | return new self(\sprintf( 25 | 'Tried to change status of Todo %s to %s. But Todo is not marked as open!', 26 | $todo->todoId()->toString(), 27 | $status->toString() 28 | )); 29 | } 30 | 31 | public static function triedToAddDeadline(TodoDeadline $deadline, TodoStatus $status): TodoNotOpen 32 | { 33 | return new self(\sprintf( 34 | 'Tried to deadline %s to a todo with status %s.', 35 | $deadline->toString(), 36 | $status->toString() 37 | )); 38 | } 39 | 40 | public static function triedToAddReminder(TodoReminder $reminder, TodoStatus $status): TodoNotOpen 41 | { 42 | return new self(\sprintf( 43 | 'Tried to add reminder %s to a todo with status %s.', 44 | $reminder->toString(), 45 | $status->toString() 46 | )); 47 | } 48 | 49 | public static function triedToSendReminder(TodoReminder $reminder, TodoStatus $status): TodoNotOpen 50 | { 51 | return new self(\sprintf( 52 | 'Tried to send a reminder %s for a todo with status %s.', 53 | $reminder->toString(), 54 | $status->toString() 55 | )); 56 | } 57 | 58 | public static function triedToExpire(TodoStatus $status): TodoNotOpen 59 | { 60 | return new self(\sprintf('Tried to expire todo with status %s.', $status->toString())); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/AddDeadlineToTodoHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\AddDeadlineToTodo; 16 | use Prooph\ProophessorDo\Model\Todo\TodoList; 17 | 18 | class AddDeadlineToTodoHandler 19 | { 20 | /** 21 | * @var TodoList 22 | */ 23 | private $todoList; 24 | 25 | public function __construct(TodoList $todoList) 26 | { 27 | $this->todoList = $todoList; 28 | } 29 | 30 | public function __invoke(AddDeadlineToTodo $command): void 31 | { 32 | $todo = $this->todoList->get($command->todoId()); 33 | $todo->addDeadline($command->userId(), $command->deadline()); 34 | 35 | $this->todoList->save($todo); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/AddReminderToTodoHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\AddReminderToTodo; 16 | use Prooph\ProophessorDo\Model\Todo\Exception\TodoNotFound; 17 | use Prooph\ProophessorDo\Model\Todo\TodoList; 18 | 19 | class AddReminderToTodoHandler 20 | { 21 | /** 22 | * @var TodoList 23 | */ 24 | private $todoList; 25 | 26 | public function __construct(TodoList $todoList) 27 | { 28 | $this->todoList = $todoList; 29 | } 30 | 31 | public function __invoke(AddReminderToTodo $command): void 32 | { 33 | $todo = $this->todoList->get($command->todoId()); 34 | 35 | if (! $todo) { 36 | throw TodoNotFound::withTodoId($command->todoId()); 37 | } 38 | 39 | $todo->addReminder($command->userId(), $command->reminder()); 40 | 41 | $this->todoList->save($todo); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/GetTodoByIdHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Query\GetTodoById; 16 | use Prooph\ProophessorDo\Projection\Todo\TodoFinder; 17 | use React\Promise\Deferred; 18 | 19 | class GetTodoByIdHandler 20 | { 21 | private $todoFinder; 22 | 23 | public function __construct(TodoFinder $todoFinder) 24 | { 25 | $this->todoFinder = $todoFinder; 26 | } 27 | 28 | public function __invoke(GetTodoById $query, Deferred $deferred = null) 29 | { 30 | $todo = $this->todoFinder->findById($query->todoId()); 31 | if (null === $deferred) { 32 | return $todo; 33 | } 34 | 35 | $deferred->resolve($todo); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/GetTodosByAssigneeIdHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Query\GetTodosByAssigneeId; 16 | use Prooph\ProophessorDo\Projection\Todo\TodoFinder; 17 | use React\Promise\Deferred; 18 | 19 | class GetTodosByAssigneeIdHandler 20 | { 21 | private $todoFinder; 22 | 23 | public function __construct(TodoFinder $todoFinder) 24 | { 25 | $this->todoFinder = $todoFinder; 26 | } 27 | 28 | public function __invoke(GetTodosByAssigneeId $query, Deferred $deferred = null) 29 | { 30 | $todos = $this->todoFinder->findByAssigneeId($query->userId()); 31 | if (null === $deferred) { 32 | return $todos; 33 | } 34 | 35 | $deferred->resolve($todos); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/MarkTodoAsDoneHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\MarkTodoAsDone; 16 | use Prooph\ProophessorDo\Model\Todo\Exception\TodoNotFound; 17 | use Prooph\ProophessorDo\Model\Todo\TodoList; 18 | 19 | class MarkTodoAsDoneHandler 20 | { 21 | /** 22 | * @var TodoList 23 | */ 24 | private $todoList; 25 | 26 | public function __construct(TodoList $todoList) 27 | { 28 | $this->todoList = $todoList; 29 | } 30 | 31 | public function __invoke(MarkTodoAsDone $command): void 32 | { 33 | $todo = $this->todoList->get($command->todoId()); 34 | 35 | if (! $todo) { 36 | throw TodoNotFound::withTodoId($command->todoId()); 37 | } 38 | 39 | $todo->markAsDone(); 40 | 41 | $this->todoList->save($todo); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/MarkTodoAsExpiredHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\MarkTodoAsExpired; 16 | use Prooph\ProophessorDo\Model\Todo\Exception\TodoNotFound; 17 | use Prooph\ProophessorDo\Model\Todo\TodoList; 18 | 19 | class MarkTodoAsExpiredHandler 20 | { 21 | /** 22 | * @var TodoList 23 | */ 24 | private $todoList; 25 | 26 | public function __construct(TodoList $todoList) 27 | { 28 | $this->todoList = $todoList; 29 | } 30 | 31 | public function __invoke(MarkTodoAsExpired $command): void 32 | { 33 | $todo = $this->todoList->get($command->todoId()); 34 | 35 | if (! $todo) { 36 | throw TodoNotFound::withTodoId($command->todoId()); 37 | } 38 | 39 | $todo->markAsExpired(); 40 | 41 | $this->todoList->save($todo); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/NotifyUserOfExpiredTodoHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\NotifyUserOfExpiredTodo; 16 | use Prooph\ProophessorDo\Model\Todo\Query\GetTodoById; 17 | use Prooph\ProophessorDo\Model\User\Query\GetUserById; 18 | use Prooph\ServiceBus\QueryBus; 19 | use Zend\Mail\Message; 20 | use Zend\Mail\Transport\TransportInterface; 21 | 22 | class NotifyUserOfExpiredTodoHandler 23 | { 24 | /** 25 | * @var QueryBus 26 | */ 27 | private $queryBus; 28 | 29 | /** 30 | * @var TransportInterface 31 | */ 32 | private $mailer; 33 | 34 | public function __construct( 35 | QueryBus $queryBus, 36 | TransportInterface $mailer 37 | ) { 38 | $this->queryBus = $queryBus; 39 | $this->mailer = $mailer; 40 | } 41 | 42 | public function __invoke(NotifyUserOfExpiredTodo $command): void 43 | { 44 | $todo = $this->getTodo($command->todoId()->toString()); 45 | if (! $todo) { 46 | return; 47 | } 48 | 49 | $user = $this->getUser($todo->assignee_id); 50 | if (! $user) { 51 | return; 52 | } 53 | 54 | $message = \sprintf( 55 | 'Hi %s! Just a heads up: your todo `%s` has expired on %s.', 56 | $user->name, 57 | $todo->text, 58 | $todo->deadline 59 | ); 60 | 61 | $mail = new Message(); 62 | $mail->setBody($message); 63 | $mail->setEncoding('utf-8'); 64 | $mail->setFrom('reminder@localhost', 'Proophessor-do'); 65 | $mail->addTo($user->email, $user->name); 66 | $mail->setSubject('Proophessor-do Todo expired'); 67 | 68 | $this->mailer->send($mail); 69 | } 70 | 71 | private function getUser(string $userId): ?\stdClass 72 | { 73 | $user = null; 74 | $this->queryBus 75 | ->dispatch(new GetUserById($userId)) 76 | ->then( 77 | function (\stdClass $result = null) use (&$user) { 78 | $user = $result; 79 | } 80 | ); 81 | 82 | return $user; 83 | } 84 | 85 | private function getTodo(string $todoId): ?\stdClass 86 | { 87 | $todo = null; 88 | $this->queryBus 89 | ->dispatch(new GetTodoById($todoId)) 90 | ->then( 91 | function (\stdClass $result = null) use (&$todo) { 92 | $todo = $result; 93 | } 94 | ); 95 | 96 | return $todo; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/PostTodoHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\PostTodo; 16 | use Prooph\ProophessorDo\Model\Todo\TodoList; 17 | use Prooph\ProophessorDo\Model\User\Exception\UserNotFound; 18 | use Prooph\ProophessorDo\Model\User\UserCollection; 19 | 20 | class PostTodoHandler 21 | { 22 | /** 23 | * @var TodoList 24 | */ 25 | private $todoList; 26 | 27 | /** 28 | * @var UserCollection 29 | */ 30 | private $userCollection; 31 | 32 | public function __construct(UserCollection $userCollection, TodoList $todoList) 33 | { 34 | $this->userCollection = $userCollection; 35 | $this->todoList = $todoList; 36 | } 37 | 38 | /** 39 | * @throws UserNotFound 40 | */ 41 | public function __invoke(PostTodo $command): void 42 | { 43 | $user = $this->userCollection->get($command->assigneeId()); 44 | 45 | if (! $user) { 46 | throw UserNotFound::withUserId($command->assigneeId()); 47 | } 48 | 49 | $todo = $user->postTodo($command->text(), $command->todoId()); 50 | 51 | $this->todoList->save($todo); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/RemindTodoAssigneeHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\RemindTodoAssignee; 16 | use Prooph\ProophessorDo\Model\Todo\Exception\TodoNotFound; 17 | use Prooph\ProophessorDo\Model\Todo\Todo; 18 | use Prooph\ProophessorDo\Model\Todo\TodoList; 19 | use Prooph\ProophessorDo\Model\Todo\TodoReminder; 20 | 21 | class RemindTodoAssigneeHandler 22 | { 23 | /** 24 | * @var TodoList 25 | */ 26 | private $todoList; 27 | 28 | public function __construct(TodoList $todoList) 29 | { 30 | $this->todoList = $todoList; 31 | } 32 | 33 | public function __invoke(RemindTodoAssignee $command): void 34 | { 35 | $todo = $this->todoList->get($command->todoId()); 36 | 37 | if (! $todo) { 38 | throw TodoNotFound::withTodoId($command->todoId()); 39 | } 40 | 41 | $reminder = $todo->reminder(); 42 | 43 | if ($this->reminderShouldBeProcessed($todo, $reminder)) { 44 | $todo->remindAssignee($reminder); 45 | 46 | $this->todoList->save($todo); 47 | } 48 | } 49 | 50 | private function reminderShouldBeProcessed(Todo $todo, TodoReminder $reminder): bool 51 | { 52 | // drop command, wrong reminder 53 | if (! $todo->reminder()->sameValueAs($reminder)) { 54 | return false; 55 | } 56 | 57 | // drop command, reminder is closed 58 | if (! $reminder->isOpen()) { 59 | return false; 60 | } 61 | 62 | // drop command, reminder is in future 63 | if ($reminder->isInTheFuture()) { 64 | return false; 65 | } 66 | 67 | return true; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/ReopenTodoHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\ReopenTodo; 16 | use Prooph\ProophessorDo\Model\Todo\Exception\TodoNotFound; 17 | use Prooph\ProophessorDo\Model\Todo\TodoList; 18 | 19 | class ReopenTodoHandler 20 | { 21 | /** 22 | * @var TodoList 23 | */ 24 | private $todoList; 25 | 26 | public function __construct(TodoList $todoList) 27 | { 28 | $this->todoList = $todoList; 29 | } 30 | 31 | public function __invoke(ReopenTodo $command): void 32 | { 33 | $todo = $this->todoList->get($command->todoId()); 34 | 35 | if (! $todo) { 36 | throw TodoNotFound::withTodoId($command->todoId()); 37 | } 38 | 39 | $todo->reopenTodo(); 40 | 41 | $this->todoList->save($todo); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Handler/SendTodoReminderMailHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\SendTodoReminderMail; 16 | use Prooph\ProophessorDo\Model\Todo\Query\GetTodoById; 17 | use Prooph\ProophessorDo\Model\User\Query\GetUserById; 18 | use Prooph\ServiceBus\QueryBus; 19 | use Zend\Mail; 20 | use Zend\Mail\Transport\TransportInterface; 21 | 22 | class SendTodoReminderMailHandler 23 | { 24 | /** 25 | * @var QueryBus 26 | */ 27 | private $queryBus; 28 | /** 29 | * @var TransportInterface 30 | */ 31 | private $mailer; 32 | 33 | public function __construct(QueryBus $queryBus, TransportInterface $mailer) 34 | { 35 | $this->queryBus = $queryBus; 36 | $this->mailer = $mailer; 37 | } 38 | 39 | public function __invoke(SendTodoReminderMail $command): void 40 | { 41 | $user = $this->getUser($command->userId()->toString()); 42 | if (! $user) { 43 | return; 44 | } 45 | 46 | $todo = $this->getTodo($command->todoId()->toString()); 47 | if (! $todo) { 48 | return; 49 | } 50 | 51 | $mail = new Mail\Message(); 52 | $mail->setBody("Hello {$user->name}. This a reminder for '{$todo->text}'. Don't be lazy!"); 53 | $mail->setFrom('reminder@localhost', 'Proophessor-do'); 54 | $mail->addTo($user->email, $user->name); 55 | $mail->setSubject('Proophessor-do Todo Reminder'); 56 | 57 | $this->mailer->send($mail); 58 | } 59 | 60 | private function getUser(string $userId): ?\stdClass 61 | { 62 | $user = null; 63 | $this->queryBus 64 | ->dispatch(new GetUserById($userId)) 65 | ->then( 66 | function (\stdClass $result = null) use (&$user) { 67 | $user = $result; 68 | } 69 | ); 70 | 71 | return $user; 72 | } 73 | 74 | private function getTodo(string $todoId): ?\stdClass 75 | { 76 | $todo = null; 77 | $this->queryBus 78 | ->dispatch(new GetTodoById($todoId)) 79 | ->then( 80 | function (\stdClass $result = null) use (&$todo) { 81 | $todo = $result; 82 | } 83 | ); 84 | 85 | return $todo; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Query/GetTodoById.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Query; 14 | 15 | final class GetTodoById 16 | { 17 | /** 18 | * @var string 19 | */ 20 | private $todoId; 21 | 22 | public function __construct(string $todoId) 23 | { 24 | $this->todoId = $todoId; 25 | } 26 | 27 | public function todoId(): string 28 | { 29 | return $this->todoId; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/Query/GetTodosByAssigneeId.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo\Query; 14 | 15 | final class GetTodosByAssigneeId 16 | { 17 | /** 18 | * @var string 19 | */ 20 | private $userId; 21 | 22 | public function __construct(string $userId) 23 | { 24 | $this->userId = $userId; 25 | } 26 | 27 | public function userId(): string 28 | { 29 | return $this->userId; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/TodoDeadline.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo; 14 | 15 | use Prooph\ProophessorDo\Model\ValueObject; 16 | 17 | final class TodoDeadline implements ValueObject 18 | { 19 | /** 20 | * @var \DateTimeImmutable 21 | */ 22 | private $deadline; 23 | 24 | /** 25 | * @var \DateTimeImmutable 26 | */ 27 | private $createdOn; 28 | 29 | public static function fromString(string $deadline): TodoDeadline 30 | { 31 | return new self($deadline); 32 | } 33 | 34 | private function __construct(string $deadline) 35 | { 36 | $this->deadline = new \DateTimeImmutable($deadline, new \DateTimeZone('UTC')); 37 | $this->createdOn = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); 38 | } 39 | 40 | public function isInThePast(): bool 41 | { 42 | return $this->deadline < $this->createdOn; 43 | } 44 | 45 | public function toString(): string 46 | { 47 | return $this->deadline->format(\DateTime::ATOM); 48 | } 49 | 50 | public function createdOn(): string 51 | { 52 | return $this->createdOn->format(\DateTime::ATOM); 53 | } 54 | 55 | public function isMet(): bool 56 | { 57 | return $this->deadline > new \DateTimeImmutable(); 58 | } 59 | 60 | public function sameValueAs(ValueObject $object): bool 61 | { 62 | return \get_class($this) === \get_class($object) 63 | && $this->deadline->format('U.u') === $object->deadline->format('U.u') 64 | && $this->createdOn->format('U.u') === $object->createdOn->format('U.u'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/TodoId.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo; 14 | 15 | use Prooph\ProophessorDo\Model\ValueObject; 16 | use Ramsey\Uuid\Uuid; 17 | use Ramsey\Uuid\UuidInterface; 18 | 19 | final class TodoId implements ValueObject 20 | { 21 | /** 22 | * @var UuidInterface 23 | */ 24 | private $uuid; 25 | 26 | public static function generate(): TodoId 27 | { 28 | return new self(Uuid::uuid4()); 29 | } 30 | 31 | public static function fromString(string $todoId): TodoId 32 | { 33 | return new self(Uuid::fromString($todoId)); 34 | } 35 | 36 | private function __construct(UuidInterface $uuid) 37 | { 38 | $this->uuid = $uuid; 39 | } 40 | 41 | public function toString(): string 42 | { 43 | return $this->uuid->toString(); 44 | } 45 | 46 | public function sameValueAs(ValueObject $other): bool 47 | { 48 | return \get_class($this) === \get_class($other) && $this->uuid->equals($other->uuid); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/TodoList.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo; 14 | 15 | interface TodoList 16 | { 17 | public function save(Todo $todo): void; 18 | 19 | public function get(TodoId $todoId): ?Todo; 20 | } 21 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/TodoReminder.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo; 14 | 15 | use DateTimeImmutable; 16 | use DateTimeZone; 17 | use Prooph\ProophessorDo\Model\ValueObject; 18 | 19 | final class TodoReminder implements ValueObject 20 | { 21 | /** 22 | * @var \DateTimeImmutable 23 | */ 24 | private $reminder; 25 | 26 | /** 27 | * @var TodoReminderStatus 28 | */ 29 | private $status; 30 | 31 | public static function from(string $reminder, string $status): TodoReminder 32 | { 33 | return new self( 34 | new DateTimeImmutable($reminder, new DateTimeZone('UTC')), 35 | TodoReminderStatus::byName($status) 36 | ); 37 | } 38 | 39 | private function __construct(DateTimeImmutable $reminder, TodoReminderStatus $status) 40 | { 41 | $this->reminder = $reminder; 42 | $this->status = $status; 43 | } 44 | 45 | public function isOpen(): bool 46 | { 47 | return $this->status->is(TodoReminderStatus::OPEN()); 48 | } 49 | 50 | public function isInThePast(): bool 51 | { 52 | return $this->reminder < new \DateTimeImmutable('now', new \DateTimeZone('UTC')); 53 | } 54 | 55 | public function isInTheFuture(): bool 56 | { 57 | return $this->reminder > new \DateTimeImmutable('now', new \DateTimeZone('UTC')); 58 | } 59 | 60 | public function status(): TodoReminderStatus 61 | { 62 | return $this->status; 63 | } 64 | 65 | public function close(): TodoReminder 66 | { 67 | return new self($this->reminder, TodoReminderStatus::CLOSED()); 68 | } 69 | 70 | public function toString(): string 71 | { 72 | return $this->reminder->format(\DateTime::ATOM); 73 | } 74 | 75 | public function sameValueAs(ValueObject $object): bool 76 | { 77 | return \get_class($this) === \get_class($object) 78 | && $this->reminder->format('U.u') === $object->reminder->format('U.u') 79 | && $this->status->is($object->status); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/TodoReminderStatus.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo; 14 | 15 | use Prooph\ProophessorDo\Model\Enum; 16 | 17 | /** 18 | * @method static TodoReminderStatus OPEN() 19 | * @method static TodoReminderStatus CLOSED() 20 | */ 21 | final class TodoReminderStatus extends Enum 22 | { 23 | public const OPEN = 'open'; 24 | public const CLOSED = 'closed'; 25 | } 26 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/TodoStatus.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo; 14 | 15 | use Prooph\ProophessorDo\Model\Enum; 16 | 17 | /** 18 | * @method static TodoStatus OPEN() 19 | * @method static TodoStatus DONE() 20 | * @method static TodoStatus EXPIRED() 21 | */ 22 | final class TodoStatus extends Enum 23 | { 24 | public const OPEN = 'open'; 25 | public const DONE = 'done'; 26 | public const EXPIRED = 'expired'; 27 | } 28 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/Todo/TodoText.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\Todo; 14 | 15 | use Assert\Assertion; 16 | use Prooph\ProophessorDo\Model\ValueObject; 17 | 18 | final class TodoText implements ValueObject 19 | { 20 | /** 21 | * @var string 22 | */ 23 | private $text; 24 | 25 | public static function fromString(string $text): self 26 | { 27 | return new self($text); 28 | } 29 | 30 | private function __construct(string $text) 31 | { 32 | try { 33 | Assertion::minLength($text, 3); 34 | } catch (\Exception $e) { 35 | throw Exception\InvalidText::reason($e->getMessage()); 36 | } 37 | 38 | $this->text = $text; 39 | } 40 | 41 | public function toString(): string 42 | { 43 | return $this->text; 44 | } 45 | 46 | public function sameValueAs(ValueObject $object): bool 47 | { 48 | return \get_class($this) === \get_class($object) && $this->text === $object->text; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Command/RegisterUser.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Command; 14 | 15 | use Assert\Assertion; 16 | use Prooph\Common\Messaging\Command; 17 | use Prooph\Common\Messaging\PayloadConstructable; 18 | use Prooph\Common\Messaging\PayloadTrait; 19 | use Prooph\ProophessorDo\Model\User\EmailAddress; 20 | use Prooph\ProophessorDo\Model\User\UserId; 21 | use Prooph\ProophessorDo\Model\User\UserName; 22 | use Zend\Validator\EmailAddress as EmailAddressValidator; 23 | 24 | final class RegisterUser extends Command implements PayloadConstructable 25 | { 26 | use PayloadTrait; 27 | 28 | public static function withData(string $userId, string $name, string $email): RegisterUser 29 | { 30 | return new self([ 31 | 'user_id' => $userId, 32 | 'name' => $name, 33 | 'email' => $email, 34 | ]); 35 | } 36 | 37 | public function userId(): UserId 38 | { 39 | return UserId::fromString($this->payload['user_id']); 40 | } 41 | 42 | public function name(): UserName 43 | { 44 | return UserName::fromString($this->payload['name']); 45 | } 46 | 47 | public function emailAddress(): EmailAddress 48 | { 49 | return EmailAddress::fromString($this->payload['email']); 50 | } 51 | 52 | protected function setPayload(array $payload): void 53 | { 54 | Assertion::keyExists($payload, 'user_id'); 55 | Assertion::uuid($payload['user_id']); 56 | Assertion::keyExists($payload, 'name'); 57 | Assertion::string($payload['name']); 58 | Assertion::keyExists($payload, 'email'); 59 | $validator = new EmailAddressValidator(); 60 | Assertion::true($validator->isValid($payload['email'])); 61 | 62 | $this->payload = $payload; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/EmailAddress.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User; 14 | 15 | use Prooph\ProophessorDo\Model\ValueObject; 16 | use Zend\Validator\EmailAddress as EmailAddressValidator; 17 | 18 | final class EmailAddress implements ValueObject 19 | { 20 | /** 21 | * @var string 22 | */ 23 | private $email; 24 | 25 | public static function fromString(string $email): EmailAddress 26 | { 27 | $validator = new EmailAddressValidator(); 28 | 29 | if (! $validator->isValid($email)) { 30 | throw new \InvalidArgumentException('Invalid email address'); 31 | } 32 | 33 | return new self($email); 34 | } 35 | 36 | private function __construct(string $email) 37 | { 38 | $this->email = $email; 39 | } 40 | 41 | public function toString(): string 42 | { 43 | return $this->email; 44 | } 45 | 46 | public function sameValueAs(ValueObject $other): bool 47 | { 48 | return \get_class($this) === \get_class($other) && $this->toString() === $other->toString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Event/UserWasRegistered.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\User\EmailAddress; 17 | use Prooph\ProophessorDo\Model\User\UserId; 18 | use Prooph\ProophessorDo\Model\User\UserName; 19 | 20 | final class UserWasRegistered extends AggregateChanged 21 | { 22 | /** 23 | * @var UserId 24 | */ 25 | private $userId; 26 | 27 | /** 28 | * @var UserName 29 | */ 30 | private $username; 31 | 32 | /** 33 | * @var EmailAddress 34 | */ 35 | private $emailAddress; 36 | 37 | public static function withData(UserId $userId, UserName $name, EmailAddress $emailAddress): UserWasRegistered 38 | { 39 | /** @var self $event */ 40 | $event = self::occur($userId->toString(), [ 41 | 'name' => $name->toString(), 42 | 'email' => $emailAddress->toString(), 43 | ]); 44 | 45 | $event->userId = $userId; 46 | $event->username = $name; 47 | $event->emailAddress = $emailAddress; 48 | 49 | return $event; 50 | } 51 | 52 | public function userId(): UserId 53 | { 54 | if (null === $this->userId) { 55 | $this->userId = UserId::fromString($this->aggregateId()); 56 | } 57 | 58 | return $this->userId; 59 | } 60 | 61 | public function name(): UserName 62 | { 63 | if (null === $this->username) { 64 | $this->username = UserName::fromString($this->payload['name']); 65 | } 66 | 67 | return $this->username; 68 | } 69 | 70 | public function emailAddress(): EmailAddress 71 | { 72 | if (null === $this->emailAddress) { 73 | $this->emailAddress = EmailAddress::fromString($this->payload['email']); 74 | } 75 | 76 | return $this->emailAddress; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Event/UserWasRegisteredAgain.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Event; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\ProophessorDo\Model\User\EmailAddress; 17 | use Prooph\ProophessorDo\Model\User\UserId; 18 | use Prooph\ProophessorDo\Model\User\UserName; 19 | 20 | final class UserWasRegisteredAgain extends AggregateChanged 21 | { 22 | /** 23 | * @var UserId 24 | */ 25 | private $userId; 26 | 27 | /** 28 | * @var UserName 29 | */ 30 | private $username; 31 | 32 | /** 33 | * @var EmailAddress 34 | */ 35 | private $emailAddress; 36 | 37 | public static function withData(UserId $userId, UserName $name, EmailAddress $emailAddress): UserWasRegisteredAgain 38 | { 39 | /** @var self $event */ 40 | $event = self::occur($userId->toString(), [ 41 | 'name' => $name->toString(), 42 | 'email' => $emailAddress->toString(), 43 | ]); 44 | 45 | $event->userId = $userId; 46 | $event->username = $name; 47 | $event->emailAddress = $emailAddress; 48 | 49 | return $event; 50 | } 51 | 52 | public function userId(): UserId 53 | { 54 | if (null === $this->userId) { 55 | $this->userId = UserId::fromString($this->aggregateId()); 56 | } 57 | 58 | return $this->userId; 59 | } 60 | 61 | public function name(): UserName 62 | { 63 | if (null === $this->username) { 64 | $this->username = UserName::fromString($this->payload['name']); 65 | } 66 | 67 | return $this->username; 68 | } 69 | 70 | public function emailAddress(): EmailAddress 71 | { 72 | if (null === $this->emailAddress) { 73 | $this->emailAddress = EmailAddress::fromString($this->payload['email']); 74 | } 75 | 76 | return $this->emailAddress; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Exception/InvalidName.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Exception; 14 | 15 | final class InvalidName extends \InvalidArgumentException 16 | { 17 | public static function reason(string $msg): InvalidName 18 | { 19 | return new self('Invalid user name because ' . $msg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Exception/UserAlreadyExists.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\User\UserId; 16 | 17 | final class UserAlreadyExists extends \InvalidArgumentException 18 | { 19 | public static function withUserId(UserId $userId): UserAlreadyExists 20 | { 21 | return new self(\sprintf('User with id %s already exists.', $userId->toString())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Exception/UserNotFound.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Exception; 14 | 15 | use Prooph\ProophessorDo\Model\User\UserId; 16 | 17 | final class UserNotFound extends \InvalidArgumentException 18 | { 19 | public static function withUserId(UserId $userId): UserNotFound 20 | { 21 | return new self(\sprintf('User with id %s cannot be found.', $userId->toString())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Handler/GetAllUsersHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\User\Query\GetAllUsers; 16 | use Prooph\ProophessorDo\Projection\User\UserFinder; 17 | use React\Promise\Deferred; 18 | 19 | class GetAllUsersHandler 20 | { 21 | private $userFinder; 22 | 23 | public function __construct(UserFinder $userFinder) 24 | { 25 | $this->userFinder = $userFinder; 26 | } 27 | 28 | public function __invoke(GetAllUsers $query, Deferred $deferred = null) 29 | { 30 | $user = $this->userFinder->findAll(); 31 | if (null === $deferred) { 32 | return $user; 33 | } 34 | 35 | $deferred->resolve($user); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Handler/GetUserByIdHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\User\Query\GetUserById; 16 | use Prooph\ProophessorDo\Projection\User\UserFinder; 17 | use React\Promise\Deferred; 18 | 19 | class GetUserByIdHandler 20 | { 21 | private $userFinder; 22 | 23 | public function __construct(UserFinder $userFinder) 24 | { 25 | $this->userFinder = $userFinder; 26 | } 27 | 28 | public function __invoke(GetUserById $query, Deferred $deferred = null) 29 | { 30 | $user = $this->userFinder->findById($query->userId()); 31 | if (null === $deferred) { 32 | return $user; 33 | } 34 | 35 | $deferred->resolve($user); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Handler/RegisterUserHandler.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Handler; 14 | 15 | use Prooph\ProophessorDo\Model\User\Command\RegisterUser; 16 | use Prooph\ProophessorDo\Model\User\Exception\UserAlreadyExists; 17 | use Prooph\ProophessorDo\Model\User\Exception\UserNotFound; 18 | use Prooph\ProophessorDo\Model\User\Service\ChecksUniqueUsersEmailAddress; 19 | use Prooph\ProophessorDo\Model\User\User; 20 | use Prooph\ProophessorDo\Model\User\UserCollection; 21 | 22 | class RegisterUserHandler 23 | { 24 | /** 25 | * @var UserCollection 26 | */ 27 | private $userCollection; 28 | 29 | /** 30 | * @var ChecksUniqueUsersEmailAddress 31 | */ 32 | private $checksUniqueUsersEmailAddress; 33 | 34 | public function __construct( 35 | UserCollection $userCollection, 36 | ChecksUniqueUsersEmailAddress $checksUniqueUsersEmailAddress 37 | ) { 38 | $this->userCollection = $userCollection; 39 | $this->checksUniqueUsersEmailAddress = $checksUniqueUsersEmailAddress; 40 | } 41 | 42 | public function __invoke(RegisterUser $command): void 43 | { 44 | if ($userId = ($this->checksUniqueUsersEmailAddress)($command->emailAddress())) { 45 | if (! $user = $this->userCollection->get($userId)) { 46 | throw UserNotFound::withUserId($userId); 47 | } 48 | 49 | $user->registerAgain($command->name()); 50 | } else { 51 | if ($user = $this->userCollection->get($command->userId())) { 52 | throw UserAlreadyExists::withUserId($command->userId()); 53 | } 54 | $user = User::registerWithData($command->userId(), $command->name(), $command->emailAddress()); 55 | } 56 | 57 | $this->userCollection->save($user); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Query/GetAllUsers.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Query; 14 | 15 | final class GetAllUsers 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Query/GetUserById.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Query; 14 | 15 | class GetUserById 16 | { 17 | /** 18 | * @var string 19 | */ 20 | private $userId; 21 | 22 | public function __construct(string $userId) 23 | { 24 | $this->userId = $userId; 25 | } 26 | 27 | public function userId(): string 28 | { 29 | return $this->userId; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/Service/ChecksUniqueUsersEmailAddress.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User\Service; 14 | 15 | use Prooph\ProophessorDo\Model\User\EmailAddress; 16 | use Prooph\ProophessorDo\Model\User\UserId; 17 | 18 | interface ChecksUniqueUsersEmailAddress 19 | { 20 | public function __invoke(EmailAddress $emailAddress): ?UserId; 21 | } 22 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/User.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User; 14 | 15 | use Prooph\EventSourcing\AggregateChanged; 16 | use Prooph\EventSourcing\AggregateRoot; 17 | use Prooph\ProophessorDo\Model\Entity; 18 | use Prooph\ProophessorDo\Model\Todo\Todo; 19 | use Prooph\ProophessorDo\Model\Todo\TodoId; 20 | use Prooph\ProophessorDo\Model\Todo\TodoText; 21 | use Prooph\ProophessorDo\Model\User\Event\UserWasRegistered; 22 | use Prooph\ProophessorDo\Model\User\Event\UserWasRegisteredAgain; 23 | 24 | final class User extends AggregateRoot implements Entity 25 | { 26 | /** 27 | * @var UserId 28 | */ 29 | private $userId; 30 | 31 | /** 32 | * @var UserName 33 | */ 34 | private $name; 35 | 36 | /** 37 | * @var EmailAddress 38 | */ 39 | private $emailAddress; 40 | 41 | public static function registerWithData( 42 | UserId $userId, 43 | UserName $name, 44 | EmailAddress $emailAddress 45 | ): User { 46 | $self = new self(); 47 | 48 | $self->recordThat(UserWasRegistered::withData($userId, $name, $emailAddress)); 49 | 50 | return $self; 51 | } 52 | 53 | public function registerAgain(UserName $name): void 54 | { 55 | $this->recordThat(UserWasRegisteredAgain::withData($this->userId, $name, $this->emailAddress)); 56 | } 57 | 58 | public function userId(): UserId 59 | { 60 | return $this->userId; 61 | } 62 | 63 | public function name(): UserName 64 | { 65 | return $this->name; 66 | } 67 | 68 | public function emailAddress(): EmailAddress 69 | { 70 | return $this->emailAddress; 71 | } 72 | 73 | public function postTodo(TodoText $text, TodoId $todoId): Todo 74 | { 75 | return Todo::post($text, $this->userId(), $todoId); 76 | } 77 | 78 | protected function aggregateId(): string 79 | { 80 | return $this->userId->toString(); 81 | } 82 | 83 | protected function whenUserWasRegistered(UserWasRegistered $event): void 84 | { 85 | $this->userId = $event->userId(); 86 | $this->name = $event->name(); 87 | $this->emailAddress = $event->emailAddress(); 88 | } 89 | 90 | protected function whenUserWasRegisteredAgain(UserWasRegisteredAgain $event): void 91 | { 92 | } 93 | 94 | public function sameIdentityAs(Entity $other): bool 95 | { 96 | return \get_class($this) === \get_class($other) && $this->userId->sameValueAs($other->userId); 97 | } 98 | 99 | /** 100 | * Apply given event 101 | */ 102 | protected function apply(AggregateChanged $e): void 103 | { 104 | $handler = $this->determineEventHandlerMethodFor($e); 105 | 106 | if (! \method_exists($this, $handler)) { 107 | throw new \RuntimeException(\sprintf( 108 | 'Missing event handler method %s for aggregate root %s', 109 | $handler, 110 | \get_class($this) 111 | )); 112 | } 113 | 114 | $this->{$handler}($e); 115 | } 116 | 117 | protected function determineEventHandlerMethodFor(AggregateChanged $e): string 118 | { 119 | return 'when' . \implode(\array_slice(\explode('\\', \get_class($e)), -1)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/UserCollection.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User; 14 | 15 | interface UserCollection 16 | { 17 | public function save(User $user): void; 18 | 19 | public function get(UserId $userId): ?User; 20 | } 21 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/UserId.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User; 14 | 15 | use Prooph\ProophessorDo\Model\ValueObject; 16 | use Ramsey\Uuid\Uuid; 17 | use Ramsey\Uuid\UuidInterface; 18 | 19 | final class UserId implements ValueObject 20 | { 21 | /** 22 | * @var UuidInterface 23 | */ 24 | private $uuid; 25 | 26 | public static function generate(): UserId 27 | { 28 | return new self(Uuid::uuid4()); 29 | } 30 | 31 | public static function fromString(string $userId): UserId 32 | { 33 | return new self(Uuid::fromString($userId)); 34 | } 35 | 36 | private function __construct(UuidInterface $uuid) 37 | { 38 | $this->uuid = $uuid; 39 | } 40 | 41 | public function toString(): string 42 | { 43 | return $this->uuid->toString(); 44 | } 45 | 46 | public function sameValueAs(ValueObject $other): bool 47 | { 48 | return \get_class($this) === \get_class($other) && $this->uuid->equals($other->uuid); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/User/UserName.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model\User; 14 | 15 | use Assert\Assertion; 16 | use Prooph\ProophessorDo\Model\ValueObject; 17 | 18 | final class UserName implements ValueObject 19 | { 20 | /** 21 | * @var string 22 | */ 23 | private $name; 24 | 25 | public static function fromString(string $name): self 26 | { 27 | return new self($name); 28 | } 29 | 30 | private function __construct(string $name) 31 | { 32 | try { 33 | Assertion::notEmpty($name); 34 | } catch (\Exception $e) { 35 | throw Exception\InvalidName::reason($e->getMessage()); 36 | } 37 | 38 | $this->name = $name; 39 | } 40 | 41 | public function toString(): string 42 | { 43 | return $this->name; 44 | } 45 | 46 | public function sameValueAs(ValueObject $object): bool 47 | { 48 | return \get_class($this) === \get_class($object) && $this->name === $object->name; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ProophessorDo/Model/ValueObject.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Model; 14 | 15 | interface ValueObject 16 | { 17 | public function sameValueAs(ValueObject $object): bool; 18 | } 19 | -------------------------------------------------------------------------------- /src/ProophessorDo/ProcessManager/SendTodoDeadlineExpiredMailProcessManager.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\ProcessManager; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\NotifyUserOfExpiredTodo; 16 | use Prooph\ProophessorDo\Model\Todo\Event\TodoWasMarkedAsExpired; 17 | use Prooph\ServiceBus\CommandBus; 18 | 19 | class SendTodoDeadlineExpiredMailProcessManager 20 | { 21 | /** 22 | * @var CommandBus 23 | */ 24 | private $commandBus; 25 | 26 | public function __construct(CommandBus $commandBus) 27 | { 28 | $this->commandBus = $commandBus; 29 | } 30 | 31 | public function __invoke(TodoWasMarkedAsExpired $event): void 32 | { 33 | $this->commandBus->dispatch(NotifyUserOfExpiredTodo::with($event->todoId())); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ProophessorDo/ProcessManager/SendTodoReminderMailProcessManager.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\ProcessManager; 14 | 15 | use Prooph\ProophessorDo\Model\Todo\Command\SendTodoReminderMail; 16 | use Prooph\ProophessorDo\Model\Todo\Event\TodoAssigneeWasReminded; 17 | use Prooph\ServiceBus\CommandBus; 18 | 19 | class SendTodoReminderMailProcessManager 20 | { 21 | /** 22 | * @var CommandBus 23 | */ 24 | private $commandBus; 25 | 26 | public function __construct(CommandBus $commandBus) 27 | { 28 | $this->commandBus = $commandBus; 29 | } 30 | 31 | public function __invoke(TodoAssigneeWasReminded $event): void 32 | { 33 | $this->commandBus->dispatch(SendTodoReminderMail::with($event->userId(), $event->todoId())); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/Table.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection; 14 | 15 | final class Table 16 | { 17 | const USER = 'read_user'; 18 | const TODO = 'read_todo'; 19 | const TODO_REMINDER = 'read_todo_reminder'; 20 | } 21 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/Todo/TodoFinder.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection\Todo; 14 | 15 | use Doctrine\DBAL\Connection; 16 | use Prooph\ProophessorDo\Model\Todo\TodoStatus; 17 | use Prooph\ProophessorDo\Projection\Table; 18 | 19 | class TodoFinder 20 | { 21 | /** 22 | * @var Connection 23 | */ 24 | private $connection; 25 | 26 | public function __construct(Connection $connection) 27 | { 28 | $this->connection = $connection; 29 | $this->connection->setFetchMode(\PDO::FETCH_OBJ); 30 | } 31 | 32 | public function findAll(): array 33 | { 34 | return $this->connection->fetchAll(\sprintf('SELECT * FROM %s', Table::TODO)); 35 | } 36 | 37 | public function findAllOpen(): array 38 | { 39 | return $this->connection->fetchAll(\sprintf("SELECT * FROM %s WHERE status = '%s'", Table::TODO, TodoStatus::OPEN)); 40 | } 41 | 42 | public function findByAssigneeId(string $assigneeId): array 43 | { 44 | return $this->connection->fetchAll( 45 | \sprintf('SELECT * FROM %s WHERE assignee_id = :assignee_id', Table::TODO), 46 | ['assignee_id' => $assigneeId] 47 | ); 48 | } 49 | 50 | public function findById(string $todoId): ?\stdClass 51 | { 52 | $stmt = $this->connection->prepare(\sprintf('SELECT * FROM %s where id = :todo_id', Table::TODO)); 53 | $stmt->bindValue('todo_id', $todoId); 54 | $stmt->execute(); 55 | 56 | $result = $stmt->fetch(); 57 | 58 | if (false === $result) { 59 | return null; 60 | } 61 | 62 | return $result; 63 | } 64 | 65 | public function findByOpenReminders(): array 66 | { 67 | $stmt = $this->connection->prepare(\sprintf('SELECT * FROM %s where reminder < NOW() AND reminded = 0', Table::TODO)); 68 | $stmt->execute(); 69 | 70 | return $stmt->fetchAll(); 71 | } 72 | 73 | public function findOpenWithPastTheirDeadline(): array 74 | { 75 | return $this->connection->fetchAll( 76 | \sprintf( 77 | "SELECT * FROM %s WHERE status = :status AND deadline < CONVERT_TZ(NOW(), @@session.time_zone, '+00:00')", 78 | Table::TODO 79 | ), [ 80 | 'status' => TodoStatus::OPEN, 81 | ] 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/Todo/TodoReadModel.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection\Todo; 14 | 15 | use Doctrine\DBAL\Connection; 16 | use Prooph\EventStore\Projection\AbstractReadModel; 17 | use Prooph\ProophessorDo\Projection\Table; 18 | 19 | final class TodoReadModel extends AbstractReadModel 20 | { 21 | /** 22 | * @var Connection 23 | */ 24 | private $connection; 25 | 26 | public function __construct(Connection $connection) 27 | { 28 | $this->connection = $connection; 29 | } 30 | 31 | public function init(): void 32 | { 33 | $tableName = Table::TODO; 34 | 35 | $sql = <<connection->prepare($sql); 50 | $statement->execute(); 51 | } 52 | 53 | public function isInitialized(): bool 54 | { 55 | $tableName = Table::TODO; 56 | 57 | $sql = "SHOW TABLES LIKE '$tableName';"; 58 | 59 | $statement = $this->connection->prepare($sql); 60 | $statement->execute(); 61 | 62 | $result = $statement->fetch(); 63 | 64 | if (false === $result) { 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | public function reset(): void 72 | { 73 | $tableName = Table::TODO; 74 | 75 | $sql = "TRUNCATE TABLE '$tableName';"; 76 | 77 | $statement = $this->connection->prepare($sql); 78 | $statement->execute(); 79 | } 80 | 81 | public function delete(): void 82 | { 83 | $tableName = Table::TODO; 84 | 85 | $sql = "DROP TABLE $tableName;"; 86 | 87 | $statement = $this->connection->prepare($sql); 88 | $statement->execute(); 89 | } 90 | 91 | protected function insert(array $data): void 92 | { 93 | $this->connection->insert(Table::TODO, $data); 94 | } 95 | 96 | protected function update(array $data, array $identifier): void 97 | { 98 | $this->connection->update( 99 | Table::TODO, 100 | $data, 101 | $identifier 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/Todo/TodoReminderFinder.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection\Todo; 14 | 15 | use Doctrine\DBAL\Connection; 16 | use Prooph\ProophessorDo\Model\Todo\TodoReminderStatus; 17 | use Prooph\ProophessorDo\Projection\Table; 18 | 19 | class TodoReminderFinder 20 | { 21 | /** 22 | * @var Connection 23 | */ 24 | private $connection; 25 | 26 | public function __construct(Connection $connection) 27 | { 28 | $this->connection = $connection; 29 | $this->connection->setFetchMode(\PDO::FETCH_OBJ); 30 | } 31 | 32 | public function findOpen(): array 33 | { 34 | $stmt = $this->connection->prepare( 35 | \sprintf( 36 | "SELECT * FROM %s where reminder < NOW() AND status = '%s'", 37 | Table::TODO_REMINDER, 38 | TodoReminderStatus::OPEN 39 | ) 40 | ); 41 | $stmt->execute(); 42 | 43 | return $stmt->fetchAll(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/Todo/TodoReminderProjection.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection\Todo; 14 | 15 | use Prooph\Bundle\EventStore\Projection\ReadModelProjection; 16 | use Prooph\EventStore\Projection\ReadModelProjector; 17 | use Prooph\ProophessorDo\Model\Todo\Event\ReminderWasAddedToTodo; 18 | use Prooph\ProophessorDo\Model\Todo\Event\TodoAssigneeWasReminded; 19 | 20 | final class TodoReminderProjection implements ReadModelProjection 21 | { 22 | public function project(ReadModelProjector $projector): ReadModelProjector 23 | { 24 | $projector 25 | ->fromStream('event_stream') 26 | ->when([ 27 | ReminderWasAddedToTodo::class => function ($state, ReminderWasAddedToTodo $event) { 28 | /** @var TodoReminderReadModel $readModel */ 29 | $readModel = $this->readModel(); 30 | $this->readModel()->stack('remove', [ 31 | 'todo_id' => $event->todoId()->toString(), 32 | ]); 33 | 34 | $reminder = $event->reminder(); 35 | 36 | /** @var TodoReminderReadModel $readModel */ 37 | $readModel->stack('insert', [ 38 | 'todo_id' => $event->todoId()->toString(), 39 | 'reminder' => $reminder->toString(), 40 | 'status' => $reminder->status()->toString(), 41 | ]); 42 | }, 43 | TodoAssigneeWasReminded::class => function ($state, TodoAssigneeWasReminded $event) { 44 | /** @var TodoReminderReadModel $readModel */ 45 | $readModel->stack( 46 | 'update', 47 | [ 48 | 'status' => $event->reminder()->status()->toString(), 49 | ], 50 | [ 51 | 'todo_id' => $event->todoId()->toString(), 52 | ] 53 | ); 54 | }, 55 | ]); 56 | 57 | return $projector; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/Todo/TodoReminderReadModel.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection\Todo; 14 | 15 | use Doctrine\DBAL\Connection; 16 | use Prooph\EventStore\Projection\AbstractReadModel; 17 | use Prooph\ProophessorDo\Projection\Table; 18 | 19 | final class TodoReminderReadModel extends AbstractReadModel 20 | { 21 | /** 22 | * @var Connection 23 | */ 24 | private $connection; 25 | 26 | public function __construct(Connection $connection) 27 | { 28 | $this->connection = $connection; 29 | } 30 | 31 | public function init(): void 32 | { 33 | $tableName = Table::TODO_REMINDER; 34 | 35 | $sql = <<connection->prepare($sql); 45 | $statement->execute(); 46 | } 47 | 48 | public function isInitialized(): bool 49 | { 50 | $tableName = Table::TODO_REMINDER; 51 | 52 | $sql = "SHOW TABLES LIKE '$tableName';"; 53 | 54 | $statement = $this->connection->prepare($sql); 55 | $statement->execute(); 56 | 57 | $result = $statement->fetch(); 58 | 59 | if (false === $result) { 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | public function reset(): void 67 | { 68 | $tableName = Table::TODO_REMINDER; 69 | 70 | $sql = "TRUNCATE TABLE '$tableName';"; 71 | 72 | $statement = $this->connection->prepare($sql); 73 | $statement->execute(); 74 | } 75 | 76 | public function delete(): void 77 | { 78 | $tableName = Table::TODO_REMINDER; 79 | 80 | $sql = "DROP TABLE $tableName;"; 81 | 82 | $statement = $this->connection->prepare($sql); 83 | $statement->execute(); 84 | } 85 | 86 | protected function insert(array $data): void 87 | { 88 | $this->connection->insert(Table::TODO_REMINDER, $data); 89 | } 90 | 91 | protected function update(array $data, array $identifier): void 92 | { 93 | $this->connection->update( 94 | Table::TODO, 95 | $data, 96 | $identifier 97 | ); 98 | } 99 | 100 | protected function remove(array $query): void 101 | { 102 | $this->connection->delete( 103 | Table::TODO_REMINDER, 104 | $query 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/User/UserFinder.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection\User; 14 | 15 | use Doctrine\DBAL\Connection; 16 | use Prooph\ProophessorDo\Projection\Table; 17 | 18 | class UserFinder 19 | { 20 | /** 21 | * @var Connection 22 | */ 23 | private $connection; 24 | 25 | public function __construct(Connection $connection) 26 | { 27 | $this->connection = $connection; 28 | $this->connection->setFetchMode(\PDO::FETCH_OBJ); 29 | } 30 | 31 | public function findAll(): array 32 | { 33 | return $this->connection->fetchAll(\sprintf('SELECT * FROM %s', Table::USER)); 34 | } 35 | 36 | public function findById(string $userId): ?\stdClass 37 | { 38 | $stmt = $this->connection->prepare(\sprintf('SELECT * FROM %s WHERE id = :user_id', Table::USER)); 39 | $stmt->bindValue('user_id', $userId); 40 | $stmt->execute(); 41 | 42 | $result = $stmt->fetch(); 43 | 44 | if (false === $result) { 45 | return null; 46 | } 47 | 48 | return $result; 49 | } 50 | 51 | public function findOneByEmailAddress(string $emailAddress): ?\stdClass 52 | { 53 | $stmt = $this->connection->prepare(\sprintf('SELECT * FROM %s WHERE email = :email LIMIT 1', Table::USER)); 54 | $stmt->bindValue('email', $emailAddress); 55 | $stmt->execute(); 56 | 57 | $result = $stmt->fetch(); 58 | 59 | if (false === $result) { 60 | return null; 61 | } 62 | 63 | return $result; 64 | } 65 | 66 | public function findUserOfTodo(string $todoId): ?\stdClass 67 | { 68 | $stmt = $this->connection->prepare(\sprintf( 69 | 'SELECT u.* FROM %s as u JOIN %s as t ON u.id = t.assignee_id WHERE t.id = :todo_id', 70 | Table::USER, 71 | Table::TODO 72 | )); 73 | $stmt->bindValue('todo_id', $todoId); 74 | $stmt->execute(); 75 | 76 | $result = $stmt->fetch(); 77 | 78 | if (false === $result) { 79 | return null; 80 | } 81 | 82 | return $result; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/ProophessorDo/Projection/User/UserProjection.php: -------------------------------------------------------------------------------- 1 | 5 | * (c) 2015-2017 Sascha-Oliver Prolic 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | declare(strict_types=1); 12 | 13 | namespace Prooph\ProophessorDo\Projection\User; 14 | 15 | use Prooph\Bundle\EventStore\Projection\ReadModelProjection; 16 | use Prooph\EventStore\Projection\ReadModelProjector; 17 | use Prooph\ProophessorDo\Model\Todo\Event\TodoWasMarkedAsDone; 18 | use Prooph\ProophessorDo\Model\Todo\Event\TodoWasMarkedAsExpired; 19 | use Prooph\ProophessorDo\Model\Todo\Event\TodoWasPosted; 20 | use Prooph\ProophessorDo\Model\Todo\Event\TodoWasReopened; 21 | use Prooph\ProophessorDo\Model\Todo\Event\TodoWasUnmarkedAsExpired; 22 | use Prooph\ProophessorDo\Model\User\Event\UserWasRegistered; 23 | 24 | /** 25 | * Class UserProjection 26 | * @package Prooph\ProophessorDo\Projection\User 27 | */ 28 | final class UserProjection implements ReadModelProjection 29 | { 30 | public function project(ReadModelProjector $projector): ReadModelProjector 31 | { 32 | $projector->fromStream('event_stream') 33 | ->when([ 34 | UserWasRegistered::class => function ($state, UserWasRegistered $event) { 35 | /** @var UserReadModel $readModel */ 36 | $readModel = $this->readModel(); 37 | $readModel->stack('insert', [ 38 | 'id' => $event->userId()->toString(), 39 | 'name' => $event->name()->toString(), 40 | 'email' => $event->emailAddress()->toString(), 41 | ]); 42 | }, 43 | TodoWasPosted::class => function ($state, TodoWasPosted $event) { 44 | /** @var UserReadModel $readModel */ 45 | $readModel = $this->readModel(); 46 | $readModel->stack('postTodo', $event->assigneeId()->toString()); 47 | }, 48 | TodoWasMarkedAsDone::class => function ($state, TodoWasMarkedAsDone $event) { 49 | /** @var UserReadModel $readModel */ 50 | $readModel = $this->readModel(); 51 | $readModel->stack('markTodoAsDone', $event->assigneeId()->toString()); 52 | }, 53 | TodoWasReopened::class => function ($state, TodoWasReopened $event) { 54 | /** @var UserReadModel $readModel */ 55 | $readModel = $this->readModel(); 56 | $readModel->stack('reopenTodo', $event->assigneeId()->toString()); 57 | }, 58 | TodoWasMarkedAsExpired::class => function ($state, TodoWasMarkedAsExpired $event) { 59 | /** @var UserReadModel $readModel */ 60 | $readModel = $this->readModel(); 61 | $readModel->stack('markTodoAsExpired', $event->assigneeId()->toString()); 62 | }, 63 | TodoWasUnmarkedAsExpired::class => function ($state, TodoWasUnmarkedAsExpired $event) { 64 | /** @var UserReadModel $readModel */ 65 | $readModel = $this->readModel(); 66 | $readModel->stack('unmarkTodoAsExpired', $event->assigneeId()->toString()); 67 | }, 68 | ]); 69 | 70 | return $projector; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Repository/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/src/Repository/.gitignore -------------------------------------------------------------------------------- /templates/base.html.twig: -------------------------------------------------------------------------------- 1 | !DOCTYPE html> 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 | -------------------------------------------------------------------------------- /templates/default/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}proophessor-do-symfony!{% endblock %} 6 | 7 | {% block stylesheets %} 8 | 9 | 10 | 11 | {% endblock %} 12 | 13 | 14 | 31 |
32 | 50 |
51 | {% block content %}{% endblock %} 52 |
53 |
54 |

© 2014 - {{ "now"|date("Y") }} by prooph software GmbH. All rights reserved.

55 |
56 |
57 |
58 | {% if sidebar_right is defined %}{{ sidebar_right|raw }}{% endif %} 59 |
60 |
61 | {% block javascripts %} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {% endblock %} 75 | {% block page_js %} 76 | {% endblock %} 77 | 78 | 79 | -------------------------------------------------------------------------------- /templates/default/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "default/base.html.twig" %} 2 | 3 | {% block content %} 4 |
5 |

Welcome to proophessor-do-symfony

6 |
7 |
8 |
9 |

Powered by Symfony and prooph components

10 |

11 | This is a simple Todo application showing you the integration of prooph components into a Symfony web application. 12 |

13 |

Database is ready?

14 |

Make sure you've configured the database connection and triggered the migrations!

15 |

About The Domain

16 |

17 | The domain of our Todo example application is very small. Two aggregates are responsible for handing the business logic: 18 |

19 |
    20 |
  • The first one is the User aggregate. Its task is to post new Todos of the user on a todo list.
  • 21 |
  • The second one is the Todo aggregate which manages the state of a Todo.
  • 22 |
  • A Todo is just a line of text describing an open task for the user who owns the Todo.
  • 23 |
  • A Todo can be marked as done
  • 24 |
  • A Todo can be reopened
  • 25 |
  • A Todo can have a Deadline
  • 26 |
  • A User can add Reminders to get notified about the open Todo.
  • 27 |
  • A User gets notified via email.
  • 28 |
29 |
30 |
31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /templates/default/riot-user-form.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |

Username should not be empty!

6 |
7 |
8 | 9 | 10 |

This is not a valid email address

11 |

The email is used to send you reminders for your Todos.

12 |
13 |
14 | 15 | cancel 16 |
17 |
18 | -------------------------------------------------------------------------------- /templates/default/riot-user-todo-form.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

Oops, that didn't make it. Typed to fast? The Todo should be at least 3 characters long. Please try again!

5 |

The Todo will be assigned to { userName }

6 |
7 | 8 |
9 | 49 | -------------------------------------------------------------------------------- /templates/default/riot-user-todo-list.html.twig: -------------------------------------------------------------------------------- 1 |
2 |

3 | The Todo list looks empty. Do you want to create a 4 | new Todo? 5 |

6 |
7 |
8 | new Todo 9 |
10 |
11 |

12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 31 | -------------------------------------------------------------------------------- /templates/default/user-list.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "default/base.html.twig" %} 2 | {% block content %} 3 |

Registered Users

4 |
5 |
6 | {% if users is empty %} 7 |
8 | No user registered yet. Do you want to add yourself? 9 |
10 | {% else %} 11 | 21 | {% endif %} 22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /templates/default/user-registration-form.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "default/base.html.twig" %} 2 | {% block content %} 3 |

Register a new User

4 |
5 |
6 | 7 |
8 |
9 | {% endblock %} 10 | {% block page_js %} 11 | 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /templates/default/user-todo-form.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "default/base.html.twig" %} 2 | {% block content %} 3 | 4 | {% if invalidUser %} 5 |

Something went wrong! We can't find the user who should be responsible for the new Todo.

6 |

7 | Please try again by selecting a user from the user list! 8 |

9 | {% else %} 10 |

Add a new Todo

11 | 12 | {% endif %} 13 | 14 | {% endblock %} 15 | {% block page_js %} 16 | {% if not invalidUser %} 17 | 29 | {% endif %} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /templates/default/user-todo-list.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "default/base.html.twig" %} 2 | {% block content %} 3 | {% if user is empty %} 4 |

User cannot be found!

5 |

6 | If you're coming from the registration process you can try to refresh the page. Maybe the operation took a bit longer 7 | than normal. If your data still isn't available please contact the support. 8 |

9 | {% else %} 10 |

Todos assigned to {{ user.name }}

11 |
12 | 13 |
14 | {% endif %} 15 | 16 | {% endblock %} 17 | {% block page_js %} 18 | {% if user is not empty %} 19 | 36 | {% endif %} 37 | {% endblock %} 38 | 39 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prooph/proophessor-do-symfony/07bb31d031359c806aa9f60ba9d7c609989a631a/tests/.gitignore --------------------------------------------------------------------------------