From 7211fbc190b43d38678d52841c1da2cc0aef1105 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 1 Apr 2019 16:04:08 +0200 Subject: [PATCH] Init --- .gitignore | 4 + .php_cs.dist | 5 + .travis.yml | 34 + LICENSE.md | 21 + README.md | 62 + composer.json | 35 + composer.lock | 2987 +++++++++++++++++ phpunit.xml | 32 + src/Api.php | 265 ++ src/Exception/ForbiddenException.php | 20 + src/Exception/MojangApiException.php | 10 + src/Exception/NoContentException.php | 16 + src/Exception/TooManyRequestsException.php | 21 + .../ResponseConverterMiddleware.php | 54 + src/Middleware/RetryMiddleware.php | 39 + src/Response/AuthenticateResponse.php | 70 + .../AuthenticationResponseUserField.php | 30 + src/Response/ProfileInfo.php | 72 + src/Response/ProfileResponse.php | 46 + src/Response/properties/Factory.php | 22 + src/Response/properties/Property.php | 31 + src/Response/properties/TexturesProperty.php | 26 + .../properties/TexturesPropertyValue.php | 90 + .../properties/TexturesPropertyValueCape.php | 21 + .../properties/TexturesPropertyValueSkin.php | 36 + tests/ApiTest.php | 347 ++ .../ResponseConverterMiddlewareTest.php | 56 + tests/Middleware/RetryMiddlewareTest.php | 24 + tests/Response/Properties/FactoryTest.php | 36 + .../Properties/TexturesPropertyValueTest.php | 50 + 30 files changed, 4562 insertions(+) create mode 100644 .gitignore create mode 100644 .php_cs.dist create mode 100644 .travis.yml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 phpunit.xml create mode 100644 src/Api.php create mode 100644 src/Exception/ForbiddenException.php create mode 100644 src/Exception/MojangApiException.php create mode 100644 src/Exception/NoContentException.php create mode 100644 src/Exception/TooManyRequestsException.php create mode 100644 src/Middleware/ResponseConverterMiddleware.php create mode 100644 src/Middleware/RetryMiddleware.php create mode 100644 src/Response/AuthenticateResponse.php create mode 100644 src/Response/AuthenticationResponseUserField.php create mode 100644 src/Response/ProfileInfo.php create mode 100644 src/Response/ProfileResponse.php create mode 100644 src/Response/properties/Factory.php create mode 100644 src/Response/properties/Property.php create mode 100644 src/Response/properties/TexturesProperty.php create mode 100644 src/Response/properties/TexturesPropertyValue.php create mode 100644 src/Response/properties/TexturesPropertyValueCape.php create mode 100644 src/Response/properties/TexturesPropertyValueSkin.php create mode 100644 tests/ApiTest.php create mode 100644 tests/Middleware/ResponseConverterMiddlewareTest.php create mode 100644 tests/Middleware/RetryMiddlewareTest.php create mode 100644 tests/Response/Properties/FactoryTest.php create mode 100644 tests/Response/Properties/TexturesPropertyValueTest.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6257b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/build/ +.phpunit.result.cache +.php_cs.cache diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..5bcacbe --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,5 @@ +in(__DIR__); +return \Ely\CS\Config::create() + ->setFinder($finder); diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..781db5a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +language: php +php: + - 7.1 + - 7.2 + - 7.3 + +cache: + directories: + - vendor + - $HOME/.composer + +env: + global: + - DEFAULT_COMPOSER_FLAGS="--optimize-autoloader --no-progress" + - COMPOSER_NO_INTERACTION=1 + +before_script: + - composer global show hirak/prestissimo -q || travis_retry composer global require $DEFAULT_COMPOSER_FLAGS hirak/prestissimo + - travis_retry composer install + - travis_retry phpenv rehash + +stages: + - Static Code Analysis + - Test + +jobs: + include: + - stage: Static Code Analysis + php: 7.3 + script: + - vendor/bin/php-cs-fixer fix -v --dry-run + +script: + - vendor/bin/phpunit diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..dde7e75 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Ely.by + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..43be519 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Mojang API + +This package provides easy access to the Minecraft related API of Mojang. +The library is built on the top of the [Guzzle HTTP client](https://github.com/guzzle/guzzle), +has custom errors handler and automatic retry in case of problems with Mojang. + +> Please note that this is not a complete implementation of all available APIs. + If you don't find the method you need, [open Issue](https://github.com/elyby/mojang-api/issues/new) + or [submit a PR](https://github.com/elyby/mojang-api/compare) with the implementation. + +[![Latest Version on Packagist][ico-version]][link-packagist] +[![Total Downloads][ico-downloads]][link-downloads] +[![Software License][ico-license]](LICENSE.md) +[![Build Status][ico-build-status]][link-build-status] + +## Installation + +To install, use composer: + +```bash +composer require ely/mojang-api +``` + +## Usage + +To get the configured `Api` object right away, just use the static `create()` method: + +```php +usernameToUUID('erickskrauch'); +echo $response->getId(); +``` + +## Testing + +```bash +$ ./vendor/bin/phpunit +``` + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Credits + +This package was designed and developed within the [Ely.by](http://ely.by) project team. We also thank all the +[contributors](link-contributors) for their help. + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. + +[ico-version]: https://img.shields.io/packagist/v/ely/mojang-api.svg?style=flat-square +[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square +[ico-downloads]: https://img.shields.io/packagist/dt/ely/mojang-api.svg?style=flat-square +[ico-build-status]: https://img.shields.io/travis/elyby/mojang-api/master.svg?style=flat-square + +[link-packagist]: https://packagist.org/packages/ely/mojang-api +[link-contributors]: ../../contributors +[link-downloads]: https://packagist.org/packages/ely/mojang-api/stats +[link-build-status]: https://travis-ci.org/elyby/mojang-api diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..09cd091 --- /dev/null +++ b/composer.json @@ -0,0 +1,35 @@ +{ + "name": "ely/mojang-api", + "description": "", + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "ErickSkrauch", + "email": "erickskrauch@yandex.ru" + } + ], + "require": { + "php": ">=7.1.0", + "ext-json": "*", + "guzzlehttp/guzzle": "^6.0.0", + "ramsey/uuid": "^3.0.0" + }, + "require-dev": { + "ely/php-code-style": "^0.3.0", + "phpunit/phpunit": "^8.0.0" + }, + "config": { + "sort-packages": true + }, + "autoload": { + "psr-4": { + "Ely\\Mojang\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Ely\\Mojang\\Test\\": "tests" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..83d0b83 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2987 @@ +{ + "_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": "18e33504c8da22065b63950a9506f00e", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "9f83dded91781a01c63574e387eaa769be769115" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", + "reference": "9f83dded91781a01c63574e387eaa769be769115", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2018-12-04T20:46:45+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0", + "satooshi/php-coveralls": ">=1.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2016-02-11T07:05:27+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0|9.99.99", + "php": "^5.4 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0|^6.5", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2018-07-19T23:38:55+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "backendtea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06T07:57:58+00:00" + } + ], + "packages-dev": [ + { + "name": "composer/semver", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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" + ], + "time": "2019-03-19T17:25:45+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "d17708133b6c276d6e42ef887a877866b909d892" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", + "reference": "d17708133b6c276d6e42ef887a877866b909d892", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.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" + ], + "time": "2019-01-28T20:25:53+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2019-03-25T19:12:02+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "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": "http://ocramius.github.com/" + } + ], + "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" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "ely/php-code-style", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/elyby/php-code-style.git", + "reference": "2140798d0aca85f4fa3e70c77bfef066ce115d1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elyby/php-code-style/zipball/2140798d0aca85f4fa3e70c77bfef066ce115d1a", + "reference": "2140798d0aca85f4fa3e70c77bfef066ce115d1a", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.13.0", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ely\\CS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Ely.by team", + "email": "team@ely.by" + }, + { + "name": "ErickSkrauch", + "email": "erickskrauch@ely.by" + } + ], + "description": "Set of PHP-CS-Fixer rules used in the development of Ely.by PHP projects", + "homepage": "https://github.com/elyby/php-code-style", + "keywords": [ + "Code style", + "php-cs-fixer" + ], + "time": "2019-02-23T17:29:08+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.14.2", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ff401e58261ffc5934a58f795b3f95b355e276cb", + "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4", + "composer/xdebug-handler": "^1.2", + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^5.6 || ^7.0", + "php-cs-fixer/diff": "^1.3", + "symfony/console": "^3.4.17 || ^4.1.6", + "symfony/event-dispatcher": "^3.0 || ^4.0", + "symfony/filesystem": "^3.0 || ^4.0", + "symfony/finder": "^3.0 || ^4.0", + "symfony/options-resolver": "^3.0 || ^4.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0 || ^4.0", + "symfony/stopwatch": "^3.0 || ^4.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.2", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.1", + "php-cs-fixer/accessible-object": "^1.0", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.0.1", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.0.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", + "phpunitgoodpractices/traits": "^1.5.1", + "symfony/phpunit-bridge": "^4.0" + }, + "suggest": { + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "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/TestCase.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dariusz RumiƄski", + "email": "dariusz.ruminski@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "time": "2019-02-17T17:44:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "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" + ], + "time": "2018-06-11T23:09:50+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "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)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.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", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "php-cs-fixer/diff", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/78bb099e9c16361126c86ce82ec4405ebab8e756", + "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "SpacePossum" + } + ], + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "time": "2018-02-15T16:58:55+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.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" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-30T07:14:17+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.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" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "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" + ], + "time": "2018-08-05T17:53:17+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0317a769a81845c390e19684d9ba25d7f6aa4707", + "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.1", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-xdebug": "^2.6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.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 provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2019-02-26T07:38:26+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "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": "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" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.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", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-02-20T10:12:59+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.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": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2018-10-30T05:52:18+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "925109f8bbe6dae28fbc7bb07446a53abd3b1c25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/925109f8bbe6dae28fbc7bb07446a53abd3b1c25", + "reference": "925109f8bbe6dae28fbc7bb07446a53abd3b1c25", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.2", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^7.0", + "phpunit/php-file-iterator": "^2.0.1", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^3.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0", + "sebastian/version": "^2.0.1" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.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": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-03-26T14:00:24+00:00" + }, + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.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" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "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": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "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": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656", + "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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" + ], + "time": "2019-02-01T05:27:49+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "shasum": "" + }, + "require": { + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "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": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2019-02-01T05:30:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-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/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "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": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "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": "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", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/console", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/9dc2299a016497f9ee620be94524e6c0af0280a9", + "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-02-23T15:17:42+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "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": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3354d2e6af986dd71f68b4e5cf4a933ab58697fb", + "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2019-02-23T15:17:42+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601", + "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2019-02-07T11:40:08+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/267b7002c1b70ea80db0833c3afe05f0fbde580a", + "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "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": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2019-02-23T15:42:05+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/3896e5a7d06fd15fa4947694c8dcdd371ff147d1", + "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "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": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2019-02-23T15:17:42+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "bc4858fb611bda58719124ca079baff854149c89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", + "reference": "bc4858fb611bda58719124ca079baff854149c89", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "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.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "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" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/process", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", + "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "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": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2019-01-24T22:05:03+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "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": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2019-01-16T20:31:39+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.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", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-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" + ], + "time": "2018-12-25T11:19:39+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1.0", + "ext-json": "*" + }, + "platform-dev": [] +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..57ccbd1 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,32 @@ + + + + + + + + + ./tests/ + + + + + ./ + + ./vendor + ./tests + + + + diff --git a/src/Api.php b/src/Api.php new file mode 100644 index 0000000..fcb4fa5 --- /dev/null +++ b/src/Api.php @@ -0,0 +1,265 @@ +client = $client; + } + + /** + * @param callable $handler HTTP handler function to use with the stack. If no + * handler is provided, the best handler for your + * system will be utilized. + * + * @return static + */ + public static function create(callable $handler = null): self { + $stack = HandlerStack::create($handler); + // use after method because middleware executes in reverse order + $stack->after('http_errors', ResponseConverterMiddleware::create(), 'mojang_response_converter'); + $stack->push(RetryMiddleware::create(), 'retry'); + + return new static(new GuzzleClient([ + 'handler' => $stack, + 'timeout' => 10, + ])); + } + + /** + * @param string $username + * @param int $atTime + * + * @return \Ely\Mojang\Response\ProfileInfo + * + * @throws \Ely\Mojang\Exception\MojangApiException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @url http://wiki.vg/Mojang_API#Username_-.3E_UUID_at_time + */ + public function usernameToUUID(string $username, int $atTime = null): Response\ProfileInfo { + $query = []; + if ($atTime !== null) { + $query['atTime'] = $atTime; + } + + $response = $this->getClient()->request('GET', "https://api.mojang.com/users/profiles/minecraft/{$username}", [ + 'query' => $query, + ]); + + $data = $this->decode($response->getBody()->getContents()); + + return Response\ProfileInfo::createFromResponse($data); + } + + /** + * @param string $uuid + * + * @return \Ely\Mojang\Response\ProfileResponse + * + * @throws \Ely\Mojang\Exception\MojangApiException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @url http://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape + */ + public function uuidToTextures(string $uuid): Response\ProfileResponse { + $response = $this->getClient()->request('GET', "https://sessionserver.mojang.com/session/minecraft/profile/{$uuid}", [ + 'query' => [ + 'unsigned' => false, + ], + ]); + $body = $this->decode($response->getBody()->getContents()); + + return new Response\ProfileResponse($body['id'], $body['name'], $body['properties']); + } + + /** + * Helper method to exchange username to the corresponding textures. + * + * @param string $username + * + * @return \Ely\Mojang\Response\ProfileResponse + * + * @throws GuzzleException + * @throws \Ely\Mojang\Exception\MojangApiException + */ + public function usernameToTextures(string $username): Response\ProfileResponse { + return $this->uuidToTextures($this->usernameToUUID($username)->getId()); + } + + /** + * @param string $login + * @param string $password + * @param string $clientToken + * + * @return \Ely\Mojang\Response\AuthenticateResponse + * + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @url https://wiki.vg/Authentication#Authenticate + */ + public function authenticate( + string $login, + string $password, + string $clientToken = null + ): Response\AuthenticateResponse { + if ($clientToken === null) { + /** @noinspection PhpUnhandledExceptionInspection */ + $clientToken = Uuid::uuid4()->toString(); + } + + $response = $this->getClient()->request('POST', 'https://authserver.mojang.com/authenticate', [ + 'json' => [ + 'username' => $login, + 'password' => $password, + 'clientToken' => $clientToken, + 'requestUser' => true, + 'agent' => [ + 'name' => 'Minecraft', + 'version' => 1, + ], + ], + ]); + $body = $this->decode($response->getBody()->getContents()); + + return new Response\AuthenticateResponse( + $body['accessToken'], + $body['clientToken'], + $body['availableProfiles'], + $body['selectedProfile'], + $body['user'] + ); + } + + /** + * @param string $accessToken + * + * @return bool + * + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @url https://wiki.vg/Authentication#Validate + */ + public function validate(string $accessToken): bool { + try { + $response = $this->getClient()->request('POST', 'https://authserver.mojang.com/authenticate', [ + 'json' => [ + 'accessToken' => $accessToken, + ], + ]); + if ($response->getStatusCode() === 204) { + return true; + } + } catch (Exception\ForbiddenException $e) { + // Suppress exception and let it just exit below + } + + return false; + } + + /** + * @param string $accessToken + * @param string $accountUuid + * @param \Psr\Http\Message\StreamInterface|resource|string $skinContents + * @param bool $isSlim + * + * @throws GuzzleException + * + * @url https://wiki.vg/Mojang_API#Upload_Skin + */ + public function uploadSkin(string $accessToken, string $accountUuid, $skinContents, bool $isSlim): void { + $this->getClient()->request('PUT', "https://api.mojang.com/user/profile/{$accountUuid}/skin", [ + 'multipart' => [ + [ + 'name' => 'file', + 'contents' => $skinContents, + 'filename' => 'char.png', + ], + [ + 'name' => 'model', + 'contents' => $isSlim ? 'slim' : '', + ], + ], + 'headers' => [ + 'Authorization' => 'Bearer ' . $accessToken, + ], + ]); + } + + /** + * @param string $accessToken + * @param string $accountUuid + * @param string $serverId + * + * @throws GuzzleException + * + * @url https://wiki.vg/Protocol_Encryption#Client + */ + public function joinServer(string $accessToken, string $accountUuid, string $serverId): void { + $this->getClient()->request('POST', 'https://sessionserver.mojang.com/session/minecraft/join', [ + 'json' => [ + 'accessToken' => $accessToken, + 'selectedProfile' => $accountUuid, + 'serverId' => $serverId, + ], + ]); + } + + /** + * @param string $username + * @param string $serverId + * + * @return \Ely\Mojang\Response\ProfileResponse + * + * @throws \Ely\Mojang\Exception\NoContentException + * @throws GuzzleException + * + * @url https://wiki.vg/Protocol_Encryption#Server + */ + public function hasJoinedServer(string $username, string $serverId): Response\ProfileResponse { + $uri = (new Uri('https://sessionserver.mojang.com/session/minecraft/hasJoined')) + ->withQuery(http_build_query([ + 'username' => $username, + 'serverId' => $serverId, + ], '', '&', PHP_QUERY_RFC3986)); + $request = new Request('GET', $uri); + $response = $this->getClient()->send($request); + $rawBody = $response->getBody()->getContents(); + if (empty($rawBody)) { + throw new Exception\NoContentException($request, $response); + } + + $body = $this->decode($rawBody); + + return new Response\ProfileResponse($body['id'], $body['name'], $body['properties']); + } + + /** + * @return ClientInterface + */ + protected function getClient(): ClientInterface { + return $this->client; + } + + private function decode(string $response): array { + return json_decode($response, true); + } + +} diff --git a/src/Exception/ForbiddenException.php b/src/Exception/ForbiddenException.php new file mode 100644 index 0000000..c00ce8c --- /dev/null +++ b/src/Exception/ForbiddenException.php @@ -0,0 +1,20 @@ +nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface { + $fn = $this->nextHandler; + /** @var PromiseInterface $promise */ + $promise = $fn($request, $options); + + return $promise->then(static function($response) use ($request) { + if ($response instanceof ResponseInterface) { + $method = $request->getMethod(); + $statusCode = $response->getStatusCode(); + if ($method === 'GET' && $statusCode === 204) { + throw new Exception\NoContentException($request, $response); + } + + if ($statusCode === 403) { + throw new Exception\ForbiddenException($request, $response); + } + + if ($statusCode === 429) { + throw new Exception\TooManyRequestsException($request, $response); + } + } + + return $response; + }); + } + + public static function create(): callable { + return static function(callable $handler): callable { + return new static($handler); + }; + } + +} diff --git a/src/Middleware/RetryMiddleware.php b/src/Middleware/RetryMiddleware.php new file mode 100644 index 0000000..0c241fe --- /dev/null +++ b/src/Middleware/RetryMiddleware.php @@ -0,0 +1,39 @@ += 2) { + return false; + } + + if ($reason instanceof ConnectException) { + return true; + } + + if ($response !== null && (int)floor($response->getStatusCode() / 100) === 5) { + return true; + } + + return false; + } + +} diff --git a/src/Response/AuthenticateResponse.php b/src/Response/AuthenticateResponse.php new file mode 100644 index 0000000..3268df2 --- /dev/null +++ b/src/Response/AuthenticateResponse.php @@ -0,0 +1,70 @@ +accessToken = $accessToken; + $this->clientToken = $clientToken; + $this->rawAvailableProfiles = $availableProfiles; + $this->rawSelectedProfile = $selectedProfile; + $this->rawUser = $user; + } + + public function getAccessToken(): string { + return $this->accessToken; + } + + public function getClientToken(): string { + return $this->clientToken; + } + + /** + * @return ProfileInfo[] + */ + public function getAvailableProfiles(): array { + return array_map([ProfileInfo::class, 'createFromResponse'], $this->rawAvailableProfiles); + } + + public function getSelectedProfile(): ProfileInfo { + return ProfileInfo::createFromResponse($this->rawSelectedProfile); + } + + public function getUser(): AuthenticationResponseUserField { + return new AuthenticationResponseUserField($this->rawUser['id'], $this->rawUser['properties']); + } + +} diff --git a/src/Response/AuthenticationResponseUserField.php b/src/Response/AuthenticationResponseUserField.php new file mode 100644 index 0000000..6a22f37 --- /dev/null +++ b/src/Response/AuthenticationResponseUserField.php @@ -0,0 +1,30 @@ +id = $id; + $this->rawProperties = $rawProperties; + } + + public function getId(): string { + return $this->id; + } + + /** + * @return \Ely\Mojang\Response\Properties\Property[] + */ + public function getProperties(): array { + return array_map([Factory::class, 'createFromProp'], $this->rawProperties); + } + +} diff --git a/src/Response/ProfileInfo.php b/src/Response/ProfileInfo.php new file mode 100644 index 0000000..6f0dd02 --- /dev/null +++ b/src/Response/ProfileInfo.php @@ -0,0 +1,72 @@ +id = $id; + $this->name = $name; + $this->isLegacy = $isLegacy; + $this->isDemo = $isDemo; + } + + public static function createFromResponse(array $response): self { + return new static( + $response['id'], + $response['name'], + $response['legacy'] ?? false, + $response['demo'] ?? false + ); + } + + /** + * @return string user's uuid without dashes + */ + public function getId(): string { + return $this->id; + } + + /** + * @return string username at the current time + */ + public function getName(): string { + return $this->name; + } + + /** + * @return bool true means, that account not migrated into Mojang account + */ + public function isLegacy(): bool { + return $this->isLegacy; + } + + /** + * @return bool true means, that account now in demo mode (not premium user) + */ + public function isDemo(): bool { + return $this->isDemo; + } + +} diff --git a/src/Response/ProfileResponse.php b/src/Response/ProfileResponse.php new file mode 100644 index 0000000..f8cdf34 --- /dev/null +++ b/src/Response/ProfileResponse.php @@ -0,0 +1,46 @@ +id = $id; + $this->name = $name; + $this->props = $rawProps; + } + + public function getId(): string { + return $this->id; + } + + public function getName(): string { + return $this->name; + } + + /** + * @return \Ely\Mojang\Response\Properties\Property[] + */ + public function getProps(): array { + return array_map([Factory::class, 'createFromProp'], $this->props); + } + +} diff --git a/src/Response/properties/Factory.php b/src/Response/properties/Factory.php new file mode 100644 index 0000000..ca82f5d --- /dev/null +++ b/src/Response/properties/Factory.php @@ -0,0 +1,22 @@ + TexturesProperty::class, + ]; + + public static function createFromProp(array $prop): Property { + $name = $prop['name']; + if (isset(static::$MAP[$name])) { + $className = static::$MAP[$name]; + return new $className($prop); + } + + return new Property($prop); + } + +} diff --git a/src/Response/properties/Property.php b/src/Response/properties/Property.php new file mode 100644 index 0000000..f18b1ff --- /dev/null +++ b/src/Response/properties/Property.php @@ -0,0 +1,31 @@ +name = $prop['name']; + $this->value = $prop['value']; + } + + public function getName(): string { + return $this->name; + } + + public function getValue(): string { + return $this->value; + } + +} diff --git a/src/Response/properties/TexturesProperty.php b/src/Response/properties/TexturesProperty.php new file mode 100644 index 0000000..948622b --- /dev/null +++ b/src/Response/properties/TexturesProperty.php @@ -0,0 +1,26 @@ +signature = $prop['signature'] ?? null; + } + + public function getTextures(): TexturesPropertyValue { + return TexturesPropertyValue::createFromRawTextures($this->value); + } + + public function getSignature(): ?string { + return $this->signature; + } + +} diff --git a/src/Response/properties/TexturesPropertyValue.php b/src/Response/properties/TexturesPropertyValue.php new file mode 100644 index 0000000..54f0779 --- /dev/null +++ b/src/Response/properties/TexturesPropertyValue.php @@ -0,0 +1,90 @@ +id = $profileId; + $this->username = $profileName; + $this->textures = $textures; + $this->timestamp = (int)floor($timestamp / 1000); + $this->signatureRequired = $signatureRequired; + } + + public static function createFromRawTextures(string $rawTextures): self { + $decoded = json_decode(base64_decode($rawTextures), true); + return new static( + $decoded['profileId'], + $decoded['profileName'], + $decoded['textures'], + $decoded['timestamp'], + $decoded['signatureRequired'] ?? false + ); + } + + public function getProfileId(): string { + return $this->id; + } + + public function getProfileName(): string { + return $this->username; + } + + public function getTimestamp(): int { + return $this->timestamp; + } + + public function isSignatureRequired(): bool { + return $this->signatureRequired; + } + + public function getSkin(): ?TexturesPropertyValueSkin { + if (!isset($this->textures['SKIN'])) { + return null; + } + + return TexturesPropertyValueSkin::createFromTextures($this->textures['SKIN']); + } + + public function getCape(): ?TexturesPropertyValueCape { + if (!isset($this->textures['CAPE'])) { + return null; + } + + return new TexturesPropertyValueCape($this->textures['CAPE']['url']); + } + +} diff --git a/src/Response/properties/TexturesPropertyValueCape.php b/src/Response/properties/TexturesPropertyValueCape.php new file mode 100644 index 0000000..68011bd --- /dev/null +++ b/src/Response/properties/TexturesPropertyValueCape.php @@ -0,0 +1,21 @@ +url = $skinUrl; + } + + public function getUrl(): string { + return $this->url; + } + +} diff --git a/src/Response/properties/TexturesPropertyValueSkin.php b/src/Response/properties/TexturesPropertyValueSkin.php new file mode 100644 index 0000000..5ffa377 --- /dev/null +++ b/src/Response/properties/TexturesPropertyValueSkin.php @@ -0,0 +1,36 @@ +url = $skinUrl; + $this->isSlim = $isSlim; + } + + public static function createFromTextures(array $textures): self { + $model = &$textures['metainfo']['model']; // ampersand to avoid notice about unexpected key + return new static($textures['url'], $model === 'slim'); + } + + public function getUrl(): string { + return $this->url; + } + + public function isSlim(): bool { + return $this->isSlim; + } + +} diff --git a/tests/ApiTest.php b/tests/ApiTest.php new file mode 100644 index 0000000..877356b --- /dev/null +++ b/tests/ApiTest.php @@ -0,0 +1,347 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $this->history = []; + $handlerStack->push(Middleware::history($this->history), 'history'); + $handlerStack->after('http_errors', ResponseConverterMiddleware::create(), 'mojang_responses'); + $handlerStack->push(RetryMiddleware::create(), 'retry'); + $client = new Client(['handler' => $handlerStack]); + $this->api = new Api($client); + } + + public function testCreate() { + $this->assertInstanceOf(Api::class, Api::create()); + } + + public function testUsernameToUuid() { + $this->mockHandler->append($this->createResponse(200, [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'legacy' => false, + ])); + + $result = $this->api->usernameToUUID('MockUsername'); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame('https://api.mojang.com/users/profiles/minecraft/MockUsername', (string)$request->getUri()); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getId()); + $this->assertSame('MockUsername', $result->getName()); + $this->assertFalse($result->isLegacy()); + $this->assertFalse($result->isDemo()); + } + + public function testUsernameToUuidWithAtParam() { + $this->mockHandler->append($this->createResponse(200, [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'legacy' => false, + ])); + + $this->api->usernameToUUID('MockUsername', 1553961511); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame( + 'https://api.mojang.com/users/profiles/minecraft/MockUsername?atTime=1553961511', + (string)$request->getUri() + ); + } + + public function testUuidToTextures() { + $this->mockHandler->append($this->createResponse(200, [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'properties' => [ + [ + 'name' => 'textures', + 'value' => base64_encode(json_encode([ + 'timestamp' => 1553961848860, + 'profileId' => '86f6e3695b764412a29820cac1d4d0d6', + 'profileName' => 'MockUsername', + 'signatureRequired' => true, + 'textures' => [ + 'SKIN' => [ + 'url' => 'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680', + ], + 'CAPE' => [ + 'url' => 'http://textures.minecraft.net/texture/capePath', + ], + ], + ])), + 'signature' => 'mocked signature value', + ], + ], + ])); + + $result = $this->api->uuidToTextures('86f6e3695b764412a29820cac1d4d0d6'); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame( + 'https://sessionserver.mojang.com/session/minecraft/profile/86f6e3695b764412a29820cac1d4d0d6?unsigned=0', + (string)$request->getUri() + ); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getId()); + $this->assertSame('MockUsername', $result->getName()); + $props = $result->getProps(); + /** @var TexturesProperty $texturesProperty */ + $texturesProperty = $props[0]; + $this->assertInstanceOf(TexturesProperty::class, $texturesProperty); + $this->assertSame('textures', $texturesProperty->getName()); + $this->assertSame('mocked signature value', $texturesProperty->getSignature()); + $textures = $texturesProperty->getTextures(); + $this->assertSame(1553961848, $textures->getTimestamp()); + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $textures->getProfileId()); + $this->assertSame('MockUsername', $textures->getProfileName()); + $this->assertTrue($textures->isSignatureRequired()); + $this->assertNotNull($textures->getSkin()); + $this->assertSame( + 'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680', + $textures->getSkin()->getUrl() + ); + $this->assertFalse($textures->getSkin()->isSlim()); + $this->assertSame('http://textures.minecraft.net/texture/capePath', $textures->getCape()->getUrl()); + } + + public function testUsernameToTextures() { + $this->mockHandler->append($this->createResponse(200, [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'legacy' => false, + ])); + $this->mockHandler->append($this->createResponse(200, [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'properties' => [], + ])); + + $this->api->usernameToTextures('MockUsername'); + /** @var \Psr\Http\Message\RequestInterface $request1 */ + /** @var \Psr\Http\Message\RequestInterface $request2 */ + [0 => ['request' => $request1], 1 => ['request' => $request2]] = $this->history; + $this->assertSame('https://api.mojang.com/users/profiles/minecraft/MockUsername', (string)$request1->getUri()); + $this->assertStringStartsWith('https://sessionserver.mojang.com/session/minecraft/profile/86f6e3695b764412a29820cac1d4d0d6', (string)$request2->getUri()); + } + + public function testAuthenticate() { + $this->mockHandler->append($this->createResponse(200, [ + 'accessToken' => 'access token value', + 'clientToken' => 'client token value', + 'availableProfiles' => [ + [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'legacy' => false, + ], + ], + 'selectedProfile' => [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'legacy' => false, + ], + 'user' => [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'properties' => [ + [ + 'name' => 'preferredLanguage', + 'value' => 'en', + ], + [ + 'name' => 'twitch_access_token', + 'value' => 'twitch oauth token', + ], + ], + ], + ])); + + $result = $this->api->authenticate('MockUsername', 'some password', 'client token value'); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame('https://authserver.mojang.com/authenticate', (string)$request->getUri()); + + $this->assertSame('access token value', $result->getAccessToken()); + $this->assertSame('client token value', $result->getClientToken()); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getAvailableProfiles()[0]->getId()); + $this->assertSame('MockUsername', $result->getAvailableProfiles()[0]->getName()); + $this->assertFalse($result->getAvailableProfiles()[0]->isLegacy()); + $this->assertFalse($result->getAvailableProfiles()[0]->isDemo()); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getSelectedProfile()->getId()); + $this->assertSame('MockUsername', $result->getSelectedProfile()->getName()); + $this->assertFalse($result->getSelectedProfile()->isLegacy()); + $this->assertFalse($result->getSelectedProfile()->isDemo()); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getUser()->getId()); + + $this->assertSame('preferredLanguage', $result->getUser()->getProperties()[0]->getName()); + $this->assertSame('en', $result->getUser()->getProperties()[0]->getValue()); + $this->assertSame('twitch_access_token', $result->getUser()->getProperties()[1]->getName()); + $this->assertSame('twitch oauth token', $result->getUser()->getProperties()[1]->getValue()); + } + + public function testAuthenticateWithNotSpecifiedClientToken() { + $this->mockHandler->append($this->createResponse(200, [ + 'accessToken' => 'access token value', + 'clientToken' => 'client token value', + 'availableProfiles' => [], + 'selectedProfile' => [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'legacy' => false, + ], + 'user' => [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'properties' => [], + ], + ])); + + $this->api->authenticate('MockUsername', 'some password'); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $body = json_decode($request->getBody()->getContents(), true); + // https://gist.github.com/johnelliott/cf77003f72f889abbc3f32785fa3df8d + $this->assertRegExp('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $body['clientToken']); + } + + public function testValidateSuccessful() { + $this->mockHandler->append(new Response(204)); + $this->assertTrue($this->api->validate('mocked access token')); + } + + public function testValidateInvalid() { + $this->mockHandler->append(new Response(403)); + $this->assertFalse($this->api->validate('mocked access token')); + } + + public function testUploadSkinNotSlim() { + $this->mockHandler->append(new Response(200)); + $this->api->uploadSkin('mocked access token', '86f6e3695b764412a29820cac1d4d0d6', 'skin contents', false); + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame('Bearer mocked access token', $request->getHeaderLine('Authorization')); + $this->assertStringNotContainsString('slim', $request->getBody()->getContents()); + } + + public function testUploadSkinSlim() { + $this->mockHandler->append(new Response(200)); + $this->api->uploadSkin('mocked access token', '86f6e3695b764412a29820cac1d4d0d6', 'skin contents', true); + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertStringContainsString('slim', $request->getBody()->getContents()); + } + + public function testJoinServer() { + $this->mockHandler->append(new Response(200)); + $this->api->joinServer('mocked access token', '86f6e3695b764412a29820cac1d4d0d6', 'ad72fe1efe364e6eb78c644a9fba1d30'); + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $params = json_decode($request->getBody()->getContents(), true); + $this->assertSame('mocked access token', $params['accessToken']); + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $params['selectedProfile']); + $this->assertSame('ad72fe1efe364e6eb78c644a9fba1d30', $params['serverId']); + } + + public function testHasJoinedServer() { + $this->mockHandler->append($this->createResponse(200, [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + 'properties' => [ + [ + 'name' => 'textures', + 'value' => base64_encode(json_encode([ + 'timestamp' => 1553961848860, + 'profileId' => '86f6e3695b764412a29820cac1d4d0d6', + 'profileName' => 'MockUsername', + 'signatureRequired' => true, + 'textures' => [ + 'SKIN' => [ + 'url' => 'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680', + ], + ], + ])), + 'signature' => 'mocked signature value', + ], + ], + ])); + $result = $this->api->hasJoinedServer('MockedUsername', 'ad72fe1efe364e6eb78c644a9fba1d30'); + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $params = parse_query($request->getUri()->getQuery()); + $this->assertSame('MockedUsername', $params['username']); + $this->assertSame('ad72fe1efe364e6eb78c644a9fba1d30', $params['serverId']); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getId()); + $this->assertSame('MockUsername', $result->getName()); + $props = $result->getProps(); + /** @var TexturesProperty $texturesProperty */ + $texturesProperty = $props[0]; + $this->assertInstanceOf(TexturesProperty::class, $texturesProperty); + $this->assertSame('textures', $texturesProperty->getName()); + $this->assertSame('mocked signature value', $texturesProperty->getSignature()); + $textures = $texturesProperty->getTextures(); + $this->assertSame(1553961848, $textures->getTimestamp()); + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $textures->getProfileId()); + $this->assertSame('MockUsername', $textures->getProfileName()); + $this->assertTrue($textures->isSignatureRequired()); + $this->assertNotNull($textures->getSkin()); + $this->assertSame( + 'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680', + $textures->getSkin()->getUrl() + ); + $this->assertFalse($textures->getSkin()->isSlim()); + $this->assertNull($textures->getCape()); + } + + public function testHasJoinedServerEmptyResponse() { + $this->mockHandler->append(new Response(200)); + $this->expectException(NoContentException::class); + $this->api->hasJoinedServer('MockedUsername', 'ad72fe1efe364e6eb78c644a9fba1d30'); + } + + private function createResponse(int $statusCode, array $response): ResponseInterface { + return new Response($statusCode, ['content-type' => 'json'], json_encode($response)); + } + +} diff --git a/tests/Middleware/ResponseConverterMiddlewareTest.php b/tests/Middleware/ResponseConverterMiddlewareTest.php new file mode 100644 index 0000000..acc6dc0 --- /dev/null +++ b/tests/Middleware/ResponseConverterMiddlewareTest.php @@ -0,0 +1,56 @@ +expectException($expectedException); + $handler = new MockHandler([$response]); + $middleware = new ResponseConverterMiddleware($handler); + $middleware($request, [])->wait(); + } + + public function getResponses(): iterable { + yield [ + new Request('GET', 'http://localhost'), + new Response(204, [], ''), + Exception\NoContentException::class, + ]; + + yield [ + new Request('GET', 'http://localhost'), + new Response( + 403, + ['Content-Type' => 'application/json'], + '{"error":"ForbiddenOperationException","errorMessage":"Invalid token"}' + ), + Exception\ForbiddenException::class, + ]; + + yield [ + new Request('GET', 'http://localhost'), + new Response( + 429, + ['Content-Type' => 'application/json'], + '{"error":"TooManyRequestsException","errorMessage":"The client has sent too many requests within a certain amount of time"}' + ), + Exception\TooManyRequestsException::class, + ]; + } + +} diff --git a/tests/Middleware/RetryMiddlewareTest.php b/tests/Middleware/RetryMiddlewareTest.php new file mode 100644 index 0000000..d10f798 --- /dev/null +++ b/tests/Middleware/RetryMiddlewareTest.php @@ -0,0 +1,24 @@ +assertFalse(RetryMiddleware::shouldRetry(0, $r, new Response(200), null), 'not retry on success response'); + $this->assertFalse(RetryMiddleware::shouldRetry(0, $r, new Response(403), null), 'not retry on client error'); + $this->assertTrue(RetryMiddleware::shouldRetry(0, $r, null, new ConnectException('', $r)), 'retry when network error happens'); + $this->assertTrue(RetryMiddleware::shouldRetry(0, $r, new Response(503), null), 'retry when 50x error 1 time'); + $this->assertTrue(RetryMiddleware::shouldRetry(1, $r, new Response(503), null), 'retry when 50x error 2 time'); + $this->assertFalse(RetryMiddleware::shouldRetry(2, $r, new Response(503), null), 'don\'t retry when 50x error 3 time'); + } + +} diff --git a/tests/Response/Properties/FactoryTest.php b/tests/Response/Properties/FactoryTest.php new file mode 100644 index 0000000..8432416 --- /dev/null +++ b/tests/Response/Properties/FactoryTest.php @@ -0,0 +1,36 @@ +assertInstanceOf($expectedType, Factory::createFromProp($inputProps)); + } + + public function getProps(): iterable { + yield [[ + 'name' => 'textures', + 'value' => 'value', + 'signature' => '123', + ], TexturesProperty::class]; + + yield [[ + 'name' => 'other', + 'value' => 'value', + ], Property::class]; + } + +} diff --git a/tests/Response/Properties/TexturesPropertyValueTest.php b/tests/Response/Properties/TexturesPropertyValueTest.php new file mode 100644 index 0000000..c1c5589 --- /dev/null +++ b/tests/Response/Properties/TexturesPropertyValueTest.php @@ -0,0 +1,50 @@ + [ + 'url' => 'skin url', + ], + ], 0); + $this->assertNotNull($object->getSkin()); + $this->assertSame('skin url', $object->getSkin()->getUrl()); + $this->assertFalse($object->getSkin()->isSlim()); + + $object = new TexturesPropertyValue('', '', [ + 'SKIN' => [ + 'url' => 'skin url', + 'metainfo' => [ + 'model' => 'slim', + ], + ], + ], 0); + $this->assertNotNull($object->getSkin()); + $this->assertSame('skin url', $object->getSkin()->getUrl()); + $this->assertTrue($object->getSkin()->isSlim()); + + $object = new TexturesPropertyValue('', '', [], 0); + $this->assertNull($object->getSkin()); + } + + public function testGetCape() { + $object = new TexturesPropertyValue('', '', [ + 'CAPE' => [ + 'url' => 'cape url', + ], + ], 0); + $this->assertNotNull($object->getCape()); + $this->assertSame('cape url', $object->getCape()->getUrl()); + + $object = new TexturesPropertyValue('', '', [], 0); + $this->assertNull($object->getCape()); + } + +}