diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..b974a2b --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,33 @@ +version: 2 + +jobs: + build: + docker: + - image: circleci/php:7.1-cli + + working_directory: ~/repo + + steps: + - checkout + + - restore_cache: + keys: + - v1-dependencies-{{ checksum "composer.json" }} + - v1-dependencies- + + - run: composer install -n --prefer-dist + + - save_cache: + paths: + - ./vendor + key: v1-dependencies-{{ checksum "composer.json" }} + + - run: mkdir test/unit/_junit + - run: vendor/bin/phpunit -c test/unit/phpunit.xml --log-junit test/unit/_junit/junit.xml -d memory_limit=512M + + - store_test_results: + path: test/unit/_junit + + - store_artifacts: + path: test/unit/_coverage + destination: TestCoverage diff --git a/.scrutinizer.yml b/.scrutinizer.yml index f6f6001..b84b29f 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,14 +1,14 @@ build: environment: php: - version: 7.0.8 + version: 7.1.0 tests: override: - - - command: 'vendor/bin/phpunit --coverage-clover=coverage' - coverage: - file: 'coverage' - format: 'php-clover' + - + command: 'vendor/bin/phpunit test/unit --coverage-clover coverage --whitelist src' + coverage: + file: 'coverage' + format: 'php-clover' checks: php: @@ -17,5 +17,5 @@ checks: filter: excluded_paths: - - test/* - - vendor/* \ No newline at end of file + - test/* + - vendor/* diff --git a/README.md b/README.md index c530420..e84b7de 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,19 @@ See also, the [JavaScript implementation][fetch-js] that ships as standard in al *** - Build status + Build status - Code quality + Code quality - Code coverage + Code coverage - Current version + Current version + + + PHP.Gt/Fetch documentation ## Example usage: compute multiple HTTP requests in parallel. diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 9bcc9a6..0000000 --- a/circle.yml +++ /dev/null @@ -1,11 +0,0 @@ -machine: - php: - version: 7.0.4 - -test: - override: - - vendor/bin/phpunit - -notify: - webhooks: - - url: https://webhooks.gitter.im/e/22c9d06e726e91ddc7c5 \ No newline at end of file diff --git a/composer.json b/composer.json index 6c508a7..0a7ca30 100644 --- a/composer.json +++ b/composer.json @@ -1,44 +1,64 @@ { - "name": "phpgt/fetch", - "description": "Asynchronous HTTP client with promises.", - "type": "library", - - "require": { - "php": ">=7.1", - "react/event-loop": "^1.0.0", - "react/promise": "^2.4", - "phpcurl/curlwrapper": "^2.1", - "react/stream": "^0.4.4" - }, - "require-dev": { - "phpunit/phpunit": "5.*|7.*" - }, - - "license": "MIT", - - "authors": [ - { - "name": "Greg Bowler", - "email": "greg.bowler@g105b.com" - } - ], - - "autoload": { - "psr-4": { - "Gt\\Fetch\\": "./src" - } - }, - - "keywords": [ - "http", - "fetch", - "async", - "asynchronous", - "xmlhttprequest", - "ajax", - "get", - "post", - "curl", - "curl_multi" - ] + "name": "phpgt/fetch", + "description": "Asynchronous HTTP client with promises for PHP 7 applications.", + "type": "library", + "license": "MIT", + + "minimum-stability": "dev", + + "provide": { + "php-http/client-implementation": "1.0", + "php-http/async-client-implementation": "1.0" + }, + + "require": { + "php": ">=7.1.0", + "ext-curl": "*", + "ext-json": "*", + "phpgt/http": "dev-master", + "phpgt/curl": "dev-master", + + "php-http/httplug": "^1.1", + "php-http/promise": "^1.0.0", + + "react/event-loop": "^1.0.0", + "react/stream": "^1.0.0", + "react/promise": "^2.7.0" + }, + + "require-dev": { + "phpunit/phpunit": "7.*" + }, + + "autoload": { + "psr-4": { + "Gt\\Fetch\\": "./src" + } + }, + + "autoload-dev": { + "psr-4": { + "Gt\\Fetch\\Test\\": "./test/unit" + } + }, + + "authors": [ + { + "name": "Greg Bowler", + "email": "greg.bowler@g105b.com" + } + ], + + "keywords": [ + "http", + "fetch", + "async", + "asynchronous", + "xmlhttprequest", + "ajax", + "get", + "post", + "curl", + "curl_multi" + ] } diff --git a/composer.lock b/composer.lock index 534ed32..a3cbb13 100644 --- a/composer.lock +++ b/composer.lock @@ -4,34 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4174bcc8fae1dd8c102c1ef9dd7330f9", + "content-hash": "c1586ff2609b6f54325decdb4fc03ac2", "packages": [ { "name": "evenement/evenement", - "version": "v2.1.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/igorw/evenement.git", - "reference": "6ba9a777870ab49f417e703229d53931ed40fd7a" + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/6ba9a777870ab49f417e703229d53931ed40fd7a", - "reference": "6ba9a777870ab49f417e703229d53931ed40fd7a", + "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.0||^5.7||^4.8.35" + "phpunit/phpunit": "^6.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, "autoload": { "psr-0": { "Evenement": "src" @@ -52,34 +47,178 @@ "event-dispatcher", "event-emitter" ], - "time": "2017-07-17T17:39:19+00:00" + "time": "2017-07-23T21:35:13+00:00" }, { - "name": "phpcurl/curlwrapper", - "version": "2.1.0", + "name": "php-http/httplug", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/phpcurl/curlwrapper.git", - "reference": "9e58c7f0d52c5f44e5a50f39d28c9870d4b1de8c" + "url": "https://github.com/php-http/httplug.git", + "reference": "7e296052447a8c8c391c619ba2058284e578e033" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpcurl/curlwrapper/zipball/9e58c7f0d52c5f44e5a50f39d28c9870d4b1de8c", - "reference": "9e58c7f0d52c5f44e5a50f39d28c9870d4b1de8c", + "url": "https://api.github.com/repos/php-http/httplug/zipball/7e296052447a8c8c391c619ba2058284e578e033", + "reference": "7e296052447a8c8c391c619ba2058284e578e033", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "time": "2018-10-18T10:18:22+00:00" + }, + { + "name": "php-http/promise", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "1cc44dc01402d407fc6da922591deebe4659826f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/1cc44dc01402d407fc6da922591deebe4659826f", + "reference": "1cc44dc01402d407fc6da922591deebe4659826f", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "time": "2017-11-22T21:24:54+00:00" + }, + { + "name": "phpgt/cookie", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/PhpGt/Cookie.git", + "reference": "a59344f71a4999f6fa68e2724504c0949f176537" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhpGt/Cookie/zipball/a59344f71a4999f6fa68e2724504c0949f176537", + "reference": "a59344f71a4999f6fa68e2724504c0949f176537", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gt\\Cookie\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Bowler", + "email": "greg.bowler@g105b.com" + } + ], + "description": "Object oriented cookie handler.", + "time": "2018-10-23T19:08:27+00:00" + }, + { + "name": "phpgt/curl", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/PhpGt/Curl.git", + "reference": "18d6d88ad03d5c77891898b923d0cea54a209091" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhpGt/Curl/zipball/18d6d88ad03d5c77891898b923d0cea54a209091", + "reference": "18d6d88ad03d5c77891898b923d0cea54a209091", "shasum": "" }, "require": { "ext-curl": "*", - "php": ">=5.5.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0", - "squizlabs/php_codesniffer": "^2.0" + "phpunit/phpunit": "7.2.7" }, "type": "library", "autoload": { "psr-4": { - "PHPCurl\\CurlWrapper\\": "src/" + "Gt\\Curl\\": "./src" } }, "notification-url": "https://packagist.org/downloads/", @@ -88,27 +227,138 @@ ], "authors": [ { - "name": "Alexey Karapetov", - "email": "karapetov@gmail.com", - "role": "Developer" + "name": "Greg Bowler", + "email": "greg.bowler@g105b.com" } ], - "description": "The simplest OOP wrapper for curl, curl_multi, curl_share functions. Use CURL as a dependency, not hard-coded!", + "description": "cURL object wrapper.", "keywords": [ - "OOP", - "Phpcurl", "curl", "curl_multi", - "curl_share", - "dependency", - "injection", - "mock", - "phpunit", - "testable", - "testing", - "wrapper" + "http", + "interface" ], - "time": "2017-01-21T03:54:06+00:00" + "time": "2018-08-08T10:39:16+00:00" + }, + { + "name": "phpgt/http", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/PhpGt/Http.git", + "reference": "874335333dc015e8cce998b30b0411e91f968b16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhpGt/Http/zipball/874335333dc015e8cce998b30b0411e91f968b16", + "reference": "874335333dc015e8cce998b30b0411e91f968b16", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "phpgt/cookie": "*", + "phpgt/input": "*", + "psr/http-message": "^1.0.1", + "willdurand/negotiation": "^2.3" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gt\\Http\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PSR-7 HTTP message implementation.", + "time": "2018-09-16T18:21:07+00:00" + }, + { + "name": "phpgt/input", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/PhpGt/Input.git", + "reference": "7b45be4a8819d10c449542ef88d1f29df345a2a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhpGt/Input/zipball/7b45be4a8819d10c449542ef88d1f29df345a2a2", + "reference": "7b45be4a8819d10c449542ef88d1f29df345a2a2", + "shasum": "" + }, + "require": { + "phpgt/http": "dev-master" + }, + "require-dev": { + "phpunit/phpunit": "^7.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gt\\Input\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Encapsulated user input.", + "time": "2018-10-23T11:54:34+00:00" + }, + { + "name": "psr/http-message", + "version": "dev-master", + "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": "react/event-loop", @@ -153,7 +403,7 @@ }, { "name": "react/promise", - "version": "v2.7.0", + "version": "2.x-dev", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", @@ -199,30 +449,26 @@ }, { "name": "react/stream", - "version": "v0.4.6", + "version": "v1.0.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "44dc7f51ea48624110136b535b9ba44fd7d0c1ee" + "reference": "fdd0140f42805d65bf9687636503db0b326d2244" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/44dc7f51ea48624110136b535b9ba44fd7d0c1ee", - "reference": "44dc7f51ea48624110136b535b9ba44fd7d0c1ee", + "url": "https://api.github.com/repos/reactphp/stream/zipball/fdd0140f42805d65bf9687636503db0b326d2244", + "reference": "fdd0140f42805d65bf9687636503db0b326d2244", "shasum": "" }, "require": { - "evenement/evenement": "^2.0|^1.0", - "php": ">=5.3.8" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" }, "require-dev": { "clue/stream-filter": "~1.2", - "react/event-loop": "^0.4|^0.3", - "react/promise": "^2.0|^1.0" - }, - "suggest": { - "react/event-loop": "^0.4", - "react/promise": "^2.0" + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { @@ -234,38 +480,97 @@ "license": [ "MIT" ], - "description": "Basic readable and writable stream interfaces that support piping.", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ + "event-driven", + "io", + "non-blocking", "pipe", - "stream" + "reactphp", + "readable", + "stream", + "writable" + ], + "time": "2018-07-11T14:38:16+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "cf78c9ac47e8e1e141bf609c71e9e2c3a7780dcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/cf78c9ac47e8e1e141bf609c71e9e2c3a7780dcf", + "reference": "cf78c9ac47e8e1e141bf609c71e9e2c3a7780dcf", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } ], - "time": "2017-01-25T14:44:14+00:00" + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "time": "2017-08-04T15:54:30+00:00" } ], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "8c8c1afee0f929f17528fe1721de5d58e15f71c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8c8c1afee0f929f17528fe1721de5d58e15f71c7", + "reference": "8c8c1afee0f929f17528fe1721de5d58e15f71c7", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^5.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -290,16 +595,16 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2018-10-15T11:30:00+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.8.1", + "version": "1.x-dev", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", @@ -347,7 +652,7 @@ }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", @@ -601,7 +906,7 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", @@ -664,7 +969,7 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.1.3", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", @@ -727,16 +1032,16 @@ }, { "name": "phpunit/php-file-iterator", - "version": "2.0.2", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "050bedf145a257b1ff02746c31894800e5122946" + "reference": "2da97ea973af95b5ccef2d5bae0f4a976f3923a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", - "reference": "050bedf145a257b1ff02746c31894800e5122946", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2da97ea973af95b5ccef2d5bae0f4a976f3923a0", + "reference": "2da97ea973af95b5ccef2d5bae0f4a976f3923a0", "shasum": "" }, "require": { @@ -773,7 +1078,7 @@ "filesystem", "iterator" ], - "time": "2018-09-13T20:33:42+00:00" + "time": "2018-10-10T04:54:51+00:00" }, { "name": "phpunit/php-text-template", @@ -818,16 +1123,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.0.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" + "reference": "9ef9968ba27999219d76ae3cef97aa0fa87bd90f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/9ef9968ba27999219d76ae3cef97aa0fa87bd90f", + "reference": "9ef9968ba27999219d76ae3cef97aa0fa87bd90f", "shasum": "" }, "require": { @@ -863,20 +1168,20 @@ "keywords": [ "timer" ], - "time": "2018-02-01T13:07:23+00:00" + "time": "2018-06-04T07:17:52+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.0.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" + "reference": "711ca0c13c66f6b66c2ecb586e56415815034330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/711ca0c13c66f6b66c2ecb586e56415815034330", + "reference": "711ca0c13c66f6b66c2ecb586e56415815034330", "shasum": "" }, "require": { @@ -912,20 +1217,20 @@ "keywords": [ "tokenizer" ], - "time": "2018-02-01T13:16:43+00:00" + "time": "2018-06-06T10:32:05+00:00" }, { "name": "phpunit/phpunit", - "version": "7.4.3", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c151651fb6ed264038d486ea262e243af72e5e64" + "reference": "4749b966a603713c35806d7a71c54492ee209c38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c151651fb6ed264038d486ea262e243af72e5e64", - "reference": "c151651fb6ed264038d486ea262e243af72e5e64", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4749b966a603713c35806d7a71c54492ee209c38", + "reference": "4749b966a603713c35806d7a71c54492ee209c38", "shasum": "" }, "require": { @@ -946,7 +1251,7 @@ "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", @@ -970,7 +1275,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.4-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -996,20 +1301,20 @@ "testing", "xunit" ], - "time": "2018-10-23T05:57:41+00:00" + "time": "2018-10-23T05:29:44+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "reference": "22f5f5ff892d51035dd1fb4cd6b224a640ffb206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/22f5f5ff892d51035dd1fb4cd6b224a640ffb206", + "reference": "22f5f5ff892d51035dd1fb4cd6b224a640ffb206", "shasum": "" }, "require": { @@ -1041,20 +1346,20 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "time": "2018-05-15T05:52:48+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.2", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + "reference": "2256ef8e824cc494ddeebaa00fabe7ab4d83fc75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2256ef8e824cc494ddeebaa00fabe7ab4d83fc75", + "reference": "2256ef8e824cc494ddeebaa00fabe7ab4d83fc75", "shasum": "" }, "require": { @@ -1105,11 +1410,11 @@ "compare", "equality" ], - "time": "2018-07-12T15:12:46+00:00" + "time": "2018-09-07T13:55:31+00:00" }, { "name": "sebastian/diff", - "version": "3.0.1", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", @@ -1165,28 +1470,28 @@ }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "c38399052e96de3846f635b8791c2e447a06040d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c38399052e96de3846f635b8791c2e447a06040d", + "reference": "c38399052e96de3846f635b8791c2e447a06040d", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1211,20 +1516,20 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2018-10-23T06:03:07+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "c8c4f196e32858e4448bc285e542b61a4c40d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c8c4f196e32858e4448bc285e542b61a4c40d9dc", + "reference": "c8c4f196e32858e4448bc285e542b61a4c40d9dc", "shasum": "" }, "require": { @@ -1278,20 +1583,20 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2018-06-28T14:22:04+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "30367ea06c5cc3bf684457ac793fb2b863d783c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/30367ea06c5cc3bf684457ac793fb2b863d783c6", + "reference": "30367ea06c5cc3bf684457ac793fb2b863d783c6", "shasum": "" }, "require": { @@ -1329,20 +1634,20 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2018-05-15T05:52:33+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.3", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "reference": "06d95dc84f06fc6cc246b8bf48facebcf0fe8069" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/06d95dc84f06fc6cc246b8bf48facebcf0fe8069", + "reference": "06d95dc84f06fc6cc246b8bf48facebcf0fe8069", "shasum": "" }, "require": { @@ -1376,20 +1681,20 @@ ], "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" + "time": "2018-05-15T05:52:18+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.1", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "7707193304715e3caddf28fc73c02c12ed6f350c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/7707193304715e3caddf28fc73c02c12ed6f350c", + "reference": "7707193304715e3caddf28fc73c02c12ed6f350c", "shasum": "" }, "require": { @@ -1421,20 +1726,20 @@ ], "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" + "time": "2018-05-15T05:50:44+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "dbe1869c13935c6080c834fc61424834b9ad5907" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/dbe1869c13935c6080c834fc61424834b9ad5907", + "reference": "dbe1869c13935c6080c834fc61424834b9ad5907", "shasum": "" }, "require": { @@ -1474,11 +1779,11 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "time": "2018-05-15T05:52:05+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.1", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", @@ -1603,16 +1908,16 @@ }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "53927dddf3afa2088b355188e143bba42159bf5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/53927dddf3afa2088b355188e143bba42159bf5d", + "reference": "53927dddf3afa2088b355188e143bba42159bf5d", "shasum": "" }, "require": { @@ -1649,16 +1954,20 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2018-05-29T14:25:02+00:00" } ], "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], + "minimum-stability": "dev", + "stability-flags": { + "phpgt/http": 20, + "phpgt/curl": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.1" + "php": ">=7.1.0", + "ext-curl": "*" }, "platform-dev": [] } diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 823f5e2..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - test/unit - - - - - - - - - - - src - - - \ No newline at end of file diff --git a/src/Body.php b/src/Body.php deleted file mode 100644 index 536b7a0..0000000 --- a/src/Body.php +++ /dev/null @@ -1,70 +0,0 @@ -promise(); - $this->readRawBodyDeferredArray []= $deferredJson; - $this->readRawBodyDeferredTransformArray []= function($data) { - return json_decode($data); - }; - - return $promise; -} - -/** - * Returns a promise that resolves with a UTF-8 encoded string. - */ -public function text():Promise { - $deferredText = new Deferred(); - $promise = $deferredText->promise(); - $this->readRawBodyDeferredArray []= $deferredText; - $this->readRawBodyDeferredTransformArray []= function($data) { - $charset = $this->getCharset(); - $toEncoding = "utf-8"; - return mb_convert_encoding($data, $toEncoding, $charset); - }; - - return $promise; -} - -}# \ No newline at end of file diff --git a/src/BodyResponse.php b/src/BodyResponse.php new file mode 100644 index 0000000..0a7b9e2 --- /dev/null +++ b/src/BodyResponse.php @@ -0,0 +1,52 @@ +promise(); + } + + public function blob():Promise { + $deferred = new Deferred(); + return $deferred->promise(); + } + + public function formData():Promise { + $deferred = new Deferred(); + return $deferred->promise(); + } + + public function json(int $depth = 512, int $options = 0):Promise { + $deferred = new Deferred(); + + $json = json_decode( + $this->getBody(), + false, + $depth, + $options + ); + if(is_null($json)) { + $errorMessage = json_last_error_msg(); + throw new JsonDecodeException($errorMessage); + } + + $deferred->resolve($json); + + return $deferred->promise(); + } + + public function text():Promise { + + var_dump($this->getBody()->read(100));die(); + $deferred = new Deferred(); + $deferred->resolve((string)$this->getBody()); + return $deferred->promise(); + } +} \ No newline at end of file diff --git a/src/BodyResponseFactory.php b/src/BodyResponseFactory.php new file mode 100644 index 0000000..d6ab123 --- /dev/null +++ b/src/BodyResponseFactory.php @@ -0,0 +1,17 @@ +getStatusCode(), + $response->getResponseHeaders(), + $response->getBody() + ); + + return $bodyResponse; + } +} \ No newline at end of file diff --git a/src/CurlHandleMissingException.php b/src/CurlHandleMissingException.php deleted file mode 100644 index 9d24279..0000000 --- a/src/CurlHandleMissingException.php +++ /dev/null @@ -1,4 +0,0 @@ -headerArray[$name] []= $value; -} - -/** - * Deletes a header from the current Headers object. Throws a HeaderException - * if the provided name does not exist. - */ -public function delete(string $name) { - -} - -/** - * Returns the first value of a given header from within a Headers object. If - * the requested header doesn't exist in the Headers object, the call - * returns null. - */ -public function get(string $name) { - if($this->has($name)) { - return $this->headerArray[$name][0]; - } - - return null; -} - -/** - * Returns an array of all the values of a header within a Headers object with - * a given name. If the requested header doesn't exist in the Headers object, - * it returns an empty array. - */ -public function getAll():array { - -} - -/** - * Returns a boolean stating whether a Headers object contains a certain header. - */ -public function has(string $name):bool { - return !empty($this->headerArray[$name]); -} - -/** - * Sets a new value for an existing header inside a Headers object, or adds the - * header if it does not already exist. - * - * The difference between set() and append() is that if the specified header - * already exists and accepts multiple values, set() overwrites the existing - * value with the new one, whereas append() appends the new value to the end of - * the set of values. - * - * @param string $name The header name - * @param string|array $value The header value, either a string or array of - * strings to append to the same header - */ -public function set(string $name, $value) { - $this->headerArray[$name] = []; - $this->append($name, $value); -} - -}# \ No newline at end of file diff --git a/src/Http.php b/src/Http.php index bfb292a..0750347 100644 --- a/src/Http.php +++ b/src/Http.php @@ -1,108 +1,127 @@ interval = $interval; - $this->loop = EventLoopFactory::create(); - $this->requestResolver = new RequestResolver($this->loop); -} - -public function __call($name, $arguments) { - switch($name) { - case Request::METHOD_GET: - case Request::METHOD_POST: - case Request::METHOD_HEAD: - case Request::METHOD_PUT: - case Request::METHOD_DELETE: - case Request::METHOD_OPTIONS: - case Request::METHOD_PATCH: - if(!isset($arguments[1])) { - $arguments[1] = []; - } - $arguments[1]["method"] = $name; - - return call_user_func_array([$this, "request"], $arguments); - break; - - default: - trigger_error("Call to undefined method " - . __CLASS__ - . "::" - . $name - . "()" - , E_USER_ERROR +use React\EventLoop\Factory as EventLoopFactory; +use React\Promise\PromiseInterface; + +class Http extends GlobalFetchHelper implements HttpClient, HttpAsyncClient { + const REFERRER = "PhpGt/Fetch"; + + /** @var float */ + protected $interval; + /** @var StreamSelectLoop */ + protected $loop; + /** @var RequestResolver */ + protected $requestResolver; + /** @var array cURL options */ + protected $options = [ + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_REFERER => self::REFERRER, + ]; + protected $timer; + + public function __construct( + array $options = [], + float $interval = 0.01 + ) { + $this->options = $options + $this->options; + $this->interval = $interval; + + $this->loop = EventLoopFactory::create(); + $this->requestResolver = new RequestResolver($this->loop); + } + + /** + * @interface HttpClient + */ + public function sendRequest( + RequestInterface $request + ):ResponseInterface { + // TODO: Implement sendRequest() method. + } + + /** + * @interface HttpAsyncClient + */ + public function sendAsyncRequest( + RequestInterface $request + ):Promise { + // TODO: Implement sendAsyncRequest() method. + } + + /** + * Creates a new Deferred object to perform the resolution of the request and + * returns a PSR-7 compatible promise that represents the result of the response + * + * Long-hand for the GlobalFetchHelper get, head, post, etc. + * + * @param string|UriInterface $input + * @param array $init + * @return PromiseInterface + */ + public function fetch($input, array $init = []):PromiseInterface { + $deferred = new Deferred(); + $promise = $deferred->promise(); + + $uri = $this->ensureUriInterface($input); + + $this->requestResolver->add( + $uri, + $init, + $deferred ); + return $promise; + } + + public function getOptions():array { + return $this->options; + } + + /** + * @param string|UriInterface $uri + * @return string + */ + public function ensureUriInterface($uri):UriInterface { + if(is_string($uri)) { + $uri = new Uri($uri); + } + + return $uri; } -} - -/** - * @param string|Request $input Defines the resource that you wish to fetch - * @param array $init An associative array containing any custom settings that - * you wish to apply to the request - * - * @return \React\Promise\Promise - */ -public function request($input, array $init = []) { - $deferred = new Deferred(); - $promise = $deferred->promise(); - - if(!$input instanceof Request) { - $input = new Request($input, $init); + + /** + * Executes all promises in parallel, returning only when all promises + * have been fulfilled. + */ + public function wait() { + $this->timer = $this->loop->addPeriodicTimer( + $this->interval, + [$this->requestResolver, "tick"] + ); + $this->loop->run(); +// $this->requestResolver->temporaryThing(); } - $this->requestResolver->add($input, $deferred); - - return $promise; -} - -/** - * Executes all promises in parallel, not returning until all requests have - * completed. - */ -public function wait() { - $this->timer = $this->loop->addPeriodicTimer( - $this->interval, [$this->requestResolver, "tick"] - ); - $this->loop->run(); -} - -/** - * Executes all promises in parallel, returning a promise that resolves when - * all HTTP requests have completed. - * - * @return \React\Promise\Promise Resolved when all HTTP requests have - * completed - */ -public function all() { - $deferred = new Deferred(); - $this->wait(); - $deferred->resolve(true); - - return $deferred->promise(); -} - -}# + /** + * Begins execution of all promises, returning its own Promise that will + * resolve when the last HTTP request is fully resolved. + */ + public function all():PromiseInterface { + $deferred = new Deferred(); + $promise = $deferred->promise(); + $this->wait(); + + $deferred->resolve(true); + return $promise; + } +} \ No newline at end of file diff --git a/src/HttpBodyException.php b/src/HttpBodyException.php deleted file mode 100644 index 99d0b14..0000000 --- a/src/HttpBodyException.php +++ /dev/null @@ -1,4 +0,0 @@ -method = $method; - - if(!empty($init["headers"]) - && is_array($init["headers"])) { - $this->headers = $init["headers"]; - } - - if(!empty($init["body"])) { - if(($method === self::METHOD_GET || $method === self::METHOD_HEAD)) { - throw new HttpInitException("body"); - } - - $this->body = $init["body"]; - } - - if(!empty($init["credentials"])) { - if(!in_array( - strtolower($init["credentials"]), self::AVAILABLE_CREDENTIALS)) { - throw new HttpInitException("credentials"); - } - } - - if(!empty($init["redirect"])) { - if(!in_array( - strtolower($init["redirect"]), self::AVAILABLE_REDIRECTS)) { - throw new HttpInitException("redirect"); - } - - $this->redirect = $init["redirect"]; - } - - if(!empty($init["referrer"])) { - if(!is_string($init["referrer"])) { - throw new HttpInitException("referrer"); - } - - $this->referrer = $init["referrer"]; - } - - if(!empty($init["integrity"])) { - if(!is_string($init["integrity"])) { - throw new HttpInitException("integrity"); - } - - $this->integrity = $init["integrity"]; - } - - $this->curl = new $curlClass($uri); - $this->curlInit($curlOpt); -} - -public function getCurlHandle() { - return $this->curl; -} - -public function getResponseCode():int { - return (int)$this->curl->getInfo(CURLINFO_HTTP_CODE); -} - -public function setStream(callable $callback) { - $this->curl->setOpt(CURLOPT_WRITEFUNCTION, $callback); -} - -private function curlInit($options = []) { - $defaultOptions = []; - - $defaultOptions[CURLOPT_CUSTOMREQUEST] = $this->method; - - if(isset($this->headers)) { - $defaultOptions[CURLOPT_HTTPHEADER] = $this->headers; - } - - if(isset($this->body)) { - $defaultOptions[CURLOPT_POSTFIELDS] = $this->body; - } -// TODO: Set up cookie jar for $this->credentials -// as described https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch - - if($this->redirect === self::REDIRECT_FOLLOW) { - $defaultOptions[CURLOPT_FOLLOWLOCATION] = true; - } - else { - $defaultOptions[CURLOPT_FOLLOWLOCATION] = false; - } - - if(isset($this->referrer)) { - $defaultOptions[CURLOPT_REFERER] = $this->referrer; - } - - $options = array_merge($defaultOptions, $options); - -// The returntransfer option MUST be set, otherwise the promise resolution -// callbacks will not be able to get the content of the HTTP requests. - $options[CURLOPT_RETURNTRANSFER] = true; - $options[CURLOPT_HEADER] = true; - - $this->curl->setOptArray($options); -} - -}# diff --git a/src/RequestResolver.php b/src/RequestResolver.php index 3c6442a..b5403de 100644 --- a/src/RequestResolver.php +++ b/src/RequestResolver.php @@ -1,118 +1,114 @@ loop = $loop; - $this->curlMulti = new $curlMultiClass(); -} - -public function add(Request $request, Deferred $deferred) { - $this->requestArray []= $request; - $this->deferredArray []= $deferred; -} - -/** - * Called from an event loop. This function periodically checks the status of - * the requests within requestArray, resolving corresponding Deferred objects - * as requests complete. - */ -public function tick() { - if(is_null($this->openConnectionCount)) { - $this->start(); + protected $loop; + + /** @var CurlInterface[] */ + protected $curlReferenceList; + /** @var Deferred[] */ + protected $deferredReferenceList; + /** @var Response[] */ + protected $responseReferenceList; + + /** @var CurlMultiInterface */ + protected $curlMulti; + + public function __construct( + LoopInterface $loop, + string $curlMultiClass = CurlMulti::class + ) { + $this->loop = $loop; + $this->curlReferenceList = []; + $this->deferredReferenceList = []; + $this->curlMulti = new $curlMultiClass(); } -// Set by the curlMulti->infoRead (passed by reference). Note this is not -// the same as the number of requests or responses that are open. - $messagesInQueue = 0; + public function add( + UriInterface $uri, + array $init, + Deferred $deferred + ):void { + $curl = new Curl($uri); - do { -// always returns false until at least one curl handle has response headers -// ready to read. (The body might not be there yet though.) - $info = $this->curlMulti->infoRead($messagesInQueue); - - if($info === false) { - break; + if(!empty($init["curlopt"])) { + $curl->setOptArray($init["curlopt"]); } - $request = $this->matchRequest($info["handle"]); - $requestIndex = array_search($request, $this->requestArray); - $httpStatusCode = $request->getResponseCode(); - $this->responseArray[$requestIndex]->complete($httpStatusCode); - - } while($messagesInQueue > 0); + $curl->setOpt(CURLOPT_RETURNTRANSFER, true); + $curl->setOpt(CURLOPT_HEADER, true); - if($this->openConnectionCount === 0) { -// TODO: Do we need to do anything else here? - $this->loop->stop(); + $this->curlMulti->add($curl); + $this->curlReferenceList []= $curl; + $this->deferredReferenceList []= $deferred; + $this->responseReferenceList []= new Response(); } -// Wait for activity on any of the handles. - $this->curlMulti->select(); - -// Execute the multi handle for processing next tick. -// openConnectionCount is passed by reference and updated by this call in each tick - $status = $this->curlMulti->exec($this->openConnectionCount); - if($status !== CURLM_OK) { - throw new CurlMultiException($status); - } -} - -/** - * Adds each request's curl handle to the multi stack. - */ -private function start() { - foreach($this->requestArray as $i => $request) { - $response = new Response($this->deferredArray[$i], $this->loop); - $this->responseArray []= $response; - $request->setStream([$response, "stream"]); - - $successCode = $this->curlMulti->add($request->getCurlHandle()); - if($successCode !== 0) { - throw new CurlMultiException($successCode); - } - } -} - -/** - * Matches and returns the Request object containing the provided curl handle. - * - * @param $ch mixed Underlying lib-curl resource - * - * @return Request - * @throws CurlHandleMissingException - */ -private function matchRequest($ch) { - foreach($this->requestArray as $request) { - if($request->getCurlHandle()->getHandle() === $ch) { - return $request; + public function tick():void { + $active = 0; + + ob_start(); + $curlMultiCode = $this->curlMulti->exec($active); + ob_end_clean(); + + while($state = $this->curlMulti->infoRead()) { + foreach($this->curlReferenceList as $i => $ch) { + if($state->getHandle() !== $ch) { + continue; + } + + $content = $this->curlMulti->getContent( + $ch + ); + $response = $this->responseReferenceList[$i]; + if($response->getStatusCode()) { + $body = $response->getBody(); + $body->write($content); + + break; + } + + list( + $headerString, + $bodyString + ) = explode( + "\r\n\r\n", + $content + ); + + + $headerParser = new Parser($headerString); + $response = $response->withProtocolVersion( + $headerParser->getProtocolVersion() + ); + $response = $response->withStatus( + $headerParser->getStatusCode() + ); + + foreach($headerParser->getKeyValues() as $key => $value) { + $response = $response->withAddedHeader( + $key, + $value + ); + } + + $body = $response->getBody(); + $body->write($bodyString); + + $this->deferredReferenceList[$i]->resolve( + BodyResponseFactory::fromResponse($response) + ); + } } } - - throw new CurlHandleMissingException($ch); -} - -}# +} \ No newline at end of file diff --git a/src/RequestResolverCurlException.php b/src/RequestResolverCurlException.php deleted file mode 100644 index 04fff14..0000000 --- a/src/RequestResolverCurlException.php +++ /dev/null @@ -1,4 +0,0 @@ -headers = new Headers(); - $this->deferredResponse = $deferredResponse; -} - -public function __get($name) { - switch($name) { - case "status": - return $this->statusCode; - break; - - default: - trigger_error("Undefined property: " - . __CLASS__ - . "::\$" - . $name - , E_USER_NOTICE - ); - return null; - } -} - -public function stream($curlHandle, string $data):int { - $bytesRead = strlen($data); - - if($this->streamTarget === self::STREAM_TARGET_HEADERS) { - if ($this->setHeaders($data) !== true) { - $this->streamTarget = self::STREAM_TARGET_BODY; - $this->curlInfo = curl_getinfo($curlHandle); - $this->deferredResponse->resolve($this); - } - } - - if($this->streamTarget === self::STREAM_TARGET_BODY) { - $this->rawBody .= $data; - - foreach ($this->readRawBodyDeferredArray as $readRawBodyDeferred) { - if (is_callable([$readRawBodyDeferred, "notify"])) { - $readRawBodyDeferred->notify($data); - } - } - } - - return $bytesRead; -} - -/** - * Called when the underlying curl handle completes. At this point, all of the - * response has arrived, so we can resolve any functions reading the body. - * - * Each deferred reader has its own transform function to manupulate the body - * into the expected format, which is called within the same iteration. - * - * @param $statusCode int The HTTP status code of the completed response - */ -public function complete(int $statusCode) { - $this->statusCode = $statusCode; - foreach($this->readRawBodyDeferredArray as $i => $readRawBodyDeferred) { - $readRawBodyDeferred->resolve( - $this->readRawBodyDeferredTransformArray[$i]($this->rawBody) - ); - } -} - -public function redirect() { -// TODO: Implement redirect() - throw new \Exception("Method not yet implemented"); -} - -/** - * Parses the raw header(s) provided as a string to this function and sets them - * using the Headers object. - * - * @param string $rawHeader The unprocessed HTTP header string, direct from the - * web server - * - * @return bool True if the header(s) have been set, false if the header section - * has ended - */ -private function setHeaders(string $rawHeader):bool { - foreach($this->parseHeaders($rawHeader) as $header => $value) { - if(empty($header) - && empty($value)) { - return false; - } - - $this->headers->append($header, $value); - } - - return true; -} - -/** - * Parses provided header string, returning KVP array. - * Code taken from http://php.net/manual/bg/function.http-parse-headers.php - * as not all systems will have PECL HTTP extension installed. - * - * @param $rawHeaders string The raw headers to be parsed - * @return array An array of headers - */ -private function parseHeaders(string $rawHeaders):array { - $headers = []; - $key = ""; - - foreach(explode("\n", $rawHeaders) as $i => $h) { - $h = explode(":", $h, 2); - - if(isset($h[1])) { - if(!isset($headers[$h[0]])) { - $headers[$h[0]] = trim($h[1]); - } - else if(is_array($headers[$h[0]])) { - $headers[$h[0]] = array_merge( - $headers[$h[0]], - array(trim($h[1])) - ); - } - else { - $headers[$h[0]] = array_merge( - array($headers[$h[0]]), - array(trim($h[1])) - ); - } - - $key = $h[0]; - } - else { - if(substr($h[0], 0, 1) === "\t") { - $headers[$key] .= "\r\n\t" . trim($h[0]); - } - else if(!$key) { - if(empty($headers[0])) { - $headers[0] = trim($h[0]); - } - } - } - } - - return $headers; -} - - -public function clone() { -// TODO: Implement clone() - throw new \Exception("Method not yet implemented"); -} - -public function error() { -// TODO: Implement error() - throw new \Exception("Method not yet implemented"); -} - -/** - * Extracts and returns the character set value from the Content-Type header - * value of $this object, if set. If not set, or none is found, the default - * encoding type is returned. - * - * @return string The character set used to store body text. - */ -private function getCharset():string { - $charset = mb_internal_encoding(); - - if(isset($this->headers) - && $this->headers->has("Content-Type")) { - $contentTypeString = $this->headers->get("Content-Type"); - $charsetEquals = "charset="; - - if(!strstr($contentTypeString, $charsetEquals)) { - return $charset; - } - - $charset = substr( - $contentTypeString, - strpos($contentTypeString, $charsetEquals) + strlen($charsetEquals) - ); - - $delimiterPosition = strpos($charset, ";"); - if($delimiterPosition > 0) { - $charset = substr($charset, 0, $delimiterPosition); - } - } - - return $charset; -} - -}# diff --git a/test/unit/GlobalFetchHelperTest.php b/test/unit/GlobalFetchHelperTest.php new file mode 100644 index 0000000..dace05d --- /dev/null +++ b/test/unit/GlobalFetchHelperTest.php @@ -0,0 +1,37 @@ + $httpMethod, + ]; + + $observerArray[$httpMethod] = $this->getMockBuilder( + GlobalFetchHelper::class + ) + ->setMethods(["fetch"]) + ->getMock(); + + $observerArray[$httpMethod]->expects($this->once()) + ->method("fetch") + ->with($uri, $initArray); + + $observerArray[$httpMethod]->$lowerCaseMethod($uri); + } + } +} \ No newline at end of file diff --git a/test/unit/Helper.php b/test/unit/Helper.php deleted file mode 100644 index 1b305b3..0000000 --- a/test/unit/Helper.php +++ /dev/null @@ -1,4 +0,0 @@ -request(self::URL); - $this->assertInstanceOf("\React\Promise\Promise", $promise); - - $promise2 = $http->all(); - $this->assertInstanceOf("\React\Promise\Promise", $promise2); -} - -public function testCallbackInvoked() { - $stubStdClass = $this->getMockBuilder(stdClass::class) - ->setMethods(["mockCallback"]) - ->getMock(); - $stubStdClass->expects($this->once()) - ->method("mockCallback"); - - $http = new Http(); - $http->request(self::URL) - ->then([$stubStdClass, "mockCallback"]); - - $http->wait(); -} - -public function testMultipleCallbacksInvoked() { - $stubStdClass = $this->getMockBuilder(stdClass::class) - ->setMethods(["mockCallback1", "mockCallback2", "mockCallback3"]) - ->getMock(); - $stubStdClass->expects($this->once()) - ->method("mockCallback1"); - $stubStdClass->expects($this->once()) - ->method("mockCallback2"); - $stubStdClass->expects($this->once()) - ->method("mockCallback3"); - - $http = new Http(); - $http->request(self::URL) - ->then([$stubStdClass, "mockCallback1"]) - ->then([$stubStdClass, "mockCallback2"]); - - $http->request(self::URL) - ->then([$stubStdClass, "mockCallback3"]); - - $http->wait(); -} - -public function testResponseType() { - $http = new Http(); - $http->request(self::URL) - ->then(function($response) { - $this->assertInstanceOf("\Gt\Fetch\Response", $response); - }); - $http->wait(); -} - -public function testAllMethod() { - $stubStdClass = $this->getMockBuilder(stdClass::class) - ->setMethods(["mockCallback"]) - ->getMock(); - $stubStdClass->expects($this->once()) - ->method("mockCallback"); - - $http = new Http(); - $http->request(self::URL); - - $http->all()->then([$stubStdClass, "mockCallback"]); -} - -public function testShorthandMethods() { - $http = new Http(); - - foreach(["get","post","head","put","delete","options","patch"] as $method) { - $http->$method(self::URL); - } - - $this->setExpectedException("PHPUnit_Framework_Error"); - $http->notAMethod(); -} - -}# \ No newline at end of file diff --git a/test/unit/HttpTest.php b/test/unit/HttpTest.php new file mode 100644 index 0000000..37663b4 --- /dev/null +++ b/test/unit/HttpTest.php @@ -0,0 +1,40 @@ +assertNotEmpty($http->getOptions()); + } + + public function testDefaultOptionsOverridden() { + $http = new Http(); + $options = $http->getOptions(); + $this->assertTrue($options[CURLOPT_FOLLOWLOCATION]); + + $options = [ + CURLOPT_FOLLOWLOCATION => false, + ]; + $http = new Http($options); + $actualOptions = $http->getOptions(); + $this->assertEquals(false, $actualOptions[CURLOPT_FOLLOWLOCATION]); + } + + public function testEnsureStringUri() { + $http = new Http(); + $uriToUse = Helper::URI_SIMPLE; + $request = new Request("GET", $uriToUse); + + $this->assertEquals($uriToUse, $http->ensureUriInterface($request)); + $this->assertEquals($uriToUse, $http->ensureUriInterface($uriToUse)); + } + +} \ No newline at end of file diff --git a/test/unit/UriTest.php b/test/unit/UriTest.php new file mode 100644 index 0000000..0d6b892 --- /dev/null +++ b/test/unit/UriTest.php @@ -0,0 +1,90 @@ +assertEquals("fake", $uri->getScheme()); + $this->assertEquals("php.gt", $uri->getAuthority()); + $this->assertEquals("", $uri->getUserInfo()); + $this->assertEquals("php.gt", $uri->getHost()); + $this->assertNull($uri->getPort()); + $this->assertEquals("/fetch", $uri->getPath()); + $this->assertEquals("", $uri->getQuery()); + $this->assertEquals("", $uri->getFragment()); + } + + public function testComplexUrl() { + $uri = new Uri(Helper::URI_COMPLEX); + + $this->assertEquals("fake", $uri->getScheme()); + $this->assertEquals("someuser:somepassword@php.gt:8008", $uri->getAuthority()); + $this->assertEquals("someuser:somepassword", $uri->getUserInfo()); + $this->assertEquals("php.gt", $uri->getHost()); + $this->assertEquals(8008, $uri->getPort()); + $this->assertEquals("/fetch", $uri->getPath()); + $this->assertEquals("id=105", $uri->getQuery()); + $this->assertEquals("example", $uri->getFragment()); + } + + public function testWithScheme() { + $uri = new Uri(Helper::URI_SIMPLE); + $newUri = $uri->withScheme("updated"); + + $this->assertEquals("updated", $newUri->getScheme()); + $this->assertEquals("updated://php.gt/fetch", (string)$newUri); + } + + public function testWithUserInfo() { + $uri = new Uri(Helper::URI_SIMPLE); + $newUri = $uri->withUserInfo("admin", "secret"); + + $this->assertEquals("admin:secret", $newUri->getUserInfo()); + $this->assertEquals("fake://admin:secret@php.gt/fetch", (string)$newUri); + } + + public function testWithHost() { + $uri = new Uri(Helper::URI_SIMPLE); + $newUri = $uri->withHost("phpgt.com"); + + $this->assertEquals("phpgt.com", $newUri->getHost()); + $this->assertEquals("fake://phpgt.com/fetch", (string)$newUri); + } + + public function testWithPort() { + $uri = new Uri(Helper::URI_SIMPLE); + $newUri = $uri->withPort(1234); + + $this->assertEquals(1234, $newUri->getPort()); + $this->assertEquals("fake://php.gt:1234/fetch", (string)$newUri); + } + + public function testWithPath() { + $uri = new Uri(Helper::URI_SIMPLE); + $newUri = $uri->withPath("/webengine"); + + $this->assertEquals("/webengine", $newUri->getPath()); + $this->assertEquals("fake://php.gt/webengine", (string)$newUri); + } + + public function testWithQuery() { + $uri = new Uri(Helper::URI_SIMPLE); + $newUri = $uri->withQuery("name=Scarlett"); + + $this->assertEquals("name=Scarlett", $newUri->getQuery()); + $this->assertEquals("fake://php.gt/fetch?name=Scarlett", (string)$newUri); + } + + public function testWithFragment() { + $uri = new Uri(Helper::URI_SIMPLE); + $newUri = $uri->withFragment("example"); + + $this->assertEquals("example", $newUri->getFragment()); + $this->assertEquals("fake://php.gt/fetch#example", (string)$newUri); + } +} \ No newline at end of file diff --git a/test/unit/phpunit.xml b/test/unit/phpunit.xml new file mode 100644 index 0000000..2aceb67 --- /dev/null +++ b/test/unit/phpunit.xml @@ -0,0 +1,19 @@ + + + + + . + + + + + + + + + + + ../../src + + + \ No newline at end of file