Chris
7 years ago
216 changed files with 21791 additions and 943 deletions
@ -0,0 +1,11 @@ |
|||||
|
<?php namespace App\SupportedApps\Contracts; |
||||
|
|
||||
|
interface Livestats { |
||||
|
|
||||
|
public function configDetails(); |
||||
|
|
||||
|
public function testConfig(); |
||||
|
|
||||
|
public function executeConfig(); |
||||
|
|
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
{ |
{ |
||||
"/css/app.css": "/css/app.css?id=4f5b9f5ba0f1f57405c8", |
"/css/app.css": "/css/app.css?id=d6ec6184a5279779b01a", |
||||
"/js/app.js": "/js/app.js?id=559585a774e3f088503a" |
"/js/app.js": "/js/app.js?id=67b9d3b008c0a8da2fd6" |
||||
} |
} |
@ -1,12 +1,13 @@ |
|||||
<h2>{{ __('app.apps.config') }} ({{ __('app.optional') }})</h2> |
<h2>{{ __('app.apps.config') }} ({{ __('app.optional') }})</h2> |
||||
<div class="items"> |
<div class="items"> |
||||
<input type="hidden" name="config[type]" value="\App\SupportedApps\Nzbget" /> |
<input type="hidden" data-config="type" class="config-item" name="config[type]" value="\App\SupportedApps\Nzbget" /> |
||||
<div class="input"> |
<div class="input"> |
||||
<label>{{ __('app.apps.username') }}</label> |
<label>{{ __('app.apps.username') }}</label> |
||||
{!! Form::text('config[username]', null, array('placeholder' => __('app.apps.username'), 'class' => 'form-control')) !!} |
{!! Form::text('config[username]', null, array('placeholder' => __('app.apps.username'), 'data-config' => 'username', 'class' => 'form-control config-item')) !!} |
||||
</div> |
</div> |
||||
<div class="input"> |
<div class="input"> |
||||
<label>{{ __('app.apps.password') }}</label> |
<label>{{ __('app.apps.password') }}</label> |
||||
{!! Form::text('config[password]', null, array('placeholder' => __('app.apps.password'), 'class' => 'form-control')) !!} |
{!! Form::text('config[password]', null, array('placeholder' => __('app.apps.password'), 'data-config' => 'password', 'class' => 'form-control config-item')) !!} |
||||
</div> |
</div> |
||||
|
<button id="test_config">Test</button> |
||||
</div> |
</div> |
File diff suppressed because it is too large
@ -0,0 +1,5 @@ |
|||||
|
phpunit.xml |
||||
|
composer.lock |
||||
|
build |
||||
|
vendor |
||||
|
coverage.clover |
@ -0,0 +1,46 @@ |
|||||
|
before_commands: |
||||
|
- "composer install --prefer-source" |
||||
|
|
||||
|
tools: |
||||
|
external_code_coverage: |
||||
|
timeout: 600 |
||||
|
php_code_coverage: |
||||
|
enabled: true |
||||
|
test_command: ./vendor/bin/phpunit |
||||
|
php_code_sniffer: |
||||
|
enabled: true |
||||
|
config: |
||||
|
standard: PSR2 |
||||
|
filter: |
||||
|
paths: ["src/*", "tests/*"] |
||||
|
php_cpd: |
||||
|
enabled: true |
||||
|
excluded_dirs: ["build/*", "tests", "vendor"] |
||||
|
php_cs_fixer: |
||||
|
enabled: true |
||||
|
config: |
||||
|
level: all |
||||
|
filter: |
||||
|
paths: ["src/*", "tests/*"] |
||||
|
php_loc: |
||||
|
enabled: true |
||||
|
excluded_dirs: ["build", "tests", "vendor"] |
||||
|
php_mess_detector: |
||||
|
enabled: true |
||||
|
config: |
||||
|
ruleset: phpmd.xml.dist |
||||
|
design_rules: { eval_expression: false } |
||||
|
filter: |
||||
|
paths: ["src/*"] |
||||
|
php_pdepend: |
||||
|
enabled: true |
||||
|
excluded_dirs: ["build", "tests", "vendor"] |
||||
|
php_analyzer: |
||||
|
enabled: true |
||||
|
filter: |
||||
|
paths: ["src/*", "tests/*"] |
||||
|
php_hhvm: |
||||
|
enabled: true |
||||
|
filter: |
||||
|
paths: ["src/*", "tests/*"] |
||||
|
sensiolabs_security_checker: true |
@ -0,0 +1,14 @@ |
|||||
|
#!/bin/sh |
||||
|
set -x |
||||
|
if [ "$TRAVIS_PHP_VERSION" = 'hhvm' ] || [ "$TRAVIS_PHP_VERSION" = 'hhvm-nightly' ] ; then |
||||
|
curl -sS https://getcomposer.org/installer > composer-installer.php |
||||
|
hhvm composer-installer.php |
||||
|
hhvm -v ResourceLimit.SocketDefaultTimeout=30 -v Http.SlowQueryThreshold=30000 composer.phar update --prefer-source |
||||
|
elif [ "$TRAVIS_PHP_VERSION" = '5.3.3' ] ; then |
||||
|
composer self-update |
||||
|
composer update --prefer-source --no-dev |
||||
|
composer dump-autoload |
||||
|
else |
||||
|
composer self-update |
||||
|
composer update --prefer-source |
||||
|
fi |
@ -0,0 +1,22 @@ |
|||||
|
language: php |
||||
|
|
||||
|
php: |
||||
|
- 5.3.3 |
||||
|
- 5.3 |
||||
|
- 5.4 |
||||
|
- 5.5 |
||||
|
- 5.6 |
||||
|
- hhvm |
||||
|
|
||||
|
before_script: |
||||
|
- ./.travis.install.sh |
||||
|
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then PHPUNIT_FLAGS="--coverage-clover coverage.clover"; else PHPUNIT_FLAGS=""; fi |
||||
|
|
||||
|
script: |
||||
|
- if [ $TRAVIS_PHP_VERSION = '5.3.3' ]; then phpunit; fi |
||||
|
- if [ $TRAVIS_PHP_VERSION != '5.3.3' ]; then ./vendor/bin/phpunit $PHPUNIT_FLAGS; fi |
||||
|
- if [ $TRAVIS_PHP_VERSION != '5.3.3' ]; then ./vendor/bin/phpcs --standard=PSR2 ./src/ ./tests/; fi |
||||
|
- if [[ $TRAVIS_PHP_VERSION != '5.3.3' && $TRAVIS_PHP_VERSION != '5.4.29' && $TRAVIS_PHP_VERSION != '5.5.13' ]]; then php -n ./vendor/bin/athletic -p ./tests/DoctrineTest/InstantiatorPerformance/ -f GroupedFormatter; fi |
||||
|
|
||||
|
after_script: |
||||
|
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then wget https://scrutinizer-ci.com/ocular.phar; php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi |
@ -0,0 +1,27 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<ruleset |
||||
|
name="Instantiator rules" |
||||
|
xmlns="http://pmd.sf.net/ruleset/1.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd" |
||||
|
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd" |
||||
|
> |
||||
|
<rule ref="rulesets/cleancode.xml"> |
||||
|
<!-- static access is used for caching purposes --> |
||||
|
<exclude name="StaticAccess"/> |
||||
|
</rule> |
||||
|
<rule ref="rulesets/codesize.xml"/> |
||||
|
<rule ref="rulesets/controversial.xml"/> |
||||
|
<rule ref="rulesets/design.xml"/> |
||||
|
<rule ref="rulesets/naming.xml"/> |
||||
|
<rule ref="rulesets/unusedcode.xml"/> |
||||
|
<rule |
||||
|
name="NPathComplexity" |
||||
|
message="The {0} {1}() has an NPath complexity of {2}. The configured NPath complexity threshold is {3}." |
||||
|
class="PHP_PMD_Rule_Design_NpathComplexity" |
||||
|
> |
||||
|
<properties> |
||||
|
<property name="minimum" description="The npath reporting threshold" value="10"/> |
||||
|
</properties> |
||||
|
</rule> |
||||
|
</ruleset> |
@ -0,0 +1,22 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<phpunit |
||||
|
bootstrap="./vendor/autoload.php" |
||||
|
colors="true" |
||||
|
convertErrorsToExceptions="true" |
||||
|
convertNoticesToExceptions="true" |
||||
|
convertWarningsToExceptions="true" |
||||
|
verbose="true" |
||||
|
stopOnFailure="false" |
||||
|
processIsolation="false" |
||||
|
backupGlobals="false" |
||||
|
syntaxCheck="true" |
||||
|
> |
||||
|
<testsuite name="Doctrine\Instantiator tests"> |
||||
|
<directory>./tests/DoctrineTest/InstantiatorTest</directory> |
||||
|
</testsuite> |
||||
|
<filter> |
||||
|
<whitelist addUncoveredFilesFromWhitelist="true"> |
||||
|
<directory suffix=".php">./src</directory> |
||||
|
</whitelist> |
||||
|
</filter> |
||||
|
</phpunit> |
@ -0,0 +1,96 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorPerformance; |
||||
|
|
||||
|
use Athletic\AthleticEvent; |
||||
|
use Doctrine\Instantiator\Instantiator; |
||||
|
|
||||
|
/** |
||||
|
* Performance tests for {@see \Doctrine\Instantiator\Instantiator} |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class InstantiatorPerformanceEvent extends AthleticEvent |
||||
|
{ |
||||
|
/** |
||||
|
* @var \Doctrine\Instantiator\Instantiator |
||||
|
*/ |
||||
|
private $instantiator; |
||||
|
|
||||
|
/** |
||||
|
* {@inheritDoc} |
||||
|
*/ |
||||
|
protected function setUp() |
||||
|
{ |
||||
|
$this->instantiator = new Instantiator(); |
||||
|
|
||||
|
$this->instantiator->instantiate(__CLASS__); |
||||
|
$this->instantiator->instantiate('ArrayObject'); |
||||
|
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SimpleSerializableAsset'); |
||||
|
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SerializableArrayObjectAsset'); |
||||
|
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\UnCloneableAsset'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @iterations 20000 |
||||
|
* @baseline |
||||
|
* @group instantiation |
||||
|
*/ |
||||
|
public function testInstantiateSelf() |
||||
|
{ |
||||
|
$this->instantiator->instantiate(__CLASS__); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @iterations 20000 |
||||
|
* @group instantiation |
||||
|
*/ |
||||
|
public function testInstantiateInternalClass() |
||||
|
{ |
||||
|
$this->instantiator->instantiate('ArrayObject'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @iterations 20000 |
||||
|
* @group instantiation |
||||
|
*/ |
||||
|
public function testInstantiateSimpleSerializableAssetClass() |
||||
|
{ |
||||
|
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SimpleSerializableAsset'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @iterations 20000 |
||||
|
* @group instantiation |
||||
|
*/ |
||||
|
public function testInstantiateSerializableArrayObjectAsset() |
||||
|
{ |
||||
|
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SerializableArrayObjectAsset'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @iterations 20000 |
||||
|
* @group instantiation |
||||
|
*/ |
||||
|
public function testInstantiateUnCloneableAsset() |
||||
|
{ |
||||
|
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\UnCloneableAsset'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,83 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTest\Exception; |
||||
|
|
||||
|
use Doctrine\Instantiator\Exception\InvalidArgumentException; |
||||
|
use PHPUnit_Framework_TestCase; |
||||
|
use ReflectionClass; |
||||
|
|
||||
|
/** |
||||
|
* Tests for {@see \Doctrine\Instantiator\Exception\InvalidArgumentException} |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
* |
||||
|
* @covers \Doctrine\Instantiator\Exception\InvalidArgumentException |
||||
|
*/ |
||||
|
class InvalidArgumentExceptionTest extends PHPUnit_Framework_TestCase |
||||
|
{ |
||||
|
public function testFromNonExistingTypeWithNonExistingClass() |
||||
|
{ |
||||
|
$className = __CLASS__ . uniqid(); |
||||
|
$exception = InvalidArgumentException::fromNonExistingClass($className); |
||||
|
|
||||
|
$this->assertInstanceOf('Doctrine\\Instantiator\\Exception\\InvalidArgumentException', $exception); |
||||
|
$this->assertSame('The provided class "' . $className . '" does not exist', $exception->getMessage()); |
||||
|
} |
||||
|
|
||||
|
public function testFromNonExistingTypeWithTrait() |
||||
|
{ |
||||
|
if (PHP_VERSION_ID < 50400) { |
||||
|
$this->markTestSkipped('Need at least PHP 5.4.0, as this test requires traits support to run'); |
||||
|
} |
||||
|
|
||||
|
$exception = InvalidArgumentException::fromNonExistingClass( |
||||
|
'DoctrineTest\\InstantiatorTestAsset\\SimpleTraitAsset' |
||||
|
); |
||||
|
|
||||
|
$this->assertSame( |
||||
|
'The provided type "DoctrineTest\\InstantiatorTestAsset\\SimpleTraitAsset" is a trait, ' |
||||
|
. 'and can not be instantiated', |
||||
|
$exception->getMessage() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public function testFromNonExistingTypeWithInterface() |
||||
|
{ |
||||
|
$exception = InvalidArgumentException::fromNonExistingClass('Doctrine\\Instantiator\\InstantiatorInterface'); |
||||
|
|
||||
|
$this->assertSame( |
||||
|
'The provided type "Doctrine\\Instantiator\\InstantiatorInterface" is an interface, ' |
||||
|
. 'and can not be instantiated', |
||||
|
$exception->getMessage() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public function testFromAbstractClass() |
||||
|
{ |
||||
|
$reflection = new ReflectionClass('DoctrineTest\\InstantiatorTestAsset\\AbstractClassAsset'); |
||||
|
$exception = InvalidArgumentException::fromAbstractClass($reflection); |
||||
|
|
||||
|
$this->assertSame( |
||||
|
'The provided class "DoctrineTest\\InstantiatorTestAsset\\AbstractClassAsset" is abstract, ' |
||||
|
. 'and can not be instantiated', |
||||
|
$exception->getMessage() |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,69 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTest\Exception; |
||||
|
|
||||
|
use Doctrine\Instantiator\Exception\UnexpectedValueException; |
||||
|
use Exception; |
||||
|
use PHPUnit_Framework_TestCase; |
||||
|
use ReflectionClass; |
||||
|
|
||||
|
/** |
||||
|
* Tests for {@see \Doctrine\Instantiator\Exception\UnexpectedValueException} |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
* |
||||
|
* @covers \Doctrine\Instantiator\Exception\UnexpectedValueException |
||||
|
*/ |
||||
|
class UnexpectedValueExceptionTest extends PHPUnit_Framework_TestCase |
||||
|
{ |
||||
|
public function testFromSerializationTriggeredException() |
||||
|
{ |
||||
|
$reflectionClass = new ReflectionClass($this); |
||||
|
$previous = new Exception(); |
||||
|
$exception = UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $previous); |
||||
|
|
||||
|
$this->assertInstanceOf('Doctrine\\Instantiator\\Exception\\UnexpectedValueException', $exception); |
||||
|
$this->assertSame($previous, $exception->getPrevious()); |
||||
|
$this->assertSame( |
||||
|
'An exception was raised while trying to instantiate an instance of "' |
||||
|
. __CLASS__ . '" via un-serialization', |
||||
|
$exception->getMessage() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public function testFromUncleanUnSerialization() |
||||
|
{ |
||||
|
$reflection = new ReflectionClass('DoctrineTest\\InstantiatorTestAsset\\AbstractClassAsset'); |
||||
|
$exception = UnexpectedValueException::fromUncleanUnSerialization($reflection, 'foo', 123, 'bar', 456); |
||||
|
|
||||
|
$this->assertInstanceOf('Doctrine\\Instantiator\\Exception\\UnexpectedValueException', $exception); |
||||
|
$this->assertSame( |
||||
|
'Could not produce an instance of "DoctrineTest\\InstantiatorTestAsset\\AbstractClassAsset" ' |
||||
|
. 'via un-serialization, since an error was triggered in file "bar" at line "456"', |
||||
|
$exception->getMessage() |
||||
|
); |
||||
|
|
||||
|
$previous = $exception->getPrevious(); |
||||
|
|
||||
|
$this->assertInstanceOf('Exception', $previous); |
||||
|
$this->assertSame('foo', $previous->getMessage()); |
||||
|
$this->assertSame(123, $previous->getCode()); |
||||
|
} |
||||
|
} |
@ -0,0 +1,219 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTest; |
||||
|
|
||||
|
use Doctrine\Instantiator\Exception\UnexpectedValueException; |
||||
|
use Doctrine\Instantiator\Instantiator; |
||||
|
use PHPUnit_Framework_TestCase; |
||||
|
use ReflectionClass; |
||||
|
|
||||
|
/** |
||||
|
* Tests for {@see \Doctrine\Instantiator\Instantiator} |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
* |
||||
|
* @covers \Doctrine\Instantiator\Instantiator |
||||
|
*/ |
||||
|
class InstantiatorTest extends PHPUnit_Framework_TestCase |
||||
|
{ |
||||
|
/** |
||||
|
* @var Instantiator |
||||
|
*/ |
||||
|
private $instantiator; |
||||
|
|
||||
|
/** |
||||
|
* {@inheritDoc} |
||||
|
*/ |
||||
|
protected function setUp() |
||||
|
{ |
||||
|
$this->instantiator = new Instantiator(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param string $className |
||||
|
* |
||||
|
* @dataProvider getInstantiableClasses |
||||
|
*/ |
||||
|
public function testCanInstantiate($className) |
||||
|
{ |
||||
|
$this->assertInstanceOf($className, $this->instantiator->instantiate($className)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param string $className |
||||
|
* |
||||
|
* @dataProvider getInstantiableClasses |
||||
|
*/ |
||||
|
public function testInstantiatesSeparateInstances($className) |
||||
|
{ |
||||
|
$instance1 = $this->instantiator->instantiate($className); |
||||
|
$instance2 = $this->instantiator->instantiate($className); |
||||
|
|
||||
|
$this->assertEquals($instance1, $instance2); |
||||
|
$this->assertNotSame($instance1, $instance2); |
||||
|
} |
||||
|
|
||||
|
public function testExceptionOnUnSerializationException() |
||||
|
{ |
||||
|
if (defined('HHVM_VERSION')) { |
||||
|
$this->markTestSkipped( |
||||
|
'As of facebook/hhvm#3432, HHVM has no PDORow, and therefore ' |
||||
|
. ' no internal final classes that cannot be instantiated' |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
$className = 'DoctrineTest\\InstantiatorTestAsset\\UnserializeExceptionArrayObjectAsset'; |
||||
|
|
||||
|
if (\PHP_VERSION_ID >= 50600) { |
||||
|
$className = 'PDORow'; |
||||
|
} |
||||
|
|
||||
|
if (\PHP_VERSION_ID === 50429 || \PHP_VERSION_ID === 50513) { |
||||
|
$className = 'DoctrineTest\\InstantiatorTestAsset\\SerializableArrayObjectAsset'; |
||||
|
} |
||||
|
|
||||
|
$this->setExpectedException('Doctrine\\Instantiator\\Exception\\UnexpectedValueException'); |
||||
|
|
||||
|
$this->instantiator->instantiate($className); |
||||
|
} |
||||
|
|
||||
|
public function testNoticeOnUnSerializationException() |
||||
|
{ |
||||
|
if (\PHP_VERSION_ID >= 50600) { |
||||
|
$this->markTestSkipped( |
||||
|
'PHP 5.6 supports `ReflectionClass#newInstanceWithoutConstructor()` for some internal classes' |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\WakeUpNoticesAsset'); |
||||
|
|
||||
|
$this->fail('No exception was raised'); |
||||
|
} catch (UnexpectedValueException $exception) { |
||||
|
$wakeUpNoticesReflection = new ReflectionClass('DoctrineTest\\InstantiatorTestAsset\\WakeUpNoticesAsset'); |
||||
|
$previous = $exception->getPrevious(); |
||||
|
|
||||
|
$this->assertInstanceOf('Exception', $previous); |
||||
|
|
||||
|
// in PHP 5.4.29 and PHP 5.5.13, this case is not a notice, but an exception being thrown |
||||
|
if (! (\PHP_VERSION_ID === 50429 || \PHP_VERSION_ID === 50513)) { |
||||
|
$this->assertSame( |
||||
|
'Could not produce an instance of "DoctrineTest\\InstantiatorTestAsset\WakeUpNoticesAsset" ' |
||||
|
. 'via un-serialization, since an error was triggered in file "' |
||||
|
. $wakeUpNoticesReflection->getFileName() . '" at line "36"', |
||||
|
$exception->getMessage() |
||||
|
); |
||||
|
|
||||
|
$this->assertSame('Something went bananas while un-serializing this instance', $previous->getMessage()); |
||||
|
$this->assertSame(\E_USER_NOTICE, $previous->getCode()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param string $invalidClassName |
||||
|
* |
||||
|
* @dataProvider getInvalidClassNames |
||||
|
*/ |
||||
|
public function testInstantiationFromNonExistingClass($invalidClassName) |
||||
|
{ |
||||
|
$this->setExpectedException('Doctrine\\Instantiator\\Exception\\InvalidArgumentException'); |
||||
|
|
||||
|
$this->instantiator->instantiate($invalidClassName); |
||||
|
} |
||||
|
|
||||
|
public function testInstancesAreNotCloned() |
||||
|
{ |
||||
|
$className = 'TemporaryClass' . uniqid(); |
||||
|
|
||||
|
eval('namespace ' . __NAMESPACE__ . '; class ' . $className . '{}'); |
||||
|
|
||||
|
$instance = $this->instantiator->instantiate(__NAMESPACE__ . '\\' . $className); |
||||
|
|
||||
|
$instance->foo = 'bar'; |
||||
|
|
||||
|
$instance2 = $this->instantiator->instantiate(__NAMESPACE__ . '\\' . $className); |
||||
|
|
||||
|
$this->assertObjectNotHasAttribute('foo', $instance2); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Provides a list of instantiable classes (existing) |
||||
|
* |
||||
|
* @return string[][] |
||||
|
*/ |
||||
|
public function getInstantiableClasses() |
||||
|
{ |
||||
|
$classes = array( |
||||
|
array('stdClass'), |
||||
|
array(__CLASS__), |
||||
|
array('Doctrine\\Instantiator\\Instantiator'), |
||||
|
array('Exception'), |
||||
|
array('PharException'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\SimpleSerializableAsset'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\ExceptionAsset'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\FinalExceptionAsset'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\PharExceptionAsset'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\UnCloneableAsset'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\XMLReaderAsset'), |
||||
|
); |
||||
|
|
||||
|
if (\PHP_VERSION_ID === 50429 || \PHP_VERSION_ID === 50513) { |
||||
|
return $classes; |
||||
|
} |
||||
|
|
||||
|
$classes = array_merge( |
||||
|
$classes, |
||||
|
array( |
||||
|
array('PharException'), |
||||
|
array('ArrayObject'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\ArrayObjectAsset'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\SerializableArrayObjectAsset'), |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
if (\PHP_VERSION_ID >= 50600) { |
||||
|
$classes[] = array('DoctrineTest\\InstantiatorTestAsset\\WakeUpNoticesAsset'); |
||||
|
$classes[] = array('DoctrineTest\\InstantiatorTestAsset\\UnserializeExceptionArrayObjectAsset'); |
||||
|
} |
||||
|
|
||||
|
return $classes; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Provides a list of instantiable classes (existing) |
||||
|
* |
||||
|
* @return string[][] |
||||
|
*/ |
||||
|
public function getInvalidClassNames() |
||||
|
{ |
||||
|
$classNames = array( |
||||
|
array(__CLASS__ . uniqid()), |
||||
|
array('Doctrine\\Instantiator\\InstantiatorInterface'), |
||||
|
array('DoctrineTest\\InstantiatorTestAsset\\AbstractClassAsset'), |
||||
|
); |
||||
|
|
||||
|
if (\PHP_VERSION_ID >= 50400) { |
||||
|
$classNames[] = array('DoctrineTest\\InstantiatorTestAsset\\SimpleTraitAsset'); |
||||
|
} |
||||
|
|
||||
|
return $classNames; |
||||
|
} |
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
/** |
||||
|
* A simple asset for an abstract class |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
abstract class AbstractClassAsset |
||||
|
{ |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use ArrayObject; |
||||
|
use BadMethodCallException; |
||||
|
|
||||
|
/** |
||||
|
* Test asset that extends an internal PHP class |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class ArrayObjectAsset extends ArrayObject |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use BadMethodCallException; |
||||
|
use Exception; |
||||
|
|
||||
|
/** |
||||
|
* Test asset that extends an internal PHP base exception |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class ExceptionAsset extends Exception |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use BadMethodCallException; |
||||
|
use Exception; |
||||
|
|
||||
|
/** |
||||
|
* Test asset that extends an internal PHP base exception |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
final class FinalExceptionAsset extends Exception |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use BadMethodCallException; |
||||
|
use Phar; |
||||
|
|
||||
|
/** |
||||
|
* Test asset that extends an internal PHP class |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class PharAsset extends Phar |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use BadMethodCallException; |
||||
|
use PharException; |
||||
|
|
||||
|
/** |
||||
|
* Test asset that extends an internal PHP class |
||||
|
* This class should be serializable without problems |
||||
|
* and without getting the "Erroneous data format for unserializing" |
||||
|
* error |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class PharExceptionAsset extends PharException |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,62 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use ArrayObject; |
||||
|
use BadMethodCallException; |
||||
|
use Serializable; |
||||
|
|
||||
|
/** |
||||
|
* Serializable test asset that also extends an internal class |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class SerializableArrayObjectAsset extends ArrayObject implements Serializable |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritDoc} |
||||
|
*/ |
||||
|
public function serialize() |
||||
|
{ |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritDoc} |
||||
|
* |
||||
|
* Should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function unserialize($serialized) |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,61 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use BadMethodCallException; |
||||
|
use Serializable; |
||||
|
|
||||
|
/** |
||||
|
* Base serializable test asset |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class SimpleSerializableAsset implements Serializable |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritDoc} |
||||
|
*/ |
||||
|
public function serialize() |
||||
|
{ |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritDoc} |
||||
|
* |
||||
|
* Should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function unserialize($serialized) |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
/** |
||||
|
* A simple trait with no attached logic |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
trait SimpleTraitAsset |
||||
|
{ |
||||
|
} |
@ -0,0 +1,50 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use BadMethodCallException; |
||||
|
|
||||
|
/** |
||||
|
* Base un-cloneable asset |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class UnCloneableAsset |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Magic `__clone` - should not be invoked |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __clone() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use ArrayObject; |
||||
|
use BadMethodCallException; |
||||
|
|
||||
|
/** |
||||
|
* A simple asset for an abstract class |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class UnserializeExceptionArrayObjectAsset extends ArrayObject |
||||
|
{ |
||||
|
/** |
||||
|
* {@inheritDoc} |
||||
|
*/ |
||||
|
public function __wakeup() |
||||
|
{ |
||||
|
throw new BadMethodCallException(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use ArrayObject; |
||||
|
|
||||
|
/** |
||||
|
* A simple asset for an abstract class |
||||
|
* |
||||
|
* @author Marco Pivetta <ocramius@gmail.com> |
||||
|
*/ |
||||
|
class WakeUpNoticesAsset extends ArrayObject |
||||
|
{ |
||||
|
/** |
||||
|
* Wakeup method called after un-serialization |
||||
|
*/ |
||||
|
public function __wakeup() |
||||
|
{ |
||||
|
trigger_error('Something went bananas while un-serializing this instance'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
<?php |
||||
|
/* |
||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
* |
||||
|
* This software consists of voluntary contributions made by many individuals |
||||
|
* and is licensed under the MIT license. For more information, see |
||||
|
* <http://www.doctrine-project.org>. |
||||
|
*/ |
||||
|
|
||||
|
namespace DoctrineTest\InstantiatorTestAsset; |
||||
|
|
||||
|
use BadMethodCallException; |
||||
|
use XMLReader; |
||||
|
|
||||
|
/** |
||||
|
* Test asset that extends an internal PHP class |
||||
|
* |
||||
|
* @author Dave Marshall <dave@atst.io> |
||||
|
*/ |
||||
|
class XMLReaderAsset extends XMLReader |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor - should not be called |
||||
|
* |
||||
|
* @throws BadMethodCallException |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
throw new BadMethodCallException('Not supposed to be called!'); |
||||
|
} |
||||
|
} |
File diff suppressed because it is too large
@ -0,0 +1,19 @@ |
|||||
|
Copyright (c) 2011-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com> |
||||
|
|
||||
|
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. |
@ -0,0 +1,89 @@ |
|||||
|
Guzzle, PHP HTTP client |
||||
|
======================= |
||||
|
|
||||
|
[![Build Status](https://travis-ci.org/guzzle/guzzle.svg?branch=master)](https://travis-ci.org/guzzle/guzzle) |
||||
|
|
||||
|
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and |
||||
|
trivial to integrate with web services. |
||||
|
|
||||
|
- Simple interface for building query strings, POST requests, streaming large |
||||
|
uploads, streaming large downloads, using HTTP cookies, uploading JSON data, |
||||
|
etc... |
||||
|
- Can send both synchronous and asynchronous requests using the same interface. |
||||
|
- Uses PSR-7 interfaces for requests, responses, and streams. This allows you |
||||
|
to utilize other PSR-7 compatible libraries with Guzzle. |
||||
|
- Abstracts away the underlying HTTP transport, allowing you to write |
||||
|
environment and transport agnostic code; i.e., no hard dependency on cURL, |
||||
|
PHP streams, sockets, or non-blocking event loops. |
||||
|
- Middleware system allows you to augment and compose client behavior. |
||||
|
|
||||
|
```php |
||||
|
$client = new \GuzzleHttp\Client(); |
||||
|
$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); |
||||
|
echo $res->getStatusCode(); |
||||
|
// 200 |
||||
|
echo $res->getHeaderLine('content-type'); |
||||
|
// 'application/json; charset=utf8' |
||||
|
echo $res->getBody(); |
||||
|
// '{"id": 1420053, "name": "guzzle", ...}' |
||||
|
|
||||
|
// Send an asynchronous request. |
||||
|
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); |
||||
|
$promise = $client->sendAsync($request)->then(function ($response) { |
||||
|
echo 'I completed! ' . $response->getBody(); |
||||
|
}); |
||||
|
$promise->wait(); |
||||
|
``` |
||||
|
|
||||
|
## Help and docs |
||||
|
|
||||
|
- [Documentation](http://guzzlephp.org/) |
||||
|
- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle) |
||||
|
- [Gitter](https://gitter.im/guzzle/guzzle) |
||||
|
|
||||
|
|
||||
|
## Installing Guzzle |
||||
|
|
||||
|
The recommended way to install Guzzle is through |
||||
|
[Composer](http://getcomposer.org). |
||||
|
|
||||
|
```bash |
||||
|
# Install Composer |
||||
|
curl -sS https://getcomposer.org/installer | php |
||||
|
``` |
||||
|
|
||||
|
Next, run the Composer command to install the latest stable version of Guzzle: |
||||
|
|
||||
|
```bash |
||||
|
php composer.phar require guzzlehttp/guzzle |
||||
|
``` |
||||
|
|
||||
|
After installing, you need to require Composer's autoloader: |
||||
|
|
||||
|
```php |
||||
|
require 'vendor/autoload.php'; |
||||
|
``` |
||||
|
|
||||
|
You can then later update Guzzle using composer: |
||||
|
|
||||
|
```bash |
||||
|
composer.phar update |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
## Version Guidance |
||||
|
|
||||
|
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | |
||||
|
|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------| |
||||
|
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 | |
||||
|
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 | |
||||
|
| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 | |
||||
|
| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 | |
||||
|
|
||||
|
[guzzle-3-repo]: https://github.com/guzzle/guzzle3 |
||||
|
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x |
||||
|
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 |
||||
|
[guzzle-6-repo]: https://github.com/guzzle/guzzle |
||||
|
[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/ |
||||
|
[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/ |
||||
|
[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/ |
File diff suppressed because it is too large
@ -0,0 +1,44 @@ |
|||||
|
{ |
||||
|
"name": "guzzlehttp/guzzle", |
||||
|
"type": "library", |
||||
|
"description": "Guzzle is a PHP HTTP client library", |
||||
|
"keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], |
||||
|
"homepage": "http://guzzlephp.org/", |
||||
|
"license": "MIT", |
||||
|
"authors": [ |
||||
|
{ |
||||
|
"name": "Michael Dowling", |
||||
|
"email": "mtdowling@gmail.com", |
||||
|
"homepage": "https://github.com/mtdowling" |
||||
|
} |
||||
|
], |
||||
|
"require": { |
||||
|
"php": ">=5.5", |
||||
|
"guzzlehttp/psr7": "^1.4", |
||||
|
"guzzlehttp/promises": "^1.0" |
||||
|
}, |
||||
|
"require-dev": { |
||||
|
"ext-curl": "*", |
||||
|
"phpunit/phpunit": "^4.0 || ^5.0", |
||||
|
"psr/log": "^1.0" |
||||
|
}, |
||||
|
"autoload": { |
||||
|
"files": ["src/functions_include.php"], |
||||
|
"psr-4": { |
||||
|
"GuzzleHttp\\": "src/" |
||||
|
} |
||||
|
}, |
||||
|
"autoload-dev": { |
||||
|
"psr-4": { |
||||
|
"GuzzleHttp\\Tests\\": "tests/" |
||||
|
} |
||||
|
}, |
||||
|
"suggest": { |
||||
|
"psr/log": "Required for using the Log middleware" |
||||
|
}, |
||||
|
"extra": { |
||||
|
"branch-alias": { |
||||
|
"dev-master": "6.2-dev" |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,414 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use GuzzleHttp\Cookie\CookieJar; |
||||
|
use GuzzleHttp\Promise; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use Psr\Http\Message\UriInterface; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
|
||||
|
/** |
||||
|
* @method ResponseInterface get(string|UriInterface $uri, array $options = []) |
||||
|
* @method ResponseInterface head(string|UriInterface $uri, array $options = []) |
||||
|
* @method ResponseInterface put(string|UriInterface $uri, array $options = []) |
||||
|
* @method ResponseInterface post(string|UriInterface $uri, array $options = []) |
||||
|
* @method ResponseInterface patch(string|UriInterface $uri, array $options = []) |
||||
|
* @method ResponseInterface delete(string|UriInterface $uri, array $options = []) |
||||
|
* @method Promise\PromiseInterface getAsync(string|UriInterface $uri, array $options = []) |
||||
|
* @method Promise\PromiseInterface headAsync(string|UriInterface $uri, array $options = []) |
||||
|
* @method Promise\PromiseInterface putAsync(string|UriInterface $uri, array $options = []) |
||||
|
* @method Promise\PromiseInterface postAsync(string|UriInterface $uri, array $options = []) |
||||
|
* @method Promise\PromiseInterface patchAsync(string|UriInterface $uri, array $options = []) |
||||
|
* @method Promise\PromiseInterface deleteAsync(string|UriInterface $uri, array $options = []) |
||||
|
*/ |
||||
|
class Client implements ClientInterface |
||||
|
{ |
||||
|
/** @var array Default request options */ |
||||
|
private $config; |
||||
|
|
||||
|
/** |
||||
|
* Clients accept an array of constructor parameters. |
||||
|
* |
||||
|
* Here's an example of creating a client using a base_uri and an array of |
||||
|
* default request options to apply to each request: |
||||
|
* |
||||
|
* $client = new Client([ |
||||
|
* 'base_uri' => 'http://www.foo.com/1.0/', |
||||
|
* 'timeout' => 0, |
||||
|
* 'allow_redirects' => false, |
||||
|
* 'proxy' => '192.168.16.1:10' |
||||
|
* ]); |
||||
|
* |
||||
|
* Client configuration settings include the following options: |
||||
|
* |
||||
|
* - handler: (callable) Function that transfers HTTP requests over the |
||||
|
* wire. The function is called with a Psr7\Http\Message\RequestInterface |
||||
|
* and array of transfer options, and must return a |
||||
|
* GuzzleHttp\Promise\PromiseInterface that is fulfilled with a |
||||
|
* Psr7\Http\Message\ResponseInterface on success. "handler" is a |
||||
|
* constructor only option that cannot be overridden in per/request |
||||
|
* options. If no handler is provided, a default handler will be created |
||||
|
* that enables all of the request options below by attaching all of the |
||||
|
* default middleware to the handler. |
||||
|
* - base_uri: (string|UriInterface) Base URI of the client that is merged |
||||
|
* into relative URIs. Can be a string or instance of UriInterface. |
||||
|
* - **: any request option |
||||
|
* |
||||
|
* @param array $config Client configuration settings. |
||||
|
* |
||||
|
* @see \GuzzleHttp\RequestOptions for a list of available request options. |
||||
|
*/ |
||||
|
public function __construct(array $config = []) |
||||
|
{ |
||||
|
if (!isset($config['handler'])) { |
||||
|
$config['handler'] = HandlerStack::create(); |
||||
|
} elseif (!is_callable($config['handler'])) { |
||||
|
throw new \InvalidArgumentException('handler must be a callable'); |
||||
|
} |
||||
|
|
||||
|
// Convert the base_uri to a UriInterface |
||||
|
if (isset($config['base_uri'])) { |
||||
|
$config['base_uri'] = Psr7\uri_for($config['base_uri']); |
||||
|
} |
||||
|
|
||||
|
$this->configureDefaults($config); |
||||
|
} |
||||
|
|
||||
|
public function __call($method, $args) |
||||
|
{ |
||||
|
if (count($args) < 1) { |
||||
|
throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); |
||||
|
} |
||||
|
|
||||
|
$uri = $args[0]; |
||||
|
$opts = isset($args[1]) ? $args[1] : []; |
||||
|
|
||||
|
return substr($method, -5) === 'Async' |
||||
|
? $this->requestAsync(substr($method, 0, -5), $uri, $opts) |
||||
|
: $this->request($method, $uri, $opts); |
||||
|
} |
||||
|
|
||||
|
public function sendAsync(RequestInterface $request, array $options = []) |
||||
|
{ |
||||
|
// Merge the base URI into the request URI if needed. |
||||
|
$options = $this->prepareDefaults($options); |
||||
|
|
||||
|
return $this->transfer( |
||||
|
$request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), |
||||
|
$options |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public function send(RequestInterface $request, array $options = []) |
||||
|
{ |
||||
|
$options[RequestOptions::SYNCHRONOUS] = true; |
||||
|
return $this->sendAsync($request, $options)->wait(); |
||||
|
} |
||||
|
|
||||
|
public function requestAsync($method, $uri = '', array $options = []) |
||||
|
{ |
||||
|
$options = $this->prepareDefaults($options); |
||||
|
// Remove request modifying parameter because it can be done up-front. |
||||
|
$headers = isset($options['headers']) ? $options['headers'] : []; |
||||
|
$body = isset($options['body']) ? $options['body'] : null; |
||||
|
$version = isset($options['version']) ? $options['version'] : '1.1'; |
||||
|
// Merge the URI into the base URI. |
||||
|
$uri = $this->buildUri($uri, $options); |
||||
|
if (is_array($body)) { |
||||
|
$this->invalidBody(); |
||||
|
} |
||||
|
$request = new Psr7\Request($method, $uri, $headers, $body, $version); |
||||
|
// Remove the option so that they are not doubly-applied. |
||||
|
unset($options['headers'], $options['body'], $options['version']); |
||||
|
|
||||
|
return $this->transfer($request, $options); |
||||
|
} |
||||
|
|
||||
|
public function request($method, $uri = '', array $options = []) |
||||
|
{ |
||||
|
$options[RequestOptions::SYNCHRONOUS] = true; |
||||
|
return $this->requestAsync($method, $uri, $options)->wait(); |
||||
|
} |
||||
|
|
||||
|
public function getConfig($option = null) |
||||
|
{ |
||||
|
return $option === null |
||||
|
? $this->config |
||||
|
: (isset($this->config[$option]) ? $this->config[$option] : null); |
||||
|
} |
||||
|
|
||||
|
private function buildUri($uri, array $config) |
||||
|
{ |
||||
|
// for BC we accept null which would otherwise fail in uri_for |
||||
|
$uri = Psr7\uri_for($uri === null ? '' : $uri); |
||||
|
|
||||
|
if (isset($config['base_uri'])) { |
||||
|
$uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); |
||||
|
} |
||||
|
|
||||
|
return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Configures the default options for a client. |
||||
|
* |
||||
|
* @param array $config |
||||
|
*/ |
||||
|
private function configureDefaults(array $config) |
||||
|
{ |
||||
|
$defaults = [ |
||||
|
'allow_redirects' => RedirectMiddleware::$defaultSettings, |
||||
|
'http_errors' => true, |
||||
|
'decode_content' => true, |
||||
|
'verify' => true, |
||||
|
'cookies' => false |
||||
|
]; |
||||
|
|
||||
|
// Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. |
||||
|
|
||||
|
// We can only trust the HTTP_PROXY environment variable in a CLI |
||||
|
// process due to the fact that PHP has no reliable mechanism to |
||||
|
// get environment variables that start with "HTTP_". |
||||
|
if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) { |
||||
|
$defaults['proxy']['http'] = getenv('HTTP_PROXY'); |
||||
|
} |
||||
|
|
||||
|
if ($proxy = getenv('HTTPS_PROXY')) { |
||||
|
$defaults['proxy']['https'] = $proxy; |
||||
|
} |
||||
|
|
||||
|
if ($noProxy = getenv('NO_PROXY')) { |
||||
|
$cleanedNoProxy = str_replace(' ', '', $noProxy); |
||||
|
$defaults['proxy']['no'] = explode(',', $cleanedNoProxy); |
||||
|
} |
||||
|
|
||||
|
$this->config = $config + $defaults; |
||||
|
|
||||
|
if (!empty($config['cookies']) && $config['cookies'] === true) { |
||||
|
$this->config['cookies'] = new CookieJar(); |
||||
|
} |
||||
|
|
||||
|
// Add the default user-agent header. |
||||
|
if (!isset($this->config['headers'])) { |
||||
|
$this->config['headers'] = ['User-Agent' => default_user_agent()]; |
||||
|
} else { |
||||
|
// Add the User-Agent header if one was not already set. |
||||
|
foreach (array_keys($this->config['headers']) as $name) { |
||||
|
if (strtolower($name) === 'user-agent') { |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
$this->config['headers']['User-Agent'] = default_user_agent(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Merges default options into the array. |
||||
|
* |
||||
|
* @param array $options Options to modify by reference |
||||
|
* |
||||
|
* @return array |
||||
|
*/ |
||||
|
private function prepareDefaults($options) |
||||
|
{ |
||||
|
$defaults = $this->config; |
||||
|
|
||||
|
if (!empty($defaults['headers'])) { |
||||
|
// Default headers are only added if they are not present. |
||||
|
$defaults['_conditional'] = $defaults['headers']; |
||||
|
unset($defaults['headers']); |
||||
|
} |
||||
|
|
||||
|
// Special handling for headers is required as they are added as |
||||
|
// conditional headers and as headers passed to a request ctor. |
||||
|
if (array_key_exists('headers', $options)) { |
||||
|
// Allows default headers to be unset. |
||||
|
if ($options['headers'] === null) { |
||||
|
$defaults['_conditional'] = null; |
||||
|
unset($options['headers']); |
||||
|
} elseif (!is_array($options['headers'])) { |
||||
|
throw new \InvalidArgumentException('headers must be an array'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Shallow merge defaults underneath options. |
||||
|
$result = $options + $defaults; |
||||
|
|
||||
|
// Remove null values. |
||||
|
foreach ($result as $k => $v) { |
||||
|
if ($v === null) { |
||||
|
unset($result[$k]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Transfers the given request and applies request options. |
||||
|
* |
||||
|
* The URI of the request is not modified and the request options are used |
||||
|
* as-is without merging in default options. |
||||
|
* |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
* |
||||
|
* @return Promise\PromiseInterface |
||||
|
*/ |
||||
|
private function transfer(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
// save_to -> sink |
||||
|
if (isset($options['save_to'])) { |
||||
|
$options['sink'] = $options['save_to']; |
||||
|
unset($options['save_to']); |
||||
|
} |
||||
|
|
||||
|
// exceptions -> http_errors |
||||
|
if (isset($options['exceptions'])) { |
||||
|
$options['http_errors'] = $options['exceptions']; |
||||
|
unset($options['exceptions']); |
||||
|
} |
||||
|
|
||||
|
$request = $this->applyOptions($request, $options); |
||||
|
$handler = $options['handler']; |
||||
|
|
||||
|
try { |
||||
|
return Promise\promise_for($handler($request, $options)); |
||||
|
} catch (\Exception $e) { |
||||
|
return Promise\rejection_for($e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Applies the array of request options to a request. |
||||
|
* |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
* |
||||
|
* @return RequestInterface |
||||
|
*/ |
||||
|
private function applyOptions(RequestInterface $request, array &$options) |
||||
|
{ |
||||
|
$modify = []; |
||||
|
|
||||
|
if (isset($options['form_params'])) { |
||||
|
if (isset($options['multipart'])) { |
||||
|
throw new \InvalidArgumentException('You cannot use ' |
||||
|
. 'form_params and multipart at the same time. Use the ' |
||||
|
. 'form_params option if you want to send application/' |
||||
|
. 'x-www-form-urlencoded requests, and the multipart ' |
||||
|
. 'option to send multipart/form-data requests.'); |
||||
|
} |
||||
|
$options['body'] = http_build_query($options['form_params'], '', '&'); |
||||
|
unset($options['form_params']); |
||||
|
$options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; |
||||
|
} |
||||
|
|
||||
|
if (isset($options['multipart'])) { |
||||
|
$options['body'] = new Psr7\MultipartStream($options['multipart']); |
||||
|
unset($options['multipart']); |
||||
|
} |
||||
|
|
||||
|
if (isset($options['json'])) { |
||||
|
$options['body'] = \GuzzleHttp\json_encode($options['json']); |
||||
|
unset($options['json']); |
||||
|
$options['_conditional']['Content-Type'] = 'application/json'; |
||||
|
} |
||||
|
|
||||
|
if (!empty($options['decode_content']) |
||||
|
&& $options['decode_content'] !== true |
||||
|
) { |
||||
|
$modify['set_headers']['Accept-Encoding'] = $options['decode_content']; |
||||
|
} |
||||
|
|
||||
|
if (isset($options['headers'])) { |
||||
|
if (isset($modify['set_headers'])) { |
||||
|
$modify['set_headers'] = $options['headers'] + $modify['set_headers']; |
||||
|
} else { |
||||
|
$modify['set_headers'] = $options['headers']; |
||||
|
} |
||||
|
unset($options['headers']); |
||||
|
} |
||||
|
|
||||
|
if (isset($options['body'])) { |
||||
|
if (is_array($options['body'])) { |
||||
|
$this->invalidBody(); |
||||
|
} |
||||
|
$modify['body'] = Psr7\stream_for($options['body']); |
||||
|
unset($options['body']); |
||||
|
} |
||||
|
|
||||
|
if (!empty($options['auth']) && is_array($options['auth'])) { |
||||
|
$value = $options['auth']; |
||||
|
$type = isset($value[2]) ? strtolower($value[2]) : 'basic'; |
||||
|
switch ($type) { |
||||
|
case 'basic': |
||||
|
$modify['set_headers']['Authorization'] = 'Basic ' |
||||
|
. base64_encode("$value[0]:$value[1]"); |
||||
|
break; |
||||
|
case 'digest': |
||||
|
// @todo: Do not rely on curl |
||||
|
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; |
||||
|
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; |
||||
|
break; |
||||
|
case 'ntlm': |
||||
|
$options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM; |
||||
|
$options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isset($options['query'])) { |
||||
|
$value = $options['query']; |
||||
|
if (is_array($value)) { |
||||
|
$value = http_build_query($value, null, '&', PHP_QUERY_RFC3986); |
||||
|
} |
||||
|
if (!is_string($value)) { |
||||
|
throw new \InvalidArgumentException('query must be a string or array'); |
||||
|
} |
||||
|
$modify['query'] = $value; |
||||
|
unset($options['query']); |
||||
|
} |
||||
|
|
||||
|
// Ensure that sink is not an invalid value. |
||||
|
if (isset($options['sink'])) { |
||||
|
// TODO: Add more sink validation? |
||||
|
if (is_bool($options['sink'])) { |
||||
|
throw new \InvalidArgumentException('sink must not be a boolean'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$request = Psr7\modify_request($request, $modify); |
||||
|
if ($request->getBody() instanceof Psr7\MultipartStream) { |
||||
|
// Use a multipart/form-data POST if a Content-Type is not set. |
||||
|
$options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' |
||||
|
. $request->getBody()->getBoundary(); |
||||
|
} |
||||
|
|
||||
|
// Merge in conditional headers if they are not present. |
||||
|
if (isset($options['_conditional'])) { |
||||
|
// Build up the changes so it's in a single clone of the message. |
||||
|
$modify = []; |
||||
|
foreach ($options['_conditional'] as $k => $v) { |
||||
|
if (!$request->hasHeader($k)) { |
||||
|
$modify['set_headers'][$k] = $v; |
||||
|
} |
||||
|
} |
||||
|
$request = Psr7\modify_request($request, $modify); |
||||
|
// Don't pass this internal value along to middleware/handlers. |
||||
|
unset($options['_conditional']); |
||||
|
} |
||||
|
|
||||
|
return $request; |
||||
|
} |
||||
|
|
||||
|
private function invalidBody() |
||||
|
{ |
||||
|
throw new \InvalidArgumentException('Passing in the "body" request ' |
||||
|
. 'option as an array to send a POST request has been deprecated. ' |
||||
|
. 'Please use the "form_params" request option to send a ' |
||||
|
. 'application/x-www-form-urlencoded request, or the "multipart" ' |
||||
|
. 'request option to send a multipart/form-data request.'); |
||||
|
} |
||||
|
} |
@ -0,0 +1,84 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use GuzzleHttp\Promise\PromiseInterface; |
||||
|
use GuzzleHttp\Exception\GuzzleException; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
use Psr\Http\Message\UriInterface; |
||||
|
|
||||
|
/** |
||||
|
* Client interface for sending HTTP requests. |
||||
|
*/ |
||||
|
interface ClientInterface |
||||
|
{ |
||||
|
const VERSION = '6.2.1'; |
||||
|
|
||||
|
/** |
||||
|
* Send an HTTP request. |
||||
|
* |
||||
|
* @param RequestInterface $request Request to send |
||||
|
* @param array $options Request options to apply to the given |
||||
|
* request and to the transfer. |
||||
|
* |
||||
|
* @return ResponseInterface |
||||
|
* @throws GuzzleException |
||||
|
*/ |
||||
|
public function send(RequestInterface $request, array $options = []); |
||||
|
|
||||
|
/** |
||||
|
* Asynchronously send an HTTP request. |
||||
|
* |
||||
|
* @param RequestInterface $request Request to send |
||||
|
* @param array $options Request options to apply to the given |
||||
|
* request and to the transfer. |
||||
|
* |
||||
|
* @return PromiseInterface |
||||
|
*/ |
||||
|
public function sendAsync(RequestInterface $request, array $options = []); |
||||
|
|
||||
|
/** |
||||
|
* Create and send an HTTP request. |
||||
|
* |
||||
|
* Use an absolute path to override the base path of the client, or a |
||||
|
* relative path to append to the base path of the client. The URL can |
||||
|
* contain the query string as well. |
||||
|
* |
||||
|
* @param string $method HTTP method. |
||||
|
* @param string|UriInterface $uri URI object or string. |
||||
|
* @param array $options Request options to apply. |
||||
|
* |
||||
|
* @return ResponseInterface |
||||
|
* @throws GuzzleException |
||||
|
*/ |
||||
|
public function request($method, $uri, array $options = []); |
||||
|
|
||||
|
/** |
||||
|
* Create and send an asynchronous HTTP request. |
||||
|
* |
||||
|
* Use an absolute path to override the base path of the client, or a |
||||
|
* relative path to append to the base path of the client. The URL can |
||||
|
* contain the query string as well. Use an array to provide a URL |
||||
|
* template and additional variables to use in the URL template expansion. |
||||
|
* |
||||
|
* @param string $method HTTP method |
||||
|
* @param string|UriInterface $uri URI object or string. |
||||
|
* @param array $options Request options to apply. |
||||
|
* |
||||
|
* @return PromiseInterface |
||||
|
*/ |
||||
|
public function requestAsync($method, $uri, array $options = []); |
||||
|
|
||||
|
/** |
||||
|
* Get a client configuration option. |
||||
|
* |
||||
|
* These options include default request options of the client, a "handler" |
||||
|
* (if utilized by the concrete client), and a "base_uri" if utilized by |
||||
|
* the concrete client. |
||||
|
* |
||||
|
* @param string|null $option The config option to retrieve. |
||||
|
* |
||||
|
* @return mixed |
||||
|
*/ |
||||
|
public function getConfig($option = null); |
||||
|
} |
@ -0,0 +1,314 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Cookie; |
||||
|
|
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
|
||||
|
/** |
||||
|
* Cookie jar that stores cookies as an array |
||||
|
*/ |
||||
|
class CookieJar implements CookieJarInterface |
||||
|
{ |
||||
|
/** @var SetCookie[] Loaded cookie data */ |
||||
|
private $cookies = []; |
||||
|
|
||||
|
/** @var bool */ |
||||
|
private $strictMode; |
||||
|
|
||||
|
/** |
||||
|
* @param bool $strictMode Set to true to throw exceptions when invalid |
||||
|
* cookies are added to the cookie jar. |
||||
|
* @param array $cookieArray Array of SetCookie objects or a hash of |
||||
|
* arrays that can be used with the SetCookie |
||||
|
* constructor |
||||
|
*/ |
||||
|
public function __construct($strictMode = false, $cookieArray = []) |
||||
|
{ |
||||
|
$this->strictMode = $strictMode; |
||||
|
|
||||
|
foreach ($cookieArray as $cookie) { |
||||
|
if (!($cookie instanceof SetCookie)) { |
||||
|
$cookie = new SetCookie($cookie); |
||||
|
} |
||||
|
$this->setCookie($cookie); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create a new Cookie jar from an associative array and domain. |
||||
|
* |
||||
|
* @param array $cookies Cookies to create the jar from |
||||
|
* @param string $domain Domain to set the cookies to |
||||
|
* |
||||
|
* @return self |
||||
|
*/ |
||||
|
public static function fromArray(array $cookies, $domain) |
||||
|
{ |
||||
|
$cookieJar = new self(); |
||||
|
foreach ($cookies as $name => $value) { |
||||
|
$cookieJar->setCookie(new SetCookie([ |
||||
|
'Domain' => $domain, |
||||
|
'Name' => $name, |
||||
|
'Value' => $value, |
||||
|
'Discard' => true |
||||
|
])); |
||||
|
} |
||||
|
|
||||
|
return $cookieJar; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @deprecated |
||||
|
*/ |
||||
|
public static function getCookieValue($value) |
||||
|
{ |
||||
|
return $value; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Evaluate if this cookie should be persisted to storage |
||||
|
* that survives between requests. |
||||
|
* |
||||
|
* @param SetCookie $cookie Being evaluated. |
||||
|
* @param bool $allowSessionCookies If we should persist session cookies |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public static function shouldPersist( |
||||
|
SetCookie $cookie, |
||||
|
$allowSessionCookies = false |
||||
|
) { |
||||
|
if ($cookie->getExpires() || $allowSessionCookies) { |
||||
|
if (!$cookie->getDiscard()) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Finds and returns the cookie based on the name |
||||
|
* |
||||
|
* @param string $name cookie name to search for |
||||
|
* @return SetCookie|null cookie that was found or null if not found |
||||
|
*/ |
||||
|
public function getCookieByName($name) |
||||
|
{ |
||||
|
// don't allow a null name |
||||
|
if($name === null) { |
||||
|
return null; |
||||
|
} |
||||
|
foreach($this->cookies as $cookie) { |
||||
|
if($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) { |
||||
|
return $cookie; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function toArray() |
||||
|
{ |
||||
|
return array_map(function (SetCookie $cookie) { |
||||
|
return $cookie->toArray(); |
||||
|
}, $this->getIterator()->getArrayCopy()); |
||||
|
} |
||||
|
|
||||
|
public function clear($domain = null, $path = null, $name = null) |
||||
|
{ |
||||
|
if (!$domain) { |
||||
|
$this->cookies = []; |
||||
|
return; |
||||
|
} elseif (!$path) { |
||||
|
$this->cookies = array_filter( |
||||
|
$this->cookies, |
||||
|
function (SetCookie $cookie) use ($path, $domain) { |
||||
|
return !$cookie->matchesDomain($domain); |
||||
|
} |
||||
|
); |
||||
|
} elseif (!$name) { |
||||
|
$this->cookies = array_filter( |
||||
|
$this->cookies, |
||||
|
function (SetCookie $cookie) use ($path, $domain) { |
||||
|
return !($cookie->matchesPath($path) && |
||||
|
$cookie->matchesDomain($domain)); |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
$this->cookies = array_filter( |
||||
|
$this->cookies, |
||||
|
function (SetCookie $cookie) use ($path, $domain, $name) { |
||||
|
return !($cookie->getName() == $name && |
||||
|
$cookie->matchesPath($path) && |
||||
|
$cookie->matchesDomain($domain)); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function clearSessionCookies() |
||||
|
{ |
||||
|
$this->cookies = array_filter( |
||||
|
$this->cookies, |
||||
|
function (SetCookie $cookie) { |
||||
|
return !$cookie->getDiscard() && $cookie->getExpires(); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public function setCookie(SetCookie $cookie) |
||||
|
{ |
||||
|
// If the name string is empty (but not 0), ignore the set-cookie |
||||
|
// string entirely. |
||||
|
$name = $cookie->getName(); |
||||
|
if (!$name && $name !== '0') { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Only allow cookies with set and valid domain, name, value |
||||
|
$result = $cookie->validate(); |
||||
|
if ($result !== true) { |
||||
|
if ($this->strictMode) { |
||||
|
throw new \RuntimeException('Invalid cookie: ' . $result); |
||||
|
} else { |
||||
|
$this->removeCookieIfEmpty($cookie); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Resolve conflicts with previously set cookies |
||||
|
foreach ($this->cookies as $i => $c) { |
||||
|
|
||||
|
// Two cookies are identical, when their path, and domain are |
||||
|
// identical. |
||||
|
if ($c->getPath() != $cookie->getPath() || |
||||
|
$c->getDomain() != $cookie->getDomain() || |
||||
|
$c->getName() != $cookie->getName() |
||||
|
) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// The previously set cookie is a discard cookie and this one is |
||||
|
// not so allow the new cookie to be set |
||||
|
if (!$cookie->getDiscard() && $c->getDiscard()) { |
||||
|
unset($this->cookies[$i]); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// If the new cookie's expiration is further into the future, then |
||||
|
// replace the old cookie |
||||
|
if ($cookie->getExpires() > $c->getExpires()) { |
||||
|
unset($this->cookies[$i]); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// If the value has changed, we better change it |
||||
|
if ($cookie->getValue() !== $c->getValue()) { |
||||
|
unset($this->cookies[$i]); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// The cookie exists, so no need to continue |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
$this->cookies[] = $cookie; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public function count() |
||||
|
{ |
||||
|
return count($this->cookies); |
||||
|
} |
||||
|
|
||||
|
public function getIterator() |
||||
|
{ |
||||
|
return new \ArrayIterator(array_values($this->cookies)); |
||||
|
} |
||||
|
|
||||
|
public function extractCookies( |
||||
|
RequestInterface $request, |
||||
|
ResponseInterface $response |
||||
|
) { |
||||
|
if ($cookieHeader = $response->getHeader('Set-Cookie')) { |
||||
|
foreach ($cookieHeader as $cookie) { |
||||
|
$sc = SetCookie::fromString($cookie); |
||||
|
if (!$sc->getDomain()) { |
||||
|
$sc->setDomain($request->getUri()->getHost()); |
||||
|
} |
||||
|
if (0 !== strpos($sc->getPath(), '/')) { |
||||
|
$sc->setPath($this->getCookiePathFromRequest($request)); |
||||
|
} |
||||
|
$this->setCookie($sc); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Computes cookie path following RFC 6265 section 5.1.4 |
||||
|
* |
||||
|
* @link https://tools.ietf.org/html/rfc6265#section-5.1.4 |
||||
|
* |
||||
|
* @param RequestInterface $request |
||||
|
* @return string |
||||
|
*/ |
||||
|
private function getCookiePathFromRequest(RequestInterface $request) |
||||
|
{ |
||||
|
$uriPath = $request->getUri()->getPath(); |
||||
|
if ('' === $uriPath) { |
||||
|
return '/'; |
||||
|
} |
||||
|
if (0 !== strpos($uriPath, '/')) { |
||||
|
return '/'; |
||||
|
} |
||||
|
if ('/' === $uriPath) { |
||||
|
return '/'; |
||||
|
} |
||||
|
if (0 === $lastSlashPos = strrpos($uriPath, '/')) { |
||||
|
return '/'; |
||||
|
} |
||||
|
|
||||
|
return substr($uriPath, 0, $lastSlashPos); |
||||
|
} |
||||
|
|
||||
|
public function withCookieHeader(RequestInterface $request) |
||||
|
{ |
||||
|
$values = []; |
||||
|
$uri = $request->getUri(); |
||||
|
$scheme = $uri->getScheme(); |
||||
|
$host = $uri->getHost(); |
||||
|
$path = $uri->getPath() ?: '/'; |
||||
|
|
||||
|
foreach ($this->cookies as $cookie) { |
||||
|
if ($cookie->matchesPath($path) && |
||||
|
$cookie->matchesDomain($host) && |
||||
|
!$cookie->isExpired() && |
||||
|
(!$cookie->getSecure() || $scheme === 'https') |
||||
|
) { |
||||
|
$values[] = $cookie->getName() . '=' |
||||
|
. $cookie->getValue(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $values |
||||
|
? $request->withHeader('Cookie', implode('; ', $values)) |
||||
|
: $request; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* If a cookie already exists and the server asks to set it again with a |
||||
|
* null value, the cookie must be deleted. |
||||
|
* |
||||
|
* @param SetCookie $cookie |
||||
|
*/ |
||||
|
private function removeCookieIfEmpty(SetCookie $cookie) |
||||
|
{ |
||||
|
$cookieValue = $cookie->getValue(); |
||||
|
if ($cookieValue === null || $cookieValue === '') { |
||||
|
$this->clear( |
||||
|
$cookie->getDomain(), |
||||
|
$cookie->getPath(), |
||||
|
$cookie->getName() |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,84 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Cookie; |
||||
|
|
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
|
||||
|
/** |
||||
|
* Stores HTTP cookies. |
||||
|
* |
||||
|
* It extracts cookies from HTTP requests, and returns them in HTTP responses. |
||||
|
* CookieJarInterface instances automatically expire contained cookies when |
||||
|
* necessary. Subclasses are also responsible for storing and retrieving |
||||
|
* cookies from a file, database, etc. |
||||
|
* |
||||
|
* @link http://docs.python.org/2/library/cookielib.html Inspiration |
||||
|
*/ |
||||
|
interface CookieJarInterface extends \Countable, \IteratorAggregate |
||||
|
{ |
||||
|
/** |
||||
|
* Create a request with added cookie headers. |
||||
|
* |
||||
|
* If no matching cookies are found in the cookie jar, then no Cookie |
||||
|
* header is added to the request and the same request is returned. |
||||
|
* |
||||
|
* @param RequestInterface $request Request object to modify. |
||||
|
* |
||||
|
* @return RequestInterface returns the modified request. |
||||
|
*/ |
||||
|
public function withCookieHeader(RequestInterface $request); |
||||
|
|
||||
|
/** |
||||
|
* Extract cookies from an HTTP response and store them in the CookieJar. |
||||
|
* |
||||
|
* @param RequestInterface $request Request that was sent |
||||
|
* @param ResponseInterface $response Response that was received |
||||
|
*/ |
||||
|
public function extractCookies( |
||||
|
RequestInterface $request, |
||||
|
ResponseInterface $response |
||||
|
); |
||||
|
|
||||
|
/** |
||||
|
* Sets a cookie in the cookie jar. |
||||
|
* |
||||
|
* @param SetCookie $cookie Cookie to set. |
||||
|
* |
||||
|
* @return bool Returns true on success or false on failure |
||||
|
*/ |
||||
|
public function setCookie(SetCookie $cookie); |
||||
|
|
||||
|
/** |
||||
|
* Remove cookies currently held in the cookie jar. |
||||
|
* |
||||
|
* Invoking this method without arguments will empty the whole cookie jar. |
||||
|
* If given a $domain argument only cookies belonging to that domain will |
||||
|
* be removed. If given a $domain and $path argument, cookies belonging to |
||||
|
* the specified path within that domain are removed. If given all three |
||||
|
* arguments, then the cookie with the specified name, path and domain is |
||||
|
* removed. |
||||
|
* |
||||
|
* @param string $domain Clears cookies matching a domain |
||||
|
* @param string $path Clears cookies matching a domain and path |
||||
|
* @param string $name Clears cookies matching a domain, path, and name |
||||
|
* |
||||
|
* @return CookieJarInterface |
||||
|
*/ |
||||
|
public function clear($domain = null, $path = null, $name = null); |
||||
|
|
||||
|
/** |
||||
|
* Discard all sessions cookies. |
||||
|
* |
||||
|
* Removes cookies that don't have an expire field or a have a discard |
||||
|
* field set to true. To be called when the user agent shuts down according |
||||
|
* to RFC 2965. |
||||
|
*/ |
||||
|
public function clearSessionCookies(); |
||||
|
|
||||
|
/** |
||||
|
* Converts the cookie jar to an array. |
||||
|
* |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function toArray(); |
||||
|
} |
@ -0,0 +1,90 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Cookie; |
||||
|
|
||||
|
/** |
||||
|
* Persists non-session cookies using a JSON formatted file |
||||
|
*/ |
||||
|
class FileCookieJar extends CookieJar |
||||
|
{ |
||||
|
/** @var string filename */ |
||||
|
private $filename; |
||||
|
|
||||
|
/** @var bool Control whether to persist session cookies or not. */ |
||||
|
private $storeSessionCookies; |
||||
|
|
||||
|
/** |
||||
|
* Create a new FileCookieJar object |
||||
|
* |
||||
|
* @param string $cookieFile File to store the cookie data |
||||
|
* @param bool $storeSessionCookies Set to true to store session cookies |
||||
|
* in the cookie jar. |
||||
|
* |
||||
|
* @throws \RuntimeException if the file cannot be found or created |
||||
|
*/ |
||||
|
public function __construct($cookieFile, $storeSessionCookies = false) |
||||
|
{ |
||||
|
$this->filename = $cookieFile; |
||||
|
$this->storeSessionCookies = $storeSessionCookies; |
||||
|
|
||||
|
if (file_exists($cookieFile)) { |
||||
|
$this->load($cookieFile); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Saves the file when shutting down |
||||
|
*/ |
||||
|
public function __destruct() |
||||
|
{ |
||||
|
$this->save($this->filename); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Saves the cookies to a file. |
||||
|
* |
||||
|
* @param string $filename File to save |
||||
|
* @throws \RuntimeException if the file cannot be found or created |
||||
|
*/ |
||||
|
public function save($filename) |
||||
|
{ |
||||
|
$json = []; |
||||
|
foreach ($this as $cookie) { |
||||
|
/** @var SetCookie $cookie */ |
||||
|
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { |
||||
|
$json[] = $cookie->toArray(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$jsonStr = \GuzzleHttp\json_encode($json); |
||||
|
if (false === file_put_contents($filename, $jsonStr)) { |
||||
|
throw new \RuntimeException("Unable to save file {$filename}"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Load cookies from a JSON formatted file. |
||||
|
* |
||||
|
* Old cookies are kept unless overwritten by newly loaded ones. |
||||
|
* |
||||
|
* @param string $filename Cookie file to load. |
||||
|
* @throws \RuntimeException if the file cannot be loaded. |
||||
|
*/ |
||||
|
public function load($filename) |
||||
|
{ |
||||
|
$json = file_get_contents($filename); |
||||
|
if (false === $json) { |
||||
|
throw new \RuntimeException("Unable to load file {$filename}"); |
||||
|
} elseif ($json === '') { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
$data = \GuzzleHttp\json_decode($json, true); |
||||
|
if (is_array($data)) { |
||||
|
foreach (json_decode($json, true) as $cookie) { |
||||
|
$this->setCookie(new SetCookie($cookie)); |
||||
|
} |
||||
|
} elseif (strlen($data)) { |
||||
|
throw new \RuntimeException("Invalid cookie file: {$filename}"); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,71 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Cookie; |
||||
|
|
||||
|
/** |
||||
|
* Persists cookies in the client session |
||||
|
*/ |
||||
|
class SessionCookieJar extends CookieJar |
||||
|
{ |
||||
|
/** @var string session key */ |
||||
|
private $sessionKey; |
||||
|
|
||||
|
/** @var bool Control whether to persist session cookies or not. */ |
||||
|
private $storeSessionCookies; |
||||
|
|
||||
|
/** |
||||
|
* Create a new SessionCookieJar object |
||||
|
* |
||||
|
* @param string $sessionKey Session key name to store the cookie |
||||
|
* data in session |
||||
|
* @param bool $storeSessionCookies Set to true to store session cookies |
||||
|
* in the cookie jar. |
||||
|
*/ |
||||
|
public function __construct($sessionKey, $storeSessionCookies = false) |
||||
|
{ |
||||
|
$this->sessionKey = $sessionKey; |
||||
|
$this->storeSessionCookies = $storeSessionCookies; |
||||
|
$this->load(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Saves cookies to session when shutting down |
||||
|
*/ |
||||
|
public function __destruct() |
||||
|
{ |
||||
|
$this->save(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Save cookies to the client session |
||||
|
*/ |
||||
|
public function save() |
||||
|
{ |
||||
|
$json = []; |
||||
|
foreach ($this as $cookie) { |
||||
|
/** @var SetCookie $cookie */ |
||||
|
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { |
||||
|
$json[] = $cookie->toArray(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$_SESSION[$this->sessionKey] = json_encode($json); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Load the contents of the client session into the data array |
||||
|
*/ |
||||
|
protected function load() |
||||
|
{ |
||||
|
if (!isset($_SESSION[$this->sessionKey])) { |
||||
|
return; |
||||
|
} |
||||
|
$data = json_decode($_SESSION[$this->sessionKey], true); |
||||
|
if (is_array($data)) { |
||||
|
foreach ($data as $cookie) { |
||||
|
$this->setCookie(new SetCookie($cookie)); |
||||
|
} |
||||
|
} elseif (strlen($data)) { |
||||
|
throw new \RuntimeException("Invalid cookie data"); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,404 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Cookie; |
||||
|
|
||||
|
/** |
||||
|
* Set-Cookie object |
||||
|
*/ |
||||
|
class SetCookie |
||||
|
{ |
||||
|
/** @var array */ |
||||
|
private static $defaults = [ |
||||
|
'Name' => null, |
||||
|
'Value' => null, |
||||
|
'Domain' => null, |
||||
|
'Path' => '/', |
||||
|
'Max-Age' => null, |
||||
|
'Expires' => null, |
||||
|
'Secure' => false, |
||||
|
'Discard' => false, |
||||
|
'HttpOnly' => false |
||||
|
]; |
||||
|
|
||||
|
/** @var array Cookie data */ |
||||
|
private $data; |
||||
|
|
||||
|
/** |
||||
|
* Create a new SetCookie object from a string |
||||
|
* |
||||
|
* @param string $cookie Set-Cookie header string |
||||
|
* |
||||
|
* @return self |
||||
|
*/ |
||||
|
public static function fromString($cookie) |
||||
|
{ |
||||
|
// Create the default return array |
||||
|
$data = self::$defaults; |
||||
|
// Explode the cookie string using a series of semicolons |
||||
|
$pieces = array_filter(array_map('trim', explode(';', $cookie))); |
||||
|
// The name of the cookie (first kvp) must include an equal sign. |
||||
|
if (empty($pieces) || !strpos($pieces[0], '=')) { |
||||
|
return new self($data); |
||||
|
} |
||||
|
|
||||
|
// Add the cookie pieces into the parsed data array |
||||
|
foreach ($pieces as $part) { |
||||
|
|
||||
|
$cookieParts = explode('=', $part, 2); |
||||
|
$key = trim($cookieParts[0]); |
||||
|
$value = isset($cookieParts[1]) |
||||
|
? trim($cookieParts[1], " \n\r\t\0\x0B") |
||||
|
: true; |
||||
|
|
||||
|
// Only check for non-cookies when cookies have been found |
||||
|
if (empty($data['Name'])) { |
||||
|
$data['Name'] = $key; |
||||
|
$data['Value'] = $value; |
||||
|
} else { |
||||
|
foreach (array_keys(self::$defaults) as $search) { |
||||
|
if (!strcasecmp($search, $key)) { |
||||
|
$data[$search] = $value; |
||||
|
continue 2; |
||||
|
} |
||||
|
} |
||||
|
$data[$key] = $value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new self($data); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param array $data Array of cookie data provided by a Cookie parser |
||||
|
*/ |
||||
|
public function __construct(array $data = []) |
||||
|
{ |
||||
|
$this->data = array_replace(self::$defaults, $data); |
||||
|
// Extract the Expires value and turn it into a UNIX timestamp if needed |
||||
|
if (!$this->getExpires() && $this->getMaxAge()) { |
||||
|
// Calculate the Expires date |
||||
|
$this->setExpires(time() + $this->getMaxAge()); |
||||
|
} elseif ($this->getExpires() && !is_numeric($this->getExpires())) { |
||||
|
$this->setExpires($this->getExpires()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function __toString() |
||||
|
{ |
||||
|
$str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; |
||||
|
foreach ($this->data as $k => $v) { |
||||
|
if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { |
||||
|
if ($k === 'Expires') { |
||||
|
$str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; |
||||
|
} else { |
||||
|
$str .= ($v === true ? $k : "{$k}={$v}") . '; '; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return rtrim($str, '; '); |
||||
|
} |
||||
|
|
||||
|
public function toArray() |
||||
|
{ |
||||
|
return $this->data; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the cookie name |
||||
|
* |
||||
|
* @return string |
||||
|
*/ |
||||
|
public function getName() |
||||
|
{ |
||||
|
return $this->data['Name']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the cookie name |
||||
|
* |
||||
|
* @param string $name Cookie name |
||||
|
*/ |
||||
|
public function setName($name) |
||||
|
{ |
||||
|
$this->data['Name'] = $name; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the cookie value |
||||
|
* |
||||
|
* @return string |
||||
|
*/ |
||||
|
public function getValue() |
||||
|
{ |
||||
|
return $this->data['Value']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the cookie value |
||||
|
* |
||||
|
* @param string $value Cookie value |
||||
|
*/ |
||||
|
public function setValue($value) |
||||
|
{ |
||||
|
$this->data['Value'] = $value; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the domain |
||||
|
* |
||||
|
* @return string|null |
||||
|
*/ |
||||
|
public function getDomain() |
||||
|
{ |
||||
|
return $this->data['Domain']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the domain of the cookie |
||||
|
* |
||||
|
* @param string $domain |
||||
|
*/ |
||||
|
public function setDomain($domain) |
||||
|
{ |
||||
|
$this->data['Domain'] = $domain; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the path |
||||
|
* |
||||
|
* @return string |
||||
|
*/ |
||||
|
public function getPath() |
||||
|
{ |
||||
|
return $this->data['Path']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the path of the cookie |
||||
|
* |
||||
|
* @param string $path Path of the cookie |
||||
|
*/ |
||||
|
public function setPath($path) |
||||
|
{ |
||||
|
$this->data['Path'] = $path; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Maximum lifetime of the cookie in seconds |
||||
|
* |
||||
|
* @return int|null |
||||
|
*/ |
||||
|
public function getMaxAge() |
||||
|
{ |
||||
|
return $this->data['Max-Age']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the max-age of the cookie |
||||
|
* |
||||
|
* @param int $maxAge Max age of the cookie in seconds |
||||
|
*/ |
||||
|
public function setMaxAge($maxAge) |
||||
|
{ |
||||
|
$this->data['Max-Age'] = $maxAge; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* The UNIX timestamp when the cookie Expires |
||||
|
* |
||||
|
* @return mixed |
||||
|
*/ |
||||
|
public function getExpires() |
||||
|
{ |
||||
|
return $this->data['Expires']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the unix timestamp for which the cookie will expire |
||||
|
* |
||||
|
* @param int $timestamp Unix timestamp |
||||
|
*/ |
||||
|
public function setExpires($timestamp) |
||||
|
{ |
||||
|
$this->data['Expires'] = is_numeric($timestamp) |
||||
|
? (int) $timestamp |
||||
|
: strtotime($timestamp); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get whether or not this is a secure cookie |
||||
|
* |
||||
|
* @return null|bool |
||||
|
*/ |
||||
|
public function getSecure() |
||||
|
{ |
||||
|
return $this->data['Secure']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set whether or not the cookie is secure |
||||
|
* |
||||
|
* @param bool $secure Set to true or false if secure |
||||
|
*/ |
||||
|
public function setSecure($secure) |
||||
|
{ |
||||
|
$this->data['Secure'] = $secure; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get whether or not this is a session cookie |
||||
|
* |
||||
|
* @return null|bool |
||||
|
*/ |
||||
|
public function getDiscard() |
||||
|
{ |
||||
|
return $this->data['Discard']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set whether or not this is a session cookie |
||||
|
* |
||||
|
* @param bool $discard Set to true or false if this is a session cookie |
||||
|
*/ |
||||
|
public function setDiscard($discard) |
||||
|
{ |
||||
|
$this->data['Discard'] = $discard; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get whether or not this is an HTTP only cookie |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function getHttpOnly() |
||||
|
{ |
||||
|
return $this->data['HttpOnly']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set whether or not this is an HTTP only cookie |
||||
|
* |
||||
|
* @param bool $httpOnly Set to true or false if this is HTTP only |
||||
|
*/ |
||||
|
public function setHttpOnly($httpOnly) |
||||
|
{ |
||||
|
$this->data['HttpOnly'] = $httpOnly; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if the cookie matches a path value. |
||||
|
* |
||||
|
* A request-path path-matches a given cookie-path if at least one of |
||||
|
* the following conditions holds: |
||||
|
* |
||||
|
* - The cookie-path and the request-path are identical. |
||||
|
* - The cookie-path is a prefix of the request-path, and the last |
||||
|
* character of the cookie-path is %x2F ("/"). |
||||
|
* - The cookie-path is a prefix of the request-path, and the first |
||||
|
* character of the request-path that is not included in the cookie- |
||||
|
* path is a %x2F ("/") character. |
||||
|
* |
||||
|
* @param string $requestPath Path to check against |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function matchesPath($requestPath) |
||||
|
{ |
||||
|
$cookiePath = $this->getPath(); |
||||
|
|
||||
|
// Match on exact matches or when path is the default empty "/" |
||||
|
if ($cookiePath === '/' || $cookiePath == $requestPath) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// Ensure that the cookie-path is a prefix of the request path. |
||||
|
if (0 !== strpos($requestPath, $cookiePath)) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Match if the last character of the cookie-path is "/" |
||||
|
if (substr($cookiePath, -1, 1) === '/') { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// Match if the first character not included in cookie path is "/" |
||||
|
return substr($requestPath, strlen($cookiePath), 1) === '/'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if the cookie matches a domain value |
||||
|
* |
||||
|
* @param string $domain Domain to check against |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function matchesDomain($domain) |
||||
|
{ |
||||
|
// Remove the leading '.' as per spec in RFC 6265. |
||||
|
// http://tools.ietf.org/html/rfc6265#section-5.2.3 |
||||
|
$cookieDomain = ltrim($this->getDomain(), '.'); |
||||
|
|
||||
|
// Domain not set or exact match. |
||||
|
if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// Matching the subdomain according to RFC 6265. |
||||
|
// http://tools.ietf.org/html/rfc6265#section-5.1.3 |
||||
|
if (filter_var($domain, FILTER_VALIDATE_IP)) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if the cookie is expired |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function isExpired() |
||||
|
{ |
||||
|
return $this->getExpires() && time() > $this->getExpires(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if the cookie is valid according to RFC 6265 |
||||
|
* |
||||
|
* @return bool|string Returns true if valid or an error message if invalid |
||||
|
*/ |
||||
|
public function validate() |
||||
|
{ |
||||
|
// Names must not be empty, but can be 0 |
||||
|
$name = $this->getName(); |
||||
|
if (empty($name) && !is_numeric($name)) { |
||||
|
return 'The cookie name must not be empty'; |
||||
|
} |
||||
|
|
||||
|
// Check if any of the invalid characters are present in the cookie name |
||||
|
if (preg_match( |
||||
|
'/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', |
||||
|
$name) |
||||
|
) { |
||||
|
return 'Cookie name must not contain invalid characters: ASCII ' |
||||
|
. 'Control characters (0-31;127), space, tab and the ' |
||||
|
. 'following characters: ()<>@,;:\"/?={}'; |
||||
|
} |
||||
|
|
||||
|
// Value must not be empty, but can be 0 |
||||
|
$value = $this->getValue(); |
||||
|
if (empty($value) && !is_numeric($value)) { |
||||
|
return 'The cookie value must not be empty'; |
||||
|
} |
||||
|
|
||||
|
// Domains must not be empty, but can be 0 |
||||
|
// A "0" is not a valid internet domain, but may be used as server name |
||||
|
// in a private network. |
||||
|
$domain = $this->getDomain(); |
||||
|
if (empty($domain) && !is_numeric($domain)) { |
||||
|
return 'The cookie domain must not be empty'; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
|
||||
|
/** |
||||
|
* Exception when an HTTP error occurs (4xx or 5xx error) |
||||
|
*/ |
||||
|
class BadResponseException extends RequestException |
||||
|
{ |
||||
|
public function __construct( |
||||
|
$message, |
||||
|
RequestInterface $request, |
||||
|
ResponseInterface $response = null, |
||||
|
\Exception $previous = null, |
||||
|
array $handlerContext = [] |
||||
|
) { |
||||
|
if (null === $response) { |
||||
|
@trigger_error( |
||||
|
'Instantiating the ' . __CLASS__ . ' class without a Response is deprecated since version 6.3 and will be removed in 7.0.', |
||||
|
E_USER_DEPRECATED |
||||
|
); |
||||
|
} |
||||
|
parent::__construct($message, $request, $response, $previous, $handlerContext); |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
/** |
||||
|
* Exception when a client error is encountered (4xx codes) |
||||
|
*/ |
||||
|
class ClientException extends BadResponseException {} |
@ -0,0 +1,37 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
/** |
||||
|
* Exception thrown when a connection cannot be established. |
||||
|
* |
||||
|
* Note that no response is present for a ConnectException |
||||
|
*/ |
||||
|
class ConnectException extends RequestException |
||||
|
{ |
||||
|
public function __construct( |
||||
|
$message, |
||||
|
RequestInterface $request, |
||||
|
\Exception $previous = null, |
||||
|
array $handlerContext = [] |
||||
|
) { |
||||
|
parent::__construct($message, $request, null, $previous, $handlerContext); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return null |
||||
|
*/ |
||||
|
public function getResponse() |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function hasResponse() |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
interface GuzzleException {} |
@ -0,0 +1,217 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
use GuzzleHttp\Promise\PromiseInterface; |
||||
|
use Psr\Http\Message\UriInterface; |
||||
|
|
||||
|
/** |
||||
|
* HTTP Request exception |
||||
|
*/ |
||||
|
class RequestException extends TransferException |
||||
|
{ |
||||
|
/** @var RequestInterface */ |
||||
|
private $request; |
||||
|
|
||||
|
/** @var ResponseInterface */ |
||||
|
private $response; |
||||
|
|
||||
|
/** @var array */ |
||||
|
private $handlerContext; |
||||
|
|
||||
|
public function __construct( |
||||
|
$message, |
||||
|
RequestInterface $request, |
||||
|
ResponseInterface $response = null, |
||||
|
\Exception $previous = null, |
||||
|
array $handlerContext = [] |
||||
|
) { |
||||
|
// Set the code of the exception if the response is set and not future. |
||||
|
$code = $response && !($response instanceof PromiseInterface) |
||||
|
? $response->getStatusCode() |
||||
|
: 0; |
||||
|
parent::__construct($message, $code, $previous); |
||||
|
$this->request = $request; |
||||
|
$this->response = $response; |
||||
|
$this->handlerContext = $handlerContext; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Wrap non-RequestExceptions with a RequestException |
||||
|
* |
||||
|
* @param RequestInterface $request |
||||
|
* @param \Exception $e |
||||
|
* |
||||
|
* @return RequestException |
||||
|
*/ |
||||
|
public static function wrapException(RequestInterface $request, \Exception $e) |
||||
|
{ |
||||
|
return $e instanceof RequestException |
||||
|
? $e |
||||
|
: new RequestException($e->getMessage(), $request, null, $e); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Factory method to create a new exception with a normalized error message |
||||
|
* |
||||
|
* @param RequestInterface $request Request |
||||
|
* @param ResponseInterface $response Response received |
||||
|
* @param \Exception $previous Previous exception |
||||
|
* @param array $ctx Optional handler context. |
||||
|
* |
||||
|
* @return self |
||||
|
*/ |
||||
|
public static function create( |
||||
|
RequestInterface $request, |
||||
|
ResponseInterface $response = null, |
||||
|
\Exception $previous = null, |
||||
|
array $ctx = [] |
||||
|
) { |
||||
|
if (!$response) { |
||||
|
return new self( |
||||
|
'Error completing request', |
||||
|
$request, |
||||
|
null, |
||||
|
$previous, |
||||
|
$ctx |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
$level = (int) floor($response->getStatusCode() / 100); |
||||
|
if ($level === 4) { |
||||
|
$label = 'Client error'; |
||||
|
$className = ClientException::class; |
||||
|
} elseif ($level === 5) { |
||||
|
$label = 'Server error'; |
||||
|
$className = ServerException::class; |
||||
|
} else { |
||||
|
$label = 'Unsuccessful request'; |
||||
|
$className = __CLASS__; |
||||
|
} |
||||
|
|
||||
|
$uri = $request->getUri(); |
||||
|
$uri = static::obfuscateUri($uri); |
||||
|
|
||||
|
// Client Error: `GET /` resulted in a `404 Not Found` response: |
||||
|
// <html> ... (truncated) |
||||
|
$message = sprintf( |
||||
|
'%s: `%s %s` resulted in a `%s %s` response', |
||||
|
$label, |
||||
|
$request->getMethod(), |
||||
|
$uri, |
||||
|
$response->getStatusCode(), |
||||
|
$response->getReasonPhrase() |
||||
|
); |
||||
|
|
||||
|
$summary = static::getResponseBodySummary($response); |
||||
|
|
||||
|
if ($summary !== null) { |
||||
|
$message .= ":\n{$summary}\n"; |
||||
|
} |
||||
|
|
||||
|
return new $className($message, $request, $response, $previous, $ctx); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get a short summary of the response |
||||
|
* |
||||
|
* Will return `null` if the response is not printable. |
||||
|
* |
||||
|
* @param ResponseInterface $response |
||||
|
* |
||||
|
* @return string|null |
||||
|
*/ |
||||
|
public static function getResponseBodySummary(ResponseInterface $response) |
||||
|
{ |
||||
|
$body = $response->getBody(); |
||||
|
|
||||
|
if (!$body->isSeekable()) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
$size = $body->getSize(); |
||||
|
|
||||
|
if ($size === 0) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
$summary = $body->read(120); |
||||
|
$body->rewind(); |
||||
|
|
||||
|
if ($size > 120) { |
||||
|
$summary .= ' (truncated...)'; |
||||
|
} |
||||
|
|
||||
|
// Matches any printable character, including unicode characters: |
||||
|
// letters, marks, numbers, punctuation, spacing, and separators. |
||||
|
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return $summary; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Obfuscates URI if there is an username and a password present |
||||
|
* |
||||
|
* @param UriInterface $uri |
||||
|
* |
||||
|
* @return UriInterface |
||||
|
*/ |
||||
|
private static function obfuscateUri($uri) |
||||
|
{ |
||||
|
$userInfo = $uri->getUserInfo(); |
||||
|
|
||||
|
if (false !== ($pos = strpos($userInfo, ':'))) { |
||||
|
return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); |
||||
|
} |
||||
|
|
||||
|
return $uri; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the request that caused the exception |
||||
|
* |
||||
|
* @return RequestInterface |
||||
|
*/ |
||||
|
public function getRequest() |
||||
|
{ |
||||
|
return $this->request; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the associated response |
||||
|
* |
||||
|
* @return ResponseInterface|null |
||||
|
*/ |
||||
|
public function getResponse() |
||||
|
{ |
||||
|
return $this->response; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if a response was received |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function hasResponse() |
||||
|
{ |
||||
|
return $this->response !== null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get contextual information about the error from the underlying handler. |
||||
|
* |
||||
|
* The contents of this array will vary depending on which handler you are |
||||
|
* using. It may also be just an empty array. Relying on this data will |
||||
|
* couple you to a specific handler, but can give more debug information |
||||
|
* when needed. |
||||
|
* |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getHandlerContext() |
||||
|
{ |
||||
|
return $this->handlerContext; |
||||
|
} |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
use Psr\Http\Message\StreamInterface; |
||||
|
|
||||
|
/** |
||||
|
* Exception thrown when a seek fails on a stream. |
||||
|
*/ |
||||
|
class SeekException extends \RuntimeException implements GuzzleException |
||||
|
{ |
||||
|
private $stream; |
||||
|
|
||||
|
public function __construct(StreamInterface $stream, $pos = 0, $msg = '') |
||||
|
{ |
||||
|
$this->stream = $stream; |
||||
|
$msg = $msg ?: 'Could not seek the stream to position ' . $pos; |
||||
|
parent::__construct($msg); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return StreamInterface |
||||
|
*/ |
||||
|
public function getStream() |
||||
|
{ |
||||
|
return $this->stream; |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
/** |
||||
|
* Exception when a server error is encountered (5xx codes) |
||||
|
*/ |
||||
|
class ServerException extends BadResponseException {} |
@ -0,0 +1,4 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
class TooManyRedirectsException extends RequestException {} |
@ -0,0 +1,4 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Exception; |
||||
|
|
||||
|
class TransferException extends \RuntimeException implements GuzzleException {} |
@ -0,0 +1,559 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use GuzzleHttp\Exception\RequestException; |
||||
|
use GuzzleHttp\Exception\ConnectException; |
||||
|
use GuzzleHttp\Promise\FulfilledPromise; |
||||
|
use GuzzleHttp\Promise\RejectedPromise; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use GuzzleHttp\Psr7\LazyOpenStream; |
||||
|
use GuzzleHttp\TransferStats; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
/** |
||||
|
* Creates curl resources from a request |
||||
|
*/ |
||||
|
class CurlFactory implements CurlFactoryInterface |
||||
|
{ |
||||
|
/** @var array */ |
||||
|
private $handles = []; |
||||
|
|
||||
|
/** @var int Total number of idle handles to keep in cache */ |
||||
|
private $maxHandles; |
||||
|
|
||||
|
/** |
||||
|
* @param int $maxHandles Maximum number of idle handles. |
||||
|
*/ |
||||
|
public function __construct($maxHandles) |
||||
|
{ |
||||
|
$this->maxHandles = $maxHandles; |
||||
|
} |
||||
|
|
||||
|
public function create(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
if (isset($options['curl']['body_as_string'])) { |
||||
|
$options['_body_as_string'] = $options['curl']['body_as_string']; |
||||
|
unset($options['curl']['body_as_string']); |
||||
|
} |
||||
|
|
||||
|
$easy = new EasyHandle; |
||||
|
$easy->request = $request; |
||||
|
$easy->options = $options; |
||||
|
$conf = $this->getDefaultConf($easy); |
||||
|
$this->applyMethod($easy, $conf); |
||||
|
$this->applyHandlerOptions($easy, $conf); |
||||
|
$this->applyHeaders($easy, $conf); |
||||
|
unset($conf['_headers']); |
||||
|
|
||||
|
// Add handler options from the request configuration options |
||||
|
if (isset($options['curl'])) { |
||||
|
$conf = array_replace($conf, $options['curl']); |
||||
|
} |
||||
|
|
||||
|
$conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); |
||||
|
$easy->handle = $this->handles |
||||
|
? array_pop($this->handles) |
||||
|
: curl_init(); |
||||
|
curl_setopt_array($easy->handle, $conf); |
||||
|
|
||||
|
return $easy; |
||||
|
} |
||||
|
|
||||
|
public function release(EasyHandle $easy) |
||||
|
{ |
||||
|
$resource = $easy->handle; |
||||
|
unset($easy->handle); |
||||
|
|
||||
|
if (count($this->handles) >= $this->maxHandles) { |
||||
|
curl_close($resource); |
||||
|
} else { |
||||
|
// Remove all callback functions as they can hold onto references |
||||
|
// and are not cleaned up by curl_reset. Using curl_setopt_array |
||||
|
// does not work for some reason, so removing each one |
||||
|
// individually. |
||||
|
curl_setopt($resource, CURLOPT_HEADERFUNCTION, null); |
||||
|
curl_setopt($resource, CURLOPT_READFUNCTION, null); |
||||
|
curl_setopt($resource, CURLOPT_WRITEFUNCTION, null); |
||||
|
curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null); |
||||
|
curl_reset($resource); |
||||
|
$this->handles[] = $resource; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Completes a cURL transaction, either returning a response promise or a |
||||
|
* rejected promise. |
||||
|
* |
||||
|
* @param callable $handler |
||||
|
* @param EasyHandle $easy |
||||
|
* @param CurlFactoryInterface $factory Dictates how the handle is released |
||||
|
* |
||||
|
* @return \GuzzleHttp\Promise\PromiseInterface |
||||
|
*/ |
||||
|
public static function finish( |
||||
|
callable $handler, |
||||
|
EasyHandle $easy, |
||||
|
CurlFactoryInterface $factory |
||||
|
) { |
||||
|
if (isset($easy->options['on_stats'])) { |
||||
|
self::invokeStats($easy); |
||||
|
} |
||||
|
|
||||
|
if (!$easy->response || $easy->errno) { |
||||
|
return self::finishError($handler, $easy, $factory); |
||||
|
} |
||||
|
|
||||
|
// Return the response if it is present and there is no error. |
||||
|
$factory->release($easy); |
||||
|
|
||||
|
// Rewind the body of the response if possible. |
||||
|
$body = $easy->response->getBody(); |
||||
|
if ($body->isSeekable()) { |
||||
|
$body->rewind(); |
||||
|
} |
||||
|
|
||||
|
return new FulfilledPromise($easy->response); |
||||
|
} |
||||
|
|
||||
|
private static function invokeStats(EasyHandle $easy) |
||||
|
{ |
||||
|
$curlStats = curl_getinfo($easy->handle); |
||||
|
$stats = new TransferStats( |
||||
|
$easy->request, |
||||
|
$easy->response, |
||||
|
$curlStats['total_time'], |
||||
|
$easy->errno, |
||||
|
$curlStats |
||||
|
); |
||||
|
call_user_func($easy->options['on_stats'], $stats); |
||||
|
} |
||||
|
|
||||
|
private static function finishError( |
||||
|
callable $handler, |
||||
|
EasyHandle $easy, |
||||
|
CurlFactoryInterface $factory |
||||
|
) { |
||||
|
// Get error information and release the handle to the factory. |
||||
|
$ctx = [ |
||||
|
'errno' => $easy->errno, |
||||
|
'error' => curl_error($easy->handle), |
||||
|
] + curl_getinfo($easy->handle); |
||||
|
$factory->release($easy); |
||||
|
|
||||
|
// Retry when nothing is present or when curl failed to rewind. |
||||
|
if (empty($easy->options['_err_message']) |
||||
|
&& (!$easy->errno || $easy->errno == 65) |
||||
|
) { |
||||
|
return self::retryFailedRewind($handler, $easy, $ctx); |
||||
|
} |
||||
|
|
||||
|
return self::createRejection($easy, $ctx); |
||||
|
} |
||||
|
|
||||
|
private static function createRejection(EasyHandle $easy, array $ctx) |
||||
|
{ |
||||
|
static $connectionErrors = [ |
||||
|
CURLE_OPERATION_TIMEOUTED => true, |
||||
|
CURLE_COULDNT_RESOLVE_HOST => true, |
||||
|
CURLE_COULDNT_CONNECT => true, |
||||
|
CURLE_SSL_CONNECT_ERROR => true, |
||||
|
CURLE_GOT_NOTHING => true, |
||||
|
]; |
||||
|
|
||||
|
// If an exception was encountered during the onHeaders event, then |
||||
|
// return a rejected promise that wraps that exception. |
||||
|
if ($easy->onHeadersException) { |
||||
|
return \GuzzleHttp\Promise\rejection_for( |
||||
|
new RequestException( |
||||
|
'An error was encountered during the on_headers event', |
||||
|
$easy->request, |
||||
|
$easy->response, |
||||
|
$easy->onHeadersException, |
||||
|
$ctx |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
$message = sprintf( |
||||
|
'cURL error %s: %s (%s)', |
||||
|
$ctx['errno'], |
||||
|
$ctx['error'], |
||||
|
'see http://curl.haxx.se/libcurl/c/libcurl-errors.html' |
||||
|
); |
||||
|
|
||||
|
// Create a connection exception if it was a specific error code. |
||||
|
$error = isset($connectionErrors[$easy->errno]) |
||||
|
? new ConnectException($message, $easy->request, null, $ctx) |
||||
|
: new RequestException($message, $easy->request, $easy->response, null, $ctx); |
||||
|
|
||||
|
return \GuzzleHttp\Promise\rejection_for($error); |
||||
|
} |
||||
|
|
||||
|
private function getDefaultConf(EasyHandle $easy) |
||||
|
{ |
||||
|
$conf = [ |
||||
|
'_headers' => $easy->request->getHeaders(), |
||||
|
CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), |
||||
|
CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), |
||||
|
CURLOPT_RETURNTRANSFER => false, |
||||
|
CURLOPT_HEADER => false, |
||||
|
CURLOPT_CONNECTTIMEOUT => 150, |
||||
|
]; |
||||
|
|
||||
|
if (defined('CURLOPT_PROTOCOLS')) { |
||||
|
$conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; |
||||
|
} |
||||
|
|
||||
|
$version = $easy->request->getProtocolVersion(); |
||||
|
if ($version == 1.1) { |
||||
|
$conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; |
||||
|
} elseif ($version == 2.0) { |
||||
|
$conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; |
||||
|
} else { |
||||
|
$conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; |
||||
|
} |
||||
|
|
||||
|
return $conf; |
||||
|
} |
||||
|
|
||||
|
private function applyMethod(EasyHandle $easy, array &$conf) |
||||
|
{ |
||||
|
$body = $easy->request->getBody(); |
||||
|
$size = $body->getSize(); |
||||
|
|
||||
|
if ($size === null || $size > 0) { |
||||
|
$this->applyBody($easy->request, $easy->options, $conf); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
$method = $easy->request->getMethod(); |
||||
|
if ($method === 'PUT' || $method === 'POST') { |
||||
|
// See http://tools.ietf.org/html/rfc7230#section-3.3.2 |
||||
|
if (!$easy->request->hasHeader('Content-Length')) { |
||||
|
$conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; |
||||
|
} |
||||
|
} elseif ($method === 'HEAD') { |
||||
|
$conf[CURLOPT_NOBODY] = true; |
||||
|
unset( |
||||
|
$conf[CURLOPT_WRITEFUNCTION], |
||||
|
$conf[CURLOPT_READFUNCTION], |
||||
|
$conf[CURLOPT_FILE], |
||||
|
$conf[CURLOPT_INFILE] |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function applyBody(RequestInterface $request, array $options, array &$conf) |
||||
|
{ |
||||
|
$size = $request->hasHeader('Content-Length') |
||||
|
? (int) $request->getHeaderLine('Content-Length') |
||||
|
: null; |
||||
|
|
||||
|
// Send the body as a string if the size is less than 1MB OR if the |
||||
|
// [curl][body_as_string] request value is set. |
||||
|
if (($size !== null && $size < 1000000) || |
||||
|
!empty($options['_body_as_string']) |
||||
|
) { |
||||
|
$conf[CURLOPT_POSTFIELDS] = (string) $request->getBody(); |
||||
|
// Don't duplicate the Content-Length header |
||||
|
$this->removeHeader('Content-Length', $conf); |
||||
|
$this->removeHeader('Transfer-Encoding', $conf); |
||||
|
} else { |
||||
|
$conf[CURLOPT_UPLOAD] = true; |
||||
|
if ($size !== null) { |
||||
|
$conf[CURLOPT_INFILESIZE] = $size; |
||||
|
$this->removeHeader('Content-Length', $conf); |
||||
|
} |
||||
|
$body = $request->getBody(); |
||||
|
if ($body->isSeekable()) { |
||||
|
$body->rewind(); |
||||
|
} |
||||
|
$conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { |
||||
|
return $body->read($length); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// If the Expect header is not present, prevent curl from adding it |
||||
|
if (!$request->hasHeader('Expect')) { |
||||
|
$conf[CURLOPT_HTTPHEADER][] = 'Expect:'; |
||||
|
} |
||||
|
|
||||
|
// cURL sometimes adds a content-type by default. Prevent this. |
||||
|
if (!$request->hasHeader('Content-Type')) { |
||||
|
$conf[CURLOPT_HTTPHEADER][] = 'Content-Type:'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function applyHeaders(EasyHandle $easy, array &$conf) |
||||
|
{ |
||||
|
foreach ($conf['_headers'] as $name => $values) { |
||||
|
foreach ($values as $value) { |
||||
|
$conf[CURLOPT_HTTPHEADER][] = "$name: $value"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Remove the Accept header if one was not set |
||||
|
if (!$easy->request->hasHeader('Accept')) { |
||||
|
$conf[CURLOPT_HTTPHEADER][] = 'Accept:'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove a header from the options array. |
||||
|
* |
||||
|
* @param string $name Case-insensitive header to remove |
||||
|
* @param array $options Array of options to modify |
||||
|
*/ |
||||
|
private function removeHeader($name, array &$options) |
||||
|
{ |
||||
|
foreach (array_keys($options['_headers']) as $key) { |
||||
|
if (!strcasecmp($key, $name)) { |
||||
|
unset($options['_headers'][$key]); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function applyHandlerOptions(EasyHandle $easy, array &$conf) |
||||
|
{ |
||||
|
$options = $easy->options; |
||||
|
if (isset($options['verify'])) { |
||||
|
if ($options['verify'] === false) { |
||||
|
unset($conf[CURLOPT_CAINFO]); |
||||
|
$conf[CURLOPT_SSL_VERIFYHOST] = 0; |
||||
|
$conf[CURLOPT_SSL_VERIFYPEER] = false; |
||||
|
} else { |
||||
|
$conf[CURLOPT_SSL_VERIFYHOST] = 2; |
||||
|
$conf[CURLOPT_SSL_VERIFYPEER] = true; |
||||
|
if (is_string($options['verify'])) { |
||||
|
// Throw an error if the file/folder/link path is not valid or doesn't exist. |
||||
|
if (!file_exists($options['verify'])) { |
||||
|
throw new \InvalidArgumentException( |
||||
|
"SSL CA bundle not found: {$options['verify']}" |
||||
|
); |
||||
|
} |
||||
|
// If it's a directory or a link to a directory use CURLOPT_CAPATH. |
||||
|
// If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. |
||||
|
if (is_dir($options['verify']) || |
||||
|
(is_link($options['verify']) && is_dir(readlink($options['verify'])))) { |
||||
|
$conf[CURLOPT_CAPATH] = $options['verify']; |
||||
|
} else { |
||||
|
$conf[CURLOPT_CAINFO] = $options['verify']; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!empty($options['decode_content'])) { |
||||
|
$accept = $easy->request->getHeaderLine('Accept-Encoding'); |
||||
|
if ($accept) { |
||||
|
$conf[CURLOPT_ENCODING] = $accept; |
||||
|
} else { |
||||
|
$conf[CURLOPT_ENCODING] = ''; |
||||
|
// Don't let curl send the header over the wire |
||||
|
$conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isset($options['sink'])) { |
||||
|
$sink = $options['sink']; |
||||
|
if (!is_string($sink)) { |
||||
|
$sink = \GuzzleHttp\Psr7\stream_for($sink); |
||||
|
} elseif (!is_dir(dirname($sink))) { |
||||
|
// Ensure that the directory exists before failing in curl. |
||||
|
throw new \RuntimeException(sprintf( |
||||
|
'Directory %s does not exist for sink value of %s', |
||||
|
dirname($sink), |
||||
|
$sink |
||||
|
)); |
||||
|
} else { |
||||
|
$sink = new LazyOpenStream($sink, 'w+'); |
||||
|
} |
||||
|
$easy->sink = $sink; |
||||
|
$conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { |
||||
|
return $sink->write($write); |
||||
|
}; |
||||
|
} else { |
||||
|
// Use a default temp stream if no sink was set. |
||||
|
$conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); |
||||
|
$easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); |
||||
|
} |
||||
|
$timeoutRequiresNoSignal = false; |
||||
|
if (isset($options['timeout'])) { |
||||
|
$timeoutRequiresNoSignal |= $options['timeout'] < 1; |
||||
|
$conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; |
||||
|
} |
||||
|
|
||||
|
// CURL default value is CURL_IPRESOLVE_WHATEVER |
||||
|
if (isset($options['force_ip_resolve'])) { |
||||
|
if ('v4' === $options['force_ip_resolve']) { |
||||
|
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; |
||||
|
} else if ('v6' === $options['force_ip_resolve']) { |
||||
|
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isset($options['connect_timeout'])) { |
||||
|
$timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; |
||||
|
$conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; |
||||
|
} |
||||
|
|
||||
|
if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { |
||||
|
$conf[CURLOPT_NOSIGNAL] = true; |
||||
|
} |
||||
|
|
||||
|
if (isset($options['proxy'])) { |
||||
|
if (!is_array($options['proxy'])) { |
||||
|
$conf[CURLOPT_PROXY] = $options['proxy']; |
||||
|
} else { |
||||
|
$scheme = $easy->request->getUri()->getScheme(); |
||||
|
if (isset($options['proxy'][$scheme])) { |
||||
|
$host = $easy->request->getUri()->getHost(); |
||||
|
if (!isset($options['proxy']['no']) || |
||||
|
!\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) |
||||
|
) { |
||||
|
$conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isset($options['cert'])) { |
||||
|
$cert = $options['cert']; |
||||
|
if (is_array($cert)) { |
||||
|
$conf[CURLOPT_SSLCERTPASSWD] = $cert[1]; |
||||
|
$cert = $cert[0]; |
||||
|
} |
||||
|
if (!file_exists($cert)) { |
||||
|
throw new \InvalidArgumentException( |
||||
|
"SSL certificate not found: {$cert}" |
||||
|
); |
||||
|
} |
||||
|
$conf[CURLOPT_SSLCERT] = $cert; |
||||
|
} |
||||
|
|
||||
|
if (isset($options['ssl_key'])) { |
||||
|
$sslKey = $options['ssl_key']; |
||||
|
if (is_array($sslKey)) { |
||||
|
$conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1]; |
||||
|
$sslKey = $sslKey[0]; |
||||
|
} |
||||
|
if (!file_exists($sslKey)) { |
||||
|
throw new \InvalidArgumentException( |
||||
|
"SSL private key not found: {$sslKey}" |
||||
|
); |
||||
|
} |
||||
|
$conf[CURLOPT_SSLKEY] = $sslKey; |
||||
|
} |
||||
|
|
||||
|
if (isset($options['progress'])) { |
||||
|
$progress = $options['progress']; |
||||
|
if (!is_callable($progress)) { |
||||
|
throw new \InvalidArgumentException( |
||||
|
'progress client option must be callable' |
||||
|
); |
||||
|
} |
||||
|
$conf[CURLOPT_NOPROGRESS] = false; |
||||
|
$conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) { |
||||
|
$args = func_get_args(); |
||||
|
// PHP 5.5 pushed the handle onto the start of the args |
||||
|
if (is_resource($args[0])) { |
||||
|
array_shift($args); |
||||
|
} |
||||
|
call_user_func_array($progress, $args); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if (!empty($options['debug'])) { |
||||
|
$conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']); |
||||
|
$conf[CURLOPT_VERBOSE] = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This function ensures that a response was set on a transaction. If one |
||||
|
* was not set, then the request is retried if possible. This error |
||||
|
* typically means you are sending a payload, curl encountered a |
||||
|
* "Connection died, retrying a fresh connect" error, tried to rewind the |
||||
|
* stream, and then encountered a "necessary data rewind wasn't possible" |
||||
|
* error, causing the request to be sent through curl_multi_info_read() |
||||
|
* without an error status. |
||||
|
*/ |
||||
|
private static function retryFailedRewind( |
||||
|
callable $handler, |
||||
|
EasyHandle $easy, |
||||
|
array $ctx |
||||
|
) { |
||||
|
try { |
||||
|
// Only rewind if the body has been read from. |
||||
|
$body = $easy->request->getBody(); |
||||
|
if ($body->tell() > 0) { |
||||
|
$body->rewind(); |
||||
|
} |
||||
|
} catch (\RuntimeException $e) { |
||||
|
$ctx['error'] = 'The connection unexpectedly failed without ' |
||||
|
. 'providing an error. The request would have been retried, ' |
||||
|
. 'but attempting to rewind the request body failed. ' |
||||
|
. 'Exception: ' . $e; |
||||
|
return self::createRejection($easy, $ctx); |
||||
|
} |
||||
|
|
||||
|
// Retry no more than 3 times before giving up. |
||||
|
if (!isset($easy->options['_curl_retries'])) { |
||||
|
$easy->options['_curl_retries'] = 1; |
||||
|
} elseif ($easy->options['_curl_retries'] == 2) { |
||||
|
$ctx['error'] = 'The cURL request was retried 3 times ' |
||||
|
. 'and did not succeed. The most likely reason for the failure ' |
||||
|
. 'is that cURL was unable to rewind the body of the request ' |
||||
|
. 'and subsequent retries resulted in the same error. Turn on ' |
||||
|
. 'the debug option to see what went wrong. See ' |
||||
|
. 'https://bugs.php.net/bug.php?id=47204 for more information.'; |
||||
|
return self::createRejection($easy, $ctx); |
||||
|
} else { |
||||
|
$easy->options['_curl_retries']++; |
||||
|
} |
||||
|
|
||||
|
return $handler($easy->request, $easy->options); |
||||
|
} |
||||
|
|
||||
|
private function createHeaderFn(EasyHandle $easy) |
||||
|
{ |
||||
|
if (isset($easy->options['on_headers'])) { |
||||
|
$onHeaders = $easy->options['on_headers']; |
||||
|
|
||||
|
if (!is_callable($onHeaders)) { |
||||
|
throw new \InvalidArgumentException('on_headers must be callable'); |
||||
|
} |
||||
|
} else { |
||||
|
$onHeaders = null; |
||||
|
} |
||||
|
|
||||
|
return function ($ch, $h) use ( |
||||
|
$onHeaders, |
||||
|
$easy, |
||||
|
&$startingResponse |
||||
|
) { |
||||
|
$value = trim($h); |
||||
|
if ($value === '') { |
||||
|
$startingResponse = true; |
||||
|
$easy->createResponse(); |
||||
|
if ($onHeaders !== null) { |
||||
|
try { |
||||
|
$onHeaders($easy->response); |
||||
|
} catch (\Exception $e) { |
||||
|
// Associate the exception with the handle and trigger |
||||
|
// a curl header write error by returning 0. |
||||
|
$easy->onHeadersException = $e; |
||||
|
return -1; |
||||
|
} |
||||
|
} |
||||
|
} elseif ($startingResponse) { |
||||
|
$startingResponse = false; |
||||
|
$easy->headers = [$value]; |
||||
|
} else { |
||||
|
$easy->headers[] = $value; |
||||
|
} |
||||
|
return strlen($h); |
||||
|
}; |
||||
|
} |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
interface CurlFactoryInterface |
||||
|
{ |
||||
|
/** |
||||
|
* Creates a cURL handle resource. |
||||
|
* |
||||
|
* @param RequestInterface $request Request |
||||
|
* @param array $options Transfer options |
||||
|
* |
||||
|
* @return EasyHandle |
||||
|
* @throws \RuntimeException when an option cannot be applied |
||||
|
*/ |
||||
|
public function create(RequestInterface $request, array $options); |
||||
|
|
||||
|
/** |
||||
|
* Release an easy handle, allowing it to be reused or closed. |
||||
|
* |
||||
|
* This function must call unset on the easy handle's "handle" property. |
||||
|
* |
||||
|
* @param EasyHandle $easy |
||||
|
*/ |
||||
|
public function release(EasyHandle $easy); |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use GuzzleHttp\Psr7; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
/** |
||||
|
* HTTP handler that uses cURL easy handles as a transport layer. |
||||
|
* |
||||
|
* When using the CurlHandler, custom curl options can be specified as an |
||||
|
* associative array of curl option constants mapping to values in the |
||||
|
* **curl** key of the "client" key of the request. |
||||
|
*/ |
||||
|
class CurlHandler |
||||
|
{ |
||||
|
/** @var CurlFactoryInterface */ |
||||
|
private $factory; |
||||
|
|
||||
|
/** |
||||
|
* Accepts an associative array of options: |
||||
|
* |
||||
|
* - factory: Optional curl factory used to create cURL handles. |
||||
|
* |
||||
|
* @param array $options Array of options to use with the handler |
||||
|
*/ |
||||
|
public function __construct(array $options = []) |
||||
|
{ |
||||
|
$this->factory = isset($options['handle_factory']) |
||||
|
? $options['handle_factory'] |
||||
|
: new CurlFactory(3); |
||||
|
} |
||||
|
|
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
if (isset($options['delay'])) { |
||||
|
usleep($options['delay'] * 1000); |
||||
|
} |
||||
|
|
||||
|
$easy = $this->factory->create($request, $options); |
||||
|
curl_exec($easy->handle); |
||||
|
$easy->errno = curl_errno($easy->handle); |
||||
|
|
||||
|
return CurlFactory::finish($this, $easy, $this->factory); |
||||
|
} |
||||
|
} |
@ -0,0 +1,197 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use GuzzleHttp\Promise as P; |
||||
|
use GuzzleHttp\Promise\Promise; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
/** |
||||
|
* Returns an asynchronous response using curl_multi_* functions. |
||||
|
* |
||||
|
* When using the CurlMultiHandler, custom curl options can be specified as an |
||||
|
* associative array of curl option constants mapping to values in the |
||||
|
* **curl** key of the provided request options. |
||||
|
* |
||||
|
* @property resource $_mh Internal use only. Lazy loaded multi-handle. |
||||
|
*/ |
||||
|
class CurlMultiHandler |
||||
|
{ |
||||
|
/** @var CurlFactoryInterface */ |
||||
|
private $factory; |
||||
|
private $selectTimeout; |
||||
|
private $active; |
||||
|
private $handles = []; |
||||
|
private $delays = []; |
||||
|
|
||||
|
/** |
||||
|
* This handler accepts the following options: |
||||
|
* |
||||
|
* - handle_factory: An optional factory used to create curl handles |
||||
|
* - select_timeout: Optional timeout (in seconds) to block before timing |
||||
|
* out while selecting curl handles. Defaults to 1 second. |
||||
|
* |
||||
|
* @param array $options |
||||
|
*/ |
||||
|
public function __construct(array $options = []) |
||||
|
{ |
||||
|
$this->factory = isset($options['handle_factory']) |
||||
|
? $options['handle_factory'] : new CurlFactory(50); |
||||
|
$this->selectTimeout = isset($options['select_timeout']) |
||||
|
? $options['select_timeout'] : 1; |
||||
|
} |
||||
|
|
||||
|
public function __get($name) |
||||
|
{ |
||||
|
if ($name === '_mh') { |
||||
|
return $this->_mh = curl_multi_init(); |
||||
|
} |
||||
|
|
||||
|
throw new \BadMethodCallException(); |
||||
|
} |
||||
|
|
||||
|
public function __destruct() |
||||
|
{ |
||||
|
if (isset($this->_mh)) { |
||||
|
curl_multi_close($this->_mh); |
||||
|
unset($this->_mh); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
$easy = $this->factory->create($request, $options); |
||||
|
$id = (int) $easy->handle; |
||||
|
|
||||
|
$promise = new Promise( |
||||
|
[$this, 'execute'], |
||||
|
function () use ($id) { return $this->cancel($id); } |
||||
|
); |
||||
|
|
||||
|
$this->addRequest(['easy' => $easy, 'deferred' => $promise]); |
||||
|
|
||||
|
return $promise; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Ticks the curl event loop. |
||||
|
*/ |
||||
|
public function tick() |
||||
|
{ |
||||
|
// Add any delayed handles if needed. |
||||
|
if ($this->delays) { |
||||
|
$currentTime = microtime(true); |
||||
|
foreach ($this->delays as $id => $delay) { |
||||
|
if ($currentTime >= $delay) { |
||||
|
unset($this->delays[$id]); |
||||
|
curl_multi_add_handle( |
||||
|
$this->_mh, |
||||
|
$this->handles[$id]['easy']->handle |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Step through the task queue which may add additional requests. |
||||
|
P\queue()->run(); |
||||
|
|
||||
|
if ($this->active && |
||||
|
curl_multi_select($this->_mh, $this->selectTimeout) === -1 |
||||
|
) { |
||||
|
// Perform a usleep if a select returns -1. |
||||
|
// See: https://bugs.php.net/bug.php?id=61141 |
||||
|
usleep(250); |
||||
|
} |
||||
|
|
||||
|
while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM); |
||||
|
|
||||
|
$this->processMessages(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Runs until all outstanding connections have completed. |
||||
|
*/ |
||||
|
public function execute() |
||||
|
{ |
||||
|
$queue = P\queue(); |
||||
|
|
||||
|
while ($this->handles || !$queue->isEmpty()) { |
||||
|
// If there are no transfers, then sleep for the next delay |
||||
|
if (!$this->active && $this->delays) { |
||||
|
usleep($this->timeToNext()); |
||||
|
} |
||||
|
$this->tick(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function addRequest(array $entry) |
||||
|
{ |
||||
|
$easy = $entry['easy']; |
||||
|
$id = (int) $easy->handle; |
||||
|
$this->handles[$id] = $entry; |
||||
|
if (empty($easy->options['delay'])) { |
||||
|
curl_multi_add_handle($this->_mh, $easy->handle); |
||||
|
} else { |
||||
|
$this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Cancels a handle from sending and removes references to it. |
||||
|
* |
||||
|
* @param int $id Handle ID to cancel and remove. |
||||
|
* |
||||
|
* @return bool True on success, false on failure. |
||||
|
*/ |
||||
|
private function cancel($id) |
||||
|
{ |
||||
|
// Cannot cancel if it has been processed. |
||||
|
if (!isset($this->handles[$id])) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
$handle = $this->handles[$id]['easy']->handle; |
||||
|
unset($this->delays[$id], $this->handles[$id]); |
||||
|
curl_multi_remove_handle($this->_mh, $handle); |
||||
|
curl_close($handle); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private function processMessages() |
||||
|
{ |
||||
|
while ($done = curl_multi_info_read($this->_mh)) { |
||||
|
$id = (int) $done['handle']; |
||||
|
curl_multi_remove_handle($this->_mh, $done['handle']); |
||||
|
|
||||
|
if (!isset($this->handles[$id])) { |
||||
|
// Probably was cancelled. |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
$entry = $this->handles[$id]; |
||||
|
unset($this->handles[$id], $this->delays[$id]); |
||||
|
$entry['easy']->errno = $done['result']; |
||||
|
$entry['deferred']->resolve( |
||||
|
CurlFactory::finish( |
||||
|
$this, |
||||
|
$entry['easy'], |
||||
|
$this->factory |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function timeToNext() |
||||
|
{ |
||||
|
$currentTime = microtime(true); |
||||
|
$nextTime = PHP_INT_MAX; |
||||
|
foreach ($this->delays as $time) { |
||||
|
if ($time < $nextTime) { |
||||
|
$nextTime = $time; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return max(0, $nextTime - $currentTime) * 1000000; |
||||
|
} |
||||
|
} |
@ -0,0 +1,92 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use GuzzleHttp\Psr7\Response; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
use Psr\Http\Message\StreamInterface; |
||||
|
|
||||
|
/** |
||||
|
* Represents a cURL easy handle and the data it populates. |
||||
|
* |
||||
|
* @internal |
||||
|
*/ |
||||
|
final class EasyHandle |
||||
|
{ |
||||
|
/** @var resource cURL resource */ |
||||
|
public $handle; |
||||
|
|
||||
|
/** @var StreamInterface Where data is being written */ |
||||
|
public $sink; |
||||
|
|
||||
|
/** @var array Received HTTP headers so far */ |
||||
|
public $headers = []; |
||||
|
|
||||
|
/** @var ResponseInterface Received response (if any) */ |
||||
|
public $response; |
||||
|
|
||||
|
/** @var RequestInterface Request being sent */ |
||||
|
public $request; |
||||
|
|
||||
|
/** @var array Request options */ |
||||
|
public $options = []; |
||||
|
|
||||
|
/** @var int cURL error number (if any) */ |
||||
|
public $errno = 0; |
||||
|
|
||||
|
/** @var \Exception Exception during on_headers (if any) */ |
||||
|
public $onHeadersException; |
||||
|
|
||||
|
/** |
||||
|
* Attach a response to the easy handle based on the received headers. |
||||
|
* |
||||
|
* @throws \RuntimeException if no headers have been received. |
||||
|
*/ |
||||
|
public function createResponse() |
||||
|
{ |
||||
|
if (empty($this->headers)) { |
||||
|
throw new \RuntimeException('No headers have been received'); |
||||
|
} |
||||
|
|
||||
|
// HTTP-version SP status-code SP reason-phrase |
||||
|
$startLine = explode(' ', array_shift($this->headers), 3); |
||||
|
$headers = \GuzzleHttp\headers_from_lines($this->headers); |
||||
|
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); |
||||
|
|
||||
|
if (!empty($this->options['decode_content']) |
||||
|
&& isset($normalizedKeys['content-encoding']) |
||||
|
) { |
||||
|
$headers['x-encoded-content-encoding'] |
||||
|
= $headers[$normalizedKeys['content-encoding']]; |
||||
|
unset($headers[$normalizedKeys['content-encoding']]); |
||||
|
if (isset($normalizedKeys['content-length'])) { |
||||
|
$headers['x-encoded-content-length'] |
||||
|
= $headers[$normalizedKeys['content-length']]; |
||||
|
|
||||
|
$bodyLength = (int) $this->sink->getSize(); |
||||
|
if ($bodyLength) { |
||||
|
$headers[$normalizedKeys['content-length']] = $bodyLength; |
||||
|
} else { |
||||
|
unset($headers[$normalizedKeys['content-length']]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Attach a response to the easy handle with the parsed headers. |
||||
|
$this->response = new Response( |
||||
|
$startLine[1], |
||||
|
$headers, |
||||
|
$this->sink, |
||||
|
substr($startLine[0], 5), |
||||
|
isset($startLine[2]) ? (string) $startLine[2] : null |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public function __get($name) |
||||
|
{ |
||||
|
$msg = $name === 'handle' |
||||
|
? 'The EasyHandle has been released' |
||||
|
: 'Invalid property: ' . $name; |
||||
|
throw new \BadMethodCallException($msg); |
||||
|
} |
||||
|
} |
@ -0,0 +1,189 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use GuzzleHttp\Exception\RequestException; |
||||
|
use GuzzleHttp\HandlerStack; |
||||
|
use GuzzleHttp\Promise\PromiseInterface; |
||||
|
use GuzzleHttp\Promise\RejectedPromise; |
||||
|
use GuzzleHttp\TransferStats; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
|
||||
|
/** |
||||
|
* Handler that returns responses or throw exceptions from a queue. |
||||
|
*/ |
||||
|
class MockHandler implements \Countable |
||||
|
{ |
||||
|
private $queue = []; |
||||
|
private $lastRequest; |
||||
|
private $lastOptions; |
||||
|
private $onFulfilled; |
||||
|
private $onRejected; |
||||
|
|
||||
|
/** |
||||
|
* Creates a new MockHandler that uses the default handler stack list of |
||||
|
* middlewares. |
||||
|
* |
||||
|
* @param array $queue Array of responses, callables, or exceptions. |
||||
|
* @param callable $onFulfilled Callback to invoke when the return value is fulfilled. |
||||
|
* @param callable $onRejected Callback to invoke when the return value is rejected. |
||||
|
* |
||||
|
* @return HandlerStack |
||||
|
*/ |
||||
|
public static function createWithMiddleware( |
||||
|
array $queue = null, |
||||
|
callable $onFulfilled = null, |
||||
|
callable $onRejected = null |
||||
|
) { |
||||
|
return HandlerStack::create(new self($queue, $onFulfilled, $onRejected)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* The passed in value must be an array of |
||||
|
* {@see Psr7\Http\Message\ResponseInterface} objects, Exceptions, |
||||
|
* callables, or Promises. |
||||
|
* |
||||
|
* @param array $queue |
||||
|
* @param callable $onFulfilled Callback to invoke when the return value is fulfilled. |
||||
|
* @param callable $onRejected Callback to invoke when the return value is rejected. |
||||
|
*/ |
||||
|
public function __construct( |
||||
|
array $queue = null, |
||||
|
callable $onFulfilled = null, |
||||
|
callable $onRejected = null |
||||
|
) { |
||||
|
$this->onFulfilled = $onFulfilled; |
||||
|
$this->onRejected = $onRejected; |
||||
|
|
||||
|
if ($queue) { |
||||
|
call_user_func_array([$this, 'append'], $queue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
if (!$this->queue) { |
||||
|
throw new \OutOfBoundsException('Mock queue is empty'); |
||||
|
} |
||||
|
|
||||
|
if (isset($options['delay'])) { |
||||
|
usleep($options['delay'] * 1000); |
||||
|
} |
||||
|
|
||||
|
$this->lastRequest = $request; |
||||
|
$this->lastOptions = $options; |
||||
|
$response = array_shift($this->queue); |
||||
|
|
||||
|
if (isset($options['on_headers'])) { |
||||
|
if (!is_callable($options['on_headers'])) { |
||||
|
throw new \InvalidArgumentException('on_headers must be callable'); |
||||
|
} |
||||
|
try { |
||||
|
$options['on_headers']($response); |
||||
|
} catch (\Exception $e) { |
||||
|
$msg = 'An error was encountered during the on_headers event'; |
||||
|
$response = new RequestException($msg, $request, $response, $e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (is_callable($response)) { |
||||
|
$response = call_user_func($response, $request, $options); |
||||
|
} |
||||
|
|
||||
|
$response = $response instanceof \Exception |
||||
|
? \GuzzleHttp\Promise\rejection_for($response) |
||||
|
: \GuzzleHttp\Promise\promise_for($response); |
||||
|
|
||||
|
return $response->then( |
||||
|
function ($value) use ($request, $options) { |
||||
|
$this->invokeStats($request, $options, $value); |
||||
|
if ($this->onFulfilled) { |
||||
|
call_user_func($this->onFulfilled, $value); |
||||
|
} |
||||
|
if (isset($options['sink'])) { |
||||
|
$contents = (string) $value->getBody(); |
||||
|
$sink = $options['sink']; |
||||
|
|
||||
|
if (is_resource($sink)) { |
||||
|
fwrite($sink, $contents); |
||||
|
} elseif (is_string($sink)) { |
||||
|
file_put_contents($sink, $contents); |
||||
|
} elseif ($sink instanceof \Psr\Http\Message\StreamInterface) { |
||||
|
$sink->write($contents); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $value; |
||||
|
}, |
||||
|
function ($reason) use ($request, $options) { |
||||
|
$this->invokeStats($request, $options, null, $reason); |
||||
|
if ($this->onRejected) { |
||||
|
call_user_func($this->onRejected, $reason); |
||||
|
} |
||||
|
return \GuzzleHttp\Promise\rejection_for($reason); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Adds one or more variadic requests, exceptions, callables, or promises |
||||
|
* to the queue. |
||||
|
*/ |
||||
|
public function append() |
||||
|
{ |
||||
|
foreach (func_get_args() as $value) { |
||||
|
if ($value instanceof ResponseInterface |
||||
|
|| $value instanceof \Exception |
||||
|
|| $value instanceof PromiseInterface |
||||
|
|| is_callable($value) |
||||
|
) { |
||||
|
$this->queue[] = $value; |
||||
|
} else { |
||||
|
throw new \InvalidArgumentException('Expected a response or ' |
||||
|
. 'exception. Found ' . \GuzzleHttp\describe_type($value)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the last received request. |
||||
|
* |
||||
|
* @return RequestInterface |
||||
|
*/ |
||||
|
public function getLastRequest() |
||||
|
{ |
||||
|
return $this->lastRequest; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the last received request options. |
||||
|
* |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getLastOptions() |
||||
|
{ |
||||
|
return $this->lastOptions; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the number of remaining items in the queue. |
||||
|
* |
||||
|
* @return int |
||||
|
*/ |
||||
|
public function count() |
||||
|
{ |
||||
|
return count($this->queue); |
||||
|
} |
||||
|
|
||||
|
private function invokeStats( |
||||
|
RequestInterface $request, |
||||
|
array $options, |
||||
|
ResponseInterface $response = null, |
||||
|
$reason = null |
||||
|
) { |
||||
|
if (isset($options['on_stats'])) { |
||||
|
$stats = new TransferStats($request, $response, 0, $reason); |
||||
|
call_user_func($options['on_stats'], $stats); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use GuzzleHttp\RequestOptions; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
/** |
||||
|
* Provides basic proxies for handlers. |
||||
|
*/ |
||||
|
class Proxy |
||||
|
{ |
||||
|
/** |
||||
|
* Sends synchronous requests to a specific handler while sending all other |
||||
|
* requests to another handler. |
||||
|
* |
||||
|
* @param callable $default Handler used for normal responses |
||||
|
* @param callable $sync Handler used for synchronous responses. |
||||
|
* |
||||
|
* @return callable Returns the composed handler. |
||||
|
*/ |
||||
|
public static function wrapSync( |
||||
|
callable $default, |
||||
|
callable $sync |
||||
|
) { |
||||
|
return function (RequestInterface $request, array $options) use ($default, $sync) { |
||||
|
return empty($options[RequestOptions::SYNCHRONOUS]) |
||||
|
? $default($request, $options) |
||||
|
: $sync($request, $options); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sends streaming requests to a streaming compatible handler while sending |
||||
|
* all other requests to a default handler. |
||||
|
* |
||||
|
* This, for example, could be useful for taking advantage of the |
||||
|
* performance benefits of curl while still supporting true streaming |
||||
|
* through the StreamHandler. |
||||
|
* |
||||
|
* @param callable $default Handler used for non-streaming responses |
||||
|
* @param callable $streaming Handler used for streaming responses |
||||
|
* |
||||
|
* @return callable Returns the composed handler. |
||||
|
*/ |
||||
|
public static function wrapStreaming( |
||||
|
callable $default, |
||||
|
callable $streaming |
||||
|
) { |
||||
|
return function (RequestInterface $request, array $options) use ($default, $streaming) { |
||||
|
return empty($options['stream']) |
||||
|
? $default($request, $options) |
||||
|
: $streaming($request, $options); |
||||
|
}; |
||||
|
} |
||||
|
} |
@ -0,0 +1,533 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp\Handler; |
||||
|
|
||||
|
use GuzzleHttp\Exception\RequestException; |
||||
|
use GuzzleHttp\Exception\ConnectException; |
||||
|
use GuzzleHttp\Promise\FulfilledPromise; |
||||
|
use GuzzleHttp\Promise\RejectedPromise; |
||||
|
use GuzzleHttp\Promise\PromiseInterface; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use GuzzleHttp\TransferStats; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
use Psr\Http\Message\StreamInterface; |
||||
|
|
||||
|
/** |
||||
|
* HTTP handler that uses PHP's HTTP stream wrapper. |
||||
|
*/ |
||||
|
class StreamHandler |
||||
|
{ |
||||
|
private $lastHeaders = []; |
||||
|
|
||||
|
/** |
||||
|
* Sends an HTTP request. |
||||
|
* |
||||
|
* @param RequestInterface $request Request to send. |
||||
|
* @param array $options Request transfer options. |
||||
|
* |
||||
|
* @return PromiseInterface |
||||
|
*/ |
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
// Sleep if there is a delay specified. |
||||
|
if (isset($options['delay'])) { |
||||
|
usleep($options['delay'] * 1000); |
||||
|
} |
||||
|
|
||||
|
$startTime = isset($options['on_stats']) ? microtime(true) : null; |
||||
|
|
||||
|
try { |
||||
|
// Does not support the expect header. |
||||
|
$request = $request->withoutHeader('Expect'); |
||||
|
|
||||
|
// Append a content-length header if body size is zero to match |
||||
|
// cURL's behavior. |
||||
|
if (0 === $request->getBody()->getSize()) { |
||||
|
$request = $request->withHeader('Content-Length', 0); |
||||
|
} |
||||
|
|
||||
|
return $this->createResponse( |
||||
|
$request, |
||||
|
$options, |
||||
|
$this->createStream($request, $options), |
||||
|
$startTime |
||||
|
); |
||||
|
} catch (\InvalidArgumentException $e) { |
||||
|
throw $e; |
||||
|
} catch (\Exception $e) { |
||||
|
// Determine if the error was a networking error. |
||||
|
$message = $e->getMessage(); |
||||
|
// This list can probably get more comprehensive. |
||||
|
if (strpos($message, 'getaddrinfo') // DNS lookup failed |
||||
|
|| strpos($message, 'Connection refused') |
||||
|
|| strpos($message, "couldn't connect to host") // error on HHVM |
||||
|
) { |
||||
|
$e = new ConnectException($e->getMessage(), $request, $e); |
||||
|
} |
||||
|
$e = RequestException::wrapException($request, $e); |
||||
|
$this->invokeStats($options, $request, $startTime, null, $e); |
||||
|
|
||||
|
return \GuzzleHttp\Promise\rejection_for($e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function invokeStats( |
||||
|
array $options, |
||||
|
RequestInterface $request, |
||||
|
$startTime, |
||||
|
ResponseInterface $response = null, |
||||
|
$error = null |
||||
|
) { |
||||
|
if (isset($options['on_stats'])) { |
||||
|
$stats = new TransferStats( |
||||
|
$request, |
||||
|
$response, |
||||
|
microtime(true) - $startTime, |
||||
|
$error, |
||||
|
[] |
||||
|
); |
||||
|
call_user_func($options['on_stats'], $stats); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function createResponse( |
||||
|
RequestInterface $request, |
||||
|
array $options, |
||||
|
$stream, |
||||
|
$startTime |
||||
|
) { |
||||
|
$hdrs = $this->lastHeaders; |
||||
|
$this->lastHeaders = []; |
||||
|
$parts = explode(' ', array_shift($hdrs), 3); |
||||
|
$ver = explode('/', $parts[0])[1]; |
||||
|
$status = $parts[1]; |
||||
|
$reason = isset($parts[2]) ? $parts[2] : null; |
||||
|
$headers = \GuzzleHttp\headers_from_lines($hdrs); |
||||
|
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream); |
||||
|
$stream = Psr7\stream_for($stream); |
||||
|
$sink = $stream; |
||||
|
|
||||
|
if (strcasecmp('HEAD', $request->getMethod())) { |
||||
|
$sink = $this->createSink($stream, $options); |
||||
|
} |
||||
|
|
||||
|
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason); |
||||
|
|
||||
|
if (isset($options['on_headers'])) { |
||||
|
try { |
||||
|
$options['on_headers']($response); |
||||
|
} catch (\Exception $e) { |
||||
|
$msg = 'An error was encountered during the on_headers event'; |
||||
|
$ex = new RequestException($msg, $request, $response, $e); |
||||
|
return \GuzzleHttp\Promise\rejection_for($ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Do not drain when the request is a HEAD request because they have |
||||
|
// no body. |
||||
|
if ($sink !== $stream) { |
||||
|
$this->drain( |
||||
|
$stream, |
||||
|
$sink, |
||||
|
$response->getHeaderLine('Content-Length') |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
$this->invokeStats($options, $request, $startTime, $response, null); |
||||
|
|
||||
|
return new FulfilledPromise($response); |
||||
|
} |
||||
|
|
||||
|
private function createSink(StreamInterface $stream, array $options) |
||||
|
{ |
||||
|
if (!empty($options['stream'])) { |
||||
|
return $stream; |
||||
|
} |
||||
|
|
||||
|
$sink = isset($options['sink']) |
||||
|
? $options['sink'] |
||||
|
: fopen('php://temp', 'r+'); |
||||
|
|
||||
|
return is_string($sink) |
||||
|
? new Psr7\LazyOpenStream($sink, 'w+') |
||||
|
: Psr7\stream_for($sink); |
||||
|
} |
||||
|
|
||||
|
private function checkDecode(array $options, array $headers, $stream) |
||||
|
{ |
||||
|
// Automatically decode responses when instructed. |
||||
|
if (!empty($options['decode_content'])) { |
||||
|
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); |
||||
|
if (isset($normalizedKeys['content-encoding'])) { |
||||
|
$encoding = $headers[$normalizedKeys['content-encoding']]; |
||||
|
if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { |
||||
|
$stream = new Psr7\InflateStream( |
||||
|
Psr7\stream_for($stream) |
||||
|
); |
||||
|
$headers['x-encoded-content-encoding'] |
||||
|
= $headers[$normalizedKeys['content-encoding']]; |
||||
|
// Remove content-encoding header |
||||
|
unset($headers[$normalizedKeys['content-encoding']]); |
||||
|
// Fix content-length header |
||||
|
if (isset($normalizedKeys['content-length'])) { |
||||
|
$headers['x-encoded-content-length'] |
||||
|
= $headers[$normalizedKeys['content-length']]; |
||||
|
|
||||
|
$length = (int) $stream->getSize(); |
||||
|
if ($length === 0) { |
||||
|
unset($headers[$normalizedKeys['content-length']]); |
||||
|
} else { |
||||
|
$headers[$normalizedKeys['content-length']] = [$length]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return [$stream, $headers]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Drains the source stream into the "sink" client option. |
||||
|
* |
||||
|
* @param StreamInterface $source |
||||
|
* @param StreamInterface $sink |
||||
|
* @param string $contentLength Header specifying the amount of |
||||
|
* data to read. |
||||
|
* |
||||
|
* @return StreamInterface |
||||
|
* @throws \RuntimeException when the sink option is invalid. |
||||
|
*/ |
||||
|
private function drain( |
||||
|
StreamInterface $source, |
||||
|
StreamInterface $sink, |
||||
|
$contentLength |
||||
|
) { |
||||
|
// If a content-length header is provided, then stop reading once |
||||
|
// that number of bytes has been read. This can prevent infinitely |
||||
|
// reading from a stream when dealing with servers that do not honor |
||||
|
// Connection: Close headers. |
||||
|
Psr7\copy_to_stream( |
||||
|
$source, |
||||
|
$sink, |
||||
|
(strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 |
||||
|
); |
||||
|
|
||||
|
$sink->seek(0); |
||||
|
$source->close(); |
||||
|
|
||||
|
return $sink; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create a resource and check to ensure it was created successfully |
||||
|
* |
||||
|
* @param callable $callback Callable that returns stream resource |
||||
|
* |
||||
|
* @return resource |
||||
|
* @throws \RuntimeException on error |
||||
|
*/ |
||||
|
private function createResource(callable $callback) |
||||
|
{ |
||||
|
$errors = null; |
||||
|
set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { |
||||
|
$errors[] = [ |
||||
|
'message' => $msg, |
||||
|
'file' => $file, |
||||
|
'line' => $line |
||||
|
]; |
||||
|
return true; |
||||
|
}); |
||||
|
|
||||
|
$resource = $callback(); |
||||
|
restore_error_handler(); |
||||
|
|
||||
|
if (!$resource) { |
||||
|
$message = 'Error creating resource: '; |
||||
|
foreach ($errors as $err) { |
||||
|
foreach ($err as $key => $value) { |
||||
|
$message .= "[$key] $value" . PHP_EOL; |
||||
|
} |
||||
|
} |
||||
|
throw new \RuntimeException(trim($message)); |
||||
|
} |
||||
|
|
||||
|
return $resource; |
||||
|
} |
||||
|
|
||||
|
private function createStream(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
static $methods; |
||||
|
if (!$methods) { |
||||
|
$methods = array_flip(get_class_methods(__CLASS__)); |
||||
|
} |
||||
|
|
||||
|
// HTTP/1.1 streams using the PHP stream wrapper require a |
||||
|
// Connection: close header |
||||
|
if ($request->getProtocolVersion() == '1.1' |
||||
|
&& !$request->hasHeader('Connection') |
||||
|
) { |
||||
|
$request = $request->withHeader('Connection', 'close'); |
||||
|
} |
||||
|
|
||||
|
// Ensure SSL is verified by default |
||||
|
if (!isset($options['verify'])) { |
||||
|
$options['verify'] = true; |
||||
|
} |
||||
|
|
||||
|
$params = []; |
||||
|
$context = $this->getDefaultContext($request, $options); |
||||
|
|
||||
|
if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { |
||||
|
throw new \InvalidArgumentException('on_headers must be callable'); |
||||
|
} |
||||
|
|
||||
|
if (!empty($options)) { |
||||
|
foreach ($options as $key => $value) { |
||||
|
$method = "add_{$key}"; |
||||
|
if (isset($methods[$method])) { |
||||
|
$this->{$method}($request, $context, $value, $params); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isset($options['stream_context'])) { |
||||
|
if (!is_array($options['stream_context'])) { |
||||
|
throw new \InvalidArgumentException('stream_context must be an array'); |
||||
|
} |
||||
|
$context = array_replace_recursive( |
||||
|
$context, |
||||
|
$options['stream_context'] |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// Microsoft NTLM authentication only supported with curl handler |
||||
|
if (isset($options['auth']) |
||||
|
&& is_array($options['auth']) |
||||
|
&& isset($options['auth'][2]) |
||||
|
&& 'ntlm' == $options['auth'][2] |
||||
|
) { |
||||
|
|
||||
|
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); |
||||
|
} |
||||
|
|
||||
|
$uri = $this->resolveHost($request, $options); |
||||
|
|
||||
|
$context = $this->createResource( |
||||
|
function () use ($context, $params) { |
||||
|
return stream_context_create($context, $params); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
return $this->createResource( |
||||
|
function () use ($uri, &$http_response_header, $context, $options) { |
||||
|
$resource = fopen((string) $uri, 'r', null, $context); |
||||
|
$this->lastHeaders = $http_response_header; |
||||
|
|
||||
|
if (isset($options['read_timeout'])) { |
||||
|
$readTimeout = $options['read_timeout']; |
||||
|
$sec = (int) $readTimeout; |
||||
|
$usec = ($readTimeout - $sec) * 100000; |
||||
|
stream_set_timeout($resource, $sec, $usec); |
||||
|
} |
||||
|
|
||||
|
return $resource; |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private function resolveHost(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
$uri = $request->getUri(); |
||||
|
|
||||
|
if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) { |
||||
|
if ('v4' === $options['force_ip_resolve']) { |
||||
|
$records = dns_get_record($uri->getHost(), DNS_A); |
||||
|
if (!isset($records[0]['ip'])) { |
||||
|
throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); |
||||
|
} |
||||
|
$uri = $uri->withHost($records[0]['ip']); |
||||
|
} elseif ('v6' === $options['force_ip_resolve']) { |
||||
|
$records = dns_get_record($uri->getHost(), DNS_AAAA); |
||||
|
if (!isset($records[0]['ipv6'])) { |
||||
|
throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); |
||||
|
} |
||||
|
$uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $uri; |
||||
|
} |
||||
|
|
||||
|
private function getDefaultContext(RequestInterface $request) |
||||
|
{ |
||||
|
$headers = ''; |
||||
|
foreach ($request->getHeaders() as $name => $value) { |
||||
|
foreach ($value as $val) { |
||||
|
$headers .= "$name: $val\r\n"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$context = [ |
||||
|
'http' => [ |
||||
|
'method' => $request->getMethod(), |
||||
|
'header' => $headers, |
||||
|
'protocol_version' => $request->getProtocolVersion(), |
||||
|
'ignore_errors' => true, |
||||
|
'follow_location' => 0, |
||||
|
], |
||||
|
]; |
||||
|
|
||||
|
$body = (string) $request->getBody(); |
||||
|
|
||||
|
if (!empty($body)) { |
||||
|
$context['http']['content'] = $body; |
||||
|
// Prevent the HTTP handler from adding a Content-Type header. |
||||
|
if (!$request->hasHeader('Content-Type')) { |
||||
|
$context['http']['header'] .= "Content-Type:\r\n"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$context['http']['header'] = rtrim($context['http']['header']); |
||||
|
|
||||
|
return $context; |
||||
|
} |
||||
|
|
||||
|
private function add_proxy(RequestInterface $request, &$options, $value, &$params) |
||||
|
{ |
||||
|
if (!is_array($value)) { |
||||
|
$options['http']['proxy'] = $value; |
||||
|
} else { |
||||
|
$scheme = $request->getUri()->getScheme(); |
||||
|
if (isset($value[$scheme])) { |
||||
|
if (!isset($value['no']) |
||||
|
|| !\GuzzleHttp\is_host_in_noproxy( |
||||
|
$request->getUri()->getHost(), |
||||
|
$value['no'] |
||||
|
) |
||||
|
) { |
||||
|
$options['http']['proxy'] = $value[$scheme]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function add_timeout(RequestInterface $request, &$options, $value, &$params) |
||||
|
{ |
||||
|
if ($value > 0) { |
||||
|
$options['http']['timeout'] = $value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function add_verify(RequestInterface $request, &$options, $value, &$params) |
||||
|
{ |
||||
|
if ($value === true) { |
||||
|
// PHP 5.6 or greater will find the system cert by default. When |
||||
|
// < 5.6, use the Guzzle bundled cacert. |
||||
|
if (PHP_VERSION_ID < 50600) { |
||||
|
$options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); |
||||
|
} |
||||
|
} elseif (is_string($value)) { |
||||
|
$options['ssl']['cafile'] = $value; |
||||
|
if (!file_exists($value)) { |
||||
|
throw new \RuntimeException("SSL CA bundle not found: $value"); |
||||
|
} |
||||
|
} elseif ($value === false) { |
||||
|
$options['ssl']['verify_peer'] = false; |
||||
|
$options['ssl']['verify_peer_name'] = false; |
||||
|
return; |
||||
|
} else { |
||||
|
throw new \InvalidArgumentException('Invalid verify request option'); |
||||
|
} |
||||
|
|
||||
|
$options['ssl']['verify_peer'] = true; |
||||
|
$options['ssl']['verify_peer_name'] = true; |
||||
|
$options['ssl']['allow_self_signed'] = false; |
||||
|
} |
||||
|
|
||||
|
private function add_cert(RequestInterface $request, &$options, $value, &$params) |
||||
|
{ |
||||
|
if (is_array($value)) { |
||||
|
$options['ssl']['passphrase'] = $value[1]; |
||||
|
$value = $value[0]; |
||||
|
} |
||||
|
|
||||
|
if (!file_exists($value)) { |
||||
|
throw new \RuntimeException("SSL certificate not found: {$value}"); |
||||
|
} |
||||
|
|
||||
|
$options['ssl']['local_cert'] = $value; |
||||
|
} |
||||
|
|
||||
|
private function add_progress(RequestInterface $request, &$options, $value, &$params) |
||||
|
{ |
||||
|
$this->addNotification( |
||||
|
$params, |
||||
|
function ($code, $a, $b, $c, $transferred, $total) use ($value) { |
||||
|
if ($code == STREAM_NOTIFY_PROGRESS) { |
||||
|
$value($total, $transferred, null, null); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private function add_debug(RequestInterface $request, &$options, $value, &$params) |
||||
|
{ |
||||
|
if ($value === false) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
static $map = [ |
||||
|
STREAM_NOTIFY_CONNECT => 'CONNECT', |
||||
|
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', |
||||
|
STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', |
||||
|
STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', |
||||
|
STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', |
||||
|
STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', |
||||
|
STREAM_NOTIFY_PROGRESS => 'PROGRESS', |
||||
|
STREAM_NOTIFY_FAILURE => 'FAILURE', |
||||
|
STREAM_NOTIFY_COMPLETED => 'COMPLETED', |
||||
|
STREAM_NOTIFY_RESOLVE => 'RESOLVE', |
||||
|
]; |
||||
|
static $args = ['severity', 'message', 'message_code', |
||||
|
'bytes_transferred', 'bytes_max']; |
||||
|
|
||||
|
$value = \GuzzleHttp\debug_resource($value); |
||||
|
$ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); |
||||
|
$this->addNotification( |
||||
|
$params, |
||||
|
function () use ($ident, $value, $map, $args) { |
||||
|
$passed = func_get_args(); |
||||
|
$code = array_shift($passed); |
||||
|
fprintf($value, '<%s> [%s] ', $ident, $map[$code]); |
||||
|
foreach (array_filter($passed) as $i => $v) { |
||||
|
fwrite($value, $args[$i] . ': "' . $v . '" '); |
||||
|
} |
||||
|
fwrite($value, "\n"); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private function addNotification(array &$params, callable $notify) |
||||
|
{ |
||||
|
// Wrap the existing function if needed. |
||||
|
if (!isset($params['notification'])) { |
||||
|
$params['notification'] = $notify; |
||||
|
} else { |
||||
|
$params['notification'] = $this->callArray([ |
||||
|
$params['notification'], |
||||
|
$notify |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private function callArray(array $functions) |
||||
|
{ |
||||
|
return function () use ($functions) { |
||||
|
$args = func_get_args(); |
||||
|
foreach ($functions as $fn) { |
||||
|
call_user_func_array($fn, $args); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
@ -0,0 +1,273 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
/** |
||||
|
* Creates a composed Guzzle handler function by stacking middlewares on top of |
||||
|
* an HTTP handler function. |
||||
|
*/ |
||||
|
class HandlerStack |
||||
|
{ |
||||
|
/** @var callable */ |
||||
|
private $handler; |
||||
|
|
||||
|
/** @var array */ |
||||
|
private $stack = []; |
||||
|
|
||||
|
/** @var callable|null */ |
||||
|
private $cached; |
||||
|
|
||||
|
/** |
||||
|
* Creates a default handler stack that can be used by clients. |
||||
|
* |
||||
|
* The returned handler will wrap the provided handler or use the most |
||||
|
* appropriate default handler for you system. The returned HandlerStack has |
||||
|
* support for cookies, redirects, HTTP error exceptions, and preparing a body |
||||
|
* before sending. |
||||
|
* |
||||
|
* The returned handler stack can be passed to a client in the "handler" |
||||
|
* option. |
||||
|
* |
||||
|
* @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 HandlerStack |
||||
|
*/ |
||||
|
public static function create(callable $handler = null) |
||||
|
{ |
||||
|
$stack = new self($handler ?: choose_handler()); |
||||
|
$stack->push(Middleware::httpErrors(), 'http_errors'); |
||||
|
$stack->push(Middleware::redirect(), 'allow_redirects'); |
||||
|
$stack->push(Middleware::cookies(), 'cookies'); |
||||
|
$stack->push(Middleware::prepareBody(), 'prepare_body'); |
||||
|
|
||||
|
return $stack; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param callable $handler Underlying HTTP handler. |
||||
|
*/ |
||||
|
public function __construct(callable $handler = null) |
||||
|
{ |
||||
|
$this->handler = $handler; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Invokes the handler stack as a composed handler |
||||
|
* |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
*/ |
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
$handler = $this->resolve(); |
||||
|
|
||||
|
return $handler($request, $options); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Dumps a string representation of the stack. |
||||
|
* |
||||
|
* @return string |
||||
|
*/ |
||||
|
public function __toString() |
||||
|
{ |
||||
|
$depth = 0; |
||||
|
$stack = []; |
||||
|
if ($this->handler) { |
||||
|
$stack[] = "0) Handler: " . $this->debugCallable($this->handler); |
||||
|
} |
||||
|
|
||||
|
$result = ''; |
||||
|
foreach (array_reverse($this->stack) as $tuple) { |
||||
|
$depth++; |
||||
|
$str = "{$depth}) Name: '{$tuple[1]}', "; |
||||
|
$str .= "Function: " . $this->debugCallable($tuple[0]); |
||||
|
$result = "> {$str}\n{$result}"; |
||||
|
$stack[] = $str; |
||||
|
} |
||||
|
|
||||
|
foreach (array_keys($stack) as $k) { |
||||
|
$result .= "< {$stack[$k]}\n"; |
||||
|
} |
||||
|
|
||||
|
return $result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the HTTP handler that actually returns a promise. |
||||
|
* |
||||
|
* @param callable $handler Accepts a request and array of options and |
||||
|
* returns a Promise. |
||||
|
*/ |
||||
|
public function setHandler(callable $handler) |
||||
|
{ |
||||
|
$this->handler = $handler; |
||||
|
$this->cached = null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns true if the builder has a handler. |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function hasHandler() |
||||
|
{ |
||||
|
return (bool) $this->handler; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Unshift a middleware to the bottom of the stack. |
||||
|
* |
||||
|
* @param callable $middleware Middleware function |
||||
|
* @param string $name Name to register for this middleware. |
||||
|
*/ |
||||
|
public function unshift(callable $middleware, $name = null) |
||||
|
{ |
||||
|
array_unshift($this->stack, [$middleware, $name]); |
||||
|
$this->cached = null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Push a middleware to the top of the stack. |
||||
|
* |
||||
|
* @param callable $middleware Middleware function |
||||
|
* @param string $name Name to register for this middleware. |
||||
|
*/ |
||||
|
public function push(callable $middleware, $name = '') |
||||
|
{ |
||||
|
$this->stack[] = [$middleware, $name]; |
||||
|
$this->cached = null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Add a middleware before another middleware by name. |
||||
|
* |
||||
|
* @param string $findName Middleware to find |
||||
|
* @param callable $middleware Middleware function |
||||
|
* @param string $withName Name to register for this middleware. |
||||
|
*/ |
||||
|
public function before($findName, callable $middleware, $withName = '') |
||||
|
{ |
||||
|
$this->splice($findName, $withName, $middleware, true); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Add a middleware after another middleware by name. |
||||
|
* |
||||
|
* @param string $findName Middleware to find |
||||
|
* @param callable $middleware Middleware function |
||||
|
* @param string $withName Name to register for this middleware. |
||||
|
*/ |
||||
|
public function after($findName, callable $middleware, $withName = '') |
||||
|
{ |
||||
|
$this->splice($findName, $withName, $middleware, false); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove a middleware by instance or name from the stack. |
||||
|
* |
||||
|
* @param callable|string $remove Middleware to remove by instance or name. |
||||
|
*/ |
||||
|
public function remove($remove) |
||||
|
{ |
||||
|
$this->cached = null; |
||||
|
$idx = is_callable($remove) ? 0 : 1; |
||||
|
$this->stack = array_values(array_filter( |
||||
|
$this->stack, |
||||
|
function ($tuple) use ($idx, $remove) { |
||||
|
return $tuple[$idx] !== $remove; |
||||
|
} |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Compose the middleware and handler into a single callable function. |
||||
|
* |
||||
|
* @return callable |
||||
|
*/ |
||||
|
public function resolve() |
||||
|
{ |
||||
|
if (!$this->cached) { |
||||
|
if (!($prev = $this->handler)) { |
||||
|
throw new \LogicException('No handler has been specified'); |
||||
|
} |
||||
|
|
||||
|
foreach (array_reverse($this->stack) as $fn) { |
||||
|
$prev = $fn[0]($prev); |
||||
|
} |
||||
|
|
||||
|
$this->cached = $prev; |
||||
|
} |
||||
|
|
||||
|
return $this->cached; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param $name |
||||
|
* @return int |
||||
|
*/ |
||||
|
private function findByName($name) |
||||
|
{ |
||||
|
foreach ($this->stack as $k => $v) { |
||||
|
if ($v[1] === $name) { |
||||
|
return $k; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new \InvalidArgumentException("Middleware not found: $name"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Splices a function into the middleware list at a specific position. |
||||
|
* |
||||
|
* @param $findName |
||||
|
* @param $withName |
||||
|
* @param callable $middleware |
||||
|
* @param $before |
||||
|
*/ |
||||
|
private function splice($findName, $withName, callable $middleware, $before) |
||||
|
{ |
||||
|
$this->cached = null; |
||||
|
$idx = $this->findByName($findName); |
||||
|
$tuple = [$middleware, $withName]; |
||||
|
|
||||
|
if ($before) { |
||||
|
if ($idx === 0) { |
||||
|
array_unshift($this->stack, $tuple); |
||||
|
} else { |
||||
|
$replacement = [$tuple, $this->stack[$idx]]; |
||||
|
array_splice($this->stack, $idx, 1, $replacement); |
||||
|
} |
||||
|
} elseif ($idx === count($this->stack) - 1) { |
||||
|
$this->stack[] = $tuple; |
||||
|
} else { |
||||
|
$replacement = [$this->stack[$idx], $tuple]; |
||||
|
array_splice($this->stack, $idx, 1, $replacement); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Provides a debug string for a given callable. |
||||
|
* |
||||
|
* @param array|callable $fn Function to write as a string. |
||||
|
* |
||||
|
* @return string |
||||
|
*/ |
||||
|
private function debugCallable($fn) |
||||
|
{ |
||||
|
if (is_string($fn)) { |
||||
|
return "callable({$fn})"; |
||||
|
} |
||||
|
|
||||
|
if (is_array($fn)) { |
||||
|
return is_string($fn[0]) |
||||
|
? "callable({$fn[0]}::{$fn[1]})" |
||||
|
: "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; |
||||
|
} |
||||
|
|
||||
|
return 'callable(' . spl_object_hash($fn) . ')'; |
||||
|
} |
||||
|
} |
@ -0,0 +1,182 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use Psr\Http\Message\MessageInterface; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
|
||||
|
/** |
||||
|
* Formats log messages using variable substitutions for requests, responses, |
||||
|
* and other transactional data. |
||||
|
* |
||||
|
* The following variable substitutions are supported: |
||||
|
* |
||||
|
* - {request}: Full HTTP request message |
||||
|
* - {response}: Full HTTP response message |
||||
|
* - {ts}: ISO 8601 date in GMT |
||||
|
* - {date_iso_8601} ISO 8601 date in GMT |
||||
|
* - {date_common_log} Apache common log date using the configured timezone. |
||||
|
* - {host}: Host of the request |
||||
|
* - {method}: Method of the request |
||||
|
* - {uri}: URI of the request |
||||
|
* - {host}: Host of the request |
||||
|
* - {version}: Protocol version |
||||
|
* - {target}: Request target of the request (path + query + fragment) |
||||
|
* - {hostname}: Hostname of the machine that sent the request |
||||
|
* - {code}: Status code of the response (if available) |
||||
|
* - {phrase}: Reason phrase of the response (if available) |
||||
|
* - {error}: Any error messages (if available) |
||||
|
* - {req_header_*}: Replace `*` with the lowercased name of a request header to add to the message |
||||
|
* - {res_header_*}: Replace `*` with the lowercased name of a response header to add to the message |
||||
|
* - {req_headers}: Request headers |
||||
|
* - {res_headers}: Response headers |
||||
|
* - {req_body}: Request body |
||||
|
* - {res_body}: Response body |
||||
|
*/ |
||||
|
class MessageFormatter |
||||
|
{ |
||||
|
/** |
||||
|
* Apache Common Log Format. |
||||
|
* @link http://httpd.apache.org/docs/2.4/logs.html#common |
||||
|
* @var string |
||||
|
*/ |
||||
|
const CLF = "{hostname} {req_header_User-Agent} - [{date_common_log}] \"{method} {target} HTTP/{version}\" {code} {res_header_Content-Length}"; |
||||
|
const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; |
||||
|
const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; |
||||
|
|
||||
|
/** @var string Template used to format log messages */ |
||||
|
private $template; |
||||
|
|
||||
|
/** |
||||
|
* @param string $template Log message template |
||||
|
*/ |
||||
|
public function __construct($template = self::CLF) |
||||
|
{ |
||||
|
$this->template = $template ?: self::CLF; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns a formatted message string. |
||||
|
* |
||||
|
* @param RequestInterface $request Request that was sent |
||||
|
* @param ResponseInterface $response Response that was received |
||||
|
* @param \Exception $error Exception that was received |
||||
|
* |
||||
|
* @return string |
||||
|
*/ |
||||
|
public function format( |
||||
|
RequestInterface $request, |
||||
|
ResponseInterface $response = null, |
||||
|
\Exception $error = null |
||||
|
) { |
||||
|
$cache = []; |
||||
|
|
||||
|
return preg_replace_callback( |
||||
|
'/{\s*([A-Za-z_\-\.0-9]+)\s*}/', |
||||
|
function (array $matches) use ($request, $response, $error, &$cache) { |
||||
|
|
||||
|
if (isset($cache[$matches[1]])) { |
||||
|
return $cache[$matches[1]]; |
||||
|
} |
||||
|
|
||||
|
$result = ''; |
||||
|
switch ($matches[1]) { |
||||
|
case 'request': |
||||
|
$result = Psr7\str($request); |
||||
|
break; |
||||
|
case 'response': |
||||
|
$result = $response ? Psr7\str($response) : ''; |
||||
|
break; |
||||
|
case 'req_headers': |
||||
|
$result = trim($request->getMethod() |
||||
|
. ' ' . $request->getRequestTarget()) |
||||
|
. ' HTTP/' . $request->getProtocolVersion() . "\r\n" |
||||
|
. $this->headers($request); |
||||
|
break; |
||||
|
case 'res_headers': |
||||
|
$result = $response ? |
||||
|
sprintf( |
||||
|
'HTTP/%s %d %s', |
||||
|
$response->getProtocolVersion(), |
||||
|
$response->getStatusCode(), |
||||
|
$response->getReasonPhrase() |
||||
|
) . "\r\n" . $this->headers($response) |
||||
|
: 'NULL'; |
||||
|
break; |
||||
|
case 'req_body': |
||||
|
$result = $request->getBody(); |
||||
|
break; |
||||
|
case 'res_body': |
||||
|
$result = $response ? $response->getBody() : 'NULL'; |
||||
|
break; |
||||
|
case 'ts': |
||||
|
case 'date_iso_8601': |
||||
|
$result = gmdate('c'); |
||||
|
break; |
||||
|
case 'date_common_log': |
||||
|
$result = date('d/M/Y:H:i:s O'); |
||||
|
break; |
||||
|
case 'method': |
||||
|
$result = $request->getMethod(); |
||||
|
break; |
||||
|
case 'version': |
||||
|
$result = $request->getProtocolVersion(); |
||||
|
break; |
||||
|
case 'uri': |
||||
|
case 'url': |
||||
|
$result = $request->getUri(); |
||||
|
break; |
||||
|
case 'target': |
||||
|
$result = $request->getRequestTarget(); |
||||
|
break; |
||||
|
case 'req_version': |
||||
|
$result = $request->getProtocolVersion(); |
||||
|
break; |
||||
|
case 'res_version': |
||||
|
$result = $response |
||||
|
? $response->getProtocolVersion() |
||||
|
: 'NULL'; |
||||
|
break; |
||||
|
case 'host': |
||||
|
$result = $request->getHeaderLine('Host'); |
||||
|
break; |
||||
|
case 'hostname': |
||||
|
$result = gethostname(); |
||||
|
break; |
||||
|
case 'code': |
||||
|
$result = $response ? $response->getStatusCode() : 'NULL'; |
||||
|
break; |
||||
|
case 'phrase': |
||||
|
$result = $response ? $response->getReasonPhrase() : 'NULL'; |
||||
|
break; |
||||
|
case 'error': |
||||
|
$result = $error ? $error->getMessage() : 'NULL'; |
||||
|
break; |
||||
|
default: |
||||
|
// handle prefixed dynamic headers |
||||
|
if (strpos($matches[1], 'req_header_') === 0) { |
||||
|
$result = $request->getHeaderLine(substr($matches[1], 11)); |
||||
|
} elseif (strpos($matches[1], 'res_header_') === 0) { |
||||
|
$result = $response |
||||
|
? $response->getHeaderLine(substr($matches[1], 11)) |
||||
|
: 'NULL'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$cache[$matches[1]] = $result; |
||||
|
return $result; |
||||
|
}, |
||||
|
$this->template |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private function headers(MessageInterface $message) |
||||
|
{ |
||||
|
$result = ''; |
||||
|
foreach ($message->getHeaders() as $name => $values) { |
||||
|
$result .= $name . ': ' . implode(', ', $values) . "\r\n"; |
||||
|
} |
||||
|
|
||||
|
return trim($result); |
||||
|
} |
||||
|
} |
@ -0,0 +1,254 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use GuzzleHttp\Cookie\CookieJarInterface; |
||||
|
use GuzzleHttp\Exception\RequestException; |
||||
|
use GuzzleHttp\Promise\RejectedPromise; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
use Psr\Log\LoggerInterface; |
||||
|
use Psr\Log\LogLevel; |
||||
|
|
||||
|
/** |
||||
|
* Functions used to create and wrap handlers with handler middleware. |
||||
|
*/ |
||||
|
final class Middleware |
||||
|
{ |
||||
|
/** |
||||
|
* Middleware that adds cookies to requests. |
||||
|
* |
||||
|
* The options array must be set to a CookieJarInterface in order to use |
||||
|
* cookies. This is typically handled for you by a client. |
||||
|
* |
||||
|
* @return callable Returns a function that accepts the next handler. |
||||
|
*/ |
||||
|
public static function cookies() |
||||
|
{ |
||||
|
return function (callable $handler) { |
||||
|
return function ($request, array $options) use ($handler) { |
||||
|
if (empty($options['cookies'])) { |
||||
|
return $handler($request, $options); |
||||
|
} elseif (!($options['cookies'] instanceof CookieJarInterface)) { |
||||
|
throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface'); |
||||
|
} |
||||
|
$cookieJar = $options['cookies']; |
||||
|
$request = $cookieJar->withCookieHeader($request); |
||||
|
return $handler($request, $options) |
||||
|
->then(function ($response) use ($cookieJar, $request) { |
||||
|
$cookieJar->extractCookies($request, $response); |
||||
|
return $response; |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that throws exceptions for 4xx or 5xx responses when the |
||||
|
* "http_error" request option is set to true. |
||||
|
* |
||||
|
* @return callable Returns a function that accepts the next handler. |
||||
|
*/ |
||||
|
public static function httpErrors() |
||||
|
{ |
||||
|
return function (callable $handler) { |
||||
|
return function ($request, array $options) use ($handler) { |
||||
|
if (empty($options['http_errors'])) { |
||||
|
return $handler($request, $options); |
||||
|
} |
||||
|
return $handler($request, $options)->then( |
||||
|
function (ResponseInterface $response) use ($request, $handler) { |
||||
|
$code = $response->getStatusCode(); |
||||
|
if ($code < 400) { |
||||
|
return $response; |
||||
|
} |
||||
|
throw RequestException::create($request, $response); |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that pushes history data to an ArrayAccess container. |
||||
|
* |
||||
|
* @param array $container Container to hold the history (by reference). |
||||
|
* |
||||
|
* @return callable Returns a function that accepts the next handler. |
||||
|
* @throws \InvalidArgumentException if container is not an array or ArrayAccess. |
||||
|
*/ |
||||
|
public static function history(&$container) |
||||
|
{ |
||||
|
if (!is_array($container) && !$container instanceof \ArrayAccess) { |
||||
|
throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); |
||||
|
} |
||||
|
|
||||
|
return function (callable $handler) use (&$container) { |
||||
|
return function ($request, array $options) use ($handler, &$container) { |
||||
|
return $handler($request, $options)->then( |
||||
|
function ($value) use ($request, &$container, $options) { |
||||
|
$container[] = [ |
||||
|
'request' => $request, |
||||
|
'response' => $value, |
||||
|
'error' => null, |
||||
|
'options' => $options |
||||
|
]; |
||||
|
return $value; |
||||
|
}, |
||||
|
function ($reason) use ($request, &$container, $options) { |
||||
|
$container[] = [ |
||||
|
'request' => $request, |
||||
|
'response' => null, |
||||
|
'error' => $reason, |
||||
|
'options' => $options |
||||
|
]; |
||||
|
return \GuzzleHttp\Promise\rejection_for($reason); |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that invokes a callback before and after sending a request. |
||||
|
* |
||||
|
* The provided listener cannot modify or alter the response. It simply |
||||
|
* "taps" into the chain to be notified before returning the promise. The |
||||
|
* before listener accepts a request and options array, and the after |
||||
|
* listener accepts a request, options array, and response promise. |
||||
|
* |
||||
|
* @param callable $before Function to invoke before forwarding the request. |
||||
|
* @param callable $after Function invoked after forwarding. |
||||
|
* |
||||
|
* @return callable Returns a function that accepts the next handler. |
||||
|
*/ |
||||
|
public static function tap(callable $before = null, callable $after = null) |
||||
|
{ |
||||
|
return function (callable $handler) use ($before, $after) { |
||||
|
return function ($request, array $options) use ($handler, $before, $after) { |
||||
|
if ($before) { |
||||
|
$before($request, $options); |
||||
|
} |
||||
|
$response = $handler($request, $options); |
||||
|
if ($after) { |
||||
|
$after($request, $options, $response); |
||||
|
} |
||||
|
return $response; |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that handles request redirects. |
||||
|
* |
||||
|
* @return callable Returns a function that accepts the next handler. |
||||
|
*/ |
||||
|
public static function redirect() |
||||
|
{ |
||||
|
return function (callable $handler) { |
||||
|
return new RedirectMiddleware($handler); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that retries requests based on the boolean result of |
||||
|
* invoking the provided "decider" function. |
||||
|
* |
||||
|
* If no delay function is provided, a simple implementation of exponential |
||||
|
* backoff will be utilized. |
||||
|
* |
||||
|
* @param callable $decider Function that accepts the number of retries, |
||||
|
* a request, [response], and [exception] and |
||||
|
* returns true if the request is to be retried. |
||||
|
* @param callable $delay Function that accepts the number of retries and |
||||
|
* returns the number of milliseconds to delay. |
||||
|
* |
||||
|
* @return callable Returns a function that accepts the next handler. |
||||
|
*/ |
||||
|
public static function retry(callable $decider, callable $delay = null) |
||||
|
{ |
||||
|
return function (callable $handler) use ($decider, $delay) { |
||||
|
return new RetryMiddleware($decider, $handler, $delay); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that logs requests, responses, and errors using a message |
||||
|
* formatter. |
||||
|
* |
||||
|
* @param LoggerInterface $logger Logs messages. |
||||
|
* @param MessageFormatter $formatter Formatter used to create message strings. |
||||
|
* @param string $logLevel Level at which to log requests. |
||||
|
* |
||||
|
* @return callable Returns a function that accepts the next handler. |
||||
|
*/ |
||||
|
public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) |
||||
|
{ |
||||
|
return function (callable $handler) use ($logger, $formatter, $logLevel) { |
||||
|
return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { |
||||
|
return $handler($request, $options)->then( |
||||
|
function ($response) use ($logger, $request, $formatter, $logLevel) { |
||||
|
$message = $formatter->format($request, $response); |
||||
|
$logger->log($logLevel, $message); |
||||
|
return $response; |
||||
|
}, |
||||
|
function ($reason) use ($logger, $request, $formatter) { |
||||
|
$response = $reason instanceof RequestException |
||||
|
? $reason->getResponse() |
||||
|
: null; |
||||
|
$message = $formatter->format($request, $response, $reason); |
||||
|
$logger->notice($message); |
||||
|
return \GuzzleHttp\Promise\rejection_for($reason); |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This middleware adds a default content-type if possible, a default |
||||
|
* content-length or transfer-encoding header, and the expect header. |
||||
|
* |
||||
|
* @return callable |
||||
|
*/ |
||||
|
public static function prepareBody() |
||||
|
{ |
||||
|
return function (callable $handler) { |
||||
|
return new PrepareBodyMiddleware($handler); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that applies a map function to the request before passing to |
||||
|
* the next handler. |
||||
|
* |
||||
|
* @param callable $fn Function that accepts a RequestInterface and returns |
||||
|
* a RequestInterface. |
||||
|
* @return callable |
||||
|
*/ |
||||
|
public static function mapRequest(callable $fn) |
||||
|
{ |
||||
|
return function (callable $handler) use ($fn) { |
||||
|
return function ($request, array $options) use ($handler, $fn) { |
||||
|
return $handler($fn($request), $options); |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Middleware that applies a map function to the resolved promise's |
||||
|
* response. |
||||
|
* |
||||
|
* @param callable $fn Function that accepts a ResponseInterface and |
||||
|
* returns a ResponseInterface. |
||||
|
* @return callable |
||||
|
*/ |
||||
|
public static function mapResponse(callable $fn) |
||||
|
{ |
||||
|
return function (callable $handler) use ($fn) { |
||||
|
return function ($request, array $options) use ($handler, $fn) { |
||||
|
return $handler($request, $options)->then($fn); |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
} |
@ -0,0 +1,123 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use GuzzleHttp\Promise\PromisorInterface; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use GuzzleHttp\Promise\EachPromise; |
||||
|
|
||||
|
/** |
||||
|
* Sends and iterator of requests concurrently using a capped pool size. |
||||
|
* |
||||
|
* The pool will read from an iterator until it is cancelled or until the |
||||
|
* iterator is consumed. When a request is yielded, the request is sent after |
||||
|
* applying the "request_options" request options (if provided in the ctor). |
||||
|
* |
||||
|
* When a function is yielded by the iterator, the function is provided the |
||||
|
* "request_options" array that should be merged on top of any existing |
||||
|
* options, and the function MUST then return a wait-able promise. |
||||
|
*/ |
||||
|
class Pool implements PromisorInterface |
||||
|
{ |
||||
|
/** @var EachPromise */ |
||||
|
private $each; |
||||
|
|
||||
|
/** |
||||
|
* @param ClientInterface $client Client used to send the requests. |
||||
|
* @param array|\Iterator $requests Requests or functions that return |
||||
|
* requests to send concurrently. |
||||
|
* @param array $config Associative array of options |
||||
|
* - concurrency: (int) Maximum number of requests to send concurrently |
||||
|
* - options: Array of request options to apply to each request. |
||||
|
* - fulfilled: (callable) Function to invoke when a request completes. |
||||
|
* - rejected: (callable) Function to invoke when a request is rejected. |
||||
|
*/ |
||||
|
public function __construct( |
||||
|
ClientInterface $client, |
||||
|
$requests, |
||||
|
array $config = [] |
||||
|
) { |
||||
|
// Backwards compatibility. |
||||
|
if (isset($config['pool_size'])) { |
||||
|
$config['concurrency'] = $config['pool_size']; |
||||
|
} elseif (!isset($config['concurrency'])) { |
||||
|
$config['concurrency'] = 25; |
||||
|
} |
||||
|
|
||||
|
if (isset($config['options'])) { |
||||
|
$opts = $config['options']; |
||||
|
unset($config['options']); |
||||
|
} else { |
||||
|
$opts = []; |
||||
|
} |
||||
|
|
||||
|
$iterable = \GuzzleHttp\Promise\iter_for($requests); |
||||
|
$requests = function () use ($iterable, $client, $opts) { |
||||
|
foreach ($iterable as $key => $rfn) { |
||||
|
if ($rfn instanceof RequestInterface) { |
||||
|
yield $key => $client->sendAsync($rfn, $opts); |
||||
|
} elseif (is_callable($rfn)) { |
||||
|
yield $key => $rfn($opts); |
||||
|
} else { |
||||
|
throw new \InvalidArgumentException('Each value yielded by ' |
||||
|
. 'the iterator must be a Psr7\Http\Message\RequestInterface ' |
||||
|
. 'or a callable that returns a promise that fulfills ' |
||||
|
. 'with a Psr7\Message\Http\ResponseInterface object.'); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
$this->each = new EachPromise($requests(), $config); |
||||
|
} |
||||
|
|
||||
|
public function promise() |
||||
|
{ |
||||
|
return $this->each->promise(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sends multiple requests concurrently and returns an array of responses |
||||
|
* and exceptions that uses the same ordering as the provided requests. |
||||
|
* |
||||
|
* IMPORTANT: This method keeps every request and response in memory, and |
||||
|
* as such, is NOT recommended when sending a large number or an |
||||
|
* indeterminate number of requests concurrently. |
||||
|
* |
||||
|
* @param ClientInterface $client Client used to send the requests |
||||
|
* @param array|\Iterator $requests Requests to send concurrently. |
||||
|
* @param array $options Passes through the options available in |
||||
|
* {@see GuzzleHttp\Pool::__construct} |
||||
|
* |
||||
|
* @return array Returns an array containing the response or an exception |
||||
|
* in the same order that the requests were sent. |
||||
|
* @throws \InvalidArgumentException if the event format is incorrect. |
||||
|
*/ |
||||
|
public static function batch( |
||||
|
ClientInterface $client, |
||||
|
$requests, |
||||
|
array $options = [] |
||||
|
) { |
||||
|
$res = []; |
||||
|
self::cmpCallback($options, 'fulfilled', $res); |
||||
|
self::cmpCallback($options, 'rejected', $res); |
||||
|
$pool = new static($client, $requests, $options); |
||||
|
$pool->promise()->wait(); |
||||
|
ksort($res); |
||||
|
|
||||
|
return $res; |
||||
|
} |
||||
|
|
||||
|
private static function cmpCallback(array &$options, $name, array &$results) |
||||
|
{ |
||||
|
if (!isset($options[$name])) { |
||||
|
$options[$name] = function ($v, $k) use (&$results) { |
||||
|
$results[$k] = $v; |
||||
|
}; |
||||
|
} else { |
||||
|
$currentFn = $options[$name]; |
||||
|
$options[$name] = function ($v, $k) use (&$results, $currentFn) { |
||||
|
$currentFn($v, $k); |
||||
|
$results[$k] = $v; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,106 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use GuzzleHttp\Promise\PromiseInterface; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
|
||||
|
/** |
||||
|
* Prepares requests that contain a body, adding the Content-Length, |
||||
|
* Content-Type, and Expect headers. |
||||
|
*/ |
||||
|
class PrepareBodyMiddleware |
||||
|
{ |
||||
|
/** @var callable */ |
||||
|
private $nextHandler; |
||||
|
|
||||
|
/** |
||||
|
* @param callable $nextHandler Next handler to invoke. |
||||
|
*/ |
||||
|
public function __construct(callable $nextHandler) |
||||
|
{ |
||||
|
$this->nextHandler = $nextHandler; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
* |
||||
|
* @return PromiseInterface |
||||
|
*/ |
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
$fn = $this->nextHandler; |
||||
|
|
||||
|
// Don't do anything if the request has no body. |
||||
|
if ($request->getBody()->getSize() === 0) { |
||||
|
return $fn($request, $options); |
||||
|
} |
||||
|
|
||||
|
$modify = []; |
||||
|
|
||||
|
// Add a default content-type if possible. |
||||
|
if (!$request->hasHeader('Content-Type')) { |
||||
|
if ($uri = $request->getBody()->getMetadata('uri')) { |
||||
|
if ($type = Psr7\mimetype_from_filename($uri)) { |
||||
|
$modify['set_headers']['Content-Type'] = $type; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Add a default content-length or transfer-encoding header. |
||||
|
if (!$request->hasHeader('Content-Length') |
||||
|
&& !$request->hasHeader('Transfer-Encoding') |
||||
|
) { |
||||
|
$size = $request->getBody()->getSize(); |
||||
|
if ($size !== null) { |
||||
|
$modify['set_headers']['Content-Length'] = $size; |
||||
|
} else { |
||||
|
$modify['set_headers']['Transfer-Encoding'] = 'chunked'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Add the expect header if needed. |
||||
|
$this->addExpectHeader($request, $options, $modify); |
||||
|
|
||||
|
return $fn(Psr7\modify_request($request, $modify), $options); |
||||
|
} |
||||
|
|
||||
|
private function addExpectHeader( |
||||
|
RequestInterface $request, |
||||
|
array $options, |
||||
|
array &$modify |
||||
|
) { |
||||
|
// Determine if the Expect header should be used |
||||
|
if ($request->hasHeader('Expect')) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
$expect = isset($options['expect']) ? $options['expect'] : null; |
||||
|
|
||||
|
// Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 |
||||
|
if ($expect === false || $request->getProtocolVersion() < 1.1) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// The expect header is unconditionally enabled |
||||
|
if ($expect === true) { |
||||
|
$modify['set_headers']['Expect'] = '100-Continue'; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// By default, send the expect header when the payload is > 1mb |
||||
|
if ($expect === null) { |
||||
|
$expect = 1048576; |
||||
|
} |
||||
|
|
||||
|
// Always add if the body cannot be rewound, the size cannot be |
||||
|
// determined, or the size is greater than the cutoff threshold |
||||
|
$body = $request->getBody(); |
||||
|
$size = $body->getSize(); |
||||
|
|
||||
|
if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { |
||||
|
$modify['set_headers']['Expect'] = '100-Continue'; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,237 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use GuzzleHttp\Exception\BadResponseException; |
||||
|
use GuzzleHttp\Exception\TooManyRedirectsException; |
||||
|
use GuzzleHttp\Promise\PromiseInterface; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
use Psr\Http\Message\UriInterface; |
||||
|
|
||||
|
/** |
||||
|
* Request redirect middleware. |
||||
|
* |
||||
|
* Apply this middleware like other middleware using |
||||
|
* {@see GuzzleHttp\Middleware::redirect()}. |
||||
|
*/ |
||||
|
class RedirectMiddleware |
||||
|
{ |
||||
|
const HISTORY_HEADER = 'X-Guzzle-Redirect-History'; |
||||
|
|
||||
|
const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History'; |
||||
|
|
||||
|
public static $defaultSettings = [ |
||||
|
'max' => 5, |
||||
|
'protocols' => ['http', 'https'], |
||||
|
'strict' => false, |
||||
|
'referer' => false, |
||||
|
'track_redirects' => false, |
||||
|
]; |
||||
|
|
||||
|
/** @var callable */ |
||||
|
private $nextHandler; |
||||
|
|
||||
|
/** |
||||
|
* @param callable $nextHandler Next handler to invoke. |
||||
|
*/ |
||||
|
public function __construct(callable $nextHandler) |
||||
|
{ |
||||
|
$this->nextHandler = $nextHandler; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
* |
||||
|
* @return PromiseInterface |
||||
|
*/ |
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
$fn = $this->nextHandler; |
||||
|
|
||||
|
if (empty($options['allow_redirects'])) { |
||||
|
return $fn($request, $options); |
||||
|
} |
||||
|
|
||||
|
if ($options['allow_redirects'] === true) { |
||||
|
$options['allow_redirects'] = self::$defaultSettings; |
||||
|
} elseif (!is_array($options['allow_redirects'])) { |
||||
|
throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); |
||||
|
} else { |
||||
|
// Merge the default settings with the provided settings |
||||
|
$options['allow_redirects'] += self::$defaultSettings; |
||||
|
} |
||||
|
|
||||
|
if (empty($options['allow_redirects']['max'])) { |
||||
|
return $fn($request, $options); |
||||
|
} |
||||
|
|
||||
|
return $fn($request, $options) |
||||
|
->then(function (ResponseInterface $response) use ($request, $options) { |
||||
|
return $this->checkRedirect($request, $options, $response); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
* @param ResponseInterface|PromiseInterface $response |
||||
|
* |
||||
|
* @return ResponseInterface|PromiseInterface |
||||
|
*/ |
||||
|
public function checkRedirect( |
||||
|
RequestInterface $request, |
||||
|
array $options, |
||||
|
ResponseInterface $response |
||||
|
) { |
||||
|
if (substr($response->getStatusCode(), 0, 1) != '3' |
||||
|
|| !$response->hasHeader('Location') |
||||
|
) { |
||||
|
return $response; |
||||
|
} |
||||
|
|
||||
|
$this->guardMax($request, $options); |
||||
|
$nextRequest = $this->modifyRequest($request, $options, $response); |
||||
|
|
||||
|
if (isset($options['allow_redirects']['on_redirect'])) { |
||||
|
call_user_func( |
||||
|
$options['allow_redirects']['on_redirect'], |
||||
|
$request, |
||||
|
$response, |
||||
|
$nextRequest->getUri() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** @var PromiseInterface|ResponseInterface $promise */ |
||||
|
$promise = $this($nextRequest, $options); |
||||
|
|
||||
|
// Add headers to be able to track history of redirects. |
||||
|
if (!empty($options['allow_redirects']['track_redirects'])) { |
||||
|
return $this->withTracking( |
||||
|
$promise, |
||||
|
(string) $nextRequest->getUri(), |
||||
|
$response->getStatusCode() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return $promise; |
||||
|
} |
||||
|
|
||||
|
private function withTracking(PromiseInterface $promise, $uri, $statusCode) |
||||
|
{ |
||||
|
return $promise->then( |
||||
|
function (ResponseInterface $response) use ($uri, $statusCode) { |
||||
|
// Note that we are pushing to the front of the list as this |
||||
|
// would be an earlier response than what is currently present |
||||
|
// in the history header. |
||||
|
$historyHeader = $response->getHeader(self::HISTORY_HEADER); |
||||
|
$statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); |
||||
|
array_unshift($historyHeader, $uri); |
||||
|
array_unshift($statusHeader, $statusCode); |
||||
|
return $response->withHeader(self::HISTORY_HEADER, $historyHeader) |
||||
|
->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private function guardMax(RequestInterface $request, array &$options) |
||||
|
{ |
||||
|
$current = isset($options['__redirect_count']) |
||||
|
? $options['__redirect_count'] |
||||
|
: 0; |
||||
|
$options['__redirect_count'] = $current + 1; |
||||
|
$max = $options['allow_redirects']['max']; |
||||
|
|
||||
|
if ($options['__redirect_count'] > $max) { |
||||
|
throw new TooManyRedirectsException( |
||||
|
"Will not follow more than {$max} redirects", |
||||
|
$request |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
* @param ResponseInterface $response |
||||
|
* |
||||
|
* @return RequestInterface |
||||
|
*/ |
||||
|
public function modifyRequest( |
||||
|
RequestInterface $request, |
||||
|
array $options, |
||||
|
ResponseInterface $response |
||||
|
) { |
||||
|
// Request modifications to apply. |
||||
|
$modify = []; |
||||
|
$protocols = $options['allow_redirects']['protocols']; |
||||
|
|
||||
|
// Use a GET request if this is an entity enclosing request and we are |
||||
|
// not forcing RFC compliance, but rather emulating what all browsers |
||||
|
// would do. |
||||
|
$statusCode = $response->getStatusCode(); |
||||
|
if ($statusCode == 303 || |
||||
|
($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict']) |
||||
|
) { |
||||
|
$modify['method'] = 'GET'; |
||||
|
$modify['body'] = ''; |
||||
|
} |
||||
|
|
||||
|
$modify['uri'] = $this->redirectUri($request, $response, $protocols); |
||||
|
Psr7\rewind_body($request); |
||||
|
|
||||
|
// Add the Referer header if it is told to do so and only |
||||
|
// add the header if we are not redirecting from https to http. |
||||
|
if ($options['allow_redirects']['referer'] |
||||
|
&& $modify['uri']->getScheme() === $request->getUri()->getScheme() |
||||
|
) { |
||||
|
$uri = $request->getUri()->withUserInfo('', ''); |
||||
|
$modify['set_headers']['Referer'] = (string) $uri; |
||||
|
} else { |
||||
|
$modify['remove_headers'][] = 'Referer'; |
||||
|
} |
||||
|
|
||||
|
// Remove Authorization header if host is different. |
||||
|
if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { |
||||
|
$modify['remove_headers'][] = 'Authorization'; |
||||
|
} |
||||
|
|
||||
|
return Psr7\modify_request($request, $modify); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set the appropriate URL on the request based on the location header |
||||
|
* |
||||
|
* @param RequestInterface $request |
||||
|
* @param ResponseInterface $response |
||||
|
* @param array $protocols |
||||
|
* |
||||
|
* @return UriInterface |
||||
|
*/ |
||||
|
private function redirectUri( |
||||
|
RequestInterface $request, |
||||
|
ResponseInterface $response, |
||||
|
array $protocols |
||||
|
) { |
||||
|
$location = Psr7\UriResolver::resolve( |
||||
|
$request->getUri(), |
||||
|
new Psr7\Uri($response->getHeaderLine('Location')) |
||||
|
); |
||||
|
|
||||
|
// Ensure that the redirect URI is allowed based on the protocols. |
||||
|
if (!in_array($location->getScheme(), $protocols)) { |
||||
|
throw new BadResponseException( |
||||
|
sprintf( |
||||
|
'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', |
||||
|
$location, |
||||
|
implode(', ', $protocols) |
||||
|
), |
||||
|
$request, |
||||
|
$response |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return $location; |
||||
|
} |
||||
|
} |
@ -0,0 +1,255 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
/** |
||||
|
* This class contains a list of built-in Guzzle request options. |
||||
|
* |
||||
|
* More documentation for each option can be found at http://guzzlephp.org/. |
||||
|
* |
||||
|
* @link http://docs.guzzlephp.org/en/v6/request-options.html |
||||
|
*/ |
||||
|
final class RequestOptions |
||||
|
{ |
||||
|
/** |
||||
|
* allow_redirects: (bool|array) Controls redirect behavior. Pass false |
||||
|
* to disable redirects, pass true to enable redirects, pass an |
||||
|
* associative to provide custom redirect settings. Defaults to "false". |
||||
|
* This option only works if your handler has the RedirectMiddleware. When |
||||
|
* passing an associative array, you can provide the following key value |
||||
|
* pairs: |
||||
|
* |
||||
|
* - max: (int, default=5) maximum number of allowed redirects. |
||||
|
* - strict: (bool, default=false) Set to true to use strict redirects |
||||
|
* meaning redirect POST requests with POST requests vs. doing what most |
||||
|
* browsers do which is redirect POST requests with GET requests |
||||
|
* - referer: (bool, default=true) Set to false to disable the Referer |
||||
|
* header. |
||||
|
* - protocols: (array, default=['http', 'https']) Allowed redirect |
||||
|
* protocols. |
||||
|
* - on_redirect: (callable) PHP callable that is invoked when a redirect |
||||
|
* is encountered. The callable is invoked with the request, the redirect |
||||
|
* response that was received, and the effective URI. Any return value |
||||
|
* from the on_redirect function is ignored. |
||||
|
*/ |
||||
|
const ALLOW_REDIRECTS = 'allow_redirects'; |
||||
|
|
||||
|
/** |
||||
|
* auth: (array) Pass an array of HTTP authentication parameters to use |
||||
|
* with the request. The array must contain the username in index [0], |
||||
|
* the password in index [1], and you can optionally provide a built-in |
||||
|
* authentication type in index [2]. Pass null to disable authentication |
||||
|
* for a request. |
||||
|
*/ |
||||
|
const AUTH = 'auth'; |
||||
|
|
||||
|
/** |
||||
|
* body: (resource|string|null|int|float|StreamInterface|callable|\Iterator) |
||||
|
* Body to send in the request. |
||||
|
*/ |
||||
|
const BODY = 'body'; |
||||
|
|
||||
|
/** |
||||
|
* cert: (string|array) Set to a string to specify the path to a file |
||||
|
* containing a PEM formatted SSL client side certificate. If a password |
||||
|
* is required, then set cert to an array containing the path to the PEM |
||||
|
* file in the first array element followed by the certificate password |
||||
|
* in the second array element. |
||||
|
*/ |
||||
|
const CERT = 'cert'; |
||||
|
|
||||
|
/** |
||||
|
* cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false) |
||||
|
* Specifies whether or not cookies are used in a request or what cookie |
||||
|
* jar to use or what cookies to send. This option only works if your |
||||
|
* handler has the `cookie` middleware. Valid values are `false` and |
||||
|
* an instance of {@see GuzzleHttp\Cookie\CookieJarInterface}. |
||||
|
*/ |
||||
|
const COOKIES = 'cookies'; |
||||
|
|
||||
|
/** |
||||
|
* connect_timeout: (float, default=0) Float describing the number of |
||||
|
* seconds to wait while trying to connect to a server. Use 0 to wait |
||||
|
* indefinitely (the default behavior). |
||||
|
*/ |
||||
|
const CONNECT_TIMEOUT = 'connect_timeout'; |
||||
|
|
||||
|
/** |
||||
|
* debug: (bool|resource) Set to true or set to a PHP stream returned by |
||||
|
* fopen() enable debug output with the HTTP handler used to send a |
||||
|
* request. |
||||
|
*/ |
||||
|
const DEBUG = 'debug'; |
||||
|
|
||||
|
/** |
||||
|
* decode_content: (bool, default=true) Specify whether or not |
||||
|
* Content-Encoding responses (gzip, deflate, etc.) are automatically |
||||
|
* decoded. |
||||
|
*/ |
||||
|
const DECODE_CONTENT = 'decode_content'; |
||||
|
|
||||
|
/** |
||||
|
* delay: (int) The amount of time to delay before sending in milliseconds. |
||||
|
*/ |
||||
|
const DELAY = 'delay'; |
||||
|
|
||||
|
/** |
||||
|
* expect: (bool|integer) Controls the behavior of the |
||||
|
* "Expect: 100-Continue" header. |
||||
|
* |
||||
|
* Set to `true` to enable the "Expect: 100-Continue" header for all |
||||
|
* requests that sends a body. Set to `false` to disable the |
||||
|
* "Expect: 100-Continue" header for all requests. Set to a number so that |
||||
|
* the size of the payload must be greater than the number in order to send |
||||
|
* the Expect header. Setting to a number will send the Expect header for |
||||
|
* all requests in which the size of the payload cannot be determined or |
||||
|
* where the body is not rewindable. |
||||
|
* |
||||
|
* By default, Guzzle will add the "Expect: 100-Continue" header when the |
||||
|
* size of the body of a request is greater than 1 MB and a request is |
||||
|
* using HTTP/1.1. |
||||
|
*/ |
||||
|
const EXPECT = 'expect'; |
||||
|
|
||||
|
/** |
||||
|
* form_params: (array) Associative array of form field names to values |
||||
|
* where each value is a string or array of strings. Sets the Content-Type |
||||
|
* header to application/x-www-form-urlencoded when no Content-Type header |
||||
|
* is already present. |
||||
|
*/ |
||||
|
const FORM_PARAMS = 'form_params'; |
||||
|
|
||||
|
/** |
||||
|
* headers: (array) Associative array of HTTP headers. Each value MUST be |
||||
|
* a string or array of strings. |
||||
|
*/ |
||||
|
const HEADERS = 'headers'; |
||||
|
|
||||
|
/** |
||||
|
* http_errors: (bool, default=true) Set to false to disable exceptions |
||||
|
* when a non- successful HTTP response is received. By default, |
||||
|
* exceptions will be thrown for 4xx and 5xx responses. This option only |
||||
|
* works if your handler has the `httpErrors` middleware. |
||||
|
*/ |
||||
|
const HTTP_ERRORS = 'http_errors'; |
||||
|
|
||||
|
/** |
||||
|
* json: (mixed) Adds JSON data to a request. The provided value is JSON |
||||
|
* encoded and a Content-Type header of application/json will be added to |
||||
|
* the request if no Content-Type header is already present. |
||||
|
*/ |
||||
|
const JSON = 'json'; |
||||
|
|
||||
|
/** |
||||
|
* multipart: (array) Array of associative arrays, each containing a |
||||
|
* required "name" key mapping to the form field, name, a required |
||||
|
* "contents" key mapping to a StreamInterface|resource|string, an |
||||
|
* optional "headers" associative array of custom headers, and an |
||||
|
* optional "filename" key mapping to a string to send as the filename in |
||||
|
* the part. If no "filename" key is present, then no "filename" attribute |
||||
|
* will be added to the part. |
||||
|
*/ |
||||
|
const MULTIPART = 'multipart'; |
||||
|
|
||||
|
/** |
||||
|
* on_headers: (callable) A callable that is invoked when the HTTP headers |
||||
|
* of the response have been received but the body has not yet begun to |
||||
|
* download. |
||||
|
*/ |
||||
|
const ON_HEADERS = 'on_headers'; |
||||
|
|
||||
|
/** |
||||
|
* on_stats: (callable) allows you to get access to transfer statistics of |
||||
|
* a request and access the lower level transfer details of the handler |
||||
|
* associated with your client. ``on_stats`` is a callable that is invoked |
||||
|
* when a handler has finished sending a request. The callback is invoked |
||||
|
* with transfer statistics about the request, the response received, or |
||||
|
* the error encountered. Included in the data is the total amount of time |
||||
|
* taken to send the request. |
||||
|
*/ |
||||
|
const ON_STATS = 'on_stats'; |
||||
|
|
||||
|
/** |
||||
|
* progress: (callable) Defines a function to invoke when transfer |
||||
|
* progress is made. The function accepts the following positional |
||||
|
* arguments: the total number of bytes expected to be downloaded, the |
||||
|
* number of bytes downloaded so far, the number of bytes expected to be |
||||
|
* uploaded, the number of bytes uploaded so far. |
||||
|
*/ |
||||
|
const PROGRESS = 'progress'; |
||||
|
|
||||
|
/** |
||||
|
* proxy: (string|array) Pass a string to specify an HTTP proxy, or an |
||||
|
* array to specify different proxies for different protocols (where the |
||||
|
* key is the protocol and the value is a proxy string). |
||||
|
*/ |
||||
|
const PROXY = 'proxy'; |
||||
|
|
||||
|
/** |
||||
|
* query: (array|string) Associative array of query string values to add |
||||
|
* to the request. This option uses PHP's http_build_query() to create |
||||
|
* the string representation. Pass a string value if you need more |
||||
|
* control than what this method provides |
||||
|
*/ |
||||
|
const QUERY = 'query'; |
||||
|
|
||||
|
/** |
||||
|
* sink: (resource|string|StreamInterface) Where the data of the |
||||
|
* response is written to. Defaults to a PHP temp stream. Providing a |
||||
|
* string will write data to a file by the given name. |
||||
|
*/ |
||||
|
const SINK = 'sink'; |
||||
|
|
||||
|
/** |
||||
|
* synchronous: (bool) Set to true to inform HTTP handlers that you intend |
||||
|
* on waiting on the response. This can be useful for optimizations. Note |
||||
|
* that a promise is still returned if you are using one of the async |
||||
|
* client methods. |
||||
|
*/ |
||||
|
const SYNCHRONOUS = 'synchronous'; |
||||
|
|
||||
|
/** |
||||
|
* ssl_key: (array|string) Specify the path to a file containing a private |
||||
|
* SSL key in PEM format. If a password is required, then set to an array |
||||
|
* containing the path to the SSL key in the first array element followed |
||||
|
* by the password required for the certificate in the second element. |
||||
|
*/ |
||||
|
const SSL_KEY = 'ssl_key'; |
||||
|
|
||||
|
/** |
||||
|
* stream: Set to true to attempt to stream a response rather than |
||||
|
* download it all up-front. |
||||
|
*/ |
||||
|
const STREAM = 'stream'; |
||||
|
|
||||
|
/** |
||||
|
* verify: (bool|string, default=true) Describes the SSL certificate |
||||
|
* verification behavior of a request. Set to true to enable SSL |
||||
|
* certificate verification using the system CA bundle when available |
||||
|
* (the default). Set to false to disable certificate verification (this |
||||
|
* is insecure!). Set to a string to provide the path to a CA bundle on |
||||
|
* disk to enable verification using a custom certificate. |
||||
|
*/ |
||||
|
const VERIFY = 'verify'; |
||||
|
|
||||
|
/** |
||||
|
* timeout: (float, default=0) Float describing the timeout of the |
||||
|
* request in seconds. Use 0 to wait indefinitely (the default behavior). |
||||
|
*/ |
||||
|
const TIMEOUT = 'timeout'; |
||||
|
|
||||
|
/** |
||||
|
* read_timeout: (float, default=default_socket_timeout ini setting) Float describing |
||||
|
* the body read timeout, for stream requests. |
||||
|
*/ |
||||
|
const READ_TIMEOUT = 'read_timeout'; |
||||
|
|
||||
|
/** |
||||
|
* version: (float) Specifies the HTTP protocol version to attempt to use. |
||||
|
*/ |
||||
|
const VERSION = 'version'; |
||||
|
|
||||
|
/** |
||||
|
* force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol |
||||
|
*/ |
||||
|
const FORCE_IP_RESOLVE = 'force_ip_resolve'; |
||||
|
} |
@ -0,0 +1,112 @@ |
|||||
|
<?php |
||||
|
namespace GuzzleHttp; |
||||
|
|
||||
|
use GuzzleHttp\Promise\PromiseInterface; |
||||
|
use GuzzleHttp\Promise\RejectedPromise; |
||||
|
use GuzzleHttp\Psr7; |
||||
|
use Psr\Http\Message\RequestInterface; |
||||
|
use Psr\Http\Message\ResponseInterface; |
||||
|
|
||||
|
/** |
||||
|
* Middleware that retries requests based on the boolean result of |
||||
|
* invoking the provided "decider" function. |
||||
|
*/ |
||||
|
class RetryMiddleware |
||||
|
{ |
||||
|
/** @var callable */ |
||||
|
private $nextHandler; |
||||
|
|
||||
|
/** @var callable */ |
||||
|
private $decider; |
||||
|
|
||||
|
/** |
||||
|
* @param callable $decider Function that accepts the number of retries, |
||||
|
* a request, [response], and [exception] and |
||||
|
* returns true if the request is to be |
||||
|
* retried. |
||||
|
* @param callable $nextHandler Next handler to invoke. |
||||
|
* @param callable $delay Function that accepts the number of retries |
||||
|
* and [response] and returns the number of |
||||
|
* milliseconds to delay. |
||||
|
*/ |
||||
|
public function __construct( |
||||
|
callable $decider, |
||||
|
callable $nextHandler, |
||||
|
callable $delay = null |
||||
|
) { |
||||
|
$this->decider = $decider; |
||||
|
$this->nextHandler = $nextHandler; |
||||
|
$this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Default exponential backoff delay function. |
||||
|
* |
||||
|
* @param $retries |
||||
|
* |
||||
|
* @return int |
||||
|
*/ |
||||
|
public static function exponentialDelay($retries) |
||||
|
{ |
||||
|
return (int) pow(2, $retries - 1); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param RequestInterface $request |
||||
|
* @param array $options |
||||
|
* |
||||
|
* @return PromiseInterface |
||||
|
*/ |
||||
|
public function __invoke(RequestInterface $request, array $options) |
||||
|
{ |
||||
|
if (!isset($options['retries'])) { |
||||
|
$options['retries'] = 0; |
||||
|
} |
||||
|
|
||||
|
$fn = $this->nextHandler; |
||||
|
return $fn($request, $options) |
||||
|
->then( |
||||
|
$this->onFulfilled($request, $options), |
||||
|
$this->onRejected($request, $options) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private function onFulfilled(RequestInterface $req, array $options) |
||||
|
{ |
||||
|
return function ($value) use ($req, $options) { |
||||
|
if (!call_user_func( |
||||
|
$this->decider, |
||||
|
$options['retries'], |
||||
|
$req, |
||||
|
$value, |
||||
|
null |
||||
|
)) { |
||||
|
return $value; |
||||
|
} |
||||
|
return $this->doRetry($req, $options, $value); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private function onRejected(RequestInterface $req, array $options) |
||||
|
{ |
||||
|
return function ($reason) use ($req, $options) { |
||||
|
if (!call_user_func( |
||||
|
$this->decider, |
||||
|
$options['retries'], |
||||
|
$req, |
||||
|
null, |
||||
|
$reason |
||||
|
)) { |
||||
|
return \GuzzleHttp\Promise\rejection_for($reason); |
||||
|
} |
||||
|
return $this->doRetry($req, $options); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) |
||||
|
{ |
||||
|
$options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); |
||||
|
|
||||
|
return $this($request, $options); |
||||
|
} |
||||
|
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue