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", |
|||
"/js/app.js": "/js/app.js?id=559585a774e3f088503a" |
|||
"/css/app.css": "/css/app.css?id=d6ec6184a5279779b01a", |
|||
"/js/app.js": "/js/app.js?id=67b9d3b008c0a8da2fd6" |
|||
} |
@ -1,12 +1,13 @@ |
|||
<h2>{{ __('app.apps.config') }} ({{ __('app.optional') }})</h2> |
|||
<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"> |
|||
<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 class="input"> |
|||
<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> |
|||
<button id="test_config">Test</button> |
|||
</div> |
@ -1,21 +1,433 @@ |
|||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ |
|||
Upstream-Name: Composer |
|||
Upstream-Contact: Jordi Boggiano <j.boggiano@seld.be> |
|||
Source: https://github.com/composer/composer |
|||
|
|||
Copyright (c) Nils Adermann, Jordi Boggiano |
|||
Files: * |
|||
Copyright: 2016, Nils Adermann <naderman@naderman.de> |
|||
2016, Jordi Boggiano <j.boggiano@seld.be> |
|||
License: Expat |
|||
|
|||
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: |
|||
Files: res/cacert.pem |
|||
Copyright: 2015, Mozilla Foundation |
|||
License: MPL-2.0 |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
Files: src/Composer/Util/RemoteFilesystem.php |
|||
src/Composer/Util/TlsHelper.php |
|||
Copyright: 2016, Nils Adermann <naderman@naderman.de> |
|||
2016, Jordi Boggiano <j.boggiano@seld.be> |
|||
2013, Evan Coury <me@evancoury.com> |
|||
License: Expat and BSD-2-Clause |
|||
|
|||
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. |
|||
License: BSD-2-Clause |
|||
Redistribution and use in source and binary forms, with or without modification, |
|||
are permitted provided that the following conditions are met: |
|||
. |
|||
* Redistributions of source code must retain the above copyright notice, |
|||
this list of conditions and the following disclaimer. |
|||
. |
|||
* Redistributions in binary form must reproduce the above copyright notice, |
|||
this list of conditions and the following disclaimer in the documentation |
|||
and/or other materials provided with the distribution. |
|||
. |
|||
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. |
|||
|
|||
License: Expat |
|||
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. |
|||
|
|||
License: MPL-2.0 |
|||
1. Definitions |
|||
-------------- |
|||
. |
|||
1.1. "Contributor" |
|||
means each individual or legal entity that creates, contributes to |
|||
the creation of, or owns Covered Software. |
|||
. |
|||
1.2. "Contributor Version" |
|||
means the combination of the Contributions of others (if any) used |
|||
by a Contributor and that particular Contributor's Contribution. |
|||
. |
|||
1.3. "Contribution" |
|||
means Covered Software of a particular Contributor. |
|||
. |
|||
1.4. "Covered Software" |
|||
means Source Code Form to which the initial Contributor has attached |
|||
the notice in Exhibit A, the Executable Form of such Source Code |
|||
Form, and Modifications of such Source Code Form, in each case |
|||
including portions thereof. |
|||
. |
|||
1.5. "Incompatible With Secondary Licenses" |
|||
means |
|||
. |
|||
(a) that the initial Contributor has attached the notice described |
|||
in Exhibit B to the Covered Software; or |
|||
. |
|||
(b) that the Covered Software was made available under the terms of |
|||
version 1.1 or earlier of the License, but not also under the |
|||
terms of a Secondary License. |
|||
. |
|||
1.6. "Executable Form" |
|||
means any form of the work other than Source Code Form. |
|||
. |
|||
1.7. "Larger Work" |
|||
means a work that combines Covered Software with other material, in |
|||
a separate file or files, that is not Covered Software. |
|||
. |
|||
1.8. "License" |
|||
means this document. |
|||
. |
|||
1.9. "Licensable" |
|||
means having the right to grant, to the maximum extent possible, |
|||
whether at the time of the initial grant or subsequently, any and |
|||
all of the rights conveyed by this License. |
|||
. |
|||
1.10. "Modifications" |
|||
means any of the following: |
|||
. |
|||
(a) any file in Source Code Form that results from an addition to, |
|||
deletion from, or modification of the contents of Covered |
|||
Software; or |
|||
. |
|||
(b) any new file in Source Code Form that contains any Covered |
|||
Software. |
|||
. |
|||
1.11. "Patent Claims" of a Contributor |
|||
means any patent claim(s), including without limitation, method, |
|||
process, and apparatus claims, in any patent Licensable by such |
|||
Contributor that would be infringed, but for the grant of the |
|||
License, by the making, using, selling, offering for sale, having |
|||
made, import, or transfer of either its Contributions or its |
|||
Contributor Version. |
|||
. |
|||
1.12. "Secondary License" |
|||
means either the GNU General Public License, Version 2.0, the GNU |
|||
Lesser General Public License, Version 2.1, the GNU Affero General |
|||
Public License, Version 3.0, or any later versions of those |
|||
licenses. |
|||
. |
|||
1.13. "Source Code Form" |
|||
means the form of the work preferred for making modifications. |
|||
. |
|||
1.14. "You" (or "Your") |
|||
means an individual or a legal entity exercising rights under this |
|||
License. For legal entities, "You" includes any entity that |
|||
controls, is controlled by, or is under common control with You. For |
|||
purposes of this definition, "control" means (a) the power, direct |
|||
or indirect, to cause the direction or management of such entity, |
|||
whether by contract or otherwise, or (b) ownership of more than |
|||
fifty percent (50%) of the outstanding shares or beneficial |
|||
ownership of such entity. |
|||
. |
|||
2. License Grants and Conditions |
|||
-------------------------------- |
|||
. |
|||
2.1. Grants |
|||
. |
|||
Each Contributor hereby grants You a world-wide, royalty-free, |
|||
non-exclusive license: |
|||
. |
|||
(a) under intellectual property rights (other than patent or trademark) |
|||
Licensable by such Contributor to use, reproduce, make available, |
|||
modify, display, perform, distribute, and otherwise exploit its |
|||
Contributions, either on an unmodified basis, with Modifications, or |
|||
as part of a Larger Work; and |
|||
. |
|||
(b) under Patent Claims of such Contributor to make, use, sell, offer |
|||
for sale, have made, import, and otherwise transfer either its |
|||
Contributions or its Contributor Version. |
|||
. |
|||
2.2. Effective Date |
|||
. |
|||
The licenses granted in Section 2.1 with respect to any Contribution |
|||
become effective for each Contribution on the date the Contributor first |
|||
distributes such Contribution. |
|||
. |
|||
2.3. Limitations on Grant Scope |
|||
. |
|||
The licenses granted in this Section 2 are the only rights granted under |
|||
this License. No additional rights or licenses will be implied from the |
|||
distribution or licensing of Covered Software under this License. |
|||
Notwithstanding Section 2.1(b) above, no patent license is granted by a |
|||
Contributor: |
|||
. |
|||
(a) for any code that a Contributor has removed from Covered Software; |
|||
or |
|||
. |
|||
(b) for infringements caused by: (i) Your and any other third party's |
|||
modifications of Covered Software, or (ii) the combination of its |
|||
Contributions with other software (except as part of its Contributor |
|||
Version); or |
|||
. |
|||
(c) under Patent Claims infringed by Covered Software in the absence of |
|||
its Contributions. |
|||
. |
|||
This License does not grant any rights in the trademarks, service marks, |
|||
or logos of any Contributor (except as may be necessary to comply with |
|||
the notice requirements in Section 3.4). |
|||
. |
|||
2.4. Subsequent Licenses |
|||
. |
|||
No Contributor makes additional grants as a result of Your choice to |
|||
distribute the Covered Software under a subsequent version of this |
|||
License (see Section 10.2) or under the terms of a Secondary License (if |
|||
permitted under the terms of Section 3.3). |
|||
. |
|||
2.5. Representation |
|||
. |
|||
Each Contributor represents that the Contributor believes its |
|||
Contributions are its original creation(s) or it has sufficient rights |
|||
to grant the rights to its Contributions conveyed by this License. |
|||
. |
|||
2.6. Fair Use |
|||
. |
|||
This License is not intended to limit any rights You have under |
|||
applicable copyright doctrines of fair use, fair dealing, or other |
|||
equivalents. |
|||
. |
|||
2.7. Conditions |
|||
. |
|||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted |
|||
in Section 2.1. |
|||
. |
|||
3. Responsibilities |
|||
------------------- |
|||
. |
|||
3.1. Distribution of Source Form |
|||
. |
|||
All distribution of Covered Software in Source Code Form, including any |
|||
Modifications that You create or to which You contribute, must be under |
|||
the terms of this License. You must inform recipients that the Source |
|||
Code Form of the Covered Software is governed by the terms of this |
|||
License, and how they can obtain a copy of this License. You may not |
|||
attempt to alter or restrict the recipients' rights in the Source Code |
|||
Form. |
|||
. |
|||
3.2. Distribution of Executable Form |
|||
. |
|||
If You distribute Covered Software in Executable Form then: |
|||
. |
|||
(a) such Covered Software must also be made available in Source Code |
|||
Form, as described in Section 3.1, and You must inform recipients of |
|||
the Executable Form how they can obtain a copy of such Source Code |
|||
Form by reasonable means in a timely manner, at a charge no more |
|||
than the cost of distribution to the recipient; and |
|||
. |
|||
(b) You may distribute such Executable Form under the terms of this |
|||
License, or sublicense it under different terms, provided that the |
|||
license for the Executable Form does not attempt to limit or alter |
|||
the recipients' rights in the Source Code Form under this License. |
|||
. |
|||
3.3. Distribution of a Larger Work |
|||
. |
|||
You may create and distribute a Larger Work under terms of Your choice, |
|||
provided that You also comply with the requirements of this License for |
|||
the Covered Software. If the Larger Work is a combination of Covered |
|||
Software with a work governed by one or more Secondary Licenses, and the |
|||
Covered Software is not Incompatible With Secondary Licenses, this |
|||
License permits You to additionally distribute such Covered Software |
|||
under the terms of such Secondary License(s), so that the recipient of |
|||
the Larger Work may, at their option, further distribute the Covered |
|||
Software under the terms of either this License or such Secondary |
|||
License(s). |
|||
. |
|||
3.4. Notices |
|||
. |
|||
You may not remove or alter the substance of any license notices |
|||
(including copyright notices, patent notices, disclaimers of warranty, |
|||
or limitations of liability) contained within the Source Code Form of |
|||
the Covered Software, except that You may alter any license notices to |
|||
the extent required to remedy known factual inaccuracies. |
|||
. |
|||
3.5. Application of Additional Terms |
|||
. |
|||
You may choose to offer, and to charge a fee for, warranty, support, |
|||
indemnity or liability obligations to one or more recipients of Covered |
|||
Software. However, You may do so only on Your own behalf, and not on |
|||
behalf of any Contributor. You must make it absolutely clear that any |
|||
such warranty, support, indemnity, or liability obligation is offered by |
|||
You alone, and You hereby agree to indemnify every Contributor for any |
|||
liability incurred by such Contributor as a result of warranty, support, |
|||
indemnity or liability terms You offer. You may include additional |
|||
disclaimers of warranty and limitations of liability specific to any |
|||
jurisdiction. |
|||
. |
|||
4. Inability to Comply Due to Statute or Regulation |
|||
--------------------------------------------------- |
|||
. |
|||
If it is impossible for You to comply with any of the terms of this |
|||
License with respect to some or all of the Covered Software due to |
|||
statute, judicial order, or regulation then You must: (a) comply with |
|||
the terms of this License to the maximum extent possible; and (b) |
|||
describe the limitations and the code they affect. Such description must |
|||
be placed in a text file included with all distributions of the Covered |
|||
Software under this License. Except to the extent prohibited by statute |
|||
or regulation, such description must be sufficiently detailed for a |
|||
recipient of ordinary skill to be able to understand it. |
|||
. |
|||
5. Termination |
|||
-------------- |
|||
. |
|||
5.1. The rights granted under this License will terminate automatically |
|||
if You fail to comply with any of its terms. However, if You become |
|||
compliant, then the rights granted under this License from a particular |
|||
Contributor are reinstated (a) provisionally, unless and until such |
|||
Contributor explicitly and finally terminates Your grants, and (b) on an |
|||
ongoing basis, if such Contributor fails to notify You of the |
|||
non-compliance by some reasonable means prior to 60 days after You have |
|||
come back into compliance. Moreover, Your grants from a particular |
|||
Contributor are reinstated on an ongoing basis if such Contributor |
|||
notifies You of the non-compliance by some reasonable means, this is the |
|||
first time You have received notice of non-compliance with this License |
|||
from such Contributor, and You become compliant prior to 30 days after |
|||
Your receipt of the notice. |
|||
. |
|||
5.2. If You initiate litigation against any entity by asserting a patent |
|||
infringement claim (excluding declaratory judgment actions, |
|||
counter-claims, and cross-claims) alleging that a Contributor Version |
|||
directly or indirectly infringes any patent, then the rights granted to |
|||
You by any and all Contributors for the Covered Software under Section |
|||
2.1 of this License shall terminate. |
|||
. |
|||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all |
|||
end user license agreements (excluding distributors and resellers) which |
|||
have been validly granted by You or Your distributors under this License |
|||
prior to termination shall survive termination. |
|||
. |
|||
************************************************************************ |
|||
* * |
|||
* 6. Disclaimer of Warranty * |
|||
* ------------------------- * |
|||
* * |
|||
* Covered Software is provided under this License on an "as is" * |
|||
* basis, without warranty of any kind, either expressed, implied, or * |
|||
* statutory, including, without limitation, warranties that the * |
|||
* Covered Software is free of defects, merchantable, fit for a * |
|||
* particular purpose or non-infringing. The entire risk as to the * |
|||
* quality and performance of the Covered Software is with You. * |
|||
* Should any Covered Software prove defective in any respect, You * |
|||
* (not any Contributor) assume the cost of any necessary servicing, * |
|||
* repair, or correction. This disclaimer of warranty constitutes an * |
|||
* essential part of this License. No use of any Covered Software is * |
|||
* authorized under this License except under this disclaimer. * |
|||
* * |
|||
************************************************************************ |
|||
. |
|||
************************************************************************ |
|||
* * |
|||
* 7. Limitation of Liability * |
|||
* -------------------------- * |
|||
* * |
|||
* Under no circumstances and under no legal theory, whether tort * |
|||
* (including negligence), contract, or otherwise, shall any * |
|||
* Contributor, or anyone who distributes Covered Software as * |
|||
* permitted above, be liable to You for any direct, indirect, * |
|||
* special, incidental, or consequential damages of any character * |
|||
* including, without limitation, damages for lost profits, loss of * |
|||
* goodwill, work stoppage, computer failure or malfunction, or any * |
|||
* and all other commercial damages or losses, even if such party * |
|||
* shall have been informed of the possibility of such damages. This * |
|||
* limitation of liability shall not apply to liability for death or * |
|||
* personal injury resulting from such party's negligence to the * |
|||
* extent applicable law prohibits such limitation. Some * |
|||
* jurisdictions do not allow the exclusion or limitation of * |
|||
* incidental or consequential damages, so this exclusion and * |
|||
* limitation may not apply to You. * |
|||
* * |
|||
************************************************************************ |
|||
. |
|||
8. Litigation |
|||
------------- |
|||
. |
|||
Any litigation relating to this License may be brought only in the |
|||
courts of a jurisdiction where the defendant maintains its principal |
|||
place of business and such litigation shall be governed by laws of that |
|||
jurisdiction, without reference to its conflict-of-law provisions. |
|||
Nothing in this Section shall prevent a party's ability to bring |
|||
cross-claims or counter-claims. |
|||
. |
|||
9. Miscellaneous |
|||
---------------- |
|||
. |
|||
This License represents the complete agreement concerning the subject |
|||
matter hereof. If any provision of this License is held to be |
|||
unenforceable, such provision shall be reformed only to the extent |
|||
necessary to make it enforceable. Any law or regulation which provides |
|||
that the language of a contract shall be construed against the drafter |
|||
shall not be used to construe this License against a Contributor. |
|||
. |
|||
10. Versions of the License |
|||
--------------------------- |
|||
. |
|||
10.1. New Versions |
|||
. |
|||
Mozilla Foundation is the license steward. Except as provided in Section |
|||
10.3, no one other than the license steward has the right to modify or |
|||
publish new versions of this License. Each version will be given a |
|||
distinguishing version number. |
|||
. |
|||
10.2. Effect of New Versions |
|||
. |
|||
You may distribute the Covered Software under the terms of the version |
|||
of the License under which You originally received the Covered Software, |
|||
or under the terms of any subsequent version published by the license |
|||
steward. |
|||
. |
|||
10.3. Modified Versions |
|||
. |
|||
If you create software not governed by this License, and you want to |
|||
create a new license for such software, you may create and use a |
|||
modified version of this License if you rename the license and remove |
|||
any references to the name of the license steward (except to note that |
|||
such modified license differs from this License). |
|||
. |
|||
10.4. Distributing Source Code Form that is Incompatible With Secondary |
|||
Licenses |
|||
. |
|||
If You choose to distribute Source Code Form that is Incompatible With |
|||
Secondary Licenses under the terms of this version of the License, the |
|||
notice described in Exhibit B of this License must be attached. |
|||
. |
|||
Exhibit A - Source Code Form License Notice |
|||
------------------------------------------- |
|||
. |
|||
This Source Code Form is subject to the terms of the Mozilla Public |
|||
License, v. 2.0. If a copy of the MPL was not distributed with this |
|||
file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|||
. |
|||
If it is not possible or desirable to put the notice in a particular |
|||
file, then You may include the notice in a location (such as a LICENSE |
|||
file in a relevant directory) where a recipient would be likely to look |
|||
for such a notice. |
|||
. |
|||
You may add additional accurate notices of copyright ownership. |
|||
. |
|||
Exhibit B - "Incompatible With Secondary Licenses" Notice |
|||
--------------------------------------------------------- |
|||
. |
|||
This Source Code Form is "Incompatible With Secondary Licenses", as |
|||
defined by the Mozilla Public License, v. 2.0. |
|||
|
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