diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..4e411c57 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,33 @@ +readme: + - src/README.md + - README.md + +feature: + - src/app/**/* + +tests: + - src/tests/**/* + +good practices: + - git-hooks/**/* + - .github/workflows/**/* + +automation: + - git-hooks/**/* + - .github/workflows/**/* + +code quality: + - git-hooks/**/* + - .github/workflows/**/* + +composer: + - src/composer.json + - composer.json + +database: + - src/database/**/* + +docker: + -docker/docker + -Dockerfile + -docker-compose.yml diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 00000000..7c724a62 --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,19 @@ +# This workflow will triage pull requests and apply a label based on the +# paths that are modified in the pull request. +# +# To use this workflow, you will need to set up a .github/labeler.yml +# file with configuration. For more information, see: +# https://github.com/actions/labeler + +name: Labeler +on: [pull_request] + +jobs: + label: + + runs-on: ubuntu-latest + + steps: + - uses: actions/labeler@v2 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 00000000..0dc15196 --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,53 @@ +name: Laravel Validation +on: + push: + pull_request: + branches: + - develop + +jobs: + validation: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup + run: | + docker-compose up -d + docker ps + - name: Build + run: | + docker exec -i laravel-php sh + cd src + composer install + cp .env.example .env + php artisan package:discover + php artisan key:generate + #- name: Database migation + # run: | + # docker exec -i laravel-php sh + # cd src + # php artisan migrate + #- name: Database migation + # run: | + # docker exec -i laravel-php sh + # cd src + # php artisan db:seed + - name: Linter + run: | + docker exec -i laravel-php sh + cd src + ./vendor/bin/phpcs -n app/* + ./vendor/bin/phpcs -n bootstrap/app.php + ./vendor/bin/phpcs -n config/* + ./vendor/bin/phpcs -n database/* + ./vendor/bin/phpcs -n postman/* + ./vendor/bin/phpcs -n public/* + ./vendor/bin/phpcs -n resources/* + ./vendor/bin/phpcs -n routes/* + ./vendor/bin/phpcs -n tests/* + - name: Test + run: | + docker exec -i laravel-php sh + cd src + ./vendor/bin/phpunit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0087b473..d72abdd1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,10 @@ bundles/ .hg/ .git/ vendor/pkg/ +vendor pyenv -Vagrantfile +Vagrantfile +mysql/ib* +*.cache +mysql/laravel/*.frm +mysql/laravel/*.ibd \ No newline at end of file diff --git a/540-example/app/datasource/database/EloquentUser540DataSource.php b/540-example/app/datasource/database/EloquentUser540DataSource.php new file mode 100644 index 00000000..816992da --- /dev/null +++ b/540-example/app/datasource/database/EloquentUser540DataSource.php @@ -0,0 +1,18 @@ +where('email', $email)->first(); + if (is_null($user)) { + throw new Exception('User not found'); + } + return $user; + } +} diff --git a/src/app/Http/Controllers/IsEarlyAdopterUserController.php b/540-example/app/http/controllers/IsEarlyAdopterUserController.php similarity index 99% rename from src/app/Http/Controllers/IsEarlyAdopterUserController.php rename to 540-example/app/http/controllers/IsEarlyAdopterUserController.php index 2367e8a8..cf2e586f 100644 --- a/src/app/Http/Controllers/IsEarlyAdopterUserController.php +++ b/540-example/app/http/controllers/IsEarlyAdopterUserController.php @@ -24,7 +24,6 @@ public function __invoke(string $email): JsonResponse { try { $isEarlyAdopter = $this->isEarlyAdopterService->execute($email); - } catch (Exception $exception) { return response()->json([ 'error' => $exception->getMessage() diff --git a/540-example/app/models/User540.php b/540-example/app/models/User540.php new file mode 100644 index 00000000..a6c0542f --- /dev/null +++ b/540-example/app/models/User540.php @@ -0,0 +1,16 @@ +eloquentUserDataSource = $eloquentUserDataSource; } diff --git a/540-example/database/factories/User540Factory.php b/540-example/database/factories/User540Factory.php new file mode 100644 index 00000000..9958ffb7 --- /dev/null +++ b/540-example/database/factories/User540Factory.php @@ -0,0 +1,30 @@ + 1, + 'name' => 'user_name', + 'email' => 'email@email.com' + ]; + } +} diff --git a/src/tests/Integration/Controller/IsEarlyAdopterUserControllerTest.php b/540-example/tests/integration/controller/IsEarlyAdopterUserControllerTest.php similarity index 85% rename from src/tests/Integration/Controller/IsEarlyAdopterUserControllerTest.php rename to 540-example/tests/integration/controller/IsEarlyAdopterUserControllerTest.php index fafed270..74f4520a 100644 --- a/src/tests/Integration/Controller/IsEarlyAdopterUserControllerTest.php +++ b/540-example/tests/integration/controller/IsEarlyAdopterUserControllerTest.php @@ -2,7 +2,7 @@ namespace Tests\Integration\Controller; -use App\Models\User; +use App\Models\User540; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\Response; use Tests\TestCase; @@ -16,7 +16,7 @@ class IsEarlyAdopterUserControllerTest extends TestCase */ public function noUserFoundForGivenEmail() { - User::factory(User::class)->create(); + User540::factory(User540::class)->create(); $response = $this->get('/api/user/another@email.com'); @@ -28,7 +28,7 @@ public function noUserFoundForGivenEmail() */ public function userIsEarlyAdopter() { - User::factory(User::class)->create(); + User540::factory(User540::class)->create(); $response = $this->get('/api/user/email@email.com'); diff --git a/540-example/tests/integration/datasources/EloquentUserDataSourceTest.php b/540-example/tests/integration/datasources/EloquentUserDataSourceTest.php new file mode 100644 index 00000000..b8a0fbfa --- /dev/null +++ b/540-example/tests/integration/datasources/EloquentUserDataSourceTest.php @@ -0,0 +1,54 @@ +create(); + $eloquentUserDataSource = new EloquentUser540DataSource(); + + $user = $eloquentUserDataSource->findByEmail('email@email.com'); + + $this->assertInstanceOf(User::class, $user); + } + + /** + * @test + */ + public function noUserIsFoundForTheGivenEmailI() + { + $eloquentUserDataSource = new EloquentUser540DataSource(); + + $this->expectException(Exception::class); + + $eloquentUserDataSource->findByEmail('email@email.com'); + } + + /** + * @test + */ + public function noUserIsFoundForTheGivenEmailII() + { + User540::factory(User540::class)->create(); + $eloquentUserDataSource = new EloquentUser540DataSource(); + + try { + $eloquentUserDataSource->findByEmail('not_known@email.com'); + } catch (Exception $exception) { + $this->assertEquals('User not found', $exception->getMessage()); + } + } +} diff --git a/src/tests/Unit/Services/EarlyAdopter/IsEarlyAdopterServiceTest.php b/540-example/tests/unit/EarlyAdopter/IsEarlyAdopterServiceTest.php similarity index 87% rename from src/tests/Unit/Services/EarlyAdopter/IsEarlyAdopterServiceTest.php rename to 540-example/tests/unit/EarlyAdopter/IsEarlyAdopterServiceTest.php index 77b2e4b8..0205e70b 100644 --- a/src/tests/Unit/Services/EarlyAdopter/IsEarlyAdopterServiceTest.php +++ b/540-example/tests/unit/EarlyAdopter/IsEarlyAdopterServiceTest.php @@ -2,8 +2,8 @@ namespace Tests\Unit\Services\EarlyAdopter; -use App\DataSource\Database\EloquentUserDataSource; -use App\Models\User; +use App\DataSource\Database\EloquentUser540DataSource; +use App\Models\User540; use App\Services\EarlyAdopter\IsEarlyAdopterService; use PHPUnit\Framework\TestCase; use Prophecy\Prophet; @@ -11,7 +11,7 @@ class IsEarlyAdopterServiceTest extends TestCase { /** - * @var EloquentUserDataSource + * @var EloquentUser540DataSource */ private $eloquentUserDataSource; @@ -27,7 +27,7 @@ protected function setUp(): void { parent::setUp(); $prophet = new Prophet(); - $this->eloquentUserDataSource = $prophet->prophesize(EloquentUserDataSource::class); + $this->eloquentUserDataSource = $prophet->prophesize(EloquentUser540DataSource::class); $this->isEarlyAdopterService = new IsEarlyAdopterService($this->eloquentUserDataSource->reveal()); } @@ -38,7 +38,7 @@ protected function setUp(): void public function userIsNotEarlyAdopter() { $email = 'not_early_adopter@email.com'; - $user = new User(); + $user = new User540(); $user->fill(['id' => 9999, 'email' => $email]); $this->eloquentUserDataSource->findByEmail($email)->shouldBeCalledOnce()->willReturn($user); @@ -54,7 +54,7 @@ public function userIsNotEarlyAdopter() public function userIsAnEarlyAdopter() { $email = 'not_early_adopter@email.com'; - $user = new User(); + $user = new User540(); $user->fill(['id' => 1, 'email' => $email]); $this->eloquentUserDataSource->findByEmail($email)->shouldBeCalledOnce()->willReturn($user); diff --git a/README.md b/README.md index a63ad459..cb0c0c0c 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,65 @@ -Laravel docker +# Crypto currency wallet API -The project must be mounted using docker: +**Based on 540 / laravel-docker project.** + +![Project Status Image](https://img.shields.io/github/workflow/status/MKoding/laravel-docker/Laravel%20Validation/develop?label=Project%20Status) +![Commit Activity Image](https://img.shields.io/github/commit-activity/m/MKoding/laravel-docker?label=Commit%20Activity) +![Last Commit Image](https://img.shields.io/github/last-commit/MKoding/laravel-docker?label=Last%20Commit) + +![Solution Status Image](https://img.shields.io/github/milestones/progress-percent/MKoding/laravel-docker/3?label=Solution) +![Tests Status Image](https://img.shields.io/github/milestones/progress-percent/MKoding/laravel-docker/2?label=Tests) +![Automation Status Image](https://img.shields.io/github/milestones/progress-percent/MKoding/laravel-docker/1?label=Automation) + +**Contributors of the project:** + +![Project Contributors Image](https://contrib.rocks/image?repo=MKoding/laravel-docker) + +*** -- Start docker daemon +## How to install the project +The project must be mounted using docker: -- Run your docker machine: docker-compose up -d +- Start docker daemon. +- Run your docker machine: + - docker-compose up -d +- Enter to the docker machine: + - docker exec -it laravel-php sh - From inside the machine: - Run: composer install - - Copy the .env.example to .env + - Run: cp .env.example .env - Run: php artisan key:generate + - Run: php artisan migrate + - Run: php artisan db:seed - Web running on http://localhost:8088 -- Api status check running on GET request: http://localhost:8088/api/status +- Api status check running on GET request on http://localhost:8088/api/status - Swagger running on http://localhost:8082/ - Postman base collection file can be found on src/postman - phpMyAdmin running on http://localhost:9191 - - user: root - - password: secret + - User: root + - Password: secret - As database we use MySQL: - - database name: laravel - - database user: root - - database password: secret - - - + - Database name: laravel + - Database user: root + - Database password: secret - To stop de container: - docker-compose stop + - docker-compose stop + +## How to colaborate with the project +### Install the pre-commit: +- From outside the machine: + - Run: composer install + - Run: cp git-hooks/pre-commit .git/hooks/pre-commit + - Run: chmod +x .git/hooks/pre-commit + +### Commit rules: +- Commit syntax: + - git commit -m "[] - \" +- If there is a "PHPCS Failed in a File", run: + - ./phpfixer +- Create one branch for each issue: + - git checkout -b \ +- If you want to merge your work with the develop branch open a pull request. +- When an issue is closed: + - git commit -m "[#issueId] - \" -:) +:) \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..4805bb2b --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "require-dev": { + "squizlabs/php_codesniffer": "^3.6", + "friendsofphp/php-cs-fixer": "^2.18", + "phpunit/phpunit": "^9.5" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..c6b59136 --- /dev/null +++ b/composer.lock @@ -0,0 +1,4125 @@ +{ + "_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#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "238a0c35a473f44e7aaf5d9b52bebf55", + "packages": [], + "packages-dev": [ + { + "name": "composer/semver", + "version": "3.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:59:24+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "31d57697eb1971712a08031cfaff5a846d10bdf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/31d57697eb1971712a08031cfaff5a846d10bdf5", + "reference": "31d57697eb1971712a08031cfaff5a846d10bdf5", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/2.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-09T19:40:06+00:00" + }, + { + "name": "doctrine/annotations", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "b17c5014ef81d212ac539f07a1001832df1b6d3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/b17c5014ef81d212ac539f07a1001832df1b6d3b", + "reference": "b17c5014ef81d212ac539f07a1001832df1b6d3b", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^0.12.20", + "phpunit/phpunit": "^7.5 || ^9.1.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.12.1" + }, + "time": "2021-02-21T21:00:45+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "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": "2020-11-10T18:47:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.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%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.18.6", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "5fed214993e7863cef88a08f214344891299b9e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/5fed214993e7863cef88a08f214344891299b9e4", + "reference": "5fed214993e7863cef88a08f214344891299b9e4", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.2 || ^2.0", + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^5.6 || ^7.0 || ^8.0", + "php-cs-fixer/diff": "^1.3", + "symfony/console": "^3.4.43 || ^4.1.6 || ^5.0", + "symfony/event-dispatcher": "^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^3.0 || ^4.0 || ^5.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.4", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.4.2", + "php-cs-fixer/accessible-object": "^1.0", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", + "phpspec/prophecy-phpunit": "^1.1 || ^2.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.13 || ^9.5", + "phpunitgoodpractices/polyfill": "^1.5", + "phpunitgoodpractices/traits": "^1.9.1", + "sanmai/phpunit-legacy-adapter": "^6.4 || ^8.2.1", + "symfony/phpunit-bridge": "^5.2.1", + "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters.", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationCaseFactory.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/Assert/AssertTokensTrait.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php", + "tests/Test/IntegrationCaseFactoryInterface.php", + "tests/Test/InternalIntegrationCaseFactory.php", + "tests/Test/IsIdenticalConstraint.php", + "tests/Test/TokensWithObservedTransformers.php", + "tests/TestCase.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz RumiƄski", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.6" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2021-04-19T19:45:11+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "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.10.4" + }, + "time": "2020-12-20T10:01:03+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, + "time": "2020-06-27T14:33:11+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "php-cs-fixer/diff", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/dbd31aeb251639ac0b9e7e29405c1441907f5759", + "reference": "dbd31aeb251639ac0b9e7e29405c1441907f5759", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "SpacePossum" + } + ], + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/diff/issues", + "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" + }, + "time": "2020-10-14T08:39:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "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", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + }, + "time": "2021-03-17T13:42:18+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f6293e1b30a2354e8428e004689671b83871edde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.10.2", + "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": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.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": "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.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-28T07:26:59+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "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": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "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-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.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": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "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": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741", + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741", + "shasum": "" + }, + "require": { + "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.1", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.3", + "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.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.4" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-23T07:16:29+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.3" + }, + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "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 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": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "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": "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": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "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": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "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.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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 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": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "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", + "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": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "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", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", + "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:55:19+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": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "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": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "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": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "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": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "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": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-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/2.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:18:59+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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 that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "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": "squizlabs/php_codesniffer", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "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": "2021-04-09T00:54:41+00:00" + }, + { + "name": "symfony/console", + "version": "v5.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/35f039df40a3b335ebf310f244cb242b3a83ac8d", + "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.6" + }, + "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": "2021-03-28T09:42:18+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.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": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "d08d6ec121a425897951900ab692b612a61d6240" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d08d6ec121a425897951900ab692b612a61d6240", + "reference": "d08d6ec121a425897951900ab692b612a61d6240", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/error-handler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.4" + }, + "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": "2021-02-18T17:12:37+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.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": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "8c86a82f51658188119e62cff0a050a12d09836f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/8c86a82f51658188119e62cff0a050a12d09836f", + "reference": "8c86a82f51658188119e62cff0a050a12d09836f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.2.6" + }, + "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": "2021-03-28T14:30:26+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "0d639a0943822626290d169965804f79400e6a04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04", + "reference": "0d639a0943822626290d169965804f79400e6a04", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.2.4" + }, + "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": "2021-02-15T18:55:04+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", + "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.2.4" + }, + "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": "2021-01-27T12:56:27+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "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.22.1" + }, + "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": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + }, + "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": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + }, + "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": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, + "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": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "metapackage", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.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": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" + }, + "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": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + }, + "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": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, + "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": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/process", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.2.4" + }, + "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": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.4.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": "2021-04-01T10:43:52+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b12274acfab9d9850c52583d136a24398cdf1a0c", + "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.2.4" + }, + "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": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/string", + "version": "v5.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", + "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.6" + }, + "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": "2021-03-17T17:12:15+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "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": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "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/master" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit new file mode 100644 index 00000000..6f8bbcdb --- /dev/null +++ b/git-hooks/pre-commit @@ -0,0 +1,86 @@ +#!/bin/sh + +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".php\{0,1\}$") + +if [[ "$STAGED_FILES" = "" ]]; then + exit 0 +fi + +PASS=true + +echo "\nValidating PHP sintax errors:\n" + +# Checking php linter +for FILE in $STAGED_FILES +do + if ! php -l "$FILE"; then + echo "\n\t\033[41mPHP Linter Failed: $FILE\033[0m\n" + PASS=false + fi +done + +if ! $PASS; then + echo "\033[41mCOMMIT FAILED:\033[0m Your commit contains files that have syntax errors. Please fix the syntax errors and try again.\n" + exit 1 +fi + +echo "\n\033[32mNo PHP syntax errors detected!\033[0m\n" + +echo "\n\nValidating PHPCS:" + +# Check for phpcs +which ./vendor/bin/phpcs &> /dev/null +if [[ "$?" == 1 ]]; then + echo "\n\t\033[41mPlease install PHPCS\033[0m" + exit 1 +fi + +# Check for phpcbf +which ./vendor/bin/phpcbf &> /dev/null +if [[ "$?" == 1 ]]; then + echo "\n\t\033[41mPlease install PHPCBF\033[0m" + exit 1 +fi + +RULESET=./phpcs.xml + +for FILE in $STAGED_FILES +do + ./vendor/bin/phpcs --standard="$RULESET" "$FILE" + + if [[ "$?" == 0 ]]; then + echo "\t\033[32mPHPCS Passed: $FILE\033[0m" + else + echo "\t\033[41mPHPCS Failed: $FILE\033[0m" + PASS=false + fi +done + +echo "\nPHPCS validation completed!\n" + +if ! $PASS; then + echo "\033[41mCOMMIT FAILED:\033[0m Your commit contains files that should pass PHPCS but do not. Please fix the PHPCS errors and try again.\n" + exit 1 +fi + +echo "\n\nValidating PHP Unit Tests:\n" + +./vendor/bin/phpunit --configuration src/phpunit.xml + +if [[ "$?" == 0 ]]; then + echo "\n\033[32mPHP Unit Tests Passed\033[0m" +else + echo "\n\033[41mPHP Unit Tests Failed\033[0m" + PASS=false +fi + +echo "\nPHP Unit Tests validation completed!" + +if ! $PASS; then + echo "\n\033[41mCOMMIT FAILED:\033[0m Your commit files should pass PHP Unit Tests but do not. Please fix the PHP Unit TestsZ errors and try again.\n" + exit 1 +else + echo "\n\033[42mCOMMIT SUCCEEDED\033[0m\n" +fi + +exit $? \ No newline at end of file diff --git a/mysql/ib_buffer_pool b/mysql/ib_buffer_pool deleted file mode 100644 index 4782db32..00000000 --- a/mysql/ib_buffer_pool +++ /dev/null @@ -1,72 +0,0 @@ -10,146 -10,22 -10,176 -10,216 -10,34 -10,238 -10,256 -11,14 -11,22 -11,6 -11,28 -11,12 -8,14 -8,18 -8,10 -17,3 -17,2 -17,1 -16,3 -20,2 -20,1 -16,2 -16,1 -15,3 -20,3 -15,2 -15,1 -12,3 -19,2 -12,2 -12,1 -19,1 -11,24 -19,3 -11,21 -11,20 -11,18 -11,10 -11,1 -11,9 -11,3 -11,2 -10,245 -10,227 -10,221 -10,215 -10,163 -10,137 -10,115 -10,3 -18,1 -10,2 -10,1 -9,7 -9,6 -18,3 -9,5 -9,4 -9,3 -9,2 -14,1 -9,1 -8,16 -14,3 -13,1 -8,15 -8,13 -8,12 -8,11 -13,3 -8,8 -8,1 diff --git a/mysql/ib_logfile0 b/mysql/ib_logfile0 deleted file mode 100644 index 65f95952..00000000 Binary files a/mysql/ib_logfile0 and /dev/null differ diff --git a/mysql/ib_logfile1 b/mysql/ib_logfile1 deleted file mode 100644 index 274bba07..00000000 Binary files a/mysql/ib_logfile1 and /dev/null differ diff --git a/mysql/ibdata1 b/mysql/ibdata1 deleted file mode 100644 index 1a7644bd..00000000 Binary files a/mysql/ibdata1 and /dev/null differ diff --git a/mysql/ibtmp1 b/mysql/ibtmp1 deleted file mode 100644 index 73f3402c..00000000 Binary files a/mysql/ibtmp1 and /dev/null differ diff --git a/mysql/laravel/migrations.frm b/mysql/laravel/migrations.frm deleted file mode 100644 index a31f416c..00000000 Binary files a/mysql/laravel/migrations.frm and /dev/null differ diff --git a/mysql/laravel/migrations.ibd b/mysql/laravel/migrations.ibd deleted file mode 100644 index 60492f71..00000000 Binary files a/mysql/laravel/migrations.ibd and /dev/null differ diff --git a/mysql/laravel/users.frm b/mysql/laravel/users.frm deleted file mode 100644 index c39ea2c8..00000000 Binary files a/mysql/laravel/users.frm and /dev/null differ diff --git a/mysql/laravel/users.ibd b/mysql/laravel/users.ibd deleted file mode 100644 index e704207e..00000000 Binary files a/mysql/laravel/users.ibd and /dev/null differ diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..f5f7cc42 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,11 @@ + + +The PSR2 coding standard. + +app/ +vendor +resources +database/ +storage/ +node_modules/ + diff --git a/phpfixer b/phpfixer new file mode 100755 index 00000000..687d275a --- /dev/null +++ b/phpfixer @@ -0,0 +1,27 @@ +#!/bin/sh + +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".php\{0,1\}$") + +if [[ "$STAGED_FILES" = "" ]]; then + exit 0 +fi + +echo "\nValidating PHPCBF:\n" + +# Check for phpcs +which ./vendor/bin/phpcbf &> /dev/null +if [[ "$?" == 1 ]]; then + echo "\t\033[41mPlease install PHPCBF\033[0m" + exit 1 +fi + +RULESET=./phpcs.xml + +for FILE in $STAGED_FILES +do + ./vendor/bin/phpcbf --standard="$RULESET" "$FILE" +done + +echo "\nPHPCBF fix completed!\n" + +exit $? \ No newline at end of file diff --git a/src/.env.example b/src/.env.example index 31729da3..01c1f4d3 100644 --- a/src/.env.example +++ b/src/.env.example @@ -2,6 +2,7 @@ APP_NAME=Laravel APP_ENV=local APP_DEBUG=true APP_URL=http://localhost +APP_KEY= LOG_CHANNEL=stack LOG_LEVEL=debug diff --git a/src/app/DataSource/Database/EloquentCoinDataSource.php b/src/app/DataSource/Database/EloquentCoinDataSource.php new file mode 100644 index 00000000..4195d892 --- /dev/null +++ b/src/app/DataSource/Database/EloquentCoinDataSource.php @@ -0,0 +1,22 @@ +select('coin_id') + ->where('coin_id', $coin_id) + ->first(); + + return ($result != null); + } +} diff --git a/src/app/DataSource/Database/EloquentUserDataSource.php b/src/app/DataSource/Database/EloquentUserDataSource.php index 7f7fb341..0ee030ea 100644 --- a/src/app/DataSource/Database/EloquentUserDataSource.php +++ b/src/app/DataSource/Database/EloquentUserDataSource.php @@ -2,17 +2,21 @@ namespace App\DataSource\Database; -use App\Models\User; -use Exception; +use Illuminate\Support\Facades\DB; class EloquentUserDataSource { - public function findByEmail($email): User + /** + * @param $user_id + * @return bool + */ + public function existsByUserId($user_id): bool { - $user = User::query()->where('email', $email)->first(); - if (is_null($user)) { - throw new Exception('User not found'); - } - return $user; + $result = DB::table('users') + ->select('user_id') + ->where('user_id', $user_id) + ->first(); + + return ($result != null); } } diff --git a/src/app/DataSource/Database/EloquentWalletCoinDataSource.php b/src/app/DataSource/Database/EloquentWalletCoinDataSource.php new file mode 100644 index 00000000..74ed032d --- /dev/null +++ b/src/app/DataSource/Database/EloquentWalletCoinDataSource.php @@ -0,0 +1,84 @@ +select('coin_id', 'wallet_id', 'amount') + ->where('coin_id', $coin_id) + ->where('wallet_id', $wallet_id) + ->first(); + + if ($result == null) { + return null; + } + + return $result->amount; + } + + /** + * @param $coin_id + * @param $wallet_id + * @param $amount + * @param $amount_usd + * @return void + */ + public function buyCoins($coin_id, $wallet_id, $amount, $amount_usd): void + { + $oldAmount = $this->getAmountByIds($coin_id, $wallet_id); + + if ($oldAmount === null) { + DB::table('walletscoins') + ->insert(['coin_id' => $coin_id, 'wallet_id' => $wallet_id, 'amount' => 0]); + } + + DB::table('walletscoins') + ->where('coin_id', $coin_id) + ->where('wallet_id', $wallet_id) + ->increment('amount', $amount); + + DB::table('wallets') + ->where('wallet_id', $wallet_id) + ->decrement('balance_usd', $amount_usd); + } + + /** + * @param $coin_id + * @param $wallet_id + * @param $amount + * @param $amount_usd + * @return void + */ + public function sellCoins($coin_id, $wallet_id, $amount, $amount_usd): void + { + $oldAmount = $this->getAmountByIds($coin_id, $wallet_id); + + if ($oldAmount !== null && $oldAmount >= $amount) { + if ($oldAmount == $amount) { + DB::table('walletscoins') + ->where('coin_id', $coin_id) + ->where('wallet_id', $wallet_id) + ->delete(); + } else { + DB::table('walletscoins') + ->where('coin_id', $coin_id) + ->where('wallet_id', $wallet_id) + ->decrement('amount', $amount); + } + + DB::table('wallets') + ->where('wallet_id', $wallet_id) + ->increment('balance_usd', $amount_usd); + return; + } + + throw new Exception("Insufficient amount to sell"); + } +} + diff --git a/src/app/DataSource/Database/EloquentWalletDataSource.php b/src/app/DataSource/Database/EloquentWalletDataSource.php new file mode 100644 index 00000000..2513d07e --- /dev/null +++ b/src/app/DataSource/Database/EloquentWalletDataSource.php @@ -0,0 +1,84 @@ +join('walletscoins', 'wallets.wallet_id', '=', 'walletscoins.wallet_id') + ->join('coins', 'walletscoins.coin_id', '=', 'coins.coin_id') + ->select('coins.coin_id', 'name', 'symbol', 'amount') + ->where('wallets.wallet_id', $wallet_id) + ->get() + ->toArray(); + } + + /** + * @param $wallet_id + * @return float|null + */ + public function getBalanceUsdByWalletId($wallet_id): ?float + { + $result = DB::table('wallets') + ->select('wallet_id', 'user_id', 'balance_usd') + ->where('wallet_id', $wallet_id) + ->first(); + + if ($result == null) { + return null; + } + + return $result->balance_usd; + } + + /** + * @param $wallet_id + * @return bool + */ + public function existsByWalletId($wallet_id): bool + { + $result = DB::table('wallets') + ->select('wallet_id') + ->where('wallet_id', $wallet_id) + ->first(); + + return ($result != null); + } + + /** + * @param $user_id + * @return string + */ + public function createWalletByUserId($user_id): string + { + $wallets = DB::table('wallets') + ->select('wallet_id') + ->where('wallet_id', 'LIKE', 'wallet-%') + ->orderBy('wallet_id', 'desc') + ->get() + ->toArray(); + + if (count($wallets) == 0 || $wallets == null) { + $wallet_id = 'wallet-000000001'; + } else { + $wallet_id = $wallets[0]->wallet_id; + $wallet_id++; + } + + DB::table('wallets') + ->insert(['wallet_id' => $wallet_id, 'user_id' => $user_id, 'balance_usd' => 0]); + + return $wallet_id; + } +} + diff --git a/src/app/DataSource/External/CoinLoreDataSource.php b/src/app/DataSource/External/CoinLoreDataSource.php new file mode 100644 index 00000000..7f9a46b8 --- /dev/null +++ b/src/app/DataSource/External/CoinLoreDataSource.php @@ -0,0 +1,21 @@ +body() == null) { + return null; + } + return json_decode($response->body())[0]->price_usd; + } +} diff --git a/src/app/Http/Controllers/GetWalletBalanceController.php b/src/app/Http/Controllers/GetWalletBalanceController.php new file mode 100644 index 00000000..a30cc2f2 --- /dev/null +++ b/src/app/Http/Controllers/GetWalletBalanceController.php @@ -0,0 +1,64 @@ +getWalletBalanceService = $getWalletBalanceService; + } + + /** + * @param string $wallet_id + * @return JsonResponse + * @throws Exception + */ + public function __invoke(string $wallet_id): JsonResponse + { + try { + $balanceUsd = $this->getWalletBalanceService->execute($wallet_id); + } catch (Exception $exception) { + return $this->exceptionHandler($exception); + } + return response()->json( + [ + 'balance_usd' => $balanceUsd + ], + Response::HTTP_OK + ); + } + + private function exceptionHandler($exception): JsonResponse + { + if ($exception->getMessage() === "Wallet not found") { + return response()->json( + [ + 'status' => 'Wallet with the specified ID was not found', 'message' => $exception->getMessage() + ], + Response::HTTP_NOT_FOUND + ); + } + return response()->json( + [ + 'status' => 'Bad Request Error', 'message' => $exception->getMessage() + ], + Response::HTTP_BAD_REQUEST + ); + } +} diff --git a/src/app/Http/Controllers/GetWalletController.php b/src/app/Http/Controllers/GetWalletController.php new file mode 100644 index 00000000..ac38b153 --- /dev/null +++ b/src/app/Http/Controllers/GetWalletController.php @@ -0,0 +1,59 @@ +getWalletService = $getWalletService; + } + + /** + * @param string $wallet_id + * @return JsonResponse + * @throws Exception + */ + public function __invoke(string $wallet_id): JsonResponse + { + try { + $wallet = $this->getWalletService->execute($wallet_id); + } catch (Exception $exception) { + return $this->exceptionHandler($exception); + } + return response()->json($wallet, Response::HTTP_OK); + } + + private function exceptionHandler($exception): JsonResponse + { + if ($exception->getMessage() === "Wallet not found") { + return response()->json( + [ + 'status' => 'Wallet with the specified ID was not found', 'message' => $exception->getMessage() + ], + Response::HTTP_NOT_FOUND + ); + } + return response()->json( + [ + 'status' => 'Bad Request Error', 'message' => $exception->getMessage() + ], + Response::HTTP_BAD_REQUEST + ); + } +} diff --git a/src/app/Http/Controllers/PostCoinBuyController.php b/src/app/Http/Controllers/PostCoinBuyController.php new file mode 100644 index 00000000..fa391506 --- /dev/null +++ b/src/app/Http/Controllers/PostCoinBuyController.php @@ -0,0 +1,79 @@ +postCoinBuyService = $postCoinBuyService; + } + + public function __invoke(Request $request) + { + try { + $coinId = $request->coin_id; + $walletId = $request->wallet_id; + $amountUsd = $request->amount_usd; + if (($coinId && $walletId && $amountUsd) == null && $amountUsd !== 0) { + throw new Exception('Insufficient arguments in the POST'); + } + if ($amountUsd < 0.01) { + throw new Exception('Insufficient amount to buy'); + } + + $this->postCoinBuyService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + return $this->exceptionHandler($exception); + } + + return response()->json( + [ + 'status' => 'Successful operation', 'message' => 'The buy has been successfully completed' + ], + Response::HTTP_OK + ); + } + + private function exceptionHandler($exception): JsonResponse + { + if ($exception->getMessage() === "Coin not found") { + return response()->json( + [ + 'status' => 'Coin with the specified ID was not found', 'message' => $exception->getMessage() + ], + Response::HTTP_NOT_FOUND + ); + } + if ($exception->getMessage() === "Wallet not found") { + return response()->json( + [ + 'status' => 'Wallet with the specified ID was not found', 'message' => $exception->getMessage() + ], + Response::HTTP_NOT_FOUND + ); + } + return response()->json( + [ + 'status' => 'Bad Request Error', 'message' => $exception->getMessage() + ], + Response::HTTP_BAD_REQUEST + ); + } +} diff --git a/src/app/Http/Controllers/PostCoinSellController.php b/src/app/Http/Controllers/PostCoinSellController.php new file mode 100644 index 00000000..3d16142c --- /dev/null +++ b/src/app/Http/Controllers/PostCoinSellController.php @@ -0,0 +1,79 @@ +postCoinSellService = $postCoinSellService; + } + + public function __invoke(Request $request) + { + try { + $coinId = $request->coin_id; + $walletId = $request->wallet_id; + $amountUsd = $request->amount_usd; + if (($coinId && $walletId && $amountUsd) == null && $amountUsd !== 0) { + throw new Exception('Insufficient arguments in the POST'); + } + if ($amountUsd < 0.01) { + throw new Exception('Insufficient amount to sell'); + } + + $this->postCoinSellService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + return $this->exceptionHandler($exception); + } + + return response()->json( + [ + 'status' => 'Successful operation', 'message' => 'The sell has been successfully completed' + ], + Response::HTTP_OK + ); + } + + private function exceptionHandler($exception): JsonResponse + { + if ($exception->getMessage() === "Coin not found") { + return response()->json( + [ + 'status' => 'Coin with the specified ID was not found', 'message' => $exception->getMessage() + ], + Response::HTTP_NOT_FOUND + ); + } + if ($exception->getMessage() === "Wallet not found") { + return response()->json( + [ + 'status' => 'Wallet with the specified ID was not found', 'message' => $exception->getMessage() + ], + Response::HTTP_NOT_FOUND + ); + } + return response()->json( + [ + 'status' => 'Bad Request Error', 'message' => $exception->getMessage() + ], + Response::HTTP_BAD_REQUEST + ); + } +} diff --git a/src/app/Http/Controllers/PostWalletOpenController.php b/src/app/Http/Controllers/PostWalletOpenController.php new file mode 100644 index 00000000..0d9cee25 --- /dev/null +++ b/src/app/Http/Controllers/PostWalletOpenController.php @@ -0,0 +1,69 @@ +postWalletOpenService = $postWalletOpenService; + } + + /** + * @param Request $request + * @return JsonResponse + */ + public function __invoke(Request $request): JsonResponse + { + try { + $userId = $request->user_id; + if ($userId == null) { + throw new Exception('Insufficient arguments in the POST'); + } + + $walletId = $this->postWalletOpenService->execute($userId); + } catch (Exception $exception) { + return $this->exceptionHandler($exception); + } + return response()->json( + [ + 'wallet_id' => $walletId + ], + Response::HTTP_OK + ); + } + + private function exceptionHandler($exception): JsonResponse + { + if ($exception->getMessage() === "User not found") { + return response()->json( + [ + 'status' => 'User with the specified ID was not found', 'message' => $exception->getMessage() + ], + Response::HTTP_NOT_FOUND + ); + } + return response()->json( + [ + 'status' => 'Bad Request Error', 'message' => $exception->getMessage() + ], + Response::HTTP_BAD_REQUEST + ); + } +} diff --git a/src/app/Http/Controllers/StatusController.php b/src/app/Http/Controllers/StatusController.php index 968e467c..23326d96 100644 --- a/src/app/Http/Controllers/StatusController.php +++ b/src/app/Http/Controllers/StatusController.php @@ -16,15 +16,17 @@ public function __invoke(): JsonResponse DB::connection()->getDatabaseName(); DB::connection()->getPdo(); } catch (Exception $exception) { - return response()->json([ - 'status' => 'Error', - 'message' => 'Database is not available', + return response()->json( + [ + 'status' => 'Error', 'message' => 'Database is not available', 'data' => $exception->getMessage() - ], Response::HTTP_NOT_FOUND); + ], + Response::HTTP_NOT_FOUND + ); } - return response()->json([ - 'status' => 'Success', - 'message' => 'Systems are up and running', - ], Response::HTTP_OK); + return response()->json( + ['status' => 'Success', 'message' => 'Systems are up and running'], + Response::HTTP_OK + ); } } diff --git a/src/app/Http/Controllers/testController.php b/src/app/Http/Controllers/testController.php deleted file mode 100644 index 30a1a099..00000000 --- a/src/app/Http/Controllers/testController.php +++ /dev/null @@ -1,19 +0,0 @@ -where('id', $id)->first(); - - return response()->json([ - 'id' => $user->name - ], Response::HTTP_OK); - } -} diff --git a/src/app/Models/Coin.php b/src/app/Models/Coin.php new file mode 100644 index 00000000..714a3979 --- /dev/null +++ b/src/app/Models/Coin.php @@ -0,0 +1,15 @@ +app->bind(EloquentUserDataSource::class, function () { - return new EloquentUserDataSource(); + $this->app->bind(EloquentUser540DataSource::class, function () { + return new EloquentUser540DataSource(); }); } } diff --git a/src/app/Services/Coin/PostCoinBuyService.php b/src/app/Services/Coin/PostCoinBuyService.php new file mode 100644 index 00000000..0d32d510 --- /dev/null +++ b/src/app/Services/Coin/PostCoinBuyService.php @@ -0,0 +1,87 @@ +eloquentCoinRepository = $eloquentCoinRepository; + $this->eloquentWalletRepository = $eloquentWalletRepository; + $this->coinLoreRepository = $coinLoreRepository; + $this->eloquentWalletCoinRepository = $eloquentWalletCoinRepository; + } + + /** + * @param string $coinId + * @param string $walletId + * @param float $amountUsd + * @return float + * @throws Exception + */ + private function calculateCoinsAmount(string $coinId, string $walletId, float $amountUsd): float + { + if (!$this->eloquentCoinRepository->existsByCoinId($coinId)) { + throw new Exception('Coin not found'); + } + if (!$this->eloquentWalletRepository->existsByWalletId($walletId)) { + throw new Exception('Wallet not found'); + } + + $price = $this->coinLoreRepository->getUsdPriceByCoinId($coinId); + if ($price == null) { + throw new Exception('External API failure'); + } + + return round($amountUsd / $price, 8); // Unidad minima del Bitcoin (Satoshi) + // 1 BTC = 100.000.000 Sats + // 0,00000001 BTC = 1 Sats + } + + /** + * @param string $coinId + * @param string $walletId + * @param float $amountUsd + * @return void + * @throws Exception + */ + public function execute(string $coinId, string $walletId, float $amountUsd): void + { + try { + $amount = $this->calculateCoinsAmount($coinId, $walletId, $amountUsd); + if ($amount == 0) { + throw new Exception('Insufficient amount to buy'); + } + } catch (Exception $exception) { + throw $exception; + } + + $this->eloquentWalletCoinRepository->buyCoins($coinId, $walletId, $amount, $amountUsd); + } +} diff --git a/src/app/Services/Coin/PostCoinSellService.php b/src/app/Services/Coin/PostCoinSellService.php new file mode 100644 index 00000000..fe2a1e65 --- /dev/null +++ b/src/app/Services/Coin/PostCoinSellService.php @@ -0,0 +1,87 @@ +eloquentCoinRepository = $eloquentCoinRepository; + $this->eloquentWalletRepository = $eloquentWalletRepository; + $this->coinLoreRepository = $coinLoreRepository; + $this->eloquentWalletCoinRepository = $eloquentWalletCoinRepository; + } + + /** + * @param string $coinId + * @param string $walletId + * @param float $amountUsd + * @return float + * @throws Exception + */ + private function calculateCoinsAmount(string $coinId, string $walletId, float $amountUsd): float + { + if (!$this->eloquentCoinRepository->existsByCoinId($coinId)) { + throw new Exception('Coin not found'); + } + if (!$this->eloquentWalletRepository->existsByWalletId($walletId)) { + throw new Exception('Wallet not found'); + } + + $price = $this->coinLoreRepository->getUsdPriceByCoinId($coinId); + if ($price == null) { + throw new Exception('External API failure'); + } + + return round($amountUsd / $price, 8); // Unidad minima del Bitcoin (Satoshi) + // 1 BTC = 100.000.000 Sats + // 0,00000001 BTC = 1 Sats + } + + /** + * @param string $coinId + * @param string $walletId + * @param float $amountUsd + * @return void + * @throws Exception + */ + public function execute(string $coinId, string $walletId, float $amountUsd): void + { + try { + $amount = $this->calculateCoinsAmount($coinId, $walletId, $amountUsd); + if ($amount == 0) { + throw new Exception('Insufficient amount to sell'); + } + } catch (Exception $exception) { + throw $exception; + } + + $this->eloquentWalletCoinRepository->sellCoins($coinId, $walletId, $amount, $amountUsd); + } +} diff --git a/src/app/Services/Wallet/GetWalletBalanceService.php b/src/app/Services/Wallet/GetWalletBalanceService.php new file mode 100644 index 00000000..cb0aad28 --- /dev/null +++ b/src/app/Services/Wallet/GetWalletBalanceService.php @@ -0,0 +1,67 @@ +eloquentWalletRepository = $eloquentWalletRepository; + $this->coinLoreRepository = $coinLoreRepository; + } + + /** + * @param string $wallet_id + * @return float + * @throws Exception + */ + public function execute(string $wallet_id): float + { + if (!$this->eloquentWalletRepository->existsByWalletId($wallet_id)) { + throw new Exception('Wallet not found'); + } + + $balanceUsd = $this->eloquentWalletRepository->getBalanceUsdByWalletId($wallet_id); + if (is_null($balanceUsd)) { + throw new Exception('Wallet balance not found'); + } + + $wallet = $this->eloquentWalletRepository->getCoinsDataByWalletId($wallet_id); + if (is_null($wallet)) { + throw new Exception('Wallet not found'); + } + + for ($i = 0; $i < count($wallet); $i++) { + $coinId = $wallet[$i]->coin_id; + $amount = $wallet[$i]->amount; + + $price = $this->coinLoreRepository->getUsdPriceByCoinId($coinId); + if (is_null($price)) { + throw new Exception('External API failure'); + } + + $valueUsd = $amount * $price; + $balanceUsd += $valueUsd; + } + + return round($balanceUsd, 2); + } +} diff --git a/src/app/Services/Wallet/GetWalletService.php b/src/app/Services/Wallet/GetWalletService.php new file mode 100644 index 00000000..d8b02d2a --- /dev/null +++ b/src/app/Services/Wallet/GetWalletService.php @@ -0,0 +1,63 @@ +eloquentWalletRepository = $eloquentWalletRepository; + $this->coinLoreRepository = $coinLoreRepository; + } + + /** + * @param string $wallet_id + * @return array|null + * @throws Exception + */ + public function execute(string $wallet_id): ?array + { + if (!$this->eloquentWalletRepository->existsByWalletId($wallet_id)) { + throw new Exception('Wallet not found'); + } + + $wallet = $this->eloquentWalletRepository->getCoinsDataByWalletId($wallet_id); + if (is_null($wallet)) { + throw new Exception('Wallet not found'); + } + + for ($i = 0; $i < count($wallet); $i++) { + $coinId = $wallet[$i]->coin_id; + $amount = $wallet[$i]->amount; + + $price = $this->coinLoreRepository->getUsdPriceByCoinId($coinId); + if (is_null($price)) { + throw new Exception('External API failure'); + } + + $valueUsd = $amount * $price; + $walletData = json_decode(json_encode($wallet[$i]), true); + $wallet[$i] = array_merge($walletData, array("value_usd" => $valueUsd)); + } + + return $wallet; + } +} diff --git a/src/app/Services/Wallet/PostWalletOpenService.php b/src/app/Services/Wallet/PostWalletOpenService.php new file mode 100644 index 00000000..8d6c758c --- /dev/null +++ b/src/app/Services/Wallet/PostWalletOpenService.php @@ -0,0 +1,44 @@ +eloquentWalletRepository = $eloquentWalletRepository; + $this->eloquentUserRepository = $eloquentUserRepository; + } + + /** + * @param string $user_id + * @return string + * @throws Exception + */ + public function execute(string $user_id): string + { + if (!$this->eloquentUserRepository->existsByUserId($user_id)) { + throw new Exception('User not found'); + } + + return $this->eloquentWalletRepository->createWalletByUserId($user_id); + } +} diff --git a/src/composer.json b/src/composer.json index 357b2f8c..9785e360 100644 --- a/src/composer.json +++ b/src/composer.json @@ -22,7 +22,8 @@ "fakerphp/faker": "^1.9.1", "mockery/mockery": "^1.4.2", "nunomaduro/collision": "^5.0", - "phpunit/phpunit": "^9.3.3" + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.6" }, "config": { "optimize-autoloader": true, diff --git a/src/composer.lock b/src/composer.lock index 573ad44d..83ff6365 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "705a6545559d10ccc15761dbafc6814f", + "content-hash": "06f3af9fdf80a803d8e41cd91b384300", "packages": [ { "name": "asm89/stack-cors", @@ -5701,16 +5701,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.4.3", + "version": "9.5.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9fa359ff5ddaa5eb2be2bedb08a6a5787a5807ab" + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9fa359ff5ddaa5eb2be2bedb08a6a5787a5807ab", - "reference": "9fa359ff5ddaa5eb2be2bedb08a6a5787a5807ab", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741", + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741", "shasum": "" }, "require": { @@ -5726,7 +5726,7 @@ "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2", + "phpunit/php-code-coverage": "^9.2.3", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -5757,7 +5757,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.4-dev" + "dev-master": "9.5-dev" } }, "autoload": { @@ -5786,7 +5786,21 @@ "testing", "xunit" ], - "time": "2020-11-10T12:53:30+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.4" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-23T07:16:29+00:00" }, { "name": "sebastian/cli-parser", @@ -6592,6 +6606,62 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "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": "2021-04-09T00:54:41+00:00" + }, { "name": "symfony/debug", "version": "v4.4.20", diff --git a/src/database/factories/CoinFactory.php b/src/database/factories/CoinFactory.php new file mode 100644 index 00000000..ce0b562d --- /dev/null +++ b/src/database/factories/CoinFactory.php @@ -0,0 +1,30 @@ + '2', + 'name' => 'Dogecoin', + 'symbol' => 'DOGE' + ]; + } +} diff --git a/src/database/factories/UserFactory.php b/src/database/factories/UserFactory.php index 88f78c62..34ca99ac 100644 --- a/src/database/factories/UserFactory.php +++ b/src/database/factories/UserFactory.php @@ -22,9 +22,7 @@ class UserFactory extends Factory public function definition() { return [ - 'id' => 1, - 'name' => 'user_name', - 'email' => 'email@email.com' + 'user_id' => 'factory-user' ]; } } diff --git a/src/database/factories/WalletCoinFactory.php b/src/database/factories/WalletCoinFactory.php new file mode 100644 index 00000000..6b7d30ad --- /dev/null +++ b/src/database/factories/WalletCoinFactory.php @@ -0,0 +1,30 @@ + 'factory-wallet', + 'coin_id' => '2', + 'amount' => 1000000 + ]; + } +} diff --git a/src/database/factories/WalletFactory.php b/src/database/factories/WalletFactory.php new file mode 100644 index 00000000..321e2880 --- /dev/null +++ b/src/database/factories/WalletFactory.php @@ -0,0 +1,30 @@ + 'factory-wallet', + 'user_id' => 'factory-user', + 'balance_usd' => 0 + ]; + } +} diff --git a/src/database/migrations/2014_10_12_000000_create_users_table.php b/src/database/migrations/2014_10_12_000000_create_users_table.php index 763a8309..d61160f3 100644 --- a/src/database/migrations/2014_10_12_000000_create_users_table.php +++ b/src/database/migrations/2014_10_12_000000_create_users_table.php @@ -14,10 +14,7 @@ class CreateUsersTable extends Migration public function up() { Schema::create('users', function (Blueprint $table) { - $table->id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamps(); + $table->string('user_id')->primary(); }); } diff --git a/src/database/migrations/2021_05_11_104022_create_wallets_table.php b/src/database/migrations/2021_05_11_104022_create_wallets_table.php new file mode 100644 index 00000000..a1e11a86 --- /dev/null +++ b/src/database/migrations/2021_05_11_104022_create_wallets_table.php @@ -0,0 +1,34 @@ +string('wallet_id')->primary(); + $table->string('user_id'); + $table->double('balance_usd', 32, 2); + + $table->foreign('user_id')->references('user_id')->on('users'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('wallets'); + } +} diff --git a/src/database/migrations/2021_05_11_104721_create_coins_table.php b/src/database/migrations/2021_05_11_104721_create_coins_table.php new file mode 100644 index 00000000..93bf3dd3 --- /dev/null +++ b/src/database/migrations/2021_05_11_104721_create_coins_table.php @@ -0,0 +1,32 @@ +string('coin_id')->primary(); + $table->string('name'); + $table->string('symbol'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('coins'); + } +} diff --git a/src/database/migrations/2021_05_11_110302_create_walletscoins_table.php b/src/database/migrations/2021_05_11_110302_create_walletscoins_table.php new file mode 100644 index 00000000..7ac5d5dd --- /dev/null +++ b/src/database/migrations/2021_05_11_110302_create_walletscoins_table.php @@ -0,0 +1,36 @@ +string('wallet_id'); + $table->string('coin_id'); + $table->double('amount', 32, 8); + + $table->primary(['wallet_id', 'coin_id']); + $table->foreign('wallet_id')->references('wallet_id')->on('wallets'); + $table->foreign('coin_id')->references('coin_id')->on('coins'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('walletscoins'); + } +} diff --git a/src/database/seeders/DatabaseSeeder.php b/src/database/seeders/DatabaseSeeder.php index 57b73b54..56b32d56 100644 --- a/src/database/seeders/DatabaseSeeder.php +++ b/src/database/seeders/DatabaseSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class DatabaseSeeder extends Seeder { @@ -14,5 +15,150 @@ class DatabaseSeeder extends Seeder public function run() { // \App\Models\User::factory(10)->create(); + + // USERS + DB::table('users')->insert([ + 'user_id' => 'test-user', + ]); + + DB::table('users')->insert([ + 'user_id' => 'mkoding', + ]); + + DB::table('users')->insert([ + 'user_id' => 'bitoiz', + ]); + + DB::table('users')->insert([ + 'user_id' => 'idoate', + ]); + + // WALLETS + DB::table('wallets')->insert([ + 'wallet_id' => 'test-wallet', + 'user_id' => 'test-user', + 'balance_usd' => 0, + ]); + + // COINS + DB::table('coins')->insert([ + 'coin_id' => '90', + 'name' => 'Bitcoin', + 'symbol' => 'BTC', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '80', + 'name' => 'Ethereum', + 'symbol' => 'ETH', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '2710', + 'name' => 'Binance Coin', + 'symbol' => 'BNB', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '58', + 'name' => 'XRP', + 'symbol' => 'XPR', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '257', + 'name' => 'Cardano', + 'symbol' => 'ADA', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '518', + 'name' => 'Tether', + 'symbol' => 'USDT', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '45219', + 'name' => 'Polkadot', + 'symbol' => 'DOT', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '2321', + 'name' => 'Bitcoin Cash', + 'symbol' => 'BCH', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '1', + 'name' => 'Litecoin', + 'symbol' => 'LTC', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '89', + 'name' => 'Stellar', + 'symbol' => 'XLM', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '2751', + 'name' => 'ChainLink', + 'symbol' => 'LINK', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '47305', + 'name' => 'Uniswap', + 'symbol' => 'UNI', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '118', + 'name' => 'Ethereum Classic', + 'symbol' => 'ETC', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '2741', + 'name' => 'VeChain', + 'symbol' => 'VET', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '32360', + 'name' => 'Theta Token', + 'symbol' => 'THETA', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '2679', + 'name' => 'EOS', + 'symbol' => 'EOS', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '2713', + 'name' => 'TRON', + 'symbol' => 'TRX', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '28', + 'name' => 'Monero', + 'symbol' => 'XMR', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '133', + 'name' => 'Neo', + 'symbol' => 'NEO', + ]); + + DB::table('coins')->insert([ + 'coin_id' => '46018', + 'name' => 'Aave', + 'symbol' => 'AAVE', + ]); } } diff --git a/src/phpcs.xml b/src/phpcs.xml new file mode 100644 index 00000000..49937628 --- /dev/null +++ b/src/phpcs.xml @@ -0,0 +1,11 @@ + + + The PSR2 coding standard. + + app/ + vendor + resources + database/ + storage/ + node_modules/ + diff --git a/src/phpunit.xml b/src/phpunit.xml index e068d84e..8d052061 100644 --- a/src/phpunit.xml +++ b/src/phpunit.xml @@ -11,6 +11,9 @@ ./tests/Integration + + ./tests/E2E + diff --git a/src/routes/api.php b/src/routes/api.php index 10f1671b..34969c3c 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -1,6 +1,10 @@ id === (int) $id; +Broadcast::channel('App.Models.User.{user_id}', function ($user, $user_id) { + return (int) $user->user_id === (int) $user_id; }); diff --git a/src/tests/E2E/E2ETest.php b/src/tests/E2E/E2ETest.php new file mode 100644 index 00000000..6da07a98 --- /dev/null +++ b/src/tests/E2E/E2ETest.php @@ -0,0 +1,98 @@ +create(); + Coin::factory(Coin::class)->create(); + } + + /** + * @test + */ + public function normalUserWork() + { + $this->get('/api/status') + ->assertStatus(Response::HTTP_OK) + ->assertExactJson([ + 'status' => 'Success', + 'message' => 'Systems are up and running' + ]); + + $newWallet = $this->postJson('/api/wallet/open', ['user_id' => 'factory-user']); + + $newWallet->assertStatus(Response::HTTP_OK) + ->assertJsonStructure([ + "wallet_id" + ]); + + $walletId = $newWallet->baseResponse->original['wallet_id']; + + $this->postJson( + '/api/coin/buy', + [ + "coin_id" => "2", + "wallet_id" => $walletId, + "amount_usd" => 50 + ] + )->assertStatus(Response::HTTP_OK)->assertExactJson([ + 'status' => 'Successful operation', + 'message' => 'The buy has been successfully completed' + ]); + + $this->get('/api/wallet/'.$walletId) + ->assertStatus(Response::HTTP_OK) + ->assertJsonFragment([ + "coin_id" => "2", + "name" => "Dogecoin", + "symbol" => "DOGE", + ]); + + $this->get('/api/wallet/'.$walletId.'/balance') + ->assertStatus(Response::HTTP_OK) + ->assertJsonStructure([ + "balance_usd" + ]); + + $this->postJson( + '/api/coin/sell', + [ + "coin_id" => "2", + "wallet_id" => $walletId, + "amount_usd" => 25 + ] + )->assertStatus(Response::HTTP_OK)->assertExactJson([ + 'status' => 'Successful operation', + 'message' => 'The sell has been successfully completed' + ]); + + $this->get('/api/wallet/'.$walletId) + ->assertStatus(Response::HTTP_OK) + ->assertJsonFragment([ + "coin_id" => "2", + "name" => "Dogecoin", + "symbol" => "DOGE", + ]); + + $this->get('/api/wallet/'.$walletId.'/balance') + ->assertStatus(Response::HTTP_OK) + ->assertJsonStructure([ + "balance_usd" + ]); + } +} diff --git a/src/tests/Integration/Controller/GetWalletBalanceControllerTest.php b/src/tests/Integration/Controller/GetWalletBalanceControllerTest.php new file mode 100644 index 00000000..6b423871 --- /dev/null +++ b/src/tests/Integration/Controller/GetWalletBalanceControllerTest.php @@ -0,0 +1,54 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + */ + public function getWalletBalanceWalletNotFound() + { + $response = $this->get('/api/wallet/error-wallet/balance'); + + $response->assertStatus(Response::HTTP_NOT_FOUND)->assertExactJson([ + 'status' => 'Wallet with the specified ID was not found', + 'message' => 'Wallet not found' + ]); + } + + /** + * @test + */ + public function getWalletBalance() + { + $response = $this->get('/api/wallet/factory-wallet/balance'); + + $response->assertStatus(Response::HTTP_OK) + ->assertJsonStructure([ + "balance_usd" + ]); + } +} diff --git a/src/tests/Integration/Controller/GetWalletControllerTest.php b/src/tests/Integration/Controller/GetWalletControllerTest.php new file mode 100644 index 00000000..86a9bcbc --- /dev/null +++ b/src/tests/Integration/Controller/GetWalletControllerTest.php @@ -0,0 +1,57 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + */ + public function getWalletWalletNotFound() + { + $response = $this->get('/api/wallet/error-wallet'); + + $response->assertStatus(Response::HTTP_NOT_FOUND)->assertExactJson([ + 'status' => 'Wallet with the specified ID was not found', + 'message' => 'Wallet not found' + ]); + } + + /** + * @test + */ + public function getWallet() + { + $response = $this->get('/api/wallet/factory-wallet'); + + $response->assertStatus(Response::HTTP_OK) + ->assertJsonFragment([ + "amount" => "1000000.0", + "coin_id" => "2", + "name" => "Dogecoin", + "symbol" => "DOGE", + ]); + } +} diff --git a/src/tests/Integration/Controller/PostCoinBuyControllerTest.php b/src/tests/Integration/Controller/PostCoinBuyControllerTest.php new file mode 100644 index 00000000..d0307fba --- /dev/null +++ b/src/tests/Integration/Controller/PostCoinBuyControllerTest.php @@ -0,0 +1,127 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + */ + public function postCoinBuyCoinNotFound() + { + $response = $this->postJson( + '/api/coin/buy', + [ + "coin_id" => "error-coin", + "wallet_id" => "factory-wallet", + "amount_usd" => 50000 + ] + ); + + $response->assertStatus(Response::HTTP_NOT_FOUND)->assertExactJson([ + 'status' => 'Coin with the specified ID was not found', + 'message' => 'Coin not found' + ]); + } + + /** + * @test + */ + public function postCoinBuyWalletNotFound() + { + $response = $this->postJson( + '/api/coin/buy', + [ + "coin_id" => "2", + "wallet_id" => "error-wallet", + "amount_usd" => 50000 + ] + ); + + $response->assertStatus(Response::HTTP_NOT_FOUND)->assertExactJson([ + 'status' => 'Wallet with the specified ID was not found', + 'message' => 'Wallet not found' + ]); + } + + /** + * @test + */ + public function postCoinBuyInsufficientAmount() + { + $response = $this->postJson( + '/api/coin/buy', + [ + "coin_id" => "2", + "wallet_id" => "factory-wallet", + "amount_usd" => 0 + ] + ); + + $response->assertStatus(Response::HTTP_BAD_REQUEST)->assertExactJson([ + 'status' => 'Bad Request Error', + 'message' => 'Insufficient amount to buy' + ]); + } + + /** + * @test + */ + public function postCoinBuyInsufficientArguments() + { + $response = $this->postJson( + '/api/coin/buy', + [ + "coin_id" => "2", + "wallet_id" => "factory-wallet" + ] + ); + + $response->assertStatus(Response::HTTP_BAD_REQUEST)->assertExactJson([ + 'status' => 'Bad Request Error', + 'message' => 'Insufficient arguments in the POST' + ]); + } + + /** + * @test + */ + public function postCoinBuy() + { + $response = $this->postJson( + '/api/coin/buy', + [ + "coin_id" => "2", + "wallet_id" => "factory-wallet", + "amount_usd" => 50 + ] + ); + + $response->assertStatus(Response::HTTP_OK)->assertExactJson([ + 'status' => 'Successful operation', + 'message' => 'The buy has been successfully completed' + ]); + } +} diff --git a/src/tests/Integration/Controller/PostCoinSellControllerTest.php b/src/tests/Integration/Controller/PostCoinSellControllerTest.php new file mode 100644 index 00000000..eb57ab93 --- /dev/null +++ b/src/tests/Integration/Controller/PostCoinSellControllerTest.php @@ -0,0 +1,127 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + */ + public function postCoinSellCoinNotFound() + { + $response = $this->postJson( + '/api/coin/sell', + [ + "coin_id" => "error-coin", + "wallet_id" => "factory-wallet", + "amount_usd" => 50000 + ] + ); + + $response->assertStatus(Response::HTTP_NOT_FOUND)->assertExactJson([ + 'status' => 'Coin with the specified ID was not found', + 'message' => 'Coin not found' + ]); + } + + /** + * @test + */ + public function postCoinSellWalletNotFound() + { + $response = $this->postJson( + '/api/coin/sell', + [ + "coin_id" => "2", + "wallet_id" => "error-wallet", + "amount_usd" => 1 + ] + ); + + $response->assertStatus(Response::HTTP_NOT_FOUND)->assertExactJson([ + 'status' => 'Wallet with the specified ID was not found', + 'message' => 'Wallet not found' + ]); + } + + /** + * @test + */ + public function postCoinSellInsufficientAmount() + { + $response = $this->postJson( + '/api/coin/sell', + [ + "coin_id" => "2", + "wallet_id" => "factory-wallet", + "amount_usd" => 0 + ] + ); + + $response->assertStatus(Response::HTTP_BAD_REQUEST)->assertExactJson([ + 'status' => 'Bad Request Error', + 'message' => 'Insufficient amount to sell' + ]); + } + + /** + * @test + */ + public function postCoinSellInsufficientArguments() + { + $response = $this->postJson( + '/api/coin/sell', + [ + "coin_id" => "2", + "wallet_id" => "factory-wallet" + ] + ); + + $response->assertStatus(Response::HTTP_BAD_REQUEST)->assertExactJson([ + 'status' => 'Bad Request Error', + 'message' => 'Insufficient arguments in the POST' + ]); + } + + /** + * @test + */ + public function postCoinSell() + { + $response = $this->postJson( + '/api/coin/sell', + [ + "coin_id" => "2", + "wallet_id" => "factory-wallet", + "amount_usd" => 1 + ] + ); + + $response->assertStatus(Response::HTTP_OK)->assertExactJson([ + 'status' => 'Successful operation', + 'message' => 'The sell has been successfully completed' + ]); + } +} diff --git a/src/tests/Integration/Controller/PostWalletOpenControllerTest.php b/src/tests/Integration/Controller/PostWalletOpenControllerTest.php new file mode 100644 index 00000000..2f142423 --- /dev/null +++ b/src/tests/Integration/Controller/PostWalletOpenControllerTest.php @@ -0,0 +1,67 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + */ + public function postWalletOpenUserNotFound() + { + $response = $this->postJson('/api/wallet/open', ['user_id' => 'error-user']); + + $response->assertStatus(Response::HTTP_NOT_FOUND)->assertExactJson([ + 'status' => 'User with the specified ID was not found', + 'message' => 'User not found' + ]); + } + + /** + * @test + */ + public function postWalletOpenInsufficientArguments() + { + $response = $this->postJson('/api/wallet/open', []); + + $response->assertStatus(Response::HTTP_BAD_REQUEST)->assertExactJson([ + 'status' => 'Bad Request Error', + 'message' => 'Insufficient arguments in the POST' + ]); + } + + /** + * @test + */ + public function postWalletOpen() + { + $response = $this->postJson('/api/wallet/open', ['user_id' => 'factory-user']); + + $response->assertStatus(Response::HTTP_OK) + ->assertJsonStructure([ + "wallet_id" + ]); + } +} diff --git a/src/tests/Integration/Controller/StatusControllerTest.php b/src/tests/Integration/Controller/StatusControllerTest.php new file mode 100644 index 00000000..78fa1132 --- /dev/null +++ b/src/tests/Integration/Controller/StatusControllerTest.php @@ -0,0 +1,41 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + */ + public function getApiStatus() + { + $response = $this->get('/api/status'); + + $response->assertStatus(Response::HTTP_OK)->assertExactJson([ + 'status' => 'Success', + 'message' => 'Systems are up and running' + ]); + } +} diff --git a/src/tests/Integration/DataSources/CoinLoreDataSourceTest.php b/src/tests/Integration/DataSources/CoinLoreDataSourceTest.php new file mode 100644 index 00000000..ae887598 --- /dev/null +++ b/src/tests/Integration/DataSources/CoinLoreDataSourceTest.php @@ -0,0 +1,39 @@ +getUsdPriceByCoinId('error-coin'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function getUsdPriceByCoinId() + { + $coinLoreDataSource = new CoinLoreDataSource(); + + $result = $coinLoreDataSource->getUsdPriceByCoinId('2'); + $this->assertIsString($result); + } +} diff --git a/src/tests/Integration/DataSources/EloquentCoinDataSourceTest.php b/src/tests/Integration/DataSources/EloquentCoinDataSourceTest.php new file mode 100644 index 00000000..2afa558e --- /dev/null +++ b/src/tests/Integration/DataSources/EloquentCoinDataSourceTest.php @@ -0,0 +1,57 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + * @throws Exception + */ + public function notExistsByCoinId() + { + $expectedResult = false; + + $eloquentCoinDataSource = new EloquentCoinDataSource(); + + $result = $eloquentCoinDataSource->existsByCoinId('error-coin'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function existsByCoinId() + { + $expectedResult = true; + + $eloquentCoinDataSource = new EloquentCoinDataSource(); + + $result = $eloquentCoinDataSource->existsByCoinId('2'); + $this->assertEquals($expectedResult, $result); + } +} diff --git a/src/tests/Integration/DataSources/EloquentUserDataSourceTest.php b/src/tests/Integration/DataSources/EloquentUserDataSourceTest.php index 9eb57448..140098bd 100644 --- a/src/tests/Integration/DataSources/EloquentUserDataSourceTest.php +++ b/src/tests/Integration/DataSources/EloquentUserDataSourceTest.php @@ -3,7 +3,10 @@ namespace Tests\Integration\DataSources; use App\DataSource\Database\EloquentUserDataSource; +use App\Models\Coin; use App\Models\User; +use App\Models\Wallet; +use App\Models\WalletCoin; use Exception; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -13,43 +16,42 @@ class EloquentUserDataSourceTest extends TestCase use RefreshDatabase; /** - * @test + * @setUp */ - public function findsUserByEmail() + protected function setUp(): void { + parent::setUp(); User::factory(User::class)->create(); - $eloquentUserDataSource = new EloquentUserDataSource(); - - $user = $eloquentUserDataSource->findByEmail('email@email.com'); - - $this->assertInstanceOf(User::class, $user); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); } /** * @test + * @throws Exception */ - public function noUserIsFoundForTheGivenEmailI() + public function notExistsByUserId() { - $eloquentUserDataSource = new EloquentUserDataSource(); + $expectedResult = false; - $this->expectException(Exception::class); + $eloquentUserDataSource = new EloquentUserDataSource(); - $eloquentUserDataSource->findByEmail('email@email.com'); + $result = $eloquentUserDataSource->existsByUserId('error-user'); + $this->assertEquals($expectedResult, $result); } /** * @test + * @throws Exception */ - public function noUserIsFoundForTheGivenEmailII() + public function existsByUserId() { - User::factory(User::class)->create(); - $eloquentUserDataSource = new EloquentUserDataSource(); + $expectedResult = true; - try { - $eloquentUserDataSource->findByEmail('not_known@email.com'); - } catch (Exception $exception) { - $this->assertEquals('User not found', $exception->getMessage()); - } + $eloquentUserDataSource = new EloquentUserDataSource(); + $result = $eloquentUserDataSource->existsByUserId('factory-user'); + $this->assertEquals($expectedResult, $result); } } diff --git a/src/tests/Integration/DataSources/EloquentWalletCoinDataSourceTest.php b/src/tests/Integration/DataSources/EloquentWalletCoinDataSourceTest.php new file mode 100644 index 00000000..5293d303 --- /dev/null +++ b/src/tests/Integration/DataSources/EloquentWalletCoinDataSourceTest.php @@ -0,0 +1,138 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + * @throws Exception + */ + public function getAmountByIdsWalletNotFound() + { + $expectedResult = null; + + $eloquentWalletCoinDataSource = new EloquentWalletCoinDataSource(); + + $result = $eloquentWalletCoinDataSource->getAmountByIds('2', 'error-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function getAmountByIdsCoinNotFound() + { + $expectedResult = null; + + $eloquentWalletCoinDataSource = new EloquentWalletCoinDataSource(); + + $result = $eloquentWalletCoinDataSource->getAmountByIds('error-coin', 'factory-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function getAmountByIds() + { + $expectedResult = 1000000; + + $eloquentWalletCoinDataSource = new EloquentWalletCoinDataSource(); + + $result = $eloquentWalletCoinDataSource->getAmountByIds('2', 'factory-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function buyCoins() + { + $eloquentWalletCoinDataSource = new EloquentWalletCoinDataSource(); + + try { + $eloquentWalletCoinDataSource->buyCoins( + '2', + 'factory-wallet', + '50', + '100' + ); + } catch (Exception $exception) { + $this->fail("Failure by exception catch! - " . $exception->getMessage()); + } + + $this->assertTrue(true); + } + + /** + * @test + * @throws Exception + */ + public function sellCoinsInsufficientAmount() + { + $eloquentWalletCoinDataSource = new EloquentWalletCoinDataSource(); + + try { + $eloquentWalletCoinDataSource->sellCoins( + '2', + 'factory-wallet', + '10000000000', + '100' + ); + } catch (Exception $exception) { + $this->assertEquals('Insufficient amount to sell', $exception->getMessage()); + return; + } + + $this->fail('Exception not catch!'); + } + + /** + * @test + * @throws Exception + */ + public function sellCoins() + { + $eloquentWalletCoinDataSource = new EloquentWalletCoinDataSource(); + + try { + $eloquentWalletCoinDataSource->sellCoins( + '2', + 'factory-wallet', + '50', + '100' + ); + } catch (Exception $exception) { + $this->fail("Failure by exception catch! - " . $exception->getMessage()); + } + + $this->assertTrue(true); + } +} diff --git a/src/tests/Integration/DataSources/EloquentWalletDataSourceTest.php b/src/tests/Integration/DataSources/EloquentWalletDataSourceTest.php new file mode 100644 index 00000000..7b0c2509 --- /dev/null +++ b/src/tests/Integration/DataSources/EloquentWalletDataSourceTest.php @@ -0,0 +1,134 @@ +create(); + Wallet::factory(Wallet::class)->create(); + Coin::factory(Coin::class)->create(); + WalletCoin::factory(WalletCoin::class)->create(); + } + + /** + * @test + * @throws Exception + */ + public function getCoinsDataByWalletIdWalletNotFound() + { + $expectedResult = array(); + + $eloquentWalletDataSource = new EloquentWalletDataSource(); + + $result = $eloquentWalletDataSource->getCoinsDataByWalletId('error-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function getCoinsDataByWalletId() + { + $expectedResult = array( + (object)[ + "coin_id" => '2', + "name" => "Dogecoin", + "symbol" => "DOGE", + "amount" => 1000000 + ] + ); + + $eloquentWalletDataSource = new EloquentWalletDataSource(); + + $result = $eloquentWalletDataSource->getCoinsDataByWalletId('factory-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function getBalanceUsdByWalletIdWalletNotFound() + { + $expectedResult = null; + + $eloquentWalletDataSource = new EloquentWalletDataSource(); + + $result = $eloquentWalletDataSource->getBalanceUsdByWalletId('error-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function getBalanceUsdByWalletId() + { + $expectedResult = 0; + + $eloquentWalletDataSource = new EloquentWalletDataSource(); + + $result = $eloquentWalletDataSource->getBalanceUsdByWalletId('factory-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function notExistsByWalletId() + { + $expectedResult = false; + + $eloquentWalletDataSource = new EloquentWalletDataSource(); + + $result = $eloquentWalletDataSource->existsByWalletId('error-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function existsByWalletId() + { + $expectedResult = true; + + $eloquentWalletDataSource = new EloquentWalletDataSource(); + + $result = $eloquentWalletDataSource->existsByWalletId('factory-wallet'); + $this->assertEquals($expectedResult, $result); + } + + /** + * @test + * @throws Exception + */ + public function createWalletByUserId() + { + $expectedResult = 'wallet-000000001'; + + $eloquentWalletDataSource = new EloquentWalletDataSource(); + + $result = $eloquentWalletDataSource->createWalletByUserId('factory-user'); + $this->assertEquals($expectedResult, $result); + } +} diff --git a/src/tests/Unit/Services/Coin/PostCoinBuyServiceTest.php b/src/tests/Unit/Services/Coin/PostCoinBuyServiceTest.php new file mode 100644 index 00000000..88678e8f --- /dev/null +++ b/src/tests/Unit/Services/Coin/PostCoinBuyServiceTest.php @@ -0,0 +1,205 @@ +eloquentCoinDataSource = $prophet->prophesize(EloquentCoinDataSource::class); + $this->eloquentWalletDataSource = $prophet->prophesize(EloquentWalletDataSource::class); + $this->eloquentWalletCoinDataSource = $prophet->prophesize(EloquentWalletCoinDataSource::class); + $this->coinLoreDataSource = $prophet->prophesize(CoinLoreDataSource::class); + + $this->postCoinBuyService = new PostCoinBuyService( + $this->eloquentCoinDataSource->reveal(), + $this->eloquentWalletDataSource->reveal(), + $this->eloquentWalletCoinDataSource->reveal(), + $this->coinLoreDataSource->reveal() + ); + } + + /** + * @test + */ + public function postCoinBuyCoinNotFound() + { + $coinId = 'error-coin'; + $walletId = 'test-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = false; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = 5000; + $expectedResult = "Coin not found"; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->buyCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinBuyService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function postCoinBuyWalletNotFound() + { + $coinId = '90'; + $walletId = 'error-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = true; + $expectedExistsByWalletIdDatabaseWalletReturn = false; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = 5000; + $expectedResult = "Wallet not found"; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->buyCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinBuyService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function postCoinBuyExternalAPIFails() + { + $coinId = '90'; + $walletId = 'test-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = true; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = null; + $expectedResult = "External API failure"; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->buyCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinBuyService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function postCoinBuyWorking() + { + $coinId = '90'; + $walletId = 'test-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = true; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = 5000; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->buyCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinBuyService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->fail("Failure by exception catch! - " . $exception->getMessage()); + } + $this->assertTrue(true); + } +} diff --git a/src/tests/Unit/Services/Coin/PostCoinSellServiceTest.php b/src/tests/Unit/Services/Coin/PostCoinSellServiceTest.php new file mode 100644 index 00000000..16ec0fee --- /dev/null +++ b/src/tests/Unit/Services/Coin/PostCoinSellServiceTest.php @@ -0,0 +1,205 @@ +eloquentCoinDataSource = $prophet->prophesize(EloquentCoinDataSource::class); + $this->eloquentWalletDataSource = $prophet->prophesize(EloquentWalletDataSource::class); + $this->eloquentWalletCoinDataSource = $prophet->prophesize(EloquentWalletCoinDataSource::class); + $this->coinLoreDataSource = $prophet->prophesize(CoinLoreDataSource::class); + + $this->postCoinSellService = new PostCoinSellService( + $this->eloquentCoinDataSource->reveal(), + $this->eloquentWalletDataSource->reveal(), + $this->eloquentWalletCoinDataSource->reveal(), + $this->coinLoreDataSource->reveal() + ); + } + + /** + * @test + */ + public function postCoinSellCoinDoesNotExist() + { + $coinId = 'error-coin'; + $walletId = 'test-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = false; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = 5000; + $expectedResult = "Coin not found"; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->sellCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinSellService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function postCoinSellWalletDoesNotExist() + { + $coinId = '90'; + $walletId = 'error-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = true; + $expectedExistsByWalletIdDatabaseWalletReturn = false; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = 5000; + $expectedResult = "Wallet not found"; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->sellCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinSellService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function postCoinSellExternalAPIFails() + { + $coinId = '90'; + $walletId = 'test-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = true; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = null; + $expectedResult = "External API failure"; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->sellCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinSellService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function postCoinSellWorking() + { + $coinId = '90'; + $walletId = 'test-wallet'; + $amountUsd = 5000; + $amount = 1; + $expectedExistsByCoinIdDatabaseCoinReturn = true; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = 5000; + + $this->eloquentCoinDataSource + ->existsByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByCoinIdDatabaseCoinReturn); + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletCoinDataSource + ->sellCoins($coinId, $walletId, $amount, $amountUsd) + ->shouldBeCalledOnce(); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->postCoinSellService->execute($coinId, $walletId, $amountUsd); + } catch (Exception $exception) { + $this->fail("Failure by exception catch! - " . $exception->getMessage()); + } + $this->assertTrue(true); + } +} diff --git a/src/tests/Unit/Services/Wallet/GetWalletBalanceServiceTest.php b/src/tests/Unit/Services/Wallet/GetWalletBalanceServiceTest.php new file mode 100644 index 00000000..db4641a5 --- /dev/null +++ b/src/tests/Unit/Services/Wallet/GetWalletBalanceServiceTest.php @@ -0,0 +1,217 @@ +eloquentWalletDataSource = $prophet->prophesize(EloquentWalletDataSource::class); + $this->coinLoreDataSource = $prophet->prophesize(CoinLoreDataSource::class); + + $this->getWalletBalanceService = new GetWalletBalanceService( + $this->eloquentWalletDataSource->reveal(), + $this->coinLoreDataSource->reveal() + ); + } + + /** + * @test + */ + public function getWalletBalanceWalletNotFound() + { + $walletId = 'error-wallet'; + $coinId = '90'; + $expectedExistsByWalletIdDatabaseWalletReturn = false; + $expectedGetCoinsDataByWalletIdDatabaseWalletReturn = null; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = "5000"; + $expectedGetBalanceByIdDatabaseWalletReturn = -2000; + $expectedResult = "Wallet not found"; + + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletDataSource + ->getCoinsDataByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetCoinsDataByWalletIdDatabaseWalletReturn); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + $this->eloquentWalletDataSource + ->getBalanceUsdByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetBalanceByIdDatabaseWalletReturn); + + try { + $this->getWalletBalanceService->execute($walletId); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function getWalletBalanceExternalAPIFails() + { + $walletId = 'test-wallet'; + $coinId = '90'; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetCoinsDataByWalletIdDatabaseWalletReturn = array( + (object)[ + "coin_id" => $coinId, + "name" => "Bitcoin", + "symbol" => "BTC", + "amount" => 2 + ] + ); + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = null; + $expectedWalletBalanceByIdDatabaseReturn = -2000; + $expectedResult = "External API failure"; + + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletDataSource + ->getCoinsDataByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetCoinsDataByWalletIdDatabaseWalletReturn); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + $this->eloquentWalletDataSource + ->getBalanceUsdByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedWalletBalanceByIdDatabaseReturn); + + try { + $this->getWalletBalanceService->execute($walletId); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function getWalletBalanceBalanceNotFound() + { + $walletId = 'test-wallet'; + $coinId = '90'; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetCoinsDataByWalletIdDatabaseWalletReturn = array( + (object)[ + "coin_id" => $coinId, + "name" => "Bitcoin", + "symbol" => "BTC", + "amount" => 2 + ] + ); + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = '5000'; + $expectedGetBalanceByIdDatabaseWalletReturn = null; + $expectedResult = "Wallet balance not found"; + + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletDataSource + ->getCoinsDataByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetCoinsDataByWalletIdDatabaseWalletReturn); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + $this->eloquentWalletDataSource + ->getBalanceUsdByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetBalanceByIdDatabaseWalletReturn); + + try { + $this->getWalletBalanceService->execute($walletId); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function getWalletBalanceWorking() + { + $walletId = 'error-wallet'; + $coinId = '90'; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetCoinsDataByWalletIdDatabaseWalletReturn = array( + (object)[ + "coin_id" => $coinId, + "name" => "Bitcoin", + "symbol" => "BTC", + "amount" => 2 + ] + ); + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = '5000'; + $expectedGetBalanceByIdDatabaseWalletReturn = -2000; + $expectedResult = 8000; + + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletDataSource + ->getCoinsDataByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetCoinsDataByWalletIdDatabaseWalletReturn); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + $this->eloquentWalletDataSource + ->getBalanceUsdByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetBalanceByIdDatabaseWalletReturn); + + try { + $result = $this->getWalletBalanceService->execute($walletId); + } catch (Exception $exception) { + $this->fail("Failure by exception catch! - " . $exception->getMessage()); + } + $this->assertEquals($expectedResult, $result); + } +} diff --git a/src/tests/Unit/Services/Wallet/GetWalletServiceTest.php b/src/tests/Unit/Services/Wallet/GetWalletServiceTest.php new file mode 100644 index 00000000..b6ae8619 --- /dev/null +++ b/src/tests/Unit/Services/Wallet/GetWalletServiceTest.php @@ -0,0 +1,162 @@ +eloquentWalletDataSource = $prophet->prophesize(EloquentWalletDataSource::class); + $this->coinLoreDataSource = $prophet->prophesize(CoinLoreDataSource::class); + + $this->getWalletService = new GetWalletService( + $this->eloquentWalletDataSource->reveal(), + $this->coinLoreDataSource->reveal() + ); + } + + /** + * @test + */ + public function getWalletWalletNotFound() + { + $walletId = 'error-wallet'; + $coinId = '90'; + $expectedExistsByWalletIdDatabaseWalletReturn = false; + $expectedGetCoinsDataByWalletIdDatabaseWalletReturn = null; + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = "5000"; + $expectedResult = "Wallet not found"; + + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletDataSource + ->getCoinsDataByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetCoinsDataByWalletIdDatabaseWalletReturn); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->getWalletService->execute($walletId); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function getWalletExternalAPIFails() + { + $walletId = 'test-wallet'; + $coinId = '90'; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetCoinsDataByWalletIdDatabaseWalletReturn = array( + (object)[ + "coin_id" => $coinId, + "name" => "Bitcoin", + "symbol" => "BTC", + "amount" => 2 + ] + ); + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = null; + $expectedResult = "External API failure"; + + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletDataSource + ->getCoinsDataByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetCoinsDataByWalletIdDatabaseWalletReturn); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $this->getWalletService->execute($walletId); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function getWalletWorking() + { + $walletId = 'test-wallet'; + $coinId = '90'; + $expectedExistsByWalletIdDatabaseWalletReturn = true; + $expectedGetCoinsDataByWalletIdDatabaseWalletReturn = array( + (object)[ + "coin_id" => $coinId, + "name" => "Bitcoin", + "symbol" => "BTC", + "amount" => 2 + ] + ); + $expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn = "5000"; + $expectedResult = array(array( + "coin_id" => $coinId, + "name" => "Bitcoin", + "symbol" => "BTC", + "amount" => 2, + "value_usd" => 10000 + )); + + $this->eloquentWalletDataSource + ->existsByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByWalletIdDatabaseWalletReturn); + $this->eloquentWalletDataSource + ->getCoinsDataByWalletId($walletId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetCoinsDataByWalletIdDatabaseWalletReturn); + $this->coinLoreDataSource + ->getUsdPriceByCoinId($coinId) + ->shouldBeCalledOnce() + ->willReturn($expectedGetUsdPriceByCoinIdDatabaseCoinLoreReturn); + + try { + $result = $this->getWalletService->execute($walletId); + } catch (Exception $exception) { + $this->fail("Failure by exception catch! - " . $exception->getMessage()); + } + $this->assertEquals($expectedResult, $result); + } +} diff --git a/src/tests/Unit/Services/Wallet/PostWalletOpenServiceTest.php b/src/tests/Unit/Services/Wallet/PostWalletOpenServiceTest.php new file mode 100644 index 00000000..ac189160 --- /dev/null +++ b/src/tests/Unit/Services/Wallet/PostWalletOpenServiceTest.php @@ -0,0 +1,96 @@ +eloquentWalletDataSource = $prophet->prophesize(EloquentWalletDataSource::class); + $this->eloquentUserDataSource = $prophet->prophesize(EloquentUserDataSource::class); + + $this->postWalletOpenService = new PostWalletOpenService( + $this->eloquentWalletDataSource->reveal(), + $this->eloquentUserDataSource->reveal() + ); + } + + /** + * @test + */ + public function postWalletOpenUserNotFound() + { + $userId = 'error-user'; + $expectedExistsByUserIdDatabaseUserReturn = false; + $expectedCreateWalletByUserIdDatabaseWalletReturn = 'wallet-000000001'; + $expectedResult = "User not found"; + + $this->eloquentUserDataSource + ->existsByUserId($userId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByUserIdDatabaseUserReturn); + $this->eloquentWalletDataSource + ->createWalletByUserId($userId) + ->shouldBeCalledOnce() + ->willReturn($expectedCreateWalletByUserIdDatabaseWalletReturn); + + try { + $this->postWalletOpenService->execute($userId); + } catch (Exception $exception) { + $this->assertEquals($expectedResult, $exception->getMessage()); + return; + } + $this->fail('Exception not catch!'); + } + + /** + * @test + */ + public function postWalletOpenWorking() + { + $userId = 'test-user'; + $expectedExistsByUserIdDatabaseUserReturn = true; + $expectedCreateWalletByUserIdDatabaseWalletReturn = 'wallet-000000001'; + $expectedResult = "wallet-000000001"; + + $this->eloquentUserDataSource + ->existsByUserId($userId) + ->shouldBeCalledOnce() + ->willReturn($expectedExistsByUserIdDatabaseUserReturn); + $this->eloquentWalletDataSource + ->createWalletByUserId($userId) + ->shouldBeCalledOnce() + ->willReturn($expectedCreateWalletByUserIdDatabaseWalletReturn); + + try { + $result = $this->postWalletOpenService->execute($userId); + } catch (Exception $exception) { + $this->fail("Failure by exception catch! - " . $exception->getMessage()); + } + $this->assertEquals($expectedResult, $result); + } +}