diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..797b003 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,70 @@ +name: Testing + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: make lint-composer + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: make composer + + - name: Pack and upload the build + uses: actions/upload-artifact@v2 + with: + name: website + path: . + + test: + if: ${{ success() }} + needs: build + runs-on: ubuntu-latest + steps: + - name: Download site build + uses: actions/download-artifact@v2 + with: + name: website + path: . + + - name: Cache Composer packages + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + - name: Lint PHP files + run: make lint-php + + - name: Check PHP styles + run: make phpcs + + - name: Static Analysis + run: make stan + + - name: Unit Tests + run: make tests + diff --git a/.gitignore b/.gitignore index c921004..41308e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ vendor/* coverage/* +bin/* + +!bin/.gitkeep +.phpunit.result.cache \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1a9ef04..0000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: php -php: - - '7.0' -install: make all diff --git a/Makefile b/Makefile index 97448df..aac01e5 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,72 @@ COMPOSER ?= composer -PROJECT = "Tic-Tac-Toe" +DOCKER_COMPOSE = docker-compose +PROJECT = "TicTacToe." +COMPOSE_PROJECT_NAME ?= $(notdir $(shell pwd)) +PHP_SERVICE = php +PHP_CMD = php -all: clear lint-composer lint-php composer phpcs test coverage +ifeq ($(RUNNER), travis) + CMD := +else + CMD := docker-compose exec $(PHP_SERVICE) +endif + +all: container-up clear composer lint-composer lint-php phpcs tests lint-composer: @echo "\n==> Validating composer.json and composer.lock:" - $(COMPOSER) validate --strict + $(CMD) $(COMPOSER) validate --strict lint-php: @echo "\n==> Validating all php files:" - @find src -type f -name \*.php | while read file; do php -l "$$file" || exit 1; done + $(CMD) find src tests -type f -iname '*php' -exec $(PHP_CMD) -l {} \; composer: - $(COMPOSER) install + @echo "\n==> Running composer install, runner $(RUNNER)" + $(CMD) $(COMPOSER) install + +lint: lint-composer lint-php clear: - rm -rf vendor + $(CMD) rm -rf vendor + $(CMD) rm -rf bin/php* phpcs: - php vendor/bin/phpcs . -np + @echo "\n==> Checking style guidelines" + $(CMD) bin/phpcs --standard=phpcs.xml -p phpcbf: - vendor/bin/phpcbf src/* - -test: - vendor/bin/phpunit + $(CMD) bin/phpcbf coverage: - vendor/bin/phpunit --coverage-html coverage + @echo "\n==> Generating coverage report" + $(CMD) bin/phpunit --coverage-html coverage + +tests: + @echo "\n==> Running tests" + $(CMD) bin/phpunit + +stan: + @echo "\n==> Running stan for analysis" + $(CMD) bin/phpstan analyse --memory-limit=-1 src + +container-stop: + @echo "\n==> Stopping docker container" + $(DOCKER_COMPOSE) stop + +container-down: + @echo "\n==> Removing docker container" + $(DOCKER_COMPOSE) down + +container-remove: + @echo "\n==> Removing docker container(s)" + $(DOCKER_COMPOSE) rm + +container-up: + @echo "\n==> Docker container building and starting ..." + $(DOCKER_COMPOSE) up --build -d + +tear-down: clear container-stop container-down container-remove + -.PHONY: lint-php lint-composer phpcs phpcbf test coverage composer clear +.PHONY: lint-php lint-composer phpcs phpcbf composer clear tests coverage container-up container-stop container-down container-remove diff --git a/README.md b/README.md index 0a82a35..b9805b2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# tic-tac-toe-sami - -[![Build Status](https://travis-ci.org/mnsami/tic-tac-toe.svg?branch=master)](https://travis-ci.org/mnsami/tic-tac-toe) +# tic-tac-toe ### Description @@ -8,7 +6,8 @@ A php-cli Tic-Tac-Toe simple two players game. ### Requirements -1. `php >= 7.0` (required for `phpunit v6.1`) +1. `php >= 8` +2. `Docker` ### Installation @@ -18,11 +17,11 @@ Run `make all` will do all the magic for you. ### Start playing -To start playing the game, you need to run `php main.php` +TODO, refactoring has to be finished first. ### Unit tests -To generate tests report, run `make test`. +To generate tests report, run `make tests`. ### Coverage diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/composer.json b/composer.json index 203e710..c2e7fb4 100644 --- a/composer.json +++ b/composer.json @@ -5,19 +5,34 @@ "type": "project", "authors": [ { - "name": "Mina Nabil Sami" + "name": "Mina Nabil Sami", + "email": "mina.nsami@gmail.com" } ], "require": { - "php": ">=7", - "squizlabs/php_codesniffer": "2.*" - }, - "require-dev": { - "phpunit/phpunit": "6.1.*" + "php": ">=8", + "ramsey/uuid": "^3.8" }, "autoload": { "psr-0": { - "TicTacToe": "src/" + "TicTacToe": "./src/" + } + }, + "autoload-dev": { + "psr-4": { + "unit\\TicTacToe\\": "./tests/unit" } + }, + "config": { + "bin-dir": "bin", + "platform": { + "php": "8" + }, + "sort-packages": true + }, + "require-dev": { + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7" } } diff --git a/composer.lock b/composer.lock index e3f446e..88e0397 100644 --- a/composer.lock +++ b/composer.lock @@ -1,121 +1,278 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bb919364e2ff349c52686ba89992197f", + "content-hash": "d14ecad59044ee066b708801ee5e9c2c", "packages": [ { - "name": "squizlabs/php_codesniffer", - "version": "2.8.1", + "name": "paragonie/random_compat", + "version": "v9.99.100", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d" + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", - "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.1.2" + "php": ">= 7" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, - "bin": [ - "scripts/phpcs", - "scripts/phpcbf" + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.9.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/ffa80ab953edd85d5b6c004f96181a538aad35a3", + "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3", + "shasum": "" + }, + "require": { + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | ^9.99.99", + "php": "^5.4 | ^7.0 | ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | >=2.1.0 <=2.3.2", + "mockery/mockery": "^0.9.11 | ^1", + "moontoast/math": "^1.1", + "nikic/php-parser": "<=4.5.0", + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1 | ^2.6", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpunit/phpunit": ">=4.8.36 <9.0.0 | >=9.3.0", + "squizlabs/php_codesniffer": "^3.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "CodeSniffer.php", - "CodeSniffer/CLI.php", - "CodeSniffer/Exception.php", - "CodeSniffer/File.php", - "CodeSniffer/Fixer.php", - "CodeSniffer/Report.php", - "CodeSniffer/Reporting.php", - "CodeSniffer/Sniff.php", - "CodeSniffer/Tokens.php", - "CodeSniffer/Reports/", - "CodeSniffer/Tokenizers/", - "CodeSniffer/DocGenerators/", - "CodeSniffer/Standards/AbstractPatternSniff.php", - "CodeSniffer/Standards/AbstractScopeSniff.php", - "CodeSniffer/Standards/AbstractVariableSniff.php", - "CodeSniffer/Standards/IncorrectPatternException.php", - "CodeSniffer/Standards/Generic/Sniffs/", - "CodeSniffer/Standards/MySource/Sniffs/", - "CodeSniffer/Standards/PEAR/Sniffs/", - "CodeSniffer/Standards/PSR1/Sniffs/", - "CodeSniffer/Standards/PSR2/Sniffs/", - "CodeSniffer/Standards/Squiz/Sniffs/", - "CodeSniffer/Standards/Zend/Sniffs/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "lead" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", "keywords": [ - "phpcs", - "standards" + "guid", + "identifier", + "uuid" ], - "time": "2017-03-01T22:17:45+00:00" + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "rss": "https://github.com/ramsey/uuid/releases.atom", + "source": "https://github.com/ramsey/uuid", + "wiki": "https://github.com/ramsey/uuid/wiki" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2021-09-25T23:07:42+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" } ], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1 || ^8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -129,40 +286,66 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "homepage": "https://ocramius.github.io/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.6.1", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { "DeepCopy\\": "src/DeepCopy/" } @@ -172,7 +355,6 @@ "MIT" ], "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", "keywords": [ "clone", "copy", @@ -180,32 +362,99 @@ "object", "object graph" ], - "time": "2017-04-12T18:52:22+00:00" + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.15.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + }, + "time": "2022-09-04T07:30:47+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -235,24 +484,28 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -282,253 +535,236 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0", + "name": "phpstan/phpstan", + "version": "1.8.6", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c386ab2741e64cc9e21729f891b28b2b10fe6618", + "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2|^8.0" }, - "require-dev": { - "phpunit/phpunit": "^4.6" + "conflict": { + "phpstan/phpstan-shim": "*" }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", + "dev", "static analysis" ], - "time": "2015-12-27T11:43:31+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "shasum": "" + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.8.6" }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "time": "2022-09-23T09:54:39+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "name": "phpunit/php-code-coverage", + "version": "9.2.17", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", "shasum": "" }, "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.14", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "9.2-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "time": "2016-11-25T06:54:22+00:00" + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-08-30T12:24:04+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.7.0", + "name": "phpunit/php-file-iterator", + "version": "3.0.6", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1|^2.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "php": ">=7.3" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2017-03-02T20:05:34+00:00" + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "5.1.1", + "name": "phpunit/php-invoker", + "version": "3.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "bc433b7af27e0ab9b6b4c6d8ec918a493875f6bc" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bc433b7af27e0ab9b6b4c6d8ec918a493875f6bc", - "reference": "bc433b7af27e0ab9b6b4c6d8ec918a493875f6bc", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.11 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^2.0", - "sebastian/version": "^2.0" + "php": ">=7.3" }, "require-dev": { - "ext-xdebug": "^2.5", - "phpunit/phpunit": "^6.0" + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1.x-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -543,40 +779,51 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "coverage", - "testing", - "xunit" + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-04-12T07:59:32+00:00" + "time": "2020-09-28T05:58:55+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "1.4.2", + "name": "phpunit/php-text-template", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -591,36 +838,53 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "filesystem", - "iterator" + "template" ], - "time": "2016-10-03T07:40:28+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" }, { - "name": "phpunit/php-text-template", - "version": "1.2.1", + "name": "phpunit/php-timer", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -637,40 +901,83 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "template" + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2020-10-26T13:16:10+00:00" }, { - "name": "phpunit/php-timer", - "version": "1.0.9", + "name": "phpunit/phpunit", + "version": "9.5.25", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "9.5-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -682,42 +989,61 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "timer" + "phpunit", + "testing", + "xunit" ], - "time": "2017-02-26T11:10:40+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2022-09-25T03:44:45+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "1.4.11", + "name": "sebastian/cli-parser", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -732,73 +1058,48 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-02-27T10:12:30+00:00" + "time": "2020-09-28T06:08:49+00:00" }, { - "name": "phpunit/phpunit", - "version": "6.1.0", + "name": "sebastian/code-unit", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2aa57c530381662b01c2cf705b03e8c12e918f1d" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2aa57c530381662b01c2cf705b03e8c12e918f1d", - "reference": "2aa57c530381662b01c2cf705b03e8c12e918f1d", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "^1.3", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.0", - "phpunit/php-file-iterator": "^1.4", - "phpunit/php-text-template": "^1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^4.0", - "sebastian/comparator": "^2.0", - "sebastian/diff": "^1.2", - "sebastian/environment": "^2.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^1.1 || ^2.0", - "sebastian/object-enumerator": "^3.0.2", - "sebastian/resource-operations": "^1.0", - "sebastian/version": "^2.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" + "php": ">=7.3" }, "require-dev": { - "ext-pdo": "*" + "phpunit/phpunit": "^9.3" }, - "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" - }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1.x-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -817,48 +1118,44 @@ "role": "lead" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-04-07T04:45:38+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { - "name": "phpunit/phpunit-mock-objects", - "version": "4.0.1", + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "eabce450df194817a7d7e27e19013569a903a2bf" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/eabce450df194817a7d7e27e19013569a903a2bf", - "reference": "eabce450df194817a7d7e27e19013569a903a2bf", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^3.0" - }, - "conflict": { - "phpunit/phpunit": "<6.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-soap": "*" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -873,42 +1170,49 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-03-03T06:30:20+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "name": "sebastian/comparator", + "version": "4.0.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -920,42 +1224,68 @@ "license": [ "BSD-3-Clause" ], - "authors": [ + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { - "name": "sebastian/comparator", - "version": "2.0.0", + "name": "sebastian/complexity", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "20f84f468cb67efee293246e6a09619b891f55f0" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/20f84f468cb67efee293246e6a09619b891f55f0", - "reference": "20f84f468cb67efee293246e6a09619b891f55f0", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^1.2", - "sebastian/exporter": "^3.0" + "nikic/php-parser": "^4.7", + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -968,56 +1298,51 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-03-03T06:26:08+00:00" + "time": "2020-10-26T15:52:27+00:00" }, { "name": "sebastian/diff", - "version": "1.4.1", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1030,46 +1355,62 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2015-12-08T07:14:41+00:00" + "time": "2020-10-26T13:10:38+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -1094,34 +1435,44 @@ "environment", "hhvm" ], - "time": "2016-11-26T07:53:53+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1134,6 +1485,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -1142,46 +1497,55 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "ext-dom": "*", + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -1189,7 +1553,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1212,34 +1576,101 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.2", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "31dd3379d16446c5d86dec32ab1ad1f378581ad8" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/31dd3379d16446c5d86dec32ab1ad1f378581ad8", - "reference": "31dd3379d16446c5d86dec32ab1ad1f378581ad8", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.0", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1259,32 +1690,42 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-03-12T15:17:29+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1304,32 +1745,42 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.0", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1342,14 +1793,14 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { "name": "Adam Harvey", "email": "aharvey@php.net" @@ -1357,29 +1808,42 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1399,29 +1863,95 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1442,57 +1972,123 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { - "name": "webmozart/assert", - "version": "1.2.0", + "name": "squizlabs/php_codesniffer", + "version": "3.7.1", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "3.x-dev" } }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2022-06-18T07:21:10+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [], @@ -1501,7 +2097,11 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7" + "php": ">=8" + }, + "platform-dev": [], + "platform-overrides": { + "php": "8" }, - "platform-dev": [] + "plugin-api-version": "2.3.0" } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0719bd6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.7' + +services: + php: + build: docker/php-cli + tty: true + working_dir: /code + restart: on-failure + volumes: + - ./:/code + - ./docker/php-cli/php.ini:/usr/local/etc/php/php.ini:ro diff --git a/docker/php-cli/Dockerfile b/docker/php-cli/Dockerfile new file mode 100644 index 0000000..f603d61 --- /dev/null +++ b/docker/php-cli/Dockerfile @@ -0,0 +1,25 @@ +FROM php:8-cli-alpine + +RUN apk update \ + && apk add git \ + curl \ + zip \ + unzip \ + vim + +RUN apk add --no-cache $PHPIZE_DEPS \ + && pecl install -f xdebug \ + && docker-php-ext-enable xdebug \ + && rm -rf /var/lib/apt/lists/* + +# Install Composer +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ + && composer --version + +# Set timezone +ENV TIMEZONE=Etc/UCT +RUN ln -snf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && echo ${TIMEZONE} > /etc/timezone \ + && printf '[PHP]\ndate.timezone = "%s"\n', ${TIMEZONE} > /usr/local/etc/php/conf.d/tzone.ini \ + && "date" + +WORKDIR /code diff --git a/docker/php-cli/php.ini b/docker/php-cli/php.ini new file mode 100644 index 0000000..4cc5da2 --- /dev/null +++ b/docker/php-cli/php.ini @@ -0,0 +1,12 @@ +date.timezone = ${TIMEZONE} +short_open_tag = Off +log_errors = On +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT +display_errors = Off +error_log = /proc/self/fd/2 +memory_limit = -1 + +; Optimizations for Symfony, as documented on http://symfony.com/doc/current/performance.html +opcache.max_accelerated_files = 20000 +realpath_cache_size = 4096K +realpath_cache_ttl = 600 diff --git a/main.php b/main.php index cee99de..a6cb138 100644 --- a/main.php +++ b/main.php @@ -3,7 +3,6 @@ require "./vendor/autoload.php"; use TicTacToe\Application; -use TicTacToe\IO\IOHandler; -$game = new Application(new IOHandler()); +$game = new Application(); $game->run(); diff --git a/phpcs.xml b/phpcs.xml index dbf467b..288bd4c 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -5,6 +5,7 @@ + tests/* */Test/* */vendor/* */coverage/* diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..c2b7eeb --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 7 + excludePaths: + # only because of the refactoring + - src/TicTacToe/Application.php + - src/TicTacToe/Board.php \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index e15153a..868e457 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,19 +1,16 @@ - - - - ./src/TicTacToe/Test/ - - - - - ./src/TicTacToe/ - - ./src/TicTacToe/Test/ - - - - ./vendor/ - - + + + + ./src/TicTacToe/ + + + ./vendor/ + + + + + ./tests/unit + + diff --git a/src/TicTacToe/Application.php b/src/TicTacToe/Application.php index c79859e..a7aeebc 100644 --- a/src/TicTacToe/Application.php +++ b/src/TicTacToe/Application.php @@ -2,68 +2,34 @@ namespace TicTacToe; -use TicTacToe\Board; -use TicTacToe\IO\IOHandler; -use TicTacToe\Player\HumanPlayer; +use TicTacToe\Engine\Infrastructure\CommandBus\GameCommandHandlerFactory; +use TicTacToe\Engine\Infrastructure\Persistence\InMemory\InMemoryEventStore; +use TicTacToe\Engine\Presentation\Console\ConsoleInput; +use TicTacToe\Engine\Presentation\Console\ConsoleOutput; +use TicTacToe\Engine\Presentation\Input; +use TicTacToe\Engine\Presentation\Output; +use TicTacToe\Shared\Infrastructure\CommandBus\CommandBusFactory; +use TicTacToe\Shared\Infrastructure\EventStore; class Application { - /** - * Maximum players for this game - * - * @const integer - */ - const MAX_PLAYERS = 2; - - /** - * String padding lenght used to show - * test in cli - * - * @const integer - */ - const STRING_PADDING_LENGTH = 64; - - /** - * Board instance - * - * @var Board - */ - protected $board = null; - - /** - * IOHandler for handling - * input from STDIN - * and output to STDOUT - * - * @var IOHandler - */ - protected $ioHandler = null; + private CommandBusFactory $commandBusFactory; - /** - * Array of players - * - * @var array - */ - protected $players; + private EventStore $eventStore; - /** - * Boolean if players choose to start game - * - * @var bool - */ - protected $isStarted = false; + private Output $output; - protected $playerTurn = 0; - protected $moves = 0; + private Input $input; /** * Constructor */ - public function __construct(IOHandler $ioHandler) + public function __construct() { - $this->board = new Board(); - $this->players = array(); - $this->ioHandler = $ioHandler; + $this->eventStore = new InMemoryEventStore(); + $this->commandBusFactory = new CommandBusFactory(new GameCommandHandlerFactory(), $this->eventStore); + $this->input = new ConsoleInput(); + $this->output = new ConsoleOutput(); } /** @@ -71,14 +37,14 @@ public function __construct(IOHandler $ioHandler) * * @return void */ - protected function processInput() + protected function processInput(): void { - $keyPressed = $this->ioHandler->readString(true); + $keyPressed = $this->input->readString(); switch ($keyPressed) { case 'd': case 'D': - $this->ioHandler->writeLine($this->board->getBoard()); + $this->output->info("D pressed"); break; case '1': case '2': @@ -89,42 +55,18 @@ protected function processInput() case '7': case '8': case '9': - return $keyPressed; + $this->output->warning('number pressed pressed'); break; case 's': case 'S': - // check in players - if (!$this->isStarted) { - $this->checkInPlayers(); - } + $this->output->success('S pressed'); break; default: - $this->ioHandler->writeLine("Unidentified input.", IOHandler::WARNING); + $this->output->error("Unidentified input."); break; } } - /** - * Is game started - * - * @return bool - */ - public function isGameStarted() - { - return $this->isStarted; - } - - public function getMovesCount() - { - return $this->moves; - } - - - public function getCellValue($location) - { - return $this->board->getCellValue($location); - } - /** * Run the game * @@ -134,237 +76,8 @@ public function getCellValue($location) */ public function run() { - $this->showWelcome(); - - while (($winner = $this->board->checkForWinner()) == null) { - if (!$this->isStarted) { - $this->processInput(); - } else { - try { - $this->nextStep(); - - if ($this->isGameDraw()) { - $this->gameIsDraw(); - break; - } - } catch (\Exception $e) { - $this->ioHandler->writeLine($e->getMessage(), IOHandler::ERROR); - continue; - } - } - } - - if (!empty($winner)) { - $this->gameHasWinner($winner); - } - } - - /** - * Check if game is draw between players - * - * @return void - */ - protected function isGameDraw() - { - return ((true == $this->board->areAllCellsFilled()) && (null == $this->board->checkForWinner())); - } - - /** - * Show winner - * - * @param string $marker Winner marker - * - * @return void - */ - protected function gameHasWinner($marker) - { - $winner = $this->getPlayerByMarker($marker); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("#", "#"), IOHandler::SUCCESS); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " "), IOHandler::SUCCESS); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("Hoorrraaayyy !"), IOHandler::SUCCESS); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding($winner->getName() . " won!"), IOHandler::SUCCESS); - foreach ($this->board->getBoard() as $boardRow) { - $this->ioHandler->writeLine($this->getBorderedStringWithPadding($boardRow), IOHandler::SUCCESS); - } - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("Thank you for playing Tic-Tac-Toe"), IOHandler::SUCCESS); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " "), IOHandler::SUCCESS); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("#", "#"), IOHandler::SUCCESS); - } - - /** - * Game is ended with draw - * - * @return void - */ - protected function gameIsDraw() - { - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("#", "#")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("It is a DRAW!")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("Thank you for playing Tic-Tac-Toe.")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("#", "#")); - } - - /** - * Play - * - * @return void - */ - private function nextStep() - { - $this->playerTurn = $this->moves % count($this->players); - $player = $this->players[$this->playerTurn]; - - $this->ioHandler->write("{$player->getName()}'s turn, please enter the location: "); - $location = $this->processInput(); - - if ($location) { - $this->board->setBoardCell($location, $player->getMarker()); - $this->moves++; - } - } - - /** - * Show the welcome message - * - * @return void - */ - protected function showWelcome() - { - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("#", "#")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("Tic-Tac-Toe Game")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("#", "#")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("Welcome to Tic-Tac-Toe")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("This is a two players tic-tac-toe game.")); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - foreach ($this->board->demoBoard() as $boardRow) { - $this->ioHandler->writeLine($this->getBorderedStringWithPadding($boardRow)); + while (true) { + $this->processInput(); } - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - $this->ioHandler->writeLine("| To start playing you need to press 's/S', then enter players |"); - $this->ioHandler->writeLine("| details (name and marker). If at anytime you want to draw the |"); - $this->ioHandler->writeLine("| board press 'd/D'. |"); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding(" ", " ")); - $this->ioHandler->writeLine("| Enjoy! |"); - $this->ioHandler->writeLine($this->getBorderedStringWithPadding("_", "_")); - $this->ioHandler->writeLine(); - $this->ioHandler->write("Press 's/S' to start the game... ", IOHandler::LIGHT_CYAN); - } - - /** - * Prompt to make players enter their name and - * choose their markers. - * - * @return void - */ - public function checkInPlayers() - { - $this->isStarted = true; - $playersCheckedIn = 0; - $player = array( - 'name' => null, - 'marker' => null - ); - while ($playersCheckedIn < self::MAX_PLAYERS) { - try { - if (!isset($player['name'])) { - $this->ioHandler->write("What is the name of Player " . ($playersCheckedIn + 1) . ": "); - $player['name'] = $this->ioHandler->readString(); - } - - if (!isset($player['marker'])) { - $this->ioHandler->write("Please choose your Tic-Tac-Toe marker i.e. 'x', 'o': "); - $marker = $this->ioHandler->readString(true); - if (!$this->isMarkerTaken($marker)) { - $this->players[] = new HumanPlayer($marker, $player['name']); - $player['marker'] = $marker; - } - } - } catch (\Exception $e) { - $this->ioHandler->writeLine($e->getMessage(), IOHandler::ERROR); - continue; - } - - $playersCheckedIn++; - unset($player); - } - } - - /** - * Get players - * - * @return array - */ - public function getPlayers() - { - return $this->players; - } - - /** - * Get Player by marker - * - * @return TicTacToe\Player\HumanPlayer - */ - protected function getPlayerByMarker($marker) - { - foreach ($this->players as $player) { - if ($marker === $player->getMarker()) { - return $player; - } - } - - return null; - } - - /** - * Check if marker is already chosen - * by another player. - * - * @param string $marker marker to check - * - * @throws \RuntimeException in case of marker already taken - * @return true if not taken, false otherwise - */ - private function isMarkerTaken($marker) - { - foreach ($this->players as $player) { - if ($marker === $player->getMarker()) { - throw new \RuntimeException("Sorry! '{$marker}' is already taken."); - } - } - - return false; - } - - /** - * Get a RIGHT and LEFT padded string with - * left and right border. - * - * @param string $string String to output - * @param string $padding Optional. Specifies the string to use for padding. Default is whitespace - * - * @return string - */ - private function getBorderedStringWithPadding($string, $padding = " ") - { - return "|" . $this->getPaddedStringForOutput($string, $padding) . "|"; - } - - /** - * Get a RIGHT and LEFT padded string used - * in showing Welcome, Game draw and Game win - * - * @param string $string String to output - * @param string $padding Optional. Specifies the string to use for padding. Default is whitespace - * - * @return string - */ - private function getPaddedStringForOutput($string, $padding = " ") - { - return str_pad($string, self::STRING_PADDING_LENGTH, $padding, STR_PAD_BOTH); } } diff --git a/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameCommand.php b/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameCommand.php new file mode 100644 index 0000000..db80400 --- /dev/null +++ b/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameCommand.php @@ -0,0 +1,39 @@ + */ + private array $playerIds; + + private int $boardSize; + + /** + * @param array $playerIds + * @param int $boardSize + */ + public function __construct(array $playerIds, int $boardSize) + { + $this->playerIds = $playerIds; + $this->boardSize = $boardSize; + } + + /** + * @return array + */ + public function playerIds(): array + { + return $this->playerIds; + } + + public function boardSize(): int + { + return $this->boardSize; + } +} diff --git a/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameHandler.php b/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameHandler.php new file mode 100644 index 0000000..015630f --- /dev/null +++ b/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameHandler.php @@ -0,0 +1,60 @@ +gameRepository = $gameRepository; + } + + /** + * @inheritDoc + */ + public function handles(): string + { + return CreateNewGameCommand::class; + } + + /** + * @param CreateNewGameCommand $command + * @inheritDoc + * @throws SorryBoardSizeIsNotValid + */ + public function handle(Command $command): DataTransformer + { + if (!$command instanceof CreateNewGameCommand) { + throw new SorryWrongCommand(); + } + + $playerIds = array_map(function (string $playerId) { + return new PlayerId($playerId); + }, $command->playerIds()); + + $game = Game::start( + new GameId(), + new Board($command->boardSize()), + ...$playerIds + ); + + $this->gameRepository->add($game); + + return new CreateNewGameResponseDto($game); + } +} diff --git a/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameResponseDto.php b/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameResponseDto.php new file mode 100644 index 0000000..15938f0 --- /dev/null +++ b/src/TicTacToe/Engine/Application/CreateNewGame/CreateNewGameResponseDto.php @@ -0,0 +1,65 @@ +gameId = (string) $game->id(); + $this->boardSize = $game->board()->size(); + $this->playerIds = $game->playerIds()->toArray(); + $this->createdAt = $game->createdAt(); + } + + public function gameId(): string + { + return $this->gameId; + } + + public function boardSize(): int + { + return $this->boardSize; + } + + /** + * @return PlayerId[] + */ + public function playerIds(): array + { + return $this->playerIds; + } + + public function createdAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + /** + * @return array|int|string> + */ + public function toArray(): array + { + return [ + 'id' => $this->gameId, + 'boardSize' => $this->boardSize, + 'playerIds' => $this->playerIds, + 'createdAt' => $this->createdAt->format(\DateTimeImmutable::ATOM) + ]; + } +} diff --git a/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerCommand.php b/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerCommand.php new file mode 100644 index 0000000..a0c80c1 --- /dev/null +++ b/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerCommand.php @@ -0,0 +1,30 @@ +name = $name; + $this->token = $token; + } + + public function name(): string + { + return $this->name; + } + + public function token(): string + { + return $this->token; + } +} diff --git a/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerHandler.php b/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerHandler.php new file mode 100644 index 0000000..d3d86b6 --- /dev/null +++ b/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerHandler.php @@ -0,0 +1,46 @@ +playerRepository = $playerRepository; + } + + /** + * @inheritDoc + */ + public function handles(): string + { + return CreateNewPlayerCommand::class; + } + + /** + * @inheritDoc + */ + public function handle(Command $command): DataTransformer + { + if (!$command instanceof CreateNewPlayerCommand) { + throw new SorryWrongCommand(); + } + $player = Player::createPlayerWithToken($command->name(), $command->token()); + + $this->playerRepository->add($player); + + return new CreateNewPlayerResponseDto($player); + } +} diff --git a/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerResponseDto.php b/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerResponseDto.php new file mode 100644 index 0000000..bd35b80 --- /dev/null +++ b/src/TicTacToe/Engine/Application/CreateNewPlayer/CreateNewPlayerResponseDto.php @@ -0,0 +1,60 @@ +playerId = (string) $player->id(); + $this->playerToken = (string) $player->playingToken(); + $this->playerName = $player->name(); + $this->createdAt = $player->createdAt(); + } + + public function playerId(): string + { + return $this->playerId; + } + + public function playerName(): string + { + return $this->playerName; + } + + public function playerToken(): string + { + return $this->playerToken; + } + + public function createdAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'id' => $this->playerId, + 'name' => $this->playerName, + 'token' => $this->playerToken, + 'createdAt' => $this->createdAt->format(\DateTimeImmutable::ATOM) + ]; + } +} diff --git a/src/TicTacToe/Engine/Application/PlayNewTurn/PlayNewTurnCommand.php b/src/TicTacToe/Engine/Application/PlayNewTurn/PlayNewTurnCommand.php new file mode 100644 index 0000000..a138ed2 --- /dev/null +++ b/src/TicTacToe/Engine/Application/PlayNewTurn/PlayNewTurnCommand.php @@ -0,0 +1,43 @@ +playerId = $playerId; + $this->position = $position; + $this->gameId = $gameId; + } + + public function gameId(): string + { + return $this->gameId; + } + + public function playerId(): string + { + return $this->playerId; + } + + public function position(): int + { + return $this->position; + } +} diff --git a/src/TicTacToe/Engine/Application/PlayNewTurn/PlayNewTurnHandler.php b/src/TicTacToe/Engine/Application/PlayNewTurn/PlayNewTurnHandler.php new file mode 100644 index 0000000..9f17559 --- /dev/null +++ b/src/TicTacToe/Engine/Application/PlayNewTurn/PlayNewTurnHandler.php @@ -0,0 +1,77 @@ +turnRepository = $turnRepository; + $this->playerRepository = $playerRepository; + $this->gameRepository = $gameRepository; + } + + /** + * @inheritDoc + */ + public function handles(): string + { + return PlayNewTurnCommand::class; + } + + /** + * @inheritDoc + */ + public function handle(Command $command): DataTransformer + { + if (!$command instanceof PlayNewTurnCommand) { + throw new SorryWrongCommand(); + } + + $playerId = $command->playerId(); + $player = $this->playerRepository->ofId(new PlayerId($playerId)); + if ($player === null) { + throw new SorryPlayerNotFound("Player of Id {$playerId} not found."); + } + + $gameId = $command->gameId(); + $game = $this->gameRepository->ofId(new GameId($gameId)); + if ($game === null) { + throw new SorryGameNotFound("Game of Id {$gameId} not found."); + } + + $turn = $player->play($game->id(), new Position($command->position())); + + $game->setBoardPiece( + $turn + ); + + $this->turnRepository->add($turn); + + return new EmptyResponseDto(); + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Board/Board.php b/src/TicTacToe/Engine/Domain/Model/Board/Board.php new file mode 100644 index 0000000..34e0b4f --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Board/Board.php @@ -0,0 +1,68 @@ + */ + private array $cells; + + private int $size; + + /** + * @throws SorryBoardSizeIsNotValid + */ + public function __construct(int $size) + { + if ($size != self::BOARD_SIZE_3) { + throw new SorryBoardSizeIsNotValid("Only allowed size for board is: " . self::BOARD_SIZE_3); + } + + $this->cells = []; + $this->size = $size; + $this->initBoard(); + } + + public function size(): int + { + return $this->size; + } + + private function initBoard(): void + { + /** @var int $position */ + foreach (Position::positions() as $position) { + $this->cells[$position] = Cell::empty(); + } + } + + public function isFull(): bool + { + /** @var Cell $cell */ + foreach ($this->cells as $cell) { + if ($cell->isEmpty()) { + return false; + } + } + + return true; + } + + public function setCell(Position $position, Cell $cell): void + { + $this->cells[$position->position()] = $cell; + } + + public static function create3By3Board(): Board + { + return new self( + self::BOARD_SIZE_3 + ); + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Board/Cell.php b/src/TicTacToe/Engine/Domain/Model/Board/Cell.php new file mode 100644 index 0000000..f29db51 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Board/Cell.php @@ -0,0 +1,37 @@ +value = $value; + } + + public function __toString(): string + { + return $this->value; + } + + public function isEmpty(): bool + { + return empty($this->value); + } + + public static function empty(): Cell + { + return new Cell(); + } + + public static function createFromPlayerToken(PlayerToken $playerToken): Cell + { + return new Cell((string) $playerToken); + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Board/Exception/SorryBoardSizeIsNotValid.php b/src/TicTacToe/Engine/Domain/Model/Board/Exception/SorryBoardSizeIsNotValid.php new file mode 100644 index 0000000..7effd9f --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Board/Exception/SorryBoardSizeIsNotValid.php @@ -0,0 +1,9 @@ +position = $position; + } + + public function position(): int + { + return $this->position; + } + + /** + * @return array + */ + public static function positions(): array + { + return self::VALID_POSITIONS; + } + + public static function isValid(int $position): bool + { + if (!in_array($position, self::VALID_POSITIONS, true)) { + throw new SorryInvalidPosition('Out of boundary position.'); + } + + return true; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/Event/GameCreated.php b/src/TicTacToe/Engine/Domain/Model/Game/Event/GameCreated.php new file mode 100644 index 0000000..a4ca3b0 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/Event/GameCreated.php @@ -0,0 +1,45 @@ +game = $game; + } + + /** + * @inheritDoc + */ + public function occurredAt(): \DateTimeImmutable + { + return $this->game->createdAt(); + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return [ + 'gameId' => (string) $this->game->id(), + 'createdAt' => $this->game->createdAt()->format(\DateTimeImmutable::ATOM) + ]; + } + + /** + * @inheritDoc + */ + public function name(): string + { + return GameCreated::class; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/Event/PlayerTurnPlayed.php b/src/TicTacToe/Engine/Domain/Model/Game/Event/PlayerTurnPlayed.php new file mode 100644 index 0000000..abdecc0 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/Event/PlayerTurnPlayed.php @@ -0,0 +1,46 @@ +turn = $turn; + } + + /** + * @inheritDoc + */ + public function occurredAt(): \DateTimeImmutable + { + return $this->turn->createdAt(); + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'turnId' => (string) $this->turn->moveId(), + 'madeBy' => (string) $this->turn->madeBy()->id(), + 'position' => $this->turn->position()->position(), + 'createdAt' => $this->turn->createdAt()->format(\DateTimeImmutable::ATOM) + ]; + } + + /** + * @inheritDoc + */ + public function name(): string + { + return PlayerTurnPlayed::class; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/Exception/SorryGameNotFound.php b/src/TicTacToe/Engine/Domain/Model/Game/Exception/SorryGameNotFound.php new file mode 100644 index 0000000..8e40a01 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/Exception/SorryGameNotFound.php @@ -0,0 +1,8 @@ + self::MAX_PLAYERS) { + throw new SorryTooManyPlayers("Only " . self::MAX_PLAYERS . " players allowed."); + } + + $this->id = $gameId; + $this->board = $board; + $this->playerIds = new PlayerIdSet(...$playerIds); + $this->createdAt = new \DateTimeImmutable(); + } + + public static function start(GameId $gameId, Board $board, PlayerId ...$playerIds): self + { + $game = new self($gameId, $board, ...$playerIds); + + $game->record( + new GameCreated($game) + ); + + return $game; + } + + public function setBoardPiece(Turn $turn): void + { + $this->board->setCell($turn->position(), $turn->cell()); + } + + public function id(): GameId + { + return $this->id; + } + + public function playerIds(): PlayerIdSet + { + return $this->playerIds; + } + + public function board(): Board + { + return $this->board; + } + + public function createdAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/GameId.php b/src/TicTacToe/Engine/Domain/Model/Game/GameId.php new file mode 100644 index 0000000..3470948 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/GameId.php @@ -0,0 +1,27 @@ +id = null === $id ? Uuid::uuid4()->toString() : $id; + } + + public function equals(GameId $gameId): bool + { + return $this->id === (string) $gameId; + } + + public function __toString(): string + { + return $this->id; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/GameRepository.php b/src/TicTacToe/Engine/Domain/Model/Game/GameRepository.php new file mode 100644 index 0000000..9b352d9 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/GameRepository.php @@ -0,0 +1,30 @@ + + */ + public function games(): array; +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/Turn.php b/src/TicTacToe/Engine/Domain/Model/Game/Turn.php new file mode 100644 index 0000000..8f61149 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/Turn.php @@ -0,0 +1,68 @@ +gameId = $gameId; + $this->moveId = $moveId; + $this->cell = $cell; + $this->madeBy = $madeBy; + $this->position = $position; + $this->createdAt = new \DateTimeImmutable(); + } + + public function gameId(): GameId + { + return $this->gameId; + } + + public function moveId(): TurnId + { + return $this->moveId; + } + + public function madeBy(): Player + { + return $this->madeBy; + } + + public function cell(): Cell + { + return $this->cell; + } + + public function position(): Position + { + return $this->position; + } + + public function createdAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/TurnId.php b/src/TicTacToe/Engine/Domain/Model/Game/TurnId.php new file mode 100644 index 0000000..25fe803 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/TurnId.php @@ -0,0 +1,26 @@ +id = null === $id ? Uuid::uuid4()->toString() : $id; + } + + public function equals(TurnId $moveId): bool + { + return $this->id === (string)$moveId; + } + + public function __toString(): string + { + return $this->id; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Game/TurnRepository.php b/src/TicTacToe/Engine/Domain/Model/Game/TurnRepository.php new file mode 100644 index 0000000..94a3efc --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Game/TurnRepository.php @@ -0,0 +1,28 @@ + + */ + public function turns(): array; +} diff --git a/src/TicTacToe/Engine/Domain/Model/Player/Event/PlayerCreated.php b/src/TicTacToe/Engine/Domain/Model/Player/Event/PlayerCreated.php new file mode 100644 index 0000000..adc26d2 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Player/Event/PlayerCreated.php @@ -0,0 +1,47 @@ +player = $player; + } + + /** + * @inheritDoc + */ + public function occurredAt(): \DateTimeImmutable + { + return $this->player->createdAt(); + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return [ + 'playerId' => (string) $this->player->id(), + 'playerName' => $this->player->name(), + 'playerToken' => (string) $this->player->playingToken(), + 'createdAt' => $this->player->createdAt()->format(\DateTimeImmutable::ATOM) + ]; + } + + /** + * @inheritDoc + */ + public function name(): string + { + return PlayerCreated::class; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Player/Exception/SorryInvalidPlayerToken.php b/src/TicTacToe/Engine/Domain/Model/Player/Exception/SorryInvalidPlayerToken.php new file mode 100644 index 0000000..a4cc849 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Player/Exception/SorryInvalidPlayerToken.php @@ -0,0 +1,9 @@ +id = $id; + $this->name = $name; + $this->playingToken = $token; + $this->createdAt = new \DateTimeImmutable(); + } + + public static function createPlayerWithTokenX(string $name): Player + { + $player = new self( + new PlayerId(), + new PlayerName($name), + PlayerToken::createGameTokenX() + ); + + $player->record( + new PlayerCreated($player) + ); + + return $player; + } + + public static function createPlayerWithTokenY(string $name): Player + { + $player = new self( + new PlayerId(), + new PlayerName($name), + PlayerToken::createGameTokenY() + ); + + $player->record( + new PlayerCreated($player) + ); + + return $player; + } + + /** + * @throws SorryPlayerNameIsTooLong + * @throws SorryPlayerNameIsTooShort + * @throws SorryInvalidPlayerToken + */ + public static function createPlayerWithToken(string $name, string $token): Player + { + $player = new self( + new PlayerId(), + new PlayerName($name), + new PlayerToken($token) + ); + + $player->record( + new PlayerCreated($player) + ); + + return $player; + } + + public function name(): string + { + return (string) $this->name; + } + + public function playingToken(): PlayerToken + { + return $this->playingToken; + } + + public function id(): PlayerId + { + return $this->id; + } + + public function createdAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function play(GameId $gameId, Position $position): Turn + { + $turn = new Turn( + $gameId, + new TurnId(), + Cell::createFromPlayerToken($this->playingToken), + $position, + $this + ); + + $this->record( + new PlayerTurnPlayed($turn) + ); + + return $turn; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Player/PlayerId.php b/src/TicTacToe/Engine/Domain/Model/Player/PlayerId.php new file mode 100644 index 0000000..a88813f --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Player/PlayerId.php @@ -0,0 +1,27 @@ +id = null === $id ? Uuid::uuid4()->toString() : $id; + } + + public function equals(PlayerId $playerId): bool + { + return $this->id === (string) $playerId; + } + + public function __toString(): string + { + return $this->id; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Player/PlayerIdSet.php b/src/TicTacToe/Engine/Domain/Model/Player/PlayerIdSet.php new file mode 100644 index 0000000..1f2eae5 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Player/PlayerIdSet.php @@ -0,0 +1,46 @@ + + */ +class PlayerIdSet implements \Countable, \IteratorAggregate +{ + /** @var PlayerId[] */ + private array $playerIds; + + public function __construct(PlayerId ...$playerIds) + { + if (empty($playerIds)) { + throw new \InvalidArgumentException('Player Id set cannot be empty'); + } + + $this->playerIds = $playerIds; + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->playerIds; + } + + /** + * @return Iterator + */ + public function getIterator(): Iterator + { + return new \ArrayIterator($this->toArray()); + } + + public function count(): int + { + return count($this->playerIds); + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Player/PlayerName.php b/src/TicTacToe/Engine/Domain/Model/Player/PlayerName.php new file mode 100644 index 0000000..ee6c8e6 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Player/PlayerName.php @@ -0,0 +1,38 @@ + self::MAX_LENGTH) { + throw new SorryPlayerNameIsTooLong("Player name is too long, maximum " . self::MAX_LENGTH); + } + + $this->name = $name; + } + + public function __toString(): string + { + return $this->name; + } +} diff --git a/src/TicTacToe/Engine/Domain/Model/Player/PlayerRepository.php b/src/TicTacToe/Engine/Domain/Model/Player/PlayerRepository.php new file mode 100644 index 0000000..8e44f83 --- /dev/null +++ b/src/TicTacToe/Engine/Domain/Model/Player/PlayerRepository.php @@ -0,0 +1,31 @@ +token = $token; + } + + public static function createGameTokenX(): PlayerToken + { + return new self(self::X_TOKEN); + } + + public static function createGameTokenY(): PlayerToken + { + return new self(self::Y_TOKEN); + } + + public function __toString(): string + { + return $this->token; + } +} diff --git a/src/TicTacToe/Engine/Infrastructure/CommandBus/GameCommandHandlerFactory.php b/src/TicTacToe/Engine/Infrastructure/CommandBus/GameCommandHandlerFactory.php new file mode 100644 index 0000000..132d0ec --- /dev/null +++ b/src/TicTacToe/Engine/Infrastructure/CommandBus/GameCommandHandlerFactory.php @@ -0,0 +1,34 @@ + function () use ($gameRepository) { + return new CreateNewGameHandler($gameRepository); + }, + CreateNewPlayerCommand::class => function () use ($playerRepository) { + return new CreateNewPlayerHandler($playerRepository); + } + ]; + } +} diff --git a/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryEventStore.php b/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryEventStore.php new file mode 100644 index 0000000..1cf6777 --- /dev/null +++ b/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryEventStore.php @@ -0,0 +1,32 @@ +events = $events; + } + + public function store(Event $event): void + { + $this->events[] = unserialize(serialize($event)); + } + + /** + * @return Event[] + */ + public function events(): array + { + return $this->events; + } +} diff --git a/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryGameRepository.php b/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryGameRepository.php new file mode 100644 index 0000000..3436ac4 --- /dev/null +++ b/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryGameRepository.php @@ -0,0 +1,52 @@ +games[(string) $game->id()] = $game; + } + + /** + * @param GameId $gameId + * @return Game|null + */ + public function ofId(GameId $gameId): ?Game + { + if (!isset($this->games[(string) $gameId])) { + return null; + } + + return $this->games[(string) $gameId]; + } + + /** + * @return GameId + */ + public function nextIdentity(): GameId + { + return new GameId(); + } + + /** + * @return array + */ + public function games(): array + { + return $this->games; + } +} diff --git a/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryPlayerRepository.php b/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryPlayerRepository.php new file mode 100644 index 0000000..1018245 --- /dev/null +++ b/src/TicTacToe/Engine/Infrastructure/Persistence/InMemory/InMemoryPlayerRepository.php @@ -0,0 +1,42 @@ + */ + private array $players = []; + + public function add(Player $player): void + { + $this->players[(string)$player->id()] = $player; + } + + public function ofId(PlayerId $playerId): ?Player + { + if (!isset($this->players[(string) $playerId])) { + return null; + } + + return $this->players[(string) $playerId]; + } + + public function nextIdentity(): PlayerId + { + return new PlayerId(); + } + + /** + * @return array + */ + public function players(): array + { + return $this->players; + } +} diff --git a/src/TicTacToe/Engine/Presentation/Console/ConsoleInput.php b/src/TicTacToe/Engine/Presentation/Console/ConsoleInput.php new file mode 100644 index 0000000..17b77af --- /dev/null +++ b/src/TicTacToe/Engine/Presentation/Console/ConsoleInput.php @@ -0,0 +1,42 @@ +readStringFromStream(); + if (empty($input) && !$allowEmpty) { + throw new \InvalidArgumentException("Input cannot be empty."); + } + + return trim($input); + } + + public function readInteger(): int + { + $input = $this->readIntegerFromStream(); + if (!empty($input)) { + throw new \InvalidArgumentException("Input cannot be empty."); + } + + return $input; + } + + protected function readStringFromStream(): string + { + $stream = fgets(STDIN); + return trim($stream !== false ? $stream : ''); + } + + protected function readIntegerFromStream(): int + { + fscanf(STDIN, "%d\n", $number); + return $number; + } +} diff --git a/src/TicTacToe/Engine/Presentation/Console/ConsoleOutput.php b/src/TicTacToe/Engine/Presentation/Console/ConsoleOutput.php new file mode 100644 index 0000000..4479c96 --- /dev/null +++ b/src/TicTacToe/Engine/Presentation/Console/ConsoleOutput.php @@ -0,0 +1,52 @@ +getBorderedStringWithPadding($message); + } + + private function getBorderedStringWithPadding(string $string, string $padding = " "): string + { + return "|" . $this->getPaddedStringForOutput($string, $padding) . "|"; + } + + private function getPaddedStringForOutput(string $string, string $padding = " "): string + { + return str_pad($string, self::STRING_PADDING_LENGTH, $padding, STR_PAD_BOTH); + } +} diff --git a/src/TicTacToe/Engine/Presentation/Input.php b/src/TicTacToe/Engine/Presentation/Input.php new file mode 100644 index 0000000..a28a7fa --- /dev/null +++ b/src/TicTacToe/Engine/Presentation/Input.php @@ -0,0 +1,12 @@ +write($line . PHP_EOL, $type); - } - } elseif (!is_array($output)) { - $this->write($output . PHP_EOL, $type); - } - } - - public function readInteger() - { - $input = $this->readIntegerFromStream(); - if (!empty($input)) { - if (!is_integer($input)) { - throw new \InvalidArgumentException("Input is not a valid 'integer'."); - } - } - - $input = intval($input); - return $input; - } - - public function readString($allowEmpty = false) - { - $input = $this->readStringFromStream(); - if (empty($input) && !$allowEmpty) { - throw new \InvalidArgumentException("Input cannot be empty."); - } - if (!is_string($input)) { - throw new \InvalidArgumentException("Input is not a valid 'string'."); - } - - return trim($input); - } -} diff --git a/src/TicTacToe/Player/HumanPlayer.php b/src/TicTacToe/Player/HumanPlayer.php deleted file mode 100644 index 9cd89e7..0000000 --- a/src/TicTacToe/Player/HumanPlayer.php +++ /dev/null @@ -1,9 +0,0 @@ -isAllowedMarker($marker)) { - $this->marker = $marker; - } - - if (empty($name)) { - $this->name = "Player_" . $marker; - } else { - $this->name = $name; - } - - $this->marker = $marker; - } - - /** - * Checks if the player choose 'x' or 'o' as - * his/her marker. - * - * @param char $marker Makrer player chose - * - * @return true if yes false otherwise - */ - protected function isAllowedMarker($marker) - { - if (in_array(strtolower($marker), $this->allowedMarkers)) { - return true; - } - - throw new \InvalidArgumentException("Only 'x' or 'y' are allowed as markers."); - } - - public function getName() - { - return $this->name; - } - - public function getMarker() - { - return $this->marker; - } -} diff --git a/src/TicTacToe/Player/PlayerInterface.php b/src/TicTacToe/Player/PlayerInterface.php deleted file mode 100644 index b1c3c63..0000000 --- a/src/TicTacToe/Player/PlayerInterface.php +++ /dev/null @@ -1,20 +0,0 @@ - + */ + public function toArray(): array; +} diff --git a/src/TicTacToe/Shared/Application/EmptyResponseDto.php b/src/TicTacToe/Shared/Application/EmptyResponseDto.php new file mode 100644 index 0000000..8983c51 --- /dev/null +++ b/src/TicTacToe/Shared/Application/EmptyResponseDto.php @@ -0,0 +1,13 @@ +supports($command)) { + throw new SorryCommandHandlerNotFound($commandName); + } + + $callBack = $handlers[$commandName]; + return $callBack(); + } + + public function supports(Command $command): bool + { + $handlers = $this::commandHandlers(); + $commandName = get_class($command); + + return array_key_exists($commandName, $handlers); + } + + /** + * @return \Closure[] + */ + abstract protected static function commandHandlers(): array; +} diff --git a/src/TicTacToe/Shared/Domain/Model/AggregateRoot.php b/src/TicTacToe/Shared/Domain/Model/AggregateRoot.php new file mode 100644 index 0000000..04faae3 --- /dev/null +++ b/src/TicTacToe/Shared/Domain/Model/AggregateRoot.php @@ -0,0 +1,34 @@ +events = []; + } + + /** + * @return Event[] + */ + public function getRecordedEvents(): array + { + return $this->events; + } + + public function resetRecordedEvents(): void + { + $this->events = []; + } + + protected function record(Event $event): void + { + $this->events[] = $event; + } +} diff --git a/src/TicTacToe/Shared/Domain/Model/Event.php b/src/TicTacToe/Shared/Domain/Model/Event.php new file mode 100644 index 0000000..ad45c6a --- /dev/null +++ b/src/TicTacToe/Shared/Domain/Model/Event.php @@ -0,0 +1,29 @@ + + */ + public function toArray(): array; + + /** + * Get event name + * + * @return string + */ + public function name(): string; +} diff --git a/src/TicTacToe/Shared/Infrastructure/CommandBus/CommandBus.php b/src/TicTacToe/Shared/Infrastructure/CommandBus/CommandBus.php new file mode 100644 index 0000000..4ee066c --- /dev/null +++ b/src/TicTacToe/Shared/Infrastructure/CommandBus/CommandBus.php @@ -0,0 +1,23 @@ +commandHandlerFactory = $commandHandlerFactory; + $this->eventStore = $eventStore; + } + + /** + * @param Command $command + * @return DataTransformer + */ + public function handle(Command $command): DataTransformer + { + $handler = $this->commandHandlerFactory->create($command); + return $handler->handle($command); + } + + public function events(): array + { + return $this->eventStore->events(); + } +} diff --git a/src/TicTacToe/Shared/Infrastructure/CommandBus/Exception/SorryCommandHandlerNotFound.php b/src/TicTacToe/Shared/Infrastructure/CommandBus/Exception/SorryCommandHandlerNotFound.php new file mode 100644 index 0000000..edecc97 --- /dev/null +++ b/src/TicTacToe/Shared/Infrastructure/CommandBus/Exception/SorryCommandHandlerNotFound.php @@ -0,0 +1,17 @@ +createMock(IOHandler::class); - - $ioHandlerStub->expects($this->exactly(4)) - ->method('readstring') - ->will($this->onConsecutiveCalls('foo','x','baar','o')); - - - $application = new Application($ioHandlerStub); - $application->checkInPlayers(); - - $players = $application->getPlayers(); - - $this->assertEquals(2, count($players)); - - $this->assertEquals('foo', $players[0]->getName()); - $this->assertEquals('x', $players[0]->getMarker()); - $this->assertEquals('baar', $players[1]->getName()); - $this->assertEquals('o', $players[1]->getMarker()); - } - - public function testNextStepSuccess() - { - $ioHandlerStub = $this->createMock(IOHandler::class); - - $ioHandlerStub->expects($this->exactly(5)) - ->method('readstring') - ->will($this->onConsecutiveCalls('foo','x','baar','o', '1')); - - - $application = new Application($ioHandlerStub); - $application->checkInPlayers(); - - $players = $application->getPlayers(); - - $this->assertEquals(0, $application->getMovesCount()); - $this->assertEquals(2, count($players)); - - $this->assertEquals('foo', $players[0]->getName()); - $this->assertEquals('x', $players[0]->getMarker()); - $this->assertEquals('baar', $players[1]->getName()); - $this->assertEquals('o', $players[1]->getMarker()); - - $this->invokeMethod($application, 'nextStep'); - $this->assertEquals(1, $application->getMovesCount()); - $this->assertNotNull($application->getCellValue(1)); - } - - public function testCheckInPlayersFailureAndRetrial() - { - $ioHandlerStub = $this->createMock(IOHandler::class); - - $ioHandlerStub->expects($this->any()) - ->method('readString') - ->will($this->onConsecutiveCalls('foo','x','baar','x', 'o')); - - $application = new Application($ioHandlerStub); - - $application->checkInPlayers(); - $players = $application->getPlayers(); - - $this->assertEquals(2, count($players)); - - $this->assertEquals('foo', $players[0]->getName()); - $this->assertEquals('x', $players[0]->getMarker()); - $this->assertEquals('baar', $players[1]->getName()); - $this->assertEquals('o', $players[1]->getMarker()); - } - - public function testGetPlayerByMarkerSuccess() - { - $ioHandlerStub = $this->createMock(IOHandler::class); - - $ioHandlerStub->expects($this->any()) - ->method('readString') - ->will($this->onConsecutiveCalls('foo','x','baar','x', 'o')); - - $application = new Application($ioHandlerStub); - - $application->checkInPlayers(); - $player = $this->invokeMethod($application, 'getPlayerByMarker', array('x')); - - $this->assertEquals('foo', $player->getName()); - $this->assertEquals('x', $player->getMarker()); - } - - public function testGetPlayerByMarkerReturnNull() - { - $ioHandlerStub = $this->createMock(IOHandler::class); - - $ioHandlerStub->expects($this->any()) - ->method('readString') - ->will($this->onConsecutiveCalls('foo','x','baar','o')); - - $application = new Application($ioHandlerStub); - - $application->checkInPlayers(); - $player = $this->invokeMethod($application, 'getPlayerByMarker', array('i')); - - $this->assertNull($player); - } - - public function testGetBorderedStringWithPadding() - { - $expected = "|############################foobaar#############################|"; - - $ioHandler = new IOHandler(); - $application = new Application($ioHandler); - $result = $this->invokeMethod($application, 'getBorderedStringWithPadding', array('foobaar', '#')); - - $this->assertEquals($expected, $result); - $this->assertEquals(66, strlen($result)); - } - - public function testGetPaddedStringForOutput() - { - $expected = "############################foobaar#############################"; - - $ioHandler = new IOHandler(); - $application = new Application($ioHandler); - $result = $this->invokeMethod($application, 'getPaddedStringForOutput', array('foobaar', '#')); - - $this->assertEquals($expected, $result); - $this->assertEquals(64, strlen($result)); - } - - public function testShowWelcome() - { - $expected = sprintf(IOHandler::INFO, "|################################################################|" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| Tic-Tac-Toe Game |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "|################################################################|" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| Welcome to Tic-Tac-Toe |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| This is a two players tic-tac-toe game. |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| 1 | 2 | 3 |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| ---|---|--- |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| 4 | 5 | 6 |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| ---|---|--- |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| 7 | 8 | 9 |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| To start playing you need to press 's/S', then enter players |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| details (name and marker). If at anytime you want to draw the |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| board press 'd/D'. |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "| Enjoy! |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "|________________________________________________________________|" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, PHP_EOL); - $expected .= sprintf(IOHandler::LIGHT_CYAN, "Press 's/S' to start the game... "); - - $ioHandler = new IOHandler(); - $application = new Application($ioHandler); - - $this->expectOutputString($expected); - $this->invokeMethod($application, 'showWelcome'); - } - - public function testIsGameDrawFalse() - { - $ioHandler = new IOHandler(); - $application = new Application($ioHandler); - - $result = $this->invokeMethod($application, 'isGameDraw'); - - $this->assertFalse($result); - } - - public function testProcessKeyPressShowBoard() - { - $ioHandlerStub = $this->getMockBuilder(IOHandler::class) - ->setMethods(['readString']) - ->getMock(); - - $ioHandlerStub->expects($this->any()) - ->method('readString') - ->willReturn('d'); - - $application = new Application($ioHandlerStub); - - $expected = ""; - $expected .= sprintf(IOHandler::INFO, " | | " . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "---|---|---" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, " | | " . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, "---|---|---" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO, " | | " . PHP_EOL); - - $this->expectOutputString($expected); - $this->invokeMethod($application, 'processInput'); - } - - public function testProcessKeyPressUnidentified() - { - $ioHandlerStub = $this->getMockBuilder(IOHandler::class) - ->setMethods(['readString']) - ->getMock(); - - $ioHandlerStub->expects($this->any()) - ->method('readString') - ->willReturn('q'); - - $application = new Application($ioHandlerStub); - - $expected = ""; - $expected .= sprintf(IOHandler::WARNING, "Unidentified input." . PHP_EOL); - - $this->expectOutputString($expected); - $this->invokeMethod($application, 'processInput'); - } - - public function testProcessInputEnterLocation() - { - $ioHandlerStub = $this->getMockBuilder(IOHandler::class) - ->setMethods(['readString']) - ->getMock(); - - $ioHandlerStub->expects($this->any()) - ->method('readString') - ->willReturn('1'); - - $application = new Application($ioHandlerStub); - - $result = $this->invokeMethod($application, 'processInput'); - $this->assertEquals('1', $result); - } - - public function testProcessKeyPressGameStart() - { - $ioHandlerStub = $this->getMockBuilder(IOHandler::class) - ->setMethods(['readString']) - ->getMock(); - - $ioHandlerStub->expects($this->exactly(5)) - ->method('readString') - ->will($this->onConsecutiveCalls('s','foo','x','baar','o')); - - $application = new Application($ioHandlerStub); - - $this->invokeMethod($application, 'processInput'); - $this->assertTrue($application->isGameStarted()); - } - - public function testGameIsDraw() - { - $expected = ""; - $expected .= sprintf(IOHandler::INFO,"|################################################################|" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO,"| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO,"| It is a DRAW! |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO,"| Thank you for playing Tic-Tac-Toe. |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO,"| |" . PHP_EOL); - $expected .= sprintf(IOHandler::INFO,"|################################################################|" . PHP_EOL); - - $ioHandler = new IOHandler(); - $application = new Application($ioHandler); - $this->expectOutputString($expected); - $this->invokeMethod($application, 'gameIsDraw'); - } -} diff --git a/src/TicTacToe/Test/BaseTest.php b/src/TicTacToe/Test/BaseTest.php deleted file mode 100644 index 06c79da..0000000 --- a/src/TicTacToe/Test/BaseTest.php +++ /dev/null @@ -1,26 +0,0 @@ -getMethod($methodName); - $method->setAccessible(true); - - return $method->invokeArgs($object, $parameters); - } -} diff --git a/src/TicTacToe/Test/BoardTest.php b/src/TicTacToe/Test/BoardTest.php deleted file mode 100644 index 3b300bf..0000000 --- a/src/TicTacToe/Test/BoardTest.php +++ /dev/null @@ -1,170 +0,0 @@ -assertTrue($board->isValidMove(1)); - } - - public function testSetBoardInvalidMove() - { - $board = new Board(); - - try { - $board->setBoardCell(1, 'x'); - $board->setBoardCell(1, 'o'); - } catch (\Exception $e) { - $this->assertInstanceOf(\LogicException::class, $e); - } - } - - public function testSetBoardThrowsOutOfRangeException() - { - $board = new Board(); - - try { - $board->setBoardCell(10, 'x'); - } catch (\Exception $e) { - $this->assertInstanceOf(\OutOfRangeException::class, $e); - } - } - - public function testCellHasValueTrue() - { - $board = new Board(); - $board->setBoardCell(1, 'x'); - $this->assertTrue($board->cellHasValue(1)); - } - - public function testCheckForWinnerTrue() - { - $board = new Board(); - - $board->setBoardCell(1, 'x'); - $board->setBoardCell(2, 'x'); - $board->setBoardCell(3, 'x'); - - $this->assertEquals('x', $board->checkForWinner()); - } - - public function testCheckForWinnerNoWinner() - { - $board = new Board(); - - $board->setBoardCell(1, 'x'); - $board->setBoardCell(2, 'x'); - $board->setBoardCell(9, 'x'); - - $this->assertEquals(null, $board->checkForWinner()); - } - - public function testGetBoardSize() - { - $board = new Board(); - - $this->assertEquals(3, $board->getBoardSize()); - } - - public function testGetBoardWithEmptyCells() - { - $board = new Board(); - $boardPlan = $board->getBoard(); - - $expected = array( - " | | ", - "---|---|---", - " | | ", - "---|---|---", - " | | ", - ); - $this->assertEquals($expected, $boardPlan); - } - - public function testGetDemoBoard() - { - $board = new Board(); - $boardPlan = $board->demoBoard(); - - $expected = array( - " 1 | 2 | 3 ", - "---|---|---", - " 4 | 5 | 6 ", - "---|---|---", - " 7 | 8 | 9 ", - ); - - $this->assertEquals($expected, $boardPlan); - } - - public function testAreAllCellsFilledTrue() - { - $board = new Board(); - foreach ($this->fillBoardNoWin() as $input) { - $board->setBoardCell($input[0], $input[1]); - } - - $this->assertTrue($board->areAllCellsFilled()); - } - - public function testAreAllCellsFilledFalse() - { - $board = new Board(); - foreach ($this->fillBoard() as $input) { - $board->setBoardCell($input[0], $input[1]); - } - - $this->assertFalse($board->areAllCellsFilled()); - } - - public function testGetCellValueNotEmpty() - { - $board = new Board(); - $board->setBoardCell(1, 'x'); - - $this->assertEquals('x', $board->getCellValue(1)); - } - - public function testGetCellValueEmpty() - { - $board = new Board(); - - $this->assertNull($board->getCellValue(1)); - } - - public function fillBoardNoWin() - { - return array( - array(1, 'x'), - array(2, 'o'), - array(3, 'x'), - array(4, 'o'), - array(5, 'x'), - array(6, 'o'), - array(7, 'o'), - array(8, 'x'), - array(9, 'o') - ); - } - - public function fillBoard() - { - return array( - array(1, 'x'), - array(2, 'o'), - array(3, 'x'), - array(4, 'o'), - array(5, 'x'), - array(6, 'o'), - array(7, 'o'), - array(8, 'x'), - ); - } -} diff --git a/src/TicTacToe/Test/IO/IOHandlerTest.php b/src/TicTacToe/Test/IO/IOHandlerTest.php deleted file mode 100644 index a817efa..0000000 --- a/src/TicTacToe/Test/IO/IOHandlerTest.php +++ /dev/null @@ -1,108 +0,0 @@ -ioHandler = $this->getMockBuilder(IOHandler::class) - ->setMethods(['readStringFromStream', 'readIntegerFromStream']) - ->getMock(); - } - - public function testReadStringEmptyInput() - { - $this->ioHandler->expects($this->once()) - ->method('readStringFromStream') - ->willReturn(''); - - try { - $string = $this->ioHandler->readString(); - } catch (\Exception $e) { - $this->assertInstanceOf(\InvalidArgumentException::class, $e); - } - } - - public function testReadStringSuccess() - { - $this->ioHandler->expects($this->once()) - ->method('readStringFromStream') - ->willReturn('foo'); - - $string = $this->ioHandler->readString(); - $this->assertEquals('foo', $string); - } - - public function testReadStringNotString() - { - $this->ioHandler->expects($this->once()) - ->method('readStringFromStream') - ->willReturn(123); - - try { - $string = $this->ioHandler->readString(); - } catch (\Exception $e) { - $this->assertInstanceOf(\InvalidArgumentException::class, $e); - } - } - - public function testReadIntegerSuccess() - { - $this->ioHandler->expects($this->once()) - ->method('readIntegerFromStream') - ->willReturn(123); - - $integer = $this->ioHandler->readInteger(); - $this->assertEquals(123, $integer); - } - - public function testReadIntegerFailure() - { - $this->ioHandler->expects($this->once()) - ->method('readIntegerFromStream') - ->willReturn("ABC"); - - try { - $integer = $this->ioHandler->readInteger(); - } catch (\Exception $e) { - $this->assertInstanceOf(\InvalidArgumentException::class, $e); - } - } - - public function testWrite() - { - $this->expectOutputString(sprintf(IOHandler::INFO, "foo")); - $this->ioHandler->write("foo"); - } - - public function testWriteWithArrayAsInput() - { - $expected = sprintf(IOHandler::INFO, "foo") . sprintf(IOHandler::INFO, "bar"); - $this->expectOutputString($expected); - $lines[] = "foo"; - $lines[] = "bar"; - $this->ioHandler->write($lines); - // var_dump($this->getActualOutput()); - } - - public function testWriteLine() - { - $this->expectOutputString(sprintf(IOHandler::INFO, "foo" . PHP_EOL)); - $this->ioHandler->writeLine("foo"); - } - - public function testWriteLineWithArrayAsInput() - { - $expected = sprintf(IOHandler::INFO, "foo" . PHP_EOL) . sprintf(IOHandler::INFO, "bar" . PHP_EOL); - $this->expectOutputString($expected); - $lines[] = "foo"; - $lines[] = "bar"; - $this->ioHandler->writeLine($lines); - } -} diff --git a/src/TicTacToe/Test/Player/HumanPlayerTest.php b/src/TicTacToe/Test/Player/HumanPlayerTest.php deleted file mode 100644 index 612d7a8..0000000 --- a/src/TicTacToe/Test/Player/HumanPlayerTest.php +++ /dev/null @@ -1,35 +0,0 @@ -assertEquals('mina', $player->getName()); - $this->assertEquals('x', $player->getMarker()); - } - - public function testGetNameWhenNameIsNotProvided() - { - $player = new HumanPlayer('x'); - - $this->assertEquals('Player_x', $player->getName()); - $this->assertEquals('x', $player->getMarker()); - } - - public function testIsAllowedMarkerWithInvalidMarker() - { - try { - $player = new HumanPlayer('1'); - } catch (\Exception $e) { - $this->assertInstanceOf(\InvalidArgumentException::class, $e); - } - } -} - diff --git a/tests/unit/Engine/Application/CreateNewPlayer/CreateNewPlayerHandlerTest.php b/tests/unit/Engine/Application/CreateNewPlayer/CreateNewPlayerHandlerTest.php new file mode 100644 index 0000000..dbb6843 --- /dev/null +++ b/tests/unit/Engine/Application/CreateNewPlayer/CreateNewPlayerHandlerTest.php @@ -0,0 +1,58 @@ +playerRepository = new InMemoryPlayerRepository(); + $this->handler = new CreateNewPlayerHandler( + $this->playerRepository + ); + } + + public function testItHandlesCorrectClass(): void + { + self::assertEquals(CreateNewPlayerCommand::class, $this->handler->handles()); + } + + public function testItCanCreateANewPlayer(): void + { + $command = $this->createCommand(); + + /** @var CreateNewPlayerResponseDto $playerDto */ + $playerDto = $this->handler->handle($command); + $retrievedPlayer = $this->playerRepository->ofId(new PlayerId($playerDto->playerId())); + self::assertEquals($retrievedPlayer->name(), $playerDto->playerName()); + self::assertEquals((string) $retrievedPlayer->playingToken(), $playerDto->playerToken()); + } + + public function testItThrowsSorryWrongCommandWhenWrongCommandPassed(): void + { + $command = new WrongCommand(); + + self::expectException(SorryWrongCommand::class); + $this->handler->handle($command); + } + + private function createCommand(string $name = 'player', string $token = 'x'): CreateNewPlayerCommand + { + return new CreateNewPlayerCommand($name, $token); + } +} diff --git a/tests/unit/Engine/Domain/Model/Board/BoardTest.php b/tests/unit/Engine/Domain/Model/Board/BoardTest.php new file mode 100644 index 0000000..ae05988 --- /dev/null +++ b/tests/unit/Engine/Domain/Model/Board/BoardTest.php @@ -0,0 +1,25 @@ +size()); + } + + public function testItThrowsSorryBoardSizeIsNotValid() + { + self::expectException(SorryBoardSizeIsNotValid::class); + new Board(4); + } +} diff --git a/tests/unit/Engine/Domain/Model/Board/CellTest.php b/tests/unit/Engine/Domain/Model/Board/CellTest.php new file mode 100644 index 0000000..dccc5d2 --- /dev/null +++ b/tests/unit/Engine/Domain/Model/Board/CellTest.php @@ -0,0 +1,32 @@ +createPlayers(2); + $game = Game::start($gameId, Board::create3By3Board(), ...$playerIds); + + self::assertEquals(2, $game->playerIds()->count()); + self::assertInstanceOf(GameId::class, $game->id()); + self::assertInstanceOf(Board::class, $game->board()); + } + + public function testItThrowsTooManyPlayersWhenMorePlayersCreated() + { + $gameId = new GameId(); + $playerIds = $this->createPlayers(3); + self::expectException(SorryTooManyPlayers::class); + Game::start($gameId, Board::create3By3Board(), ...$playerIds); + } + + public function randomizeString(int $length) + { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charactersLength = strlen($characters); + $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + return $randomString; + } + + public function createPlayers(int $count) + { + $players = []; + for($i = 0; $i < $count; $i++) { + if ($i % 2 == 0) { + $players[] = Player::createPlayerWithTokenX($this->randomizeString(5))->id(); + } else { + $players[] = Player::createPlayerWithTokenY($this->randomizeString(5))->id(); + } + } + + return $players; + } +} diff --git a/tests/unit/Engine/Domain/Model/Player/Event/PlayerCreatedTest.php b/tests/unit/Engine/Domain/Model/Player/Event/PlayerCreatedTest.php new file mode 100644 index 0000000..a294e34 --- /dev/null +++ b/tests/unit/Engine/Domain/Model/Player/Event/PlayerCreatedTest.php @@ -0,0 +1,39 @@ +createPlayerX(); + $event = new PlayerCreated($player); + + self::assertEquals($player->createdAt()->getTimestamp(), $event->occurredAt()->getTimestamp()); + + $payload = $event->toArray(); + self::assertEquals(PlayerCreated::class, $event->name()); + + self::assertArrayHasKey('playerId', $payload); + self::assertEquals((string) $player->id(), $payload['playerId']); + + self::assertArrayHasKey('playerName', $payload); + self::assertEquals($player->name(), $payload['playerName']); + + self::assertArrayHasKey('playerToken', $payload); + self::assertEquals($player->playingToken(), $payload['playerToken']); + } + + public function createPlayerX($name = 'foobar') + { + $player = Player::createPlayerWithTokenX($name); + + return $player; + } +} diff --git a/tests/unit/Engine/Domain/Model/Player/PlayerIdTest.php b/tests/unit/Engine/Domain/Model/Player/PlayerIdTest.php new file mode 100644 index 0000000..def5b11 --- /dev/null +++ b/tests/unit/Engine/Domain/Model/Player/PlayerIdTest.php @@ -0,0 +1,28 @@ +name()); + self::assertEquals('x', $player->playingToken()); + self::assertInstanceOf(PlayerId::class, $player->id()); + } + + public function testPlayerCreatedWithPlayingTokenSuccessfully() + { + $player = Player::createPlayerWithToken( + "mina", + 'x' + ); + + self::assertEquals("mina", $player->name()); + self::assertEquals('x', $player->playingToken()); + self::assertInstanceOf(PlayerId::class, $player->id()); + } + + public function testPlayerThrowsInvalidPlayerTokenWhenInvalidToken() + { + self::expectException(SorryInvalidPlayerToken::class); + $player = Player::createPlayerWithToken( + "mina", + 'z' + ); + } + + public function testPlayerCreatedWithPlayingTokenXSuccessfully() + { + $player = Player::createPlayerWithTokenX("mina"); + + self::assertEquals("mina", $player->name()); + self::assertEquals('x', $player->playingToken()); + self::assertInstanceOf(PlayerId::class, $player->id()); + self::assertNotNull($player->id()); + } + + public function testPlayerCreatedWithPlayingTokenYSuccessfully() + { + $player = Player::createPlayerWithTokenY("mina"); + + self::assertEquals("mina", $player->name()); + self::assertEquals('y', $player->playingToken()); + self::assertInstanceOf(PlayerId::class, $player->id()); + self::assertNotNull($player->id()); + } + + public function testItThrowsExceptionIfPlayerNameIsShort() + { + self::expectException(SorryPlayerNameIsTooShort::class); + Player::createPlayerWithTokenX("m"); + } + + public function testItThrowsExceptionIfPlayerNameIsLong() + { + self::expectException(SorryPlayerNameIsTooLong::class); + Player::createPlayerWithTokenX("FGLAHETLDGHAELTRHG53Y693576935sfkhglkshfgshfgkfkgherg53yt3589gh358gh359hg395gh395"); + } +} diff --git a/tests/unit/Engine/Domain/Model/Player/PlayerTokenTest.php b/tests/unit/Engine/Domain/Model/Player/PlayerTokenTest.php new file mode 100644 index 0000000..f361b8c --- /dev/null +++ b/tests/unit/Engine/Domain/Model/Player/PlayerTokenTest.php @@ -0,0 +1,32 @@ +repository = new InMemoryPlayerRepository(); + } + + public function testItCanSaveAndGetPlayerByIdentity() + { + $player = $this->createPlayer(); + + $this->repository->add($player); + + self::assertEquals(1, count($this->repository->players())); + + $retrievedPlayer = $this->repository->ofId($player->id()); + self::assertEquals($player->id(), $retrievedPlayer->id()); + self::assertEquals($player->name(), $retrievedPlayer->name()); + self::assertEquals($player->playingToken(), $retrievedPlayer->playingToken()); + } + + public function testItGenerateNextIdentity() + { + $nextId = $this->repository->nextIdentity(); + + self::assertInstanceOf(PlayerId::class, $nextId); + self::assertTrue(Uuid::isValid((string) $nextId)); + } + + public function testItReturnsNullIfPlayerNotFound() + { + $playerId = new PlayerId('1'); + $retrievedPlayer = $this->repository->ofId($playerId); + self::assertEquals(null, $retrievedPlayer); + } + + protected function createPlayer(string $name = 'foobar') + { + $player = Player::createPlayerWithTokenX($name); + + return $player; + } +} diff --git a/tests/unit/WrongCommand.php b/tests/unit/WrongCommand.php new file mode 100644 index 0000000..9f89cf8 --- /dev/null +++ b/tests/unit/WrongCommand.php @@ -0,0 +1,12 @@ +