diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7d2568 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/*.ide +/vendors diff --git a/README.md b/README.md index 4d6c6d4..859531b 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,25 @@ More examples will be posted in my blog: #### Immutable * https://github.com/matthiasnoback/convenient-immutability @matthiasnoback + +## Use case example + +Domain requirements: + +Mr. Smith is the proud owner of the restaurant Smith's Pizzeria. He wish to have a site to manage it's company and promote it's products on the Web. Here are the requirement he wishes you to implement. + +* The owner is the only employee granted to create new or delete recipes. +* Recipes have ingredients that can be categorized with allergens +* a cashier takes the orders of phone, drive through or take out customers +* a delivery boy (or girl) delivers prepared meals to the customers who ordered by phone +* a waitress takes orders to seated clients +* waitress serve the meals to in house customers. +* performance bonus are alloted to each trimester based on waiting time of customers. Lower it is greater the bonus. +* bonus are calculated according to the following chart, based on type of client and average time to serve clients (todo) +* recipes are not available to customers as long as they have not been released. +* recipes can be back ordered which means that no customers can order them. +* retired recipes no longer appears on the menu, they have been removed from production. +* front desk, drive through and in house customers do not keep order history, they are considered anonymous customer, while Web and phone customers are identified with their phone number to track their orders. +* delivery boy pickup the customer order at an hour, and are expected to deliver it in order of priority based on ordered at time. +* Waitress should serve all meals of the customer table at the same time, but for bonus reason, the clock start ticking when the first table customer order is entered to the last meal served +* cashier make the customer pay on order, while delivery boy make them pay when order delivered, and waitress make them pay at end of diner. diff --git a/behat.yml b/behat.yml new file mode 100644 index 0000000..5cf65d3 --- /dev/null +++ b/behat.yml @@ -0,0 +1,7 @@ +default: + suites: + admin_features: + paths: [ %paths.base%/features ] + contexts: [ Example\ApplicationContext ] + filters: + role: owner diff --git a/composer.json b/composer.json index bedee7e..ec8c01e 100644 --- a/composer.json +++ b/composer.json @@ -1,20 +1,32 @@ { - "name": "vendor_name/package_name", - "description": "description_text", + "name": "php-ddd/php-ddd", + "description": "Example of structure and implementation of DDD in php with Symfony and Doctrine", "minimum-stability": "stable", - "license": "proprietary", + "license": "MIT", "authors": [ { - "name": "author's name", + "name": "", "email": "email@example.com" } ], "require": { - "phpunit/phpunit": "^5.2" + "php": "^5.5", + "behat/behat": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" }, "autoload": { "psr-4": { - "Example\\": ["src", "tests"] + "Example\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "Example\\": ["tests", "features/bootstrap"] + } + }, + "config": { + "bin-dir": "bin" } } diff --git a/composer.lock b/composer.lock index 916fa3f..f8f71e7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,698 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "3edc21681cf1c8df008a7cd291994294", - "content-hash": "263f3cdae3f404aafeb5af56fbe8f819", + "hash": "fc0fb8199f76b78509ebd4d1d598b891", + "content-hash": "2438ca6b9e4accb0c68d911d0040f6c2", "packages": [ + { + "name": "behat/behat", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "359d987b3064d78f2d3a6ba3a355277f3b09b47f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/359d987b3064d78f2d3a6ba3a355277f3b09b47f", + "reference": "359d987b3064d78f2d3a6ba3a355277f3b09b47f", + "shasum": "" + }, + "require": { + "behat/gherkin": "~4.4", + "behat/transliterator": "~1.0", + "ext-mbstring": "*", + "php": ">=5.3.3", + "symfony/class-loader": "~2.1|~3.0", + "symfony/config": "~2.3|~3.0", + "symfony/console": "~2.1|~3.0", + "symfony/dependency-injection": "~2.1|~3.0", + "symfony/event-dispatcher": "~2.1|~3.0", + "symfony/translation": "~2.3|~3.0", + "symfony/yaml": "~2.1|~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "symfony/process": "~2.1|~3.0" + }, + "suggest": { + "behat/mink-extension": "for integration with Mink testing framework", + "behat/symfony2-extension": "for integration with Symfony2 web framework", + "behat/yii-extension": "for integration with Yii web framework" + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Behat": "src/", + "Behat\\Testwork": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Scenario-oriented BDD framework for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "Agile", + "BDD", + "ScenarioBDD", + "Scrum", + "StoryBDD", + "User story", + "business", + "development", + "documentation", + "examples", + "symfony", + "testing" + ], + "time": "2016-03-28 07:04:45" + }, + { + "name": "behat/gherkin", + "version": "v4.4.1", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", + "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "symfony/yaml": "~2.1" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2015-12-30 14:47:00" + }, + { + "name": "behat/transliterator", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "868e05be3a9f25ba6424c2dd4849567f50715003" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003", + "reference": "868e05be3a9f25ba6424c2dd4849567f50715003", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Transliterator": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "time": "2015-09-28 16:26:35" + }, + { + "name": "symfony/class-loader", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877", + "reference": "cbb7e6a9c0213a0cffa5d9065ee8214ca4e83877", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2016-03-30 10:41:14" + }, + { + "name": "symfony/config", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "980ee40c28f00acff8906c11b778aab5f0db74c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/980ee40c28f00acff8906c11b778aab5f0db74c2", + "reference": "980ee40c28f00acff8906c11b778aab5f0db74c2", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2016-03-04 07:55:57" + }, + { + "name": "symfony/console", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6b1175135bc2a74c08a28d89761272de8beed8cd", + "reference": "6b1175135bc2a74c08a28d89761272de8beed8cd", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2016-03-16 17:00:50" + }, + { + "name": "symfony/dependency-injection", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "6a9058101b591edced21ca3c83c80a3978f5c6b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6a9058101b591edced21ca3c83c80a3978f5c6b0", + "reference": "6a9058101b591edced21ca3c83c80a3978f5c6b0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "symfony/config": "", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2016-03-30 10:41:14" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9002dcf018d884d294b1ef20a6f968efc1128f39", + "reference": "9002dcf018d884d294b1ef20a6f968efc1128f39", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2016-03-10 10:34:12" + }, + { + "name": "symfony/filesystem", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "f82499a459dcade2ea56df94cc58b19c8bde3d20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/f82499a459dcade2ea56df94cc58b19c8bde3d20", + "reference": "f82499a459dcade2ea56df94cc58b19c8bde3d20", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2016-03-27 10:24:39" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "1289d16209491b584839022f29257ad859b8532d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d", + "reference": "1289d16209491b584839022f29257ad859b8532d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/translation", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/f7a07af51ea067745a521dab1e3152044a2fb1f2", + "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2016-03-25 01:41:20" + }, + { + "name": "symfony/yaml", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "0047c8366744a16de7516622c5b7355336afae96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96", + "reference": "0047c8366744a16de7516622c5b7355336afae96", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-03-04 07:55:57" + } + ], + "packages-dev": [ { "name": "doctrine/instantiator", "version": "1.0.5", @@ -61,48 +750,6 @@ ], "time": "2015-06-14 21:17:01" }, - { - "name": "myclabs/deep-copy", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e3abefcd7f106677fd352cd7c187d6c969aa9ddc", - "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2015-11-07 22:20:37" - }, { "name": "phpdocumentor/reflection-docblock", "version": "2.0.4", @@ -216,40 +863,39 @@ }, { "name": "phpunit/php-code-coverage", - "version": "3.3.1", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2431befdd451fac43fbcde94d1a92fb3b8b68f86" + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2431befdd451fac43fbcde94d1a92fb3b8b68f86", - "reference": "2431befdd451fac43fbcde94d1a92fb3b8b68f86", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", + "php": ">=5.3.3", "phpunit/php-file-iterator": "~1.3", "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", + "phpunit/php-token-stream": "~1.3", "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0|~2.0" + "sebastian/version": "~1.0" }, "require-dev": { "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~5" + "phpunit/phpunit": "~4" }, "suggest": { "ext-dom": "*", - "ext-xdebug": ">=2.4.0", + "ext-xdebug": ">=2.2.1", "ext-xmlwriter": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3.x-dev" + "dev-master": "2.2.x-dev" } }, "autoload": { @@ -275,7 +921,7 @@ "testing", "xunit" ], - "time": "2016-04-08 08:14:53" + "time": "2015-10-06 15:47:00" }, { "name": "phpunit/php-file-iterator", @@ -457,16 +1103,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.3.2", + "version": "4.8.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2c6da3536035617bae3fe3db37283c9e0eb63ab3" + "reference": "a1066c562c52900a142a0e2bbf0582994671385e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2c6da3536035617bae3fe3db37283c9e0eb63ab3", - "reference": "2c6da3536035617bae3fe3db37283c9e0eb63ab3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e", + "reference": "a1066c562c52900a142a0e2bbf0582994671385e", "shasum": "" }, "require": { @@ -475,22 +1121,19 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-spl": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", + "php": ">=5.3.3", "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^3.3.0", + "phpunit/php-code-coverage": "~2.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.1", + "phpunit/php-timer": ">=1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", "sebastian/comparator": "~1.1", "sebastian/diff": "~1.2", "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", - "sebastian/object-enumerator": "~1.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", + "sebastian/version": "~1.0", "symfony/yaml": "~2.1|~3.0" }, "suggest": { @@ -502,7 +1145,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "4.8.x-dev" } }, "autoload": { @@ -528,30 +1171,30 @@ "testing", "xunit" ], - "time": "2016-04-12 16:20:08" + "time": "2016-03-14 06:16:08" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.1.3", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "151c96874bff6fe61a25039df60e776613a61489" + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/151c96874bff6fe61a25039df60e776613a61489", - "reference": "151c96874bff6fe61a25039df60e776613a61489", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", - "php": ">=5.6", + "php": ">=5.3.3", "phpunit/php-text-template": "~1.2", "sebastian/exporter": "~1.2" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "~4.4" }, "suggest": { "ext-soap": "*" @@ -559,7 +1202,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "2.3.x-dev" } }, "autoload": { @@ -584,52 +1227,7 @@ "mock", "xunit" ], - "time": "2016-04-20 14:39:26" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13 06:45:14" + "time": "2015-10-02 06:51:40" }, { "name": "sebastian/comparator", @@ -914,52 +1512,6 @@ ], "time": "2015-10-12 03:26:01" }, - { - "name": "sebastian/object-enumerator", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", - "shasum": "" - }, - "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-01-28 13:25:10" - }, { "name": "sebastian/recursion-context", "version": "1.0.2", @@ -1013,71 +1565,21 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "time": "2015-11-11 19:50:13" }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" - }, { "name": "sebastian/version", - "version": "2.0.0", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", "shasum": "" }, - "require": { - "php": ">=5.6" - }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "classmap": [ "src/" @@ -1096,64 +1598,16 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-02-04 12:56:52" - }, - { - "name": "symfony/yaml", - "version": "v3.0.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "0047c8366744a16de7516622c5b7355336afae96" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96", - "reference": "0047c8366744a16de7516622c5b7355336afae96", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2016-03-04 07:55:57" + "time": "2015-06-21 13:59:46" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "php": "^5.5" + }, "platform-dev": [] } diff --git a/features/bootstrap/ApplicationContext.php b/features/bootstrap/ApplicationContext.php new file mode 100644 index 0000000..cf24db9 --- /dev/null +++ b/features/bootstrap/ApplicationContext.php @@ -0,0 +1,129 @@ +owners = new OwnerCollection(); + $this->administrationService = new AdministrationService($this->owners, $publisher); + + // Sale context + $this->employees = new EmployeeCollection(); + new CookService($this->employees, $publisher); + new CashierService($this->employees, $publisher); + new WaitressService($this->employees, $publisher); + + // Shipping context + new CreateDeliveryBoyHandler($this->employees, $publisher); + } + + /** + * @Given :name is the store owner + */ + public function isTheStoreOwner($name) + { + $this->owners->saveOwner( + new Owner(new OwnerId(FullName::fromSingleString($name)), FullName::fromSingleString($name)) + ); + } + + /** + * @Given :ownerName already employs :employeeName as :role + */ + public function alreadyEmploysAs($ownerName, $employeeName, $role) + { + $this->owners->ownerWithId(new OwnerId(FullName::fromSingleString($ownerName))) + ->hire(FullName::fromSingleString($employeeName), JobTitle::fromString($role)); + } + + /** + * @Given :applicantName postulate on a job offering + */ + public function postulateOnAJobOffering($applicantName) + { + } + + /** + * @When :ownerName hires :applicantName as the new :role + */ + public function hiresAsTheNew($ownerName, $applicantName, $role) + { + $this->administrationService->hireCandidate( + new HireCandidateCommand( + new OwnerId(FullName::fromSingleString($ownerName)), + FullName::fromSingleString($applicantName), + JobTitle::fromString($role) + ) + ); + } + + /** + * @Then :ownerName should have :employeeCount employees + */ + public function shouldHaveEmployees($ownerName, $employeeCount) + { + $owner = $this->owners->ownerWithId(new OwnerId(FullName::fromSingleString($ownerName))); + + Assert::assertAttributeCount((int) $employeeCount, 'candidates', $owner); + } + + /** + * @Then There should be :employeeCount employees with :role title + */ + public function thereShouldBeEmployeesWithTitle($employeeCount, $role) + { + Assert::assertCount( + (int) $employeeCount, $this->employees->employeesWithTitle(JobTitle::fromString($role)) + ); + } +} diff --git a/features/manage-store.feature b/features/manage-store.feature new file mode 100644 index 0000000..0f947ce --- /dev/null +++ b/features/manage-store.feature @@ -0,0 +1,42 @@ +Feature: + In order to help me make money + As a owner + I need to manage the store + + Background: + Given 'John' is the store owner + And 'John' already employs 'Jake' as 'delivery boy' + And 'John' already employs 'Judy' as 'waitress' + And 'John' already employs 'Mark' as 'cashier' + + Scenario: Hiring new cashier to help customer pay for meals + Given 'Jane' postulate on a job offering + When 'John' hires 'Jane' as the new 'cashier' + Then 'John' should have 4 employees + And There should be 2 employees with 'cashier' title + # todo cashier receives payment from customer for order + # todo Should be in sale context + + Scenario: Hiring new cook to cook meals + Given 'Luke' postulate on a job offering + When 'John' hires 'Luke' as the new 'cook' + Then 'John' should have 4 employees + And There should be 1 employees with 'cook' title + # todo cook prepare order of customer + # todo Should be in sale context + + Scenario: Hiring new waitress to take order from customer + Given 'Leia' postulate on a job offering + When 'John' hires 'Leia' as the new 'waitress' + Then 'John' should have 4 employees + And There should be 2 employees with 'waitress' title + # todo waitress take order from customer (create order) + # todo Should be in sale context + + Scenario: Hiring new delivery boy to deliver meals + Given 'Han' postulate on a job offering + When 'John' hires 'Han' as the new 'delivery boy' + Then 'John' should have 4 employees + And There should be 2 employees with 'delivery boy' title + # todo delivery boy deliver meal to customer + # todo Should be in shipping context diff --git a/src/ApplicationBundle/Controller/CalculationController.php b/src/ApplicationBundle/Controller/CalculationController.php deleted file mode 100644 index d21287d..0000000 --- a/src/ApplicationBundle/Controller/CalculationController.php +++ /dev/null @@ -1,49 +0,0 @@ -handler = $handler; - } - - /** - * @Route("/calculation/{type}") - * @Template() - */ - public function indexAction($type) - { - $command = $this->getFormData($type); - - $this->handler->handle($command); // todo would probably be the command bus - // Get ID from entity and redirect, no return values required - } - - /** - * @param string $type - * - * @return CalculationCommand - */ - private function getFormData($type) - { - // Form handling generates the configured command object - $command = new CalculationCommand(); - $command->quantity = 12; - $command->subTotal = 1000; - - // Form would configure the command with values - return $command; - } -} diff --git a/src/Domain/Administration/Application/AdministrationService.php b/src/Domain/Administration/Application/AdministrationService.php new file mode 100644 index 0000000..f7aa8b2 --- /dev/null +++ b/src/Domain/Administration/Application/AdministrationService.php @@ -0,0 +1,48 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Administration\Application; + +use Example\Domain\Administration\DomainModel\CandidateRepository; +use Example\Domain\Administration\DomainModel\OwnerRepository; +use Example\Domain\Common\Application\EventPublisher; + +final class AdministrationService +{ + /** + * @var OwnerRepository + */ + private $ownerRepository; + + /** + * @var EventPublisher + */ + private $publisher; + + /** + * @param OwnerRepository $ownerRepository + * @param EventPublisher $publisher + */ + public function __construct(OwnerRepository $ownerRepository, EventPublisher $publisher) + { + $this->ownerRepository = $ownerRepository; + $this->publisher = $publisher; + } + + /** + * @param HireCandidateCommand $command + */ + public function hireCandidate(HireCandidateCommand $command) + { + $owner = $this->ownerRepository->ownerWithId($command->hiredBy()); + $owner->hire($command->candidateName(), $command->title()); + + $this->ownerRepository->saveOwner($owner); + + $this->publisher->publish($owner->uncommitedEvents()); + } +} diff --git a/src/Domain/Administration/Application/HireCandidateCommand.php b/src/Domain/Administration/Application/HireCandidateCommand.php new file mode 100644 index 0000000..3c7a664 --- /dev/null +++ b/src/Domain/Administration/Application/HireCandidateCommand.php @@ -0,0 +1,66 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Administration\Application; + +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Administration\DomainModel\Identity\OwnerId; +use Example\Domain\Common\DomainModel\JobTitle; + +final class HireCandidateCommand +{ + /** + * @var OwnerId + */ + private $hiredBy; + + /** + * @var FullName + */ + private $candidateName; + + /** + * @var JobTitle + */ + private $title; + + /** + * @param OwnerId $hiredBy + * @param FullName $candidateName + * @param JobTitle $title + */ + public function __construct(OwnerId $hiredBy, FullName $candidateName, JobTitle $title) + { + $this->hiredBy = $hiredBy; + $this->candidateName = $candidateName; + $this->title = $title; + } + + /** + * @return FullName + */ + public function candidateName() + { + return $this->candidateName; + } + + /** + * @return OwnerId + */ + public function hiredBy() + { + return $this->hiredBy; + } + + /** + * @return JobTitle + */ + public function title() + { + return $this->title; + } +} diff --git a/src/Domain/Administration/DomainModel/Candidate.php b/src/Domain/Administration/DomainModel/Candidate.php new file mode 100644 index 0000000..faeb130 --- /dev/null +++ b/src/Domain/Administration/DomainModel/Candidate.php @@ -0,0 +1,35 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Administration\DomainModel; + +use Example\Domain\Administration\DomainModel\Identity\AdministrationIdGenerator; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; + +final class Candidate +{ + /** + * @var FullName + */ + private $name; + + /** + * @var JobTitle + */ + private $title; + + /** + * @param FullName $name + * @param JobTitle $title + */ + public function __construct(FullName $name, JobTitle $title) + { + $this->name = $name; + $this->title = $title; + } +} diff --git a/src/Domain/Administration/DomainModel/Event/CandidateWasHired.php b/src/Domain/Administration/DomainModel/Event/CandidateWasHired.php new file mode 100644 index 0000000..4a8dacb --- /dev/null +++ b/src/Domain/Administration/DomainModel/Event/CandidateWasHired.php @@ -0,0 +1,67 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Administration\DomainModel\Event; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Administration\DomainModel\Identity\OwnerId; +use Example\Domain\Common\DomainModel\JobTitle; + +final class CandidateWasHired implements DomainEvent +{ + /** + * @var OwnerId + */ + private $hiredBy; + + /** + * @var FullName + */ + private $candidateName; + + /** + * @var JobTitle + */ + private $title; + + /** + * @param OwnerId $hiredBy + * @param FullName $name + * @param JobTitle $title + */ + public function __construct(OwnerId $hiredBy, FullName $name, JobTitle $title) + { + $this->hiredBy = $hiredBy; + $this->candidateName = $name; + $this->title = $title; + } + + /** + * @return FullName + */ + public function candidateName() + { + return $this->candidateName; + } + + /** + * @return OwnerId + */ + public function hiredBy() + { + return $this->hiredBy; + } + + /** + * @return JobTitle + */ + public function title() + { + return $this->title; + } +} diff --git a/src/Domain/Administration/DomainModel/Identity/OwnerId.php b/src/Domain/Administration/DomainModel/Identity/OwnerId.php new file mode 100644 index 0000000..6aafbe6 --- /dev/null +++ b/src/Domain/Administration/DomainModel/Identity/OwnerId.php @@ -0,0 +1,44 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Administration\DomainModel\Identity; + +use Example\Domain\Administration\DomainModel\Owner; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\Identity\Identity; + +final class OwnerId implements Identity +{ + /** + * @var FullName + */ + private $name; + + /** + * @param FullName $name + */ + public function __construct(FullName $name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getEntityClass() + { + return Owner::class; + } + + /** + * @return mixed + */ + public function id() + { + return $this->name->toString(); + } +} diff --git a/src/Domain/Administration/DomainModel/Owner.php b/src/Domain/Administration/DomainModel/Owner.php new file mode 100644 index 0000000..814ef0a --- /dev/null +++ b/src/Domain/Administration/DomainModel/Owner.php @@ -0,0 +1,65 @@ +identity = $id; + } + + /** + * @return OwnerId + */ + public function getIdentity() + { + return $this->identity; + } + + /** + * @param FullName $name + * @param JobTitle $title + */ + public function hire(FullName $name, JobTitle $title) + { + $this->mutate(new CandidateWasHired($this->identity, $name, $title)); + } + + /** + * @param CandidateWasHired $event + */ + protected function onCandidateWasHired(CandidateWasHired $event) + { + $this->candidates[] = new Candidate($event->candidateName(), $event->title()); + } + + /** + * @param string $id + * + * @return Owner + */ + public static function fakeWithId($id) + { + return new self(new OwnerId(FullName::fromSingleString($id))); + } +} diff --git a/src/Domain/Administration/DomainModel/OwnerRepository.php b/src/Domain/Administration/DomainModel/OwnerRepository.php new file mode 100644 index 0000000..7f74f92 --- /dev/null +++ b/src/Domain/Administration/DomainModel/OwnerRepository.php @@ -0,0 +1,25 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Administration\DomainModel; + +use Example\Domain\Administration\DomainModel\Identity\OwnerId; + +interface OwnerRepository +{ + /** + * @param OwnerId $ownerId + * + * @return Owner + */ + public function ownerWithId(OwnerId $ownerId); + + /** + * @param Owner $owner + */ + public function saveOwner(Owner $owner); +} diff --git a/src/Domain/Common/Application/CommandHandler.php b/src/Domain/Common/Application/CommandHandler.php new file mode 100644 index 0000000..04c011d --- /dev/null +++ b/src/Domain/Common/Application/CommandHandler.php @@ -0,0 +1,12 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\Application; + +interface CommandHandler +{ +} diff --git a/src/Domain/Common/Application/DomainCommand.php b/src/Domain/Common/Application/DomainCommand.php new file mode 100644 index 0000000..f9b1498 --- /dev/null +++ b/src/Domain/Common/Application/DomainCommand.php @@ -0,0 +1,12 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\Application; + +interface DomainCommand +{ +} diff --git a/src/Domain/Common/Application/EventPublisher.php b/src/Domain/Common/Application/EventPublisher.php new file mode 100644 index 0000000..26637b4 --- /dev/null +++ b/src/Domain/Common/Application/EventPublisher.php @@ -0,0 +1,25 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\Application; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; + +interface EventPublisher +{ + /** + * @param DomainEvent[] $events + */ + public function publish(array $events); + + /** + * @param string $eventClassName The class full name of the event + * @param callable $listener An object that listens on events + * @param string $method + */ + public function addListener($eventClassName, $listener, $method); +} diff --git a/src/Domain/Common/DomainModel/AggregateRoot.php b/src/Domain/Common/DomainModel/AggregateRoot.php new file mode 100644 index 0000000..b78a6e4 --- /dev/null +++ b/src/Domain/Common/DomainModel/AggregateRoot.php @@ -0,0 +1,58 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\DomainModel; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; + +abstract class AggregateRoot +{ + /** + * @var DomainEvent[] + */ + private $mutations = []; + + /** + * @return DomainEvent[] + */ + public function uncommitedEvents() { + $mutations = $this->mutations; + $this->mutations = []; + + return $mutations; + } + + /** + * @param DomainEvent $event + * @throws \RuntimeException + */ + protected function mutate(DomainEvent $event) + { + $method = $this->getEventMethod($event); + if (! method_exists($this, $method)) { + throw new \RuntimeException("The mutation '{$method}' do not exists."); + } + + $this->$method($event); + $this->mutations[] = $event; + } + + /** + * @param DomainEvent $event + * + * @return string + */ + private function getEventMethod(DomainEvent $event) + { + $class = get_class($event); + $parts = explode('\\', $class); + $name = array_pop($parts); + $method = 'on' . $name; + + return $method; + } +} diff --git a/src/Domain/Common/DomainModel/Event/DomainEvent.php b/src/Domain/Common/DomainModel/Event/DomainEvent.php new file mode 100644 index 0000000..98efadd --- /dev/null +++ b/src/Domain/Common/DomainModel/Event/DomainEvent.php @@ -0,0 +1,12 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\DomainModel\Event; + +interface DomainEvent +{ +} diff --git a/src/Domain/Common/DomainModel/FullName.php b/src/Domain/Common/DomainModel/FullName.php new file mode 100644 index 0000000..303396f --- /dev/null +++ b/src/Domain/Common/DomainModel/FullName.php @@ -0,0 +1,86 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\DomainModel; + +final class FullName +{ + /** + * @var string + */ + private $firstName; + + /** + * @var string + */ + private $lastName; + + /** + * @param string $firstName + * @param string $lastName + */ + private function __construct($firstName, $lastName) + { + $this->firstName = $firstName; + $this->lastName = $lastName; + } + + /** + * @return string + */ + public function firstName() + { + return $this->firstName; + } + + /** + * @return string + */ + public function lastName() + { + return $this->lastName; + } + + /** + * @param FullName $name + * + * @return bool + */ + public function equal(FullName $name) + { + return $name->firstName === $this->firstName && $name->lastName === $this->lastName; + } + + /** + * @return string + */ + public function toString() + { + return '[FullName] ' . $this->firstName . ' ' . $this->lastName; + } + + /** + * @param string $firstName + * @param string $lastName + * + * @return FullName + */ + public static function fromString($firstName, $lastName) + { + return new self($firstName, $lastName); + } + + /** + * @param string $name + * + * @return FullName + */ + public static function fromSingleString($name) + { + return new self('Mr/Miss', $name); + } +} diff --git a/src/Domain/Common/DomainModel/Identity/Identity.php b/src/Domain/Common/DomainModel/Identity/Identity.php new file mode 100644 index 0000000..fbaf06b --- /dev/null +++ b/src/Domain/Common/DomainModel/Identity/Identity.php @@ -0,0 +1,21 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\DomainModel\Identity; + +interface Identity +{ + /** + * @return string + */ + public function getEntityClass(); + + /** + * @return mixed + */ + public function id(); +} diff --git a/src/Domain/Common/DomainModel/JobTitle.php b/src/Domain/Common/DomainModel/JobTitle.php new file mode 100644 index 0000000..a670b8d --- /dev/null +++ b/src/Domain/Common/DomainModel/JobTitle.php @@ -0,0 +1,101 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\DomainModel; + +use Behat\Transliterator\Transliterator; +use Example\Domain\Common\Exception\InvalidArgumentException; + +final class JobTitle +{ + /** + * @var string + */ + private $title; + + /** + * @param string $title + */ + private function __construct($title) + { + $this->title = $title; + } + + /** + * @param JobTitle $title + * + * @return bool + */ + public function equal(JobTitle $title) + { + return $title->title === $this->title; + } + + /** + * @return string + */ + public function title() + { + return $this->title; + } + + /** + * @param string $title + * + * @throws InvalidArgumentException + * @return JobTitle + */ + public static function fromString($title) + { + $title = str_replace('-', '', Transliterator::transliterate($title)); + if (! method_exists(self::class, $title)) { + throw InvalidArgumentException::invalidJobTitle($title); + } + + return self::$title(); + } + + /** + * @return JobTitle + */ + public static function Cashier() + { + return new self('Cashier'); + } + + /** + * @return JobTitle + */ + public static function DeliveryBoy() + { + return new self('DeliveryBoy'); + } + + /** + * @return JobTitle + */ + public static function Waitress() + { + return new self('Waitress'); + } + + /** + * @return JobTitle + */ + public static function Cook() + { + return new self('Cook'); + } + + /** + * @return JobTitle + */ + private static function Fake() + { + return new self('fake'); + } +} diff --git a/src/Domain/Common/Exception/CommandHandlerException.php b/src/Domain/Common/Exception/CommandHandlerException.php new file mode 100644 index 0000000..d8a2ae7 --- /dev/null +++ b/src/Domain/Common/Exception/CommandHandlerException.php @@ -0,0 +1,28 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\Exception; + +use Example\Domain\Common\Application\CommandHandler; +use Example\Domain\Common\Application\DomainCommand; + +final class CommandHandlerException extends \Exception +{ + /** + * @param DomainCommand $command + * @param CommandHandler $handler + * + * @return CommandHandlerException + */ + public static function unsupportedCommandException(DomainCommand $command, CommandHandler $handler) + { + $commandClass = get_class($command); + $handlerClass = get_class($handler); + + return new self("The command handler '{$handlerClass}' do not support the command '{$commandClass}'."); + } +} diff --git a/src/Domain/Common/Exception/EntityNotFoundException.php b/src/Domain/Common/Exception/EntityNotFoundException.php new file mode 100644 index 0000000..958e8dc --- /dev/null +++ b/src/Domain/Common/Exception/EntityNotFoundException.php @@ -0,0 +1,38 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\Exception; + +use Example\Domain\Common\DomainModel\Identity\Identity; + +final class EntityNotFoundException extends \Exception +{ + /** + * @param Identity $identity + * + * @return EntityNotFoundException + */ + public static function entityWithIdentity(Identity $identity) + { + return new self( + "The entity of type '{$identity->getEntityClass()}' with identity '{$identity->id()}' could not be found." + ); + } + + /** + * @param string $class + * @param string|object $value + * + * @return EntityNotFoundException + */ + public static function entityWithValue($class, $value) + { + return new self( + "The entity of type '{$class}' with value '{$value}' could not be found." + ); + } +} diff --git a/src/Domain/Common/Exception/InvalidArgumentException.php b/src/Domain/Common/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..ec78319 --- /dev/null +++ b/src/Domain/Common/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Common\Exception; + +final class InvalidArgumentException extends \Exception +{ + /** + * @param string $title + * + * @return InvalidArgumentException + */ + public static function invalidJobTitle($title) + { + return new self("The job title '{$title}' is not supported."); + } +} diff --git a/src/Domain/DormerIdentityGenerator.php b/src/Domain/DormerIdentityGenerator.php deleted file mode 100644 index 38235af..0000000 --- a/src/Domain/DormerIdentityGenerator.php +++ /dev/null @@ -1,13 +0,0 @@ -id = $id->id(); - } - - /** - * @return CalculationId - */ - public function getIdentity() - { - return new CalculationId($this->id); - } - - /** - * @param string $name - * @param int $subTotal - * @param int $quantity - * - * @return DormerCalculationPrice - */ - public function addPrice($name, $subTotal, $quantity) { - $price = new DormerCalculationPrice($this, $name, $subTotal, $quantity); - $this->prices[$name] = $price; - - return $price; - } - - /** - * @return int - */ - public function getTotal() - { - $total = 0; - foreach ($this->prices as $price) { - $total += $price->calculateTotal(); - } - - return $total; - } -} diff --git a/src/Domain/Model/DormerCalculationPrice.php b/src/Domain/Model/DormerCalculationPrice.php deleted file mode 100644 index f8998cd..0000000 --- a/src/Domain/Model/DormerCalculationPrice.php +++ /dev/null @@ -1,59 +0,0 @@ -calculation = $calculation; - $this->name = $name; - $this->subtotal = $subTotal; - $this->quantity = $quantity; - } - - /** - * @return int - */ - public function calculateTotal() - { - return $this->subtotal * $this->quantity; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } -} diff --git a/src/Domain/Model/Identity/CalculationId.php b/src/Domain/Model/Identity/CalculationId.php deleted file mode 100644 index 9cdf332..0000000 --- a/src/Domain/Model/Identity/CalculationId.php +++ /dev/null @@ -1,27 +0,0 @@ -id = $id; - } - - /** - * @return string - */ - public function id() - { - return $this->id; - } -} diff --git a/src/Domain/Repository/DormerCalculationRepository.php b/src/Domain/Repository/DormerCalculationRepository.php deleted file mode 100644 index 1aba50d..0000000 --- a/src/Domain/Repository/DormerCalculationRepository.php +++ /dev/null @@ -1,13 +0,0 @@ - (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\Application; + +use Example\Domain\Administration\DomainModel\Event\CandidateWasHired; +use Example\Domain\Common\Application\EventPublisher; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Cashier; +use Example\Domain\Sale\DomainModel\EmployeeRepository; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class CashierService +{ + /** + * @var EmployeeRepository + */ + private $employeeRepository; + + /** + * @var EventPublisher + */ + private $publisher; + + /** + * @param EmployeeRepository $employeeRepository + * @param EventPublisher $publisher + */ + public function __construct(EmployeeRepository $employeeRepository, EventPublisher $publisher) + { + $this->employeeRepository = $employeeRepository; + $publisher->addListener(CandidateWasHired::class, $this, 'onCandidateWasHired'); + $this->publisher = $publisher; + } + + /** + * @param CandidateWasHired $event + */ + public function onCandidateWasHired(CandidateWasHired $event) + { + if (! $event->title()->equal(JobTitle::Cashier())) { + return; + } + + $cashier = new Cashier(EmployeeId::fromName($event->candidateName()), $event->candidateName()); + + $this->employeeRepository->saveEmployee($cashier); + $this->publisher->publish($cashier->uncommitedEvents()); + } +} diff --git a/src/Domain/Sale/Application/CookService.php b/src/Domain/Sale/Application/CookService.php new file mode 100644 index 0000000..ee5ccc5 --- /dev/null +++ b/src/Domain/Sale/Application/CookService.php @@ -0,0 +1,54 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\Application; + +use Example\Domain\Administration\DomainModel\Event\CandidateWasHired; +use Example\Domain\Common\Application\EventPublisher; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Cook; +use Example\Domain\Sale\DomainModel\EmployeeRepository; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class CookService +{ + /** + * @var EmployeeRepository + */ + private $employeeRepository; + + /** + * @var EventPublisher + */ + private $publisher; + + /** + * @param EmployeeRepository $employeeRepository + * @param EventPublisher $publisher + */ + public function __construct(EmployeeRepository $employeeRepository, EventPublisher $publisher) + { + $this->employeeRepository = $employeeRepository; + $publisher->addListener(CandidateWasHired::class, $this, 'onCandidateWasHired'); + $this->publisher = $publisher; + } + + /** + * @param CandidateWasHired $event + */ + public function onCandidateWasHired(CandidateWasHired $event) + { + if (! $event->title()->equal(JobTitle::Cook())) { + return; + } + + $cook = new Cook(EmployeeId::fromName($event->candidateName()), $event->candidateName()); + + $this->employeeRepository->saveEmployee($cook); + $this->publisher->publish($cook->uncommitedEvents()); + } +} diff --git a/src/Domain/Sale/Application/WaitressService.php b/src/Domain/Sale/Application/WaitressService.php new file mode 100644 index 0000000..b732468 --- /dev/null +++ b/src/Domain/Sale/Application/WaitressService.php @@ -0,0 +1,54 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\Application; + +use Example\Domain\Administration\DomainModel\Event\CandidateWasHired; +use Example\Domain\Common\Application\EventPublisher; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\EmployeeRepository; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; +use Example\Domain\Sale\DomainModel\Waitress; + +final class WaitressService +{ + /** + * @var EmployeeRepository + */ + private $employeeRepository; + + /** + * @var EventPublisher + */ + private $publisher; + + /** + * @param EmployeeRepository $employeeRepository + * @param EventPublisher $publisher + */ + public function __construct(EmployeeRepository $employeeRepository, EventPublisher $publisher) + { + $this->employeeRepository = $employeeRepository; + $publisher->addListener(CandidateWasHired::class, $this, 'onCandidateWasHired'); + $this->publisher = $publisher; + } + + /** + * @param CandidateWasHired $event + */ + public function onCandidateWasHired(CandidateWasHired $event) + { + if (! $event->title()->equal(JobTitle::Waitress())) { + return; + } + + $waitress = new Waitress(EmployeeId::fromName($event->candidateName()), $event->candidateName()); + + $this->employeeRepository->saveEmployee($waitress); + $this->publisher->publish($waitress->uncommitedEvents()); + } +} diff --git a/src/Domain/Sale/DomainModel/Cashier.php b/src/Domain/Sale/DomainModel/Cashier.php new file mode 100644 index 0000000..8682ec5 --- /dev/null +++ b/src/Domain/Sale/DomainModel/Cashier.php @@ -0,0 +1,79 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\AggregateRoot; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Event\CashierWasCreated; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class Cashier extends AggregateRoot implements Employee +{ + /** + * @var mixed + */ + private $id; + + /** + * @var string + */ + private $firstName; + + /** + * @var string + */ + private $lastName; + + /** + * @param EmployeeId $id + * @param FullName $name + */ + public function __construct(EmployeeId $id, FullName $name) + { + $this->mutate(new CashierWasCreated($id, $name)); + } + + /** + * @return EmployeeId + */ + public function getIdentity() + { + return new EmployeeId($this->id); + } + + /** + * @param JobTitle $title + * + * @return bool + */ + public function matchTitle(JobTitle $title) + { + return $title->equal(JobTitle::Cashier()); + } + + /** + * @param CashierWasCreated $event + */ + protected function onCashierWasCreated(CashierWasCreated $event) + { + $this->id = $event->employeeId()->id(); + $this->firstName = $event->name()->firstName(); + $this->lastName = $event->name()->lastName(); + } + + /** + * @param EmployeeRole $role + * + * @return Receipt + */ + public function receivePayment(OrderId $id, PaymentMethod $paymentMethod) + { + throw new \RuntimeException('Method ' . __METHOD__ . ' not implemented yet.'); + } +} diff --git a/src/Domain/Sale/DomainModel/Cook.php b/src/Domain/Sale/DomainModel/Cook.php new file mode 100644 index 0000000..250e114 --- /dev/null +++ b/src/Domain/Sale/DomainModel/Cook.php @@ -0,0 +1,79 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\AggregateRoot; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Event\CookWasCreated; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class Cook extends AggregateRoot implements Employee +{ + /** + * @var mixed + */ + private $id; + + /** + * @var string + */ + private $firstName; + + /** + * @var string + */ + private $lastName; + + /** + * @param EmployeeId $id + * @param FullName $name + */ + public function __construct(EmployeeId $id, FullName $name) + { + $this->mutate(new CookWasCreated($id, $name)); + } + + /** + * @return EmployeeId + */ + public function getIdentity() + { + return new EmployeeId($this->id); + } + + /** + * @param JobTitle $title + * + * @return bool + */ + public function matchTitle(JobTitle $title) + { + return $title->equal(JobTitle::Cook()); + } + + /** + * @param CookWasCreated $event + */ + protected function onCookWasCreated(CookWasCreated $event) + { + $this->id = $event->employeeId()->id(); + $this->firstName = $event->name()->firstName(); + $this->lastName = $event->name()->lastName(); + } + + /** + * @param OrderId $orderId + * + * @return Meal + */ + public function prepareOrder(OrderId $orderId) + { + throw new \RuntimeException('Method ' . __METHOD__ . ' not implemented yet.'); + } +} diff --git a/src/Domain/Sale/DomainModel/Employee.php b/src/Domain/Sale/DomainModel/Employee.php new file mode 100644 index 0000000..865c41b --- /dev/null +++ b/src/Domain/Sale/DomainModel/Employee.php @@ -0,0 +1,26 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +interface Employee +{ + /** + * @return EmployeeId + */ + public function getIdentity(); + + /** + * @param JobTitle $title + * + * @return bool + */ + public function matchTitle(JobTitle $title); +} diff --git a/src/Domain/Sale/DomainModel/EmployeeRepository.php b/src/Domain/Sale/DomainModel/EmployeeRepository.php new file mode 100644 index 0000000..3e9e00e --- /dev/null +++ b/src/Domain/Sale/DomainModel/EmployeeRepository.php @@ -0,0 +1,35 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Common\Exception\EntityNotFoundException; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +interface EmployeeRepository +{ + /** + * @param EmployeeId $id + * + * @return Employee + * @throws EntityNotFoundException when not found + */ + public function employeeWithIdentity(EmployeeId $id); + + /** + * @param Employee $employee + */ + public function saveEmployee(Employee $employee); + + /** + * @param JobTitle $title + * + * @return Employee[] + */ + public function employeesWithTitle(JobTitle $title); +} diff --git a/src/Domain/Sale/DomainModel/Event/CashierWasCreated.php b/src/Domain/Sale/DomainModel/Event/CashierWasCreated.php new file mode 100644 index 0000000..6f10be5 --- /dev/null +++ b/src/Domain/Sale/DomainModel/Event/CashierWasCreated.php @@ -0,0 +1,51 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel\Event; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class CashierWasCreated implements DomainEvent +{ + /** + * @var EmployeeId + */ + private $employeeId; + + /** + * @var FullName + */ + private $name; + + /** + * @param EmployeeId $employeeId + * @param FullName $name + */ + public function __construct(EmployeeId $employeeId, FullName $name) + { + $this->employeeId = $employeeId; + $this->name = $name; + } + + /** + * @return EmployeeId + */ + public function employeeId() + { + return $this->employeeId; + } + + /** + * @return FullName + */ + public function name() + { + return $this->name; + } +} diff --git a/src/Domain/Sale/DomainModel/Event/CookWasCreated.php b/src/Domain/Sale/DomainModel/Event/CookWasCreated.php new file mode 100644 index 0000000..f70e6e0 --- /dev/null +++ b/src/Domain/Sale/DomainModel/Event/CookWasCreated.php @@ -0,0 +1,51 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel\Event; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class CookWasCreated implements DomainEvent +{ + /** + * @var EmployeeId + */ + private $employeeId; + + /** + * @var FullName + */ + private $name; + + /** + * @param EmployeeId $employeeId + * @param FullName $name + */ + public function __construct(EmployeeId $employeeId, FullName $name) + { + $this->employeeId = $employeeId; + $this->name = $name; + } + + /** + * @return EmployeeId + */ + public function employeeId() + { + return $this->employeeId; + } + + /** + * @return FullName + */ + public function name() + { + return $this->name; + } +} diff --git a/src/Domain/Sale/DomainModel/Event/WaitressWasCreated.php b/src/Domain/Sale/DomainModel/Event/WaitressWasCreated.php new file mode 100644 index 0000000..e3803ba --- /dev/null +++ b/src/Domain/Sale/DomainModel/Event/WaitressWasCreated.php @@ -0,0 +1,51 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel\Event; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class WaitressWasCreated implements DomainEvent +{ + /** + * @var EmployeeId + */ + private $employeeId; + + /** + * @var FullName + */ + private $name; + + /** + * @param EmployeeId $employeeId + * @param FullName $name + */ + public function __construct(EmployeeId $employeeId, FullName $name) + { + $this->employeeId = $employeeId; + $this->name = $name; + } + + /** + * @return EmployeeId + */ + public function employeeId() + { + return $this->employeeId; + } + + /** + * @return FullName + */ + public function name() + { + return $this->name; + } +} diff --git a/src/Domain/Sale/DomainModel/Identity/EmployeeId.php b/src/Domain/Sale/DomainModel/Identity/EmployeeId.php new file mode 100644 index 0000000..a251aaf --- /dev/null +++ b/src/Domain/Sale/DomainModel/Identity/EmployeeId.php @@ -0,0 +1,54 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel\Identity; + +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\Identity\Identity; +use Example\Domain\Sale\DomainModel\Employee; + +final class EmployeeId implements Identity +{ + /** + * @var mixed + */ + private $id; + + /** + * @param mixed $id + */ + public function __construct($id) + { + $this->id = $id; + } + + /** + * @return string + */ + public function getEntityClass() + { + return Employee::class; + } + + /** + * @return mixed + */ + public function id() + { + return $this->id; + } + + /** + * @param FullName $name + * + * @return EmployeeId + */ + public static function fromName(FullName $name) + { + return new self($name->toString()); + } +} diff --git a/src/Domain/Sale/DomainModel/Waitress.php b/src/Domain/Sale/DomainModel/Waitress.php new file mode 100644 index 0000000..33a14e3 --- /dev/null +++ b/src/Domain/Sale/DomainModel/Waitress.php @@ -0,0 +1,102 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\AggregateRoot; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Event\WaitressWasCreated; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class Waitress extends AggregateRoot implements Employee +{ + /** + * @var mixed + */ + private $id; + + /** + * @var string + */ + private $firstName; + + /** + * @var string + */ + private $lastName; + + /** + * @param EmployeeId $id + * @param FullName $name + */ + public function __construct(EmployeeId $id, FullName $name) + { + $this->mutate(new WaitressWasCreated($id, $name)); + } + + /** + * @return EmployeeId + */ + public function getIdentity() + { + return new EmployeeId($this->id); + } + + /** + * @param JobTitle $title + * + * @return bool + */ + public function matchTitle(JobTitle $title) + { + return $title->equal(JobTitle::Waitress()); + } + + /** + * @param WaitressWasCreated $event + */ + protected function onWaitressWasCreated(WaitressWasCreated $event) + { + $this->id = $event->employeeId()->id(); + $this->firstName = $event->name()->firstName(); + $this->lastName = $event->name()->lastName(); + } + + /** + * @param ItemId $itemId + * @param int $quantity + * + * @return Order + */ + public function takeOrder(ItemId $itemId, $quantity) + { + throw new \RuntimeException('Method ' . __METHOD__ . ' not implemented yet.'); + } + + /** + * @param ItemId $itemId + * @param int $quantity + * + * @return Order + */ + public function cancelItem(OrderId $orderId, ItemId $itemId, $quantity) + { + throw new \RuntimeException('Method ' . __METHOD__ . ' not implemented yet.'); + } + + /** + * @param ItemId $itemId + * @param int $quantity + * + * @return Order + */ + public function addItem(OrderId $orderId, ItemId $itemId, $quantity) + { + throw new \RuntimeException('Method ' . __METHOD__ . ' not implemented yet.'); + } +} diff --git a/src/Domain/Shipping/Application/CreateDeliveryBoyCommand.php b/src/Domain/Shipping/Application/CreateDeliveryBoyCommand.php new file mode 100644 index 0000000..47886af --- /dev/null +++ b/src/Domain/Shipping/Application/CreateDeliveryBoyCommand.php @@ -0,0 +1,35 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Shipping\Application; + +use Example\Domain\Common\Application\DomainCommand; +use Example\Domain\Common\DomainModel\FullName; + +final class CreateDeliveryBoyCommand implements DomainCommand +{ + /** + * @var FullName + */ + private $name; + + /** + * @param FullName $name + */ + public function __construct(FullName $name) + { + $this->name = $name; + } + + /** + * @return FullName + */ + public function name() + { + return $this->name; + } +} diff --git a/src/Domain/Shipping/Application/CreateDeliveryBoyHandler.php b/src/Domain/Shipping/Application/CreateDeliveryBoyHandler.php new file mode 100644 index 0000000..a1e6aa9 --- /dev/null +++ b/src/Domain/Shipping/Application/CreateDeliveryBoyHandler.php @@ -0,0 +1,69 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Shipping\Application; + +use Example\Domain\Administration\DomainModel\Event\CandidateWasHired; +use Example\Domain\Common\Application\DomainCommand; +use Example\Domain\Common\Application\EventPublisher; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Common\Exception\CommandHandlerException; +use Example\Domain\Shipping\DomainModel\DeliveryBoy; +use Example\Domain\Shipping\DomainModel\DeliveryBoyRepository; +use Example\Domain\Shipping\DomainModel\Identity\DeliveryBoyId; + +final class CreateDeliveryBoyHandler +{ + /** + * @var DeliveryBoyRepository + */ + private $repository; + + /** + * @var EventPublisher + */ + private $publisher; + + /** + * @param DeliveryBoyRepository $repository + * @param EventPublisher $publisher + */ + public function __construct(DeliveryBoyRepository $repository, EventPublisher $publisher) + { + $this->repository = $repository; + $publisher->addListener(CandidateWasHired::class, $this, 'onCandidateWasHired'); + $this->publisher = $publisher; + } + + /** + * @param DomainCommand $command + * @throws \Example\Domain\Common\Exception\CommandHandlerException + */ + public function handle(DomainCommand $command) + { + if (! $command instanceof CreateDeliveryBoyCommand) { + throw CommandHandlerException::unsupportedCommandException($command, $this); + } + + $deliveryBoy = new DeliveryBoy(new DeliveryBoyId($command->name())); + $this->repository->saveDeliveryBoy($deliveryBoy); + } + + /** + * @param CandidateWasHired $event + */ + public function onCandidateWasHired(CandidateWasHired $event) + { + if (! $event->title()->equal(JobTitle::DeliveryBoy())) { + return; + } + + $this->handle( + new CreateDeliveryBoyCommand($event->candidateName()) + ); + } +} diff --git a/src/Domain/Shipping/DomainModel/DeliveryBoy.php b/src/Domain/Shipping/DomainModel/DeliveryBoy.php new file mode 100644 index 0000000..9d043d8 --- /dev/null +++ b/src/Domain/Shipping/DomainModel/DeliveryBoy.php @@ -0,0 +1,48 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Shipping\DomainModel; + +use Example\Domain\Common\DomainModel\AggregateRoot; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Shipping\DomainModel\Identity\DeliveryBoyId; + +final class DeliveryBoy extends AggregateRoot +{ + /** + * @var string + */ + private $id; + + /** + * @param DeliveryBoyId $id + */ + public function __construct(DeliveryBoyId $id) + { + $this->id = $id->id(); + } + + /** + * @return DeliveryBoyId + */ + public function getIdentity() + { + return new DeliveryBoyId(FullName::fromSingleString($this->id)); + } + + /** + * @param JobTitle $title + * + * @return bool + * todo part of employee interface, should not cross boundary + */ + public function matchTitle(JobTitle $title) + { + return $title->equal(JobTitle::DeliveryBoy()); + } +} diff --git a/src/Domain/Shipping/DomainModel/DeliveryBoyRepository.php b/src/Domain/Shipping/DomainModel/DeliveryBoyRepository.php new file mode 100644 index 0000000..fe99525 --- /dev/null +++ b/src/Domain/Shipping/DomainModel/DeliveryBoyRepository.php @@ -0,0 +1,16 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Shipping\DomainModel; + +interface DeliveryBoyRepository +{ + /** + * @param DeliveryBoy $deliveryBoy + */ + public function saveDeliveryBoy(DeliveryBoy $deliveryBoy); +} diff --git a/src/Domain/Shipping/DomainModel/Identity/DeliveryBoyId.php b/src/Domain/Shipping/DomainModel/Identity/DeliveryBoyId.php new file mode 100644 index 0000000..7a66efa --- /dev/null +++ b/src/Domain/Shipping/DomainModel/Identity/DeliveryBoyId.php @@ -0,0 +1,44 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Shipping\DomainModel\Identity; + +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\Identity\Identity; +use Example\Domain\Shipping\DomainModel\DeliveryBoy; + +final class DeliveryBoyId implements Identity +{ + /** + * @var FullName + */ + private $name; + + /** + * @param FullName $name + */ + public function __construct(FullName $name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getEntityClass() + { + return DeliveryBoy::class; + } + + /** + * @return mixed + */ + public function id() + { + return '[Delivery boy] ' . $this->name->toString(); + } +} diff --git a/src/Infrastructure/CQRS/Command/CalculationCommand.php b/src/Infrastructure/CQRS/Command/CalculationCommand.php deleted file mode 100644 index faddcf6..0000000 --- a/src/Infrastructure/CQRS/Command/CalculationCommand.php +++ /dev/null @@ -1,14 +0,0 @@ -repository = $repository; - $this->generator = $generator; - } - - /** - * @param CalculationCommand $command - */ - public function handle($command) - { - $entity = new DormerCalculation($this->generator->generateDormerIdentity()); - $entity->addPrice('total', $command->subTotal, $command->quantity); - - $this->repository->saveCalculation($entity); - } -} diff --git a/src/Infrastructure/InMemory/EmployeeCollection.php b/src/Infrastructure/InMemory/EmployeeCollection.php new file mode 100644 index 0000000..194baa8 --- /dev/null +++ b/src/Infrastructure/InMemory/EmployeeCollection.php @@ -0,0 +1,81 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Infrastructure\InMemory; + +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Common\Exception\EntityNotFoundException; +use Example\Domain\Sale\DomainModel\Employee; +use Example\Domain\Sale\DomainModel\EmployeeRepository; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; +use Example\Domain\Shipping\DomainModel\DeliveryBoy; +use Example\Domain\Shipping\DomainModel\DeliveryBoyRepository; + +final class EmployeeCollection implements EmployeeRepository, DeliveryBoyRepository, \Countable +{ + /** + * @var Employee[] + */ + private $employees = []; + + /** + * @param EmployeeId $id + * + * @return Employee + * @throws EntityNotFoundException when not found + */ + public function employeeWithIdentity(EmployeeId $id) + { + if (! isset($this->employees[$id->id()])) { + throw EntityNotFoundException::entityWithIdentity($id); + } + + return $this->employees[$id->id()]; + } + + /** + * @param Employee $employee + */ + public function saveEmployee(Employee $employee) + { + $this->employees[$employee->getIdentity()->id()] = $employee; + } + + /** + * @return int The custom count as an integer. + */ + public function count() + { + return count($this->employees); + } + + /** + * @param JobTitle $title + * + * @throws \Example\Domain\Common\Exception\EntityNotFoundException + * @return Employee[] + */ + public function employeesWithTitle(JobTitle $title) + { + $employees = []; + foreach ($this->employees as $employee) { + if ($employee->matchTitle($title)) { + $employees[] = $employee; + } + } + + return $employees; + } + + /** + * @param DeliveryBoy $deliveryBoy + */ + public function saveDeliveryBoy(DeliveryBoy $deliveryBoy) + { + $this->employees[$deliveryBoy->getIdentity()->id()] = $deliveryBoy; + } +} diff --git a/src/Infrastructure/InMemory/OwnerCollection.php b/src/Infrastructure/InMemory/OwnerCollection.php new file mode 100644 index 0000000..62d14c9 --- /dev/null +++ b/src/Infrastructure/InMemory/OwnerCollection.php @@ -0,0 +1,52 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Infrastructure\InMemory; + +use Example\Domain\Administration\DomainModel\Identity\OwnerId; +use Example\Domain\Administration\DomainModel\Owner; +use Example\Domain\Administration\DomainModel\OwnerRepository; +use Example\Domain\Common\Exception\EntityNotFoundException; + +final class OwnerCollection implements OwnerRepository, \Countable +{ + /** + * @var Owner[] + */ + private $owners = []; + + /** + * @param OwnerId $ownerId + * + * @throws EntityNotFoundException + * @return Owner + */ + public function ownerWithId(OwnerId $ownerId) + { + if (! isset($this->owners[$ownerId->id()])) { + throw EntityNotFoundException::entityWithIdentity($ownerId); + } + + return $this->owners[$ownerId->id()]; + } + + /** + * @param Owner $owner + */ + public function saveOwner(Owner $owner) + { + $this->owners[$owner->getIdentity()->id()] = $owner; + } + + /** + * @return int + */ + public function count() + { + return count($this->owners); + } +} diff --git a/src/Infrastructure/Symfony/EventAdapter.php b/src/Infrastructure/Symfony/EventAdapter.php new file mode 100644 index 0000000..7e486c8 --- /dev/null +++ b/src/Infrastructure/Symfony/EventAdapter.php @@ -0,0 +1,40 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Infrastructure\Symfony; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; +use Symfony\Component\EventDispatcher\Event; + +final class EventAdapter extends Event +{ + /** + * @var DomainEvent + */ + private $event; + + /** + * @param DomainEvent $event + */ + public function __construct(DomainEvent $event) + { + $this->event = $event; + } + + public function getName() + { + return get_class($this->event); + } + + /** + * @return DomainEvent + */ + public function getWrappedEvent() + { + return $this->event; + } +} diff --git a/src/Infrastructure/Symfony/SymfonyPublisher.php b/src/Infrastructure/Symfony/SymfonyPublisher.php new file mode 100644 index 0000000..03a4f2f --- /dev/null +++ b/src/Infrastructure/Symfony/SymfonyPublisher.php @@ -0,0 +1,50 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Infrastructure\Symfony; + +use Example\Domain\Common\Application\EventPublisher; +use Example\Domain\Common\DomainModel\Event\DomainEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; + +final class SymfonyPublisher implements EventPublisher +{ + /** + * @var EventDispatcher + */ + private $dispatcher; + + public function __construct() + { + $this->dispatcher = new EventDispatcher(); + } + + /** + * @param DomainEvent[] $events + */ + public function publish(array $events) + { + foreach ($events as $event) { + $eventAdapter = new EventAdapter($event); + $this->dispatcher->dispatch($eventAdapter->getName(), $eventAdapter); + } + } + + /** + * @param string $eventClassName The class full name of the event + * @param callable $listener An object that listens on events + * @param string $method + */ + public function addListener($eventClassName, $listener, $method) + { + $transformer = function (EventAdapter $adapter) use ($listener, $method) { + call_user_func([$listener, $method], $adapter->getWrappedEvent()); + }; + + $this->dispatcher->addListener($eventClassName, $transformer); + } +} diff --git a/src/Infrastructure/UniqueIdGenerator.php b/src/Infrastructure/UniqueIdGenerator.php deleted file mode 100644 index 4bc62a0..0000000 --- a/src/Infrastructure/UniqueIdGenerator.php +++ /dev/null @@ -1,17 +0,0 @@ - (http://github.com/yvoyer) + */ + +namespace Example\Domain\Administration\DomainModel; + +use Example\Domain\Administration\DomainModel\Event\CandidateWasHired; +use Example\Domain\Administration\DomainModel\Identity\OwnerId; +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; + +final class OwnerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Owner + */ + private $owner; + + public function setUp() + { + $this->owner = new Owner(new OwnerId(FullName::fromSingleString('owner'))); + } + + public function test_it_should_create_a_owner() + { + $this->assertSame(FullName::fromSingleString('owner')->toString(), $this->owner->getIdentity()->id()); + } + + public function test_it_should_hire_candidate() + { + $this->owner->hire(FullName::fromSingleString('candidate'), JobTitle::Cashier()); + $events = $this->owner->uncommitedEvents(); + $this->assertCount(1, $events); + $event = $events[0]; + /** + * @var $event CandidateWasHired + */ + $this->assertInstanceOf(CandidateWasHired::class, $event); + $this->assertEquals(FullName::fromSingleString('candidate'), $event->candidateName()); + $this->assertEquals($this->owner->getIdentity(), $event->hiredBy()); + $this->assertEquals(JobTitle::Cashier(), $event->title()); + } + + /** + * @depends test_it_should_create_a_owner + */ + public function test_it_should_create_new_recipe_with_ingredients() + { + $this->markTestIncomplete('TODO'); + $recipe = $owner->newRecipe(new RecipeId(), RecipeName::fromString('All dress Pizza'), $ingredients = []); + + $this->assertInstanceOf(Recipe::class, $recipe, 'Owner should create recipe'); + $this->assertFalse($recipe->isReleased(), 'Recipe should not be released by default'); + $this->assertFalse($recipe->isRetired(), 'Recipe should not be retired by default'); + } + + /** + * @depends test_it_should_create_new_recipe_with_ingredients + */ + public function test_it_should_release_recipe() + { + $this->markTestIncomplete('TODO'); + $recipe = $owner->releaseRecipe(new RecipeId()); + $this->assertTrue($recipe->isReleased(), 'Recipe should be released'); + $this->assertFalse($recipe->isRetired(), 'Recipe should not be retired'); + } + + /** + * @depends test_it_should_release_recipe + */ + public function test_it_should_retire_released_recipe() + { + $this->markTestIncomplete('TODO'); + $recipe = $owner->retireRecipe(new RecipeId()); + $this->assertFalse($recipe->isReleased(), 'Recipe should not be released'); + $this->assertTrue($recipe->isRetired(), 'Recipe should be retired'); + } +} diff --git a/tests/Domain/Common/DomainModel/FullNameTest.php b/tests/Domain/Common/DomainModel/FullNameTest.php new file mode 100644 index 0000000..bbc00bd --- /dev/null +++ b/tests/Domain/Common/DomainModel/FullNameTest.php @@ -0,0 +1,38 @@ +assertInstanceOf(FullName::class, $name); + $this->assertSame('first', $name->firstName()); + $this->assertSame('last', $name->lastName()); + } + + public function test_it_should_be_converted_to_string() + { + $this->assertSame('[FullName] First Last', FullName::fromString('First', 'Last')->toString()); + } + + public function test_it_should_be_create_from_single_string() + { + $name = FullName::fromSingleString('name'); + $this->assertInstanceOf(FullName::class, $name); + $this->assertSame('Mr/Miss', $name->firstName()); + $this->assertSame('name', $name->lastName()); + } + + public function test_it_should_return_equality() + { + $lowerJohnDoe = FullName::fromString('john', 'doe'); + $upperJohnDoe = FullName::fromString('John', 'Doe'); + $janeDoe = FullName::fromString('Jane', 'Doe'); + + $this->assertTrue($lowerJohnDoe->equal($lowerJohnDoe)); + $this->assertFalse($lowerJohnDoe->equal($upperJohnDoe)); + $this->assertFalse($lowerJohnDoe->equal($janeDoe)); + } +} diff --git a/tests/Domain/Sale/DomainModel/CashierTest.php b/tests/Domain/Sale/DomainModel/CashierTest.php new file mode 100644 index 0000000..63be939 --- /dev/null +++ b/tests/Domain/Sale/DomainModel/CashierTest.php @@ -0,0 +1,51 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Event\CashierWasCreated; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class CashierTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Cashier + */ + private $cashier; + + public function setUp() + { + $this->cashier = new Cashier(new EmployeeId(123), FullName::fromString('Joe', 'Blow')); + } + + public function test_it_should_have_an_identity() + { + $this->assertEquals(new EmployeeId(123), $this->cashier->getIdentity()); + $this->assertSame(123, $this->cashier->getIdentity()->id()); + } + + public function test_it_should_match_title() + { + $this->assertFalse($this->cashier->matchTitle(JobTitle::Cook())); + $this->assertTrue($this->cashier->matchTitle(JobTitle::Cashier())); + } + + public function test_it_should_generate_an_event_on_creation() + { + $events = $this->cashier->uncommitedEvents(); + $this->assertCount(1, $events); + /** + * @var CashierWasCreated $event + */ + $event = $events[0]; + $this->assertInstanceOf(CashierWasCreated::class, $event); + $this->assertEquals(new EmployeeId(123), $event->employeeId()); + $this->assertEquals(FullName::fromString('Joe', 'Blow'), $event->name()); + } +} diff --git a/tests/Domain/Sale/DomainModel/CookTest.php b/tests/Domain/Sale/DomainModel/CookTest.php new file mode 100644 index 0000000..f6a7187 --- /dev/null +++ b/tests/Domain/Sale/DomainModel/CookTest.php @@ -0,0 +1,51 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Event\CookWasCreated; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class CookTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Cook + */ + private $cook; + + public function setUp() + { + $this->cook = new Cook(new EmployeeId(123), FullName::fromString('Joe', 'Blow')); + } + + public function test_it_should_have_an_identity() + { + $this->assertEquals(new EmployeeId(123), $this->cook->getIdentity()); + $this->assertSame(123, $this->cook->getIdentity()->id()); + } + + public function test_it_should_match_title() + { + $this->assertTrue($this->cook->matchTitle(JobTitle::Cook())); + $this->assertFalse($this->cook->matchTitle(JobTitle::Cashier())); + } + + public function test_it_should_generate_an_event_on_creation() + { + $events = $this->cook->uncommitedEvents(); + $this->assertCount(1, $events); + /** + * @var CookWasCreated $event + */ + $event = $events[0]; + $this->assertInstanceOf(CookWasCreated::class, $event); + $this->assertEquals(new EmployeeId(123), $event->employeeId()); + $this->assertEquals(FullName::fromString('Joe', 'Blow'), $event->name()); + } +} diff --git a/tests/Domain/Sale/DomainModel/WaitressTest.php b/tests/Domain/Sale/DomainModel/WaitressTest.php new file mode 100644 index 0000000..262023f --- /dev/null +++ b/tests/Domain/Sale/DomainModel/WaitressTest.php @@ -0,0 +1,51 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Domain\Sale\DomainModel; + +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Event\WaitressWasCreated; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class WaitressTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Waitress + */ + private $waitress; + + public function setUp() + { + $this->waitress = new Waitress(new EmployeeId(123), FullName::fromString('Joe', 'Blow')); + } + + public function test_it_should_have_an_identity() + { + $this->assertEquals(new EmployeeId(123), $this->waitress->getIdentity()); + $this->assertSame(123, $this->waitress->getIdentity()->id()); + } + + public function test_it_should_match_title() + { + $this->assertTrue($this->waitress->matchTitle(JobTitle::Waitress())); + $this->assertFalse($this->waitress->matchTitle(JobTitle::Cashier())); + } + + public function test_it_should_generate_an_event_on_creation() + { + $events = $this->waitress->uncommitedEvents(); + $this->assertCount(1, $events); + /** + * @var WaitressWasCreated $event + */ + $event = $events[0]; + $this->assertInstanceOf(WaitressWasCreated::class, $event); + $this->assertEquals(new EmployeeId(123), $event->employeeId()); + $this->assertEquals(FullName::fromString('Joe', 'Blow'), $event->name()); + } +} diff --git a/tests/Infrastructure/InMemory/EmployeeCollectionTest.php b/tests/Infrastructure/InMemory/EmployeeCollectionTest.php new file mode 100644 index 0000000..4c3aa46 --- /dev/null +++ b/tests/Infrastructure/InMemory/EmployeeCollectionTest.php @@ -0,0 +1,80 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Infrastructure\InMemory; + +use Example\Domain\Common\DomainModel\FullName; +use Example\Domain\Common\DomainModel\JobTitle; +use Example\Domain\Sale\DomainModel\Cashier; +use Example\Domain\Sale\DomainModel\Cook; +use Example\Domain\Sale\DomainModel\Employee; +use Example\Domain\Sale\DomainModel\Identity\EmployeeId; + +final class EmployeeCollectionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var EmployeeCollection + */ + private $collection; + + public function setUp() + { + $this->collection = new EmployeeCollection(); + } + + public function test_it_should_return_employee_with_id() + { + $employee = $this->createEmployee(new EmployeeId('fixture'), JobTitle::fromString('fake')); + + $this->assertCount(0, $this->collection); + $this->collection->saveEmployee($employee); + $this->assertCount(1, $this->collection); + $this->assertSame($employee, $this->collection->employeeWithIdentity($employee->getIdentity())); + } + + /** + * @expectedException \Example\Domain\Common\Exception\EntityNotFoundException + * @expectedExceptionMessage The entity of type 'Example\Domain\Sale\DomainModel\Employee' with identity '123' could not + */ + public function test_it_should_throw_exception_when_not_found() + { + $this->assertCount(0, $this->collection); + $this->collection->employeeWithIdentity(new EmployeeId(123)); + } + + public function test_it_should_return_the_employees_with_title() + { + $cook1 = new Cook(new EmployeeId('cook-1'), FullName::fromSingleString('cook1')); + $cashier = new Cashier(new EmployeeId('cashier'), FullName::fromSingleString('cashier')); + $cook2 = new Cook(new EmployeeId('cook-2'), FullName::fromSingleString('cook2'));; + + $this->collection->saveEmployee($cook1); + $this->collection->saveEmployee($cashier); + $this->collection->saveEmployee($cook2); + + $this->assertCount(3, $this->collection); + $this->assertCount(1, $this->collection->employeesWithTitle(JobTitle::Cashier())); + $this->assertCount(2, $this->collection->employeesWithTitle(JobTitle::Cook())); + $this->assertCount(0, $this->collection->employeesWithTitle(JobTitle::Waitress())); + } + + /** + * @param EmployeeId $id + * @param JobTitle $title + * + * @return \PHPUnit_Framework_MockObject_MockObject|Employee + */ + private function createEmployee(EmployeeId $id, JobTitle $title) + { + $mock = $this->getMock(Employee::class); + $mock + ->method('getIdentity') + ->willReturn($id); + + return $mock; + } +} diff --git a/tests/Infrastructure/InMemory/OwnerCollectionTest.php b/tests/Infrastructure/InMemory/OwnerCollectionTest.php new file mode 100644 index 0000000..918930f --- /dev/null +++ b/tests/Infrastructure/InMemory/OwnerCollectionTest.php @@ -0,0 +1,44 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Infrastructure\InMemory; + +use Example\Domain\Administration\DomainModel\Identity\OwnerId; +use Example\Domain\Administration\DomainModel\Owner; +use Example\Domain\Common\DomainModel\FullName; + +final class OwnerCollectionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var OwnerCollection + */ + private $collection; + + public function setUp() + { + $this->collection = new OwnerCollection(); + } + + public function test_it_should_return_owner_with_id() + { + $owner = Owner::fakeWithId('id'); + $this->collection->saveOwner($owner); + + $this->assertCount(1, $this->collection); + $this->assertSame($owner, $this->collection->ownerWithId($owner->getIdentity())); + } + + /** + * @expectedException \Example\Domain\Common\Exception\EntityNotFoundException + * @expectedExceptionMessage The entity of type 'Example\Domain\Administration\DomainModel\Owner' with identity '[FullNa + */ + public function test_it_should_throw_exception_when_owner_not_found() + { + $this->assertCount(0, $this->collection); + $this->collection->ownerWithId(new OwnerId(FullName::fromSingleString('invalid'))); + } +} diff --git a/tests/Infrastructure/Symfony/SymfonyPublisherTest.php b/tests/Infrastructure/Symfony/SymfonyPublisherTest.php new file mode 100644 index 0000000..758e898 --- /dev/null +++ b/tests/Infrastructure/Symfony/SymfonyPublisherTest.php @@ -0,0 +1,45 @@ + (http://github.com/yvoyer) + */ + +namespace Example\Infrastructure\Symfony; + +use Example\Domain\Common\DomainModel\Event\DomainEvent; + +final class SymfonyPublisherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var SymfonyPublisher + */ + private $symfonyPublisher; + + /** + * @var bool + */ + private $triggered = false; + + public function setUp() + { + $this->symfonyPublisher = new SymfonyPublisher(); + } + + public function test_it_should_publish_event_to_listener() + { + $this->symfonyPublisher->addListener(FakeEvent::class, $this, 'setTriggered'); + $this->assertFalse($this->triggered); + $this->symfonyPublisher->publish([new FakeEvent()]); + $this->assertTrue($this->triggered); + } + + public function setTriggered(FakeEvent $event) + { + $this->triggered = true; + } +} + +final class FakeEvent implements DomainEvent +{ +} diff --git a/tests/WorkflowTest.php b/tests/WorkflowTest.php deleted file mode 100644 index 1c15162..0000000 --- a/tests/WorkflowTest.php +++ /dev/null @@ -1,36 +0,0 @@ -indexAction(CalculationCommand::BASIC_TYPE); - - /** - * @var DormerCalculation $createdEntity - */ - $createdEntity = $repository->find('uniqueid'); - - $this->assertInstanceOf(DormerCalculation::class, $createdEntity); - $this->assertAttributeCount(1, 'prices', $createdEntity); - $this->assertAttributeContainsOnly(DormerCalculationPrice::class, 'prices', $createdEntity); - $this->assertSame(12000, $createdEntity->getTotal()); - } -}