6da233de by Jeff Balicki

commit

1 parent c348f982
Showing 1000 changed files with 0 additions and 4988 deletions

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

1 <?php
2
3 // autoload.php @generated by Composer
4
5 require_once __DIR__ . '/composer' . '/autoload_real.php';
6
7 return ComposerAutoloaderInit0d114684bec13fba4103960eaa00d106::getLoader();
1
2 Copyright (c) 2016 Nils Adermann, Jordi Boggiano
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is furnished
9 to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21
This diff could not be displayed because it is too large.
1 <?php
2
3 // autoload_files.php @generated by Composer
4
5 $vendorDir = dirname(dirname(__FILE__));
6 $baseDir = dirname($vendorDir);
7
8 return array(
9 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
10 );
1 <?php
2
3 // autoload_namespaces.php @generated by Composer
4
5 $vendorDir = dirname(dirname(__FILE__));
6 $baseDir = dirname($vendorDir);
7
8 return array(
9 'Twig_' => array($vendorDir . '/twig/twig/lib'),
10 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'),
11 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'),
12 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
13 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
14 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'),
15 'Silex' => array($vendorDir . '/silex/silex/src'),
16 'Psr\\Log\\' => array($vendorDir . '/psr/log'),
17 'Pimple' => array($vendorDir . '/pimple/pimple/lib'),
18 );
1 <?php
2
3 // autoload_psr4.php @generated by Composer
4
5 $vendorDir = dirname(dirname(__FILE__));
6 $baseDir = dirname($vendorDir);
7
8 return array(
9 'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
10 'GuzzleHttp\\Stream\\' => array($vendorDir . '/guzzlehttp/streams/src'),
11 'GuzzleHttp\\Ring\\' => array($vendorDir . '/guzzlehttp/ringphp/src'),
12 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
13 );
1 <?php
2
3 // autoload_real.php @generated by Composer
4
5 class ComposerAutoloaderInit0d114684bec13fba4103960eaa00d106
6 {
7 private static $loader;
8
9 public static function loadClassLoader($class)
10 {
11 if ('Composer\Autoload\ClassLoader' === $class) {
12 require __DIR__ . '/ClassLoader.php';
13 }
14 }
15
16 public static function getLoader()
17 {
18 if (null !== self::$loader) {
19 return self::$loader;
20 }
21
22 spl_autoload_register(array('ComposerAutoloaderInit0d114684bec13fba4103960eaa00d106', 'loadClassLoader'), true, true);
23 self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24 spl_autoload_unregister(array('ComposerAutoloaderInit0d114684bec13fba4103960eaa00d106', 'loadClassLoader'));
25
26 $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
27 if ($useStaticLoader) {
28 require_once __DIR__ . '/autoload_static.php';
29
30 call_user_func(\Composer\Autoload\ComposerStaticInit0d114684bec13fba4103960eaa00d106::getInitializer($loader));
31 } else {
32 $map = require __DIR__ . '/autoload_namespaces.php';
33 foreach ($map as $namespace => $path) {
34 $loader->set($namespace, $path);
35 }
36
37 $map = require __DIR__ . '/autoload_psr4.php';
38 foreach ($map as $namespace => $path) {
39 $loader->setPsr4($namespace, $path);
40 }
41
42 $classMap = require __DIR__ . '/autoload_classmap.php';
43 if ($classMap) {
44 $loader->addClassMap($classMap);
45 }
46 }
47
48 $loader->register(true);
49
50 if ($useStaticLoader) {
51 $includeFiles = Composer\Autoload\ComposerStaticInit0d114684bec13fba4103960eaa00d106::$files;
52 } else {
53 $includeFiles = require __DIR__ . '/autoload_files.php';
54 }
55 foreach ($includeFiles as $fileIdentifier => $file) {
56 composerRequire0d114684bec13fba4103960eaa00d106($fileIdentifier, $file);
57 }
58
59 return $loader;
60 }
61 }
62
63 function composerRequire0d114684bec13fba4103960eaa00d106($fileIdentifier, $file)
64 {
65 if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
66 require $file;
67
68 $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
69 }
70 }
This diff could not be displayed because it is too large.
apiclient @ 107dc7d3
1 Subproject commit 107dc7d30fa9c6e5b75ce1200665e4fc3adfba6e
1 phpunit.xml
2 composer.phar
3 composer.lock
4 composer-test.lock
5 vendor/
6 build/artifacts/
7 artifacts/
8 docs/_build
9 docs/*.pyc
10 .idea
11 .DS_STORE
1 Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
2
3 Permission is hereby granted, free of charge, to any person obtaining a copy
4 of this software and associated documentation files (the "Software"), to deal
5 in the Software without restriction, including without limitation the rights
6 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 copies of the Software, and to permit persons to whom the Software is
8 furnished to do so, subject to the following conditions:
9
10 The above copyright notice and this permission notice shall be included in
11 all copies or substantial portions of the Software.
12
13 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 THE SOFTWARE.
1 all: clean coverage docs
2
3 start-server:
4 cd vendor/guzzlehttp/ringphp && make start-server
5
6 stop-server:
7 cd vendor/guzzlehttp/ringphp && make stop-server
8
9 test: start-server
10 vendor/bin/phpunit
11 $(MAKE) stop-server
12
13 coverage: start-server
14 vendor/bin/phpunit --coverage-html=artifacts/coverage
15 $(MAKE) stop-server
16
17 view-coverage:
18 open artifacts/coverage/index.html
19
20 clean:
21 rm -rf artifacts/*
22
23 docs:
24 cd docs && make html && cd ..
25
26 view-docs:
27 open docs/_build/html/index.html
28
29 tag:
30 $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))
31 @echo Tagging $(TAG)
32 chag update $(TAG)
33 sed -i '' -e "s/VERSION = '.*'/VERSION = '$(TAG)'/" src/ClientInterface.php
34 php -l src/ClientInterface.php
35 git add -A
36 git commit -m '$(TAG) release'
37 chag tag
38
39 perf: start-server
40 php tests/perf.php
41 $(MAKE) stop-server
42
43 package: burgomaster
44 php build/packager.php
45
46 burgomaster:
47 mkdir -p build/artifacts
48 curl -s https://raw.githubusercontent.com/mtdowling/Burgomaster/0.0.2/src/Burgomaster.php > build/artifacts/Burgomaster.php
49
50 .PHONY: docs burgomaster
1 Guzzle, PHP HTTP client and webservice framework
2 ================================================
3
4 [![Build Status](https://secure.travis-ci.org/guzzle/guzzle.png?branch=master)](http://travis-ci.org/guzzle/guzzle)
5
6 Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
7 trivial to integrate with web services.
8
9 - Manages things like persistent connections, represents query strings as
10 collections, simplifies sending streaming POST requests with fields and
11 files, and abstracts away the underlying HTTP transport layer.
12 - Can send both synchronous and asynchronous requests using the same interface
13 without requiring a dependency on a specific event loop.
14 - Pluggable HTTP adapters allows Guzzle to integrate with any method you choose
15 for sending HTTP requests over the wire (e.g., cURL, sockets, PHP's stream
16 wrapper, non-blocking event loops like ReactPHP.
17 - Guzzle makes it so that you no longer need to fool around with cURL options,
18 stream contexts, or sockets.
19
20 ```php
21 $client = new GuzzleHttp\Client();
22 $response = $client->get('http://guzzlephp.org');
23 $res = $client->get('https://api.github.com/user', ['auth' => ['user', 'pass']]);
24 echo $res->getStatusCode();
25 // "200"
26 echo $res->getHeader('content-type');
27 // 'application/json; charset=utf8'
28 echo $res->getBody();
29 // {"type":"User"...'
30 var_export($res->json());
31 // Outputs the JSON decoded data
32
33 // Send an asynchronous request.
34 $req = $client->createRequest('GET', 'http://httpbin.org', ['future' => true]);
35 $client->send($req)->then(function ($response) {
36 echo 'I completed! ' . $response;
37 });
38 ```
39
40 Get more information and answers with the
41 [Documentation](http://guzzlephp.org/),
42 [Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle),
43 and [Gitter](https://gitter.im/guzzle/guzzle).
44
45 ### Installing via Composer
46
47 The recommended way to install Guzzle is through
48 [Composer](http://getcomposer.org).
49
50 ```bash
51 # Install Composer
52 curl -sS https://getcomposer.org/installer | php
53 ```
54
55 Next, run the Composer command to install the latest stable version of Guzzle:
56
57 ```bash
58 composer require guzzlehttp/guzzle
59 ```
60
61 After installing, you need to require Composer's autoloader:
62
63 ```php
64 require 'vendor/autoload.php';
65 ```
66
67 ### Documentation
68
69 More information can be found in the online documentation at
70 http://guzzlephp.org/.
1 <?php
2 require __DIR__ . '/artifacts/Burgomaster.php';
3
4 $stageDirectory = __DIR__ . '/artifacts/staging';
5 $projectRoot = __DIR__ . '/../';
6 $packager = new \Burgomaster($stageDirectory, $projectRoot);
7
8 // Copy basic files to the stage directory. Note that we have chdir'd onto
9 // the $projectRoot directory, so use relative paths.
10 foreach (['README.md', 'LICENSE'] as $file) {
11 $packager->deepCopy($file, $file);
12 }
13
14 // Copy each dependency to the staging directory. Copy *.php and *.pem files.
15 $packager->recursiveCopy('src', 'GuzzleHttp', ['php']);
16 $packager->recursiveCopy('vendor/react/promise/src', 'React/Promise');
17 $packager->recursiveCopy('vendor/guzzlehttp/ringphp/src', 'GuzzleHttp/Ring');
18 $packager->recursiveCopy('vendor/guzzlehttp/streams/src', 'GuzzleHttp/Stream');
19 $packager->createAutoloader(['React/Promise/functions.php']);
20 $packager->createPhar(__DIR__ . '/artifacts/guzzle.phar');
21 $packager->createZip(__DIR__ . '/artifacts/guzzle.zip');
1 {
2 "name": "guzzlehttp/guzzle",
3 "type": "library",
4 "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
5 "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
6 "homepage": "http://guzzlephp.org/",
7 "license": "MIT",
8 "authors": [
9 {
10 "name": "Michael Dowling",
11 "email": "mtdowling@gmail.com",
12 "homepage": "https://github.com/mtdowling"
13 }
14 ],
15 "require": {
16 "php": ">=5.4.0",
17 "guzzlehttp/ringphp": "~1.0"
18 },
19 "require-dev": {
20 "ext-curl": "*",
21 "psr/log": "~1.0",
22 "phpunit/phpunit": "~4.0"
23 },
24 "autoload": {
25 "psr-4": {
26 "GuzzleHttp\\": "src/"
27 }
28 },
29 "autoload-dev": {
30 "psr-4": {
31 "GuzzleHttp\\Tests\\": "tests/"
32 }
33 },
34 "extra": {
35 "branch-alias": {
36 "dev-master": "5.0-dev"
37 }
38 }
39 }
1 # Makefile for Sphinx documentation
2 #
3
4 # You can set these variables from the command line.
5 SPHINXOPTS =
6 SPHINXBUILD = sphinx-build
7 PAPER =
8 BUILDDIR = _build
9
10 # Internal variables.
11 PAPEROPT_a4 = -D latex_paper_size=a4
12 PAPEROPT_letter = -D latex_paper_size=letter
13 ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 # the i18n builder cannot share the environment and doctrees with the others
15 I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16
17 .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18
19 help:
20 @echo "Please use \`make <target>' where <target> is one of"
21 @echo " html to make standalone HTML files"
22 @echo " dirhtml to make HTML files named index.html in directories"
23 @echo " singlehtml to make a single large HTML file"
24 @echo " pickle to make pickle files"
25 @echo " json to make JSON files"
26 @echo " htmlhelp to make HTML files and a HTML help project"
27 @echo " qthelp to make HTML files and a qthelp project"
28 @echo " devhelp to make HTML files and a Devhelp project"
29 @echo " epub to make an epub"
30 @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 @echo " text to make text files"
33 @echo " man to make manual pages"
34 @echo " texinfo to make Texinfo files"
35 @echo " info to make Texinfo files and run them through makeinfo"
36 @echo " gettext to make PO message catalogs"
37 @echo " changes to make an overview of all changed/added/deprecated items"
38 @echo " linkcheck to check all external links for integrity"
39 @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40
41 clean:
42 -rm -rf $(BUILDDIR)/*
43
44 html:
45 $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 @echo
47 @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48
49 dirhtml:
50 $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 @echo
52 @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53
54 singlehtml:
55 $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 @echo
57 @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58
59 pickle:
60 $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 @echo
62 @echo "Build finished; now you can process the pickle files."
63
64 json:
65 $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 @echo
67 @echo "Build finished; now you can process the JSON files."
68
69 htmlhelp:
70 $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 @echo
72 @echo "Build finished; now you can run HTML Help Workshop with the" \
73 ".hhp project file in $(BUILDDIR)/htmlhelp."
74
75 qthelp:
76 $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 @echo
78 @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Guzzle.qhcp"
81 @echo "To view the help file:"
82 @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Guzzle.qhc"
83
84 devhelp:
85 $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 @echo
87 @echo "Build finished."
88 @echo "To view the help file:"
89 @echo "# mkdir -p $$HOME/.local/share/devhelp/Guzzle"
90 @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Guzzle"
91 @echo "# devhelp"
92
93 epub:
94 $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 @echo
96 @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97
98 latex:
99 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 @echo
101 @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 "(use \`make latexpdf' here to do that automatically)."
104
105 latexpdf:
106 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 @echo "Running LaTeX files through pdflatex..."
108 $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110
111 text:
112 $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 @echo
114 @echo "Build finished. The text files are in $(BUILDDIR)/text."
115
116 man:
117 $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 @echo
119 @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120
121 texinfo:
122 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 @echo
124 @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 @echo "Run \`make' in that directory to run these through makeinfo" \
126 "(use \`make info' here to do that automatically)."
127
128 info:
129 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 @echo "Running Texinfo files through makeinfo..."
131 make -C $(BUILDDIR)/texinfo info
132 @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133
134 gettext:
135 $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 @echo
137 @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138
139 changes:
140 $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 @echo
142 @echo "The overview file is in $(BUILDDIR)/changes."
143
144 linkcheck:
145 $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 @echo
147 @echo "Link check complete; look for any errors in the above output " \
148 "or in $(BUILDDIR)/linkcheck/output.txt."
149
150 doctest:
151 $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 @echo "Testing of doctests in the sources finished, look at the " \
153 "results in $(BUILDDIR)/doctest/output.txt."
1 <li><a href="https://github.com/guzzle/guzzle">GitHub</a></li>
2 <li><a href="https://groups.google.com/forum/?hl=en#!forum/guzzle">Forum</a></li>
3 <li><a href="irc:irc.freenode.com/#guzzlephp">IRC</a></li>
1 import sys, os
2 from sphinx.highlighting import lexers
3 from pygments.lexers.web import PhpLexer
4
5
6 lexers['php'] = PhpLexer(startinline=True, linenos=1)
7 lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1)
8 primary_domain = 'php'
9
10 extensions = []
11 templates_path = ['_templates']
12 source_suffix = '.rst'
13 master_doc = 'index'
14 project = u'Guzzle'
15 copyright = u'2014, Michael Dowling'
16 version = '5.0.0'
17 html_title = "Guzzle Documentation"
18 html_short_title = "Guzzle"
19
20 exclude_patterns = ['_build']
21 html_static_path = ['_static']
22
23 on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
24
25 if not on_rtd: # only import and set the theme if we're building docs locally
26 import sphinx_rtd_theme
27 html_theme = 'sphinx_rtd_theme'
28 html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
1 ===
2 FAQ
3 ===
4
5 Why should I use Guzzle?
6 ========================
7
8 Guzzle makes it easy to send HTTP requests and super simple to integrate with
9 web services. Guzzle manages things like persistent connections, represents
10 query strings as collections, makes it simple to send streaming POST requests
11 with fields and files, and abstracts away the underlying HTTP transport layer.
12 By providing an object oriented interface for HTTP clients, requests, responses,
13 headers, and message bodies, Guzzle makes it so that you no longer need to fool
14 around with cURL options, stream contexts, or sockets.
15
16 **Asynchronous and Synchronous Requests**
17
18 Guzzle allows you to send both asynchronous and synchronous requests using the
19 same interface and no direct dependency on an event loop. This flexibility
20 allows Guzzle to send an HTTP request using the most appropriate HTTP handler
21 based on the request being sent. For example, when sending synchronous
22 requests, Guzzle will by default send requests using cURL easy handles to
23 ensure you're using the fastest possible method for serially transferring HTTP
24 requests. When sending asynchronous requests, Guzzle might use cURL's multi
25 interface or any other asynchronous handler you configure. When you request
26 streaming data, Guzzle will by default use PHP's stream wrapper.
27
28 **Streams**
29
30 Request and response message bodies use :doc:`Guzzle Streams <streams>`,
31 allowing you to stream data without needing to load it all into memory.
32 Guzzle's stream layer provides a large suite of functionality:
33
34 - You can modify streams at runtime using custom or a number of
35 pre-made decorators.
36 - You can emit progress events as data is read from a stream.
37 - You can validate the integrity of a stream using a rolling hash as data is
38 read from a stream.
39
40 **Event System and Plugins**
41
42 Guzzle's event system allows you to completely modify the behavior of a client
43 or request at runtime to cater them for any API. You can send a request with a
44 client, and the client can do things like automatically retry your request if
45 it fails, automatically redirect, log HTTP messages that are sent over the
46 wire, emit progress events as data is uploaded and downloaded, sign requests
47 using OAuth 1.0, verify the integrity of messages before and after they are
48 sent over the wire, and anything else you might need.
49
50 **Testable**
51
52 Another important aspect of Guzzle is that it's really
53 :doc:`easy to test clients <testing>`. You can mock HTTP responses and when
54 testing an handler implementation, Guzzle provides a mock node.js web server.
55
56 **Ecosystem**
57
58 Guzzle has a large `ecosystem of plugins <http://guzzle.readthedocs.org/en/latest/index.html#http-components>`_,
59 including `service descriptions <https://github.com/guzzle/guzzle-services>`_
60 which allows you to abstract web services using service descriptions. These
61 service descriptions define how to serialize an HTTP request and how to parse
62 an HTTP response into a more meaningful model object.
63
64 - `Guzzle Command <https://github.com/guzzle/command>`_: Provides the building
65 blocks for service description abstraction.
66 - `Guzzle Services <https://github.com/guzzle/guzzle-services>`_: Provides an
67 implementation of "Guzzle Command" that utilizes Guzzle's service description
68 format.
69
70 Does Guzzle require cURL?
71 =========================
72
73 No. Guzzle can use any HTTP handler to send requests. This means that Guzzle
74 can be used with cURL, PHP's stream wrapper, sockets, and non-blocking libraries
75 like `React <http://reactphp.org/>`_. You just need to configure a
76 `RingPHP <http://guzzle-ring.readthedocs.org/en/latest/>`_ handler to use a
77 different method of sending requests.
78
79 .. note::
80
81 Guzzle has historically only utilized cURL to send HTTP requests. cURL is
82 an amazing HTTP client (arguably the best), and Guzzle will continue to use
83 it by default when it is available. It is rare, but some developers don't
84 have cURL installed on their systems or run into version specific issues.
85 By allowing swappable HTTP handlers, Guzzle is now much more customizable
86 and able to adapt to fit the needs of more developers.
87
88 Can Guzzle send asynchronous requests?
89 ======================================
90
91 Yes. Pass the ``future`` true request option to a request to send it
92 asynchronously. Guzzle will then return a ``GuzzleHttp\Message\FutureResponse``
93 object that can be used synchronously by accessing the response object like a
94 normal response, and it can be used asynchronously using a promise that is
95 notified when the response is resolved with a real response or rejected with an
96 exception.
97
98 .. code-block:: php
99
100 $request = $client->createRequest('GET', ['future' => true]);
101 $client->send($request)->then(function ($response) {
102 echo 'Got a response! ' . $response;
103 });
104
105 You can force an asynchronous response to complete using the ``wait()`` method
106 of a response.
107
108 .. code-block:: php
109
110 $request = $client->createRequest('GET', ['future' => true]);
111 $futureResponse = $client->send($request);
112 $futureResponse->wait();
113
114 How can I add custom cURL options?
115 ==================================
116
117 cURL offer a huge number of `customizable options <http://us1.php.net/curl_setopt>`_.
118 While Guzzle normalizes many of these options across different handlers, there
119 are times when you need to set custom cURL options. This can be accomplished
120 by passing an associative array of cURL settings in the **curl** key of the
121 **config** request option.
122
123 For example, let's say you need to customize the outgoing network interface
124 used with a client.
125
126 .. code-block:: php
127
128 $client->get('/', [
129 'config' => [
130 'curl' => [
131 CURLOPT_INTERFACE => 'xxx.xxx.xxx.xxx'
132 ]
133 ]
134 ]);
135
136 How can I add custom stream context options?
137 ============================================
138
139 You can pass custom `stream context options <http://www.php.net/manual/en/context.php>`_
140 using the **stream_context** key of the **config** request option. The
141 **stream_context** array is an associative array where each key is a PHP
142 transport, and each value is an associative array of transport options.
143
144 For example, let's say you need to customize the outgoing network interface
145 used with a client and allow self-signed certificates.
146
147 .. code-block:: php
148
149 $client->get('/', [
150 'stream' => true,
151 'config' => [
152 'stream_context' => [
153 'ssl' => [
154 'allow_self_signed' => true
155 ],
156 'socket' => [
157 'bindto' => 'xxx.xxx.xxx.xxx'
158 ]
159 ]
160 ]
161 ]);
162
163 Why am I getting an SSL verification error?
164 ===========================================
165
166 You need to specify the path on disk to the CA bundle used by Guzzle for
167 verifying the peer certificate. See :ref:`verify-option`.
168
169 What is this Maximum function nesting error?
170 ============================================
171
172 Maximum function nesting level of '100' reached, aborting
173
174 You could run into this error if you have the XDebug extension installed and
175 you execute a lot of requests in callbacks. This error message comes
176 specifically from the XDebug extension. PHP itself does not have a function
177 nesting limit. Change this setting in your php.ini to increase the limit::
178
179 xdebug.max_nesting_level = 1000
180
181 Why am I getting a 417 error response?
182 ======================================
183
184 This can occur for a number of reasons, but if you are sending PUT, POST, or
185 PATCH requests with an ``Expect: 100-Continue`` header, a server that does not
186 support this header will return a 417 response. You can work around this by
187 setting the ``expect`` request option to ``false``:
188
189 .. code-block:: php
190
191 $client = new GuzzleHttp\Client();
192
193 // Disable the expect header on a single request
194 $response = $client->put('/', [], 'the body', [
195 'expect' => false
196 ]);
197
198 // Disable the expect header on all client requests
199 $client->setDefaultOption('expect', false)
1 ================
2 RingPHP Handlers
3 ================
4
5 Guzzle uses RingPHP handlers to send HTTP requests over the wire.
6 RingPHP provides a low-level library that can be used to "glue" Guzzle with
7 any transport method you choose. By default, Guzzle utilizes cURL and PHP's
8 stream wrappers to send HTTP requests.
9
10 RingPHP handlers makes it extremely simple to integrate Guzzle with any
11 HTTP transport. For example, you could quite easily bridge Guzzle and React
12 to use Guzzle in React's event loop.
13
14 Using a handler
15 ---------------
16
17 You can change the handler used by a client using the ``handler`` option in the
18 ``GuzzleHttp\Client`` constructor.
19
20 .. code-block:: php
21
22 use GuzzleHttp\Client;
23 use GuzzleHttp\Ring\Client\MockHandler;
24
25 // Create a mock handler that always returns a 200 response.
26 $handler = new MockHandler(['status' => 200]);
27
28 // Configure to client to use the mock handler.
29 $client = new Client(['handler' => $handler]);
30
31 At its core, handlers are simply PHP callables that accept a request array
32 and return a ``GuzzleHttp\Ring\Future\FutureArrayInterface``. This future array
33 can be used just like a normal PHP array, causing it to block, or you can use
34 the promise interface using the ``then()`` method of the future. Guzzle hooks
35 up to the RingPHP project using a very simple bridge class
36 (``GuzzleHttp\RingBridge``).
37
38 Creating a handler
39 ------------------
40
41 See the `RingPHP <http://ringphp.readthedocs.org>`_ project
42 documentation for more information on creating custom handlers that can be
43 used with Guzzle clients.
1 .. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services
2
3 ======
4 Guzzle
5 ======
6
7 Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
8 trivial to integrate with web services.
9
10 - Manages things like persistent connections, represents query strings as
11 collections, simplifies sending streaming POST requests with fields and
12 files, and abstracts away the underlying HTTP transport layer.
13 - Can send both synchronous and asynchronous requests using the same interface
14 without requiring a dependency on a specific event loop.
15 - Pluggable HTTP handlers allows Guzzle to integrate with any method you choose
16 for sending HTTP requests over the wire (e.g., cURL, sockets, PHP's stream
17 wrapper, non-blocking event loops like `React <http://reactphp.org/>`_, etc.).
18 - Guzzle makes it so that you no longer need to fool around with cURL options,
19 stream contexts, or sockets.
20
21 .. code-block:: php
22
23 $client = new GuzzleHttp\Client();
24 $response = $client->get('http://guzzlephp.org');
25 $res = $client->get('https://api.github.com/user', ['auth' => ['user', 'pass']]);
26 echo $res->getStatusCode();
27 // "200"
28 echo $res->getHeader('content-type');
29 // 'application/json; charset=utf8'
30 echo $res->getBody();
31 // {"type":"User"...'
32 var_export($res->json());
33 // Outputs the JSON decoded data
34
35 // Send an asynchronous request.
36 $req = $client->createRequest('GET', 'http://httpbin.org', ['future' => true]);
37 $client->send($req)->then(function ($response) {
38 echo 'I completed! ' . $response;
39 });
40
41 User guide
42 ----------
43
44 .. toctree::
45 :maxdepth: 2
46
47 overview
48 quickstart
49 clients
50 http-messages
51 events
52 streams
53 handlers
54 testing
55 faq
56
57 HTTP Components
58 ---------------
59
60 There are a number of optional libraries you can use along with Guzzle's HTTP
61 layer to add capabilities to the client.
62
63 `Log Subscriber <https://github.com/guzzle/log-subscriber>`_
64 Logs HTTP requests and responses sent over the wire using customizable
65 log message templates.
66
67 `OAuth Subscriber <https://github.com/guzzle/oauth-subscriber>`_
68 Signs requests using OAuth 1.0.
69
70 `Cache Subscriber <https://github.com/guzzle/cache-subscriber>`_
71 Implements a private transparent proxy cache that caches HTTP responses.
72
73 `Retry Subscriber <https://github.com/guzzle/retry-subscriber>`_
74 Retries failed requests using customizable retry strategies (e.g., retry
75 based on response status code, cURL error codes, etc.)
76
77 `Message Integrity Subscriber <https://github.com/guzzle/message-integrity-subscriber>`_
78 Verifies the message integrity of HTTP responses using customizable
79 validators. This plugin can be used, for example, to verify the Content-MD5
80 headers of responses.
81
82 Service Description Commands
83 ----------------------------
84
85 You can use the **Guzzle Command** library to encapsulate interaction with a
86 web service using command objects. Building on top of Guzzle's command
87 abstraction allows you to easily implement things like service description that
88 can be used to serialize requests and parse responses using a meta-description
89 of a web service.
90
91 `Guzzle Command <https://github.com/guzzle/command>`_
92 Provides the foundational elements used to build high-level, command based,
93 web service clients with Guzzle.
94
95 `Guzzle Services <https://github.com/guzzle/guzzle-services>`_
96 Provides an implementation of the *Guzzle Command* library that uses
97 Guzzle service descriptions to describe web services, serialize requests,
98 and parse responses into easy to use model structures.
1 ========
2 Overview
3 ========
4
5 Requirements
6 ============
7
8 #. PHP 5.4.0
9 #. To use the PHP stream handler, ``allow_url_fopen`` must be enabled in your
10 system's php.ini.
11 #. To use the cURL handler, you must have a recent version of cURL >= 7.16.2
12 compiled with OpenSSL and zlib.
13
14 .. note::
15
16 Guzzle no longer requires cURL in order to send HTTP requests. Guzzle will
17 use the PHP stream wrapper to send HTTP requests if cURL is not installed.
18 Alternatively, you can provide your own HTTP handler used to send requests.
19
20 .. _installation:
21
22 Installation
23 ============
24
25 The recommended way to install Guzzle is with `Composer <http://getcomposer.org>`_. Composer is a dependency
26 management tool for PHP that allows you to declare the dependencies your project needs and installs them into your
27 project.
28
29 .. code-block:: bash
30
31 # Install Composer
32 curl -sS https://getcomposer.org/installer | php
33
34 You can add Guzzle as a dependency using the composer.phar CLI:
35
36 .. code-block:: bash
37
38 php composer.phar require guzzlehttp/guzzle:~5.0
39
40 Alternatively, you can specify Guzzle as a dependency in your project's
41 existing composer.json file:
42
43 .. code-block:: js
44
45 {
46 "require": {
47 "guzzlehttp/guzzle": "~5.0"
48 }
49 }
50
51 After installing, you need to require Composer's autoloader:
52
53 .. code-block:: php
54
55 require 'vendor/autoload.php';
56
57 You can find out more on how to install Composer, configure autoloading, and
58 other best-practices for defining dependencies at `getcomposer.org <http://getcomposer.org>`_.
59
60 Bleeding edge
61 -------------
62
63 During your development, you can keep up with the latest changes on the master
64 branch by setting the version requirement for Guzzle to ``~5.0@dev``.
65
66 .. code-block:: js
67
68 {
69 "require": {
70 "guzzlehttp/guzzle": "~5.0@dev"
71 }
72 }
73
74 License
75 =======
76
77 Licensed using the `MIT license <http://opensource.org/licenses/MIT>`_.
78
79 Copyright (c) 2014 Michael Dowling <https://github.com/mtdowling>
80
81 Permission is hereby granted, free of charge, to any person obtaining a copy
82 of this software and associated documentation files (the "Software"), to deal
83 in the Software without restriction, including without limitation the rights
84 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
85 copies of the Software, and to permit persons to whom the Software is
86 furnished to do so, subject to the following conditions:
87
88 The above copyright notice and this permission notice shall be included in
89 all copies or substantial portions of the Software.
90
91 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
92 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
93 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
94 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
95 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
96 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
97 THE SOFTWARE.
98
99 Contributing
100 ============
101
102 Guidelines
103 ----------
104
105 1. Guzzle follows PSR-0, PSR-1, and PSR-2.
106 2. Guzzle is meant to be lean and fast with very few dependencies.
107 3. Guzzle has a minimum PHP version requirement of PHP 5.4. Pull requests must
108 not require a PHP version greater than PHP 5.4.
109 4. All pull requests must include unit tests to ensure the change works as
110 expected and to prevent regressions.
111
112 Running the tests
113 -----------------
114
115 In order to contribute, you'll need to checkout the source from GitHub and
116 install Guzzle's dependencies using Composer:
117
118 .. code-block:: bash
119
120 git clone https://github.com/guzzle/guzzle.git
121 cd guzzle && curl -s http://getcomposer.org/installer | php && ./composer.phar install --dev
122
123 Guzzle is unit tested with PHPUnit. Run the tests using the vendored PHPUnit
124 binary:
125
126 .. code-block:: bash
127
128 vendor/bin/phpunit
129
130 .. note::
131
132 You'll need to install node.js v0.5.0 or newer in order to perform
133 integration tests on Guzzle's HTTP handlers.
134
135 Reporting a security vulnerability
136 ==================================
137
138 We want to ensure that Guzzle is a secure HTTP client library for everyone. If
139 you've discovered a security vulnerability in Guzzle, we appreciate your help
140 in disclosing it to us in a `responsible manner <http://en.wikipedia.org/wiki/Responsible_disclosure>`_.
141
142 Publicly disclosing a vulnerability can put the entire community at risk. If
143 you've discovered a security concern, please email us at
144 security@guzzlephp.org. We'll work with you to make sure that we understand the
145 scope of the issue, and that we fully address your concern. We consider
146 correspondence sent to security@guzzlephp.org our highest priority, and work to
147 address any issues that arise as quickly as possible.
148
149 After a security vulnerability has been corrected, a security hotfix release will
150 be deployed as soon as possible.
1 Sphinx>=1.2b1
2 guzzle_sphinx_theme>=0.6.0
1 =======
2 Streams
3 =======
4
5 Guzzle uses stream objects to represent request and response message bodies.
6 These stream objects allow you to work with various types of data all using a
7 common interface.
8
9 HTTP messages consist of a start-line, headers, and a body. The body of an HTTP
10 message can be very small or extremely large. Attempting to represent the body
11 of a message as a string can easily consume more memory than intended because
12 the body must be stored completely in memory. Attempting to store the body of a
13 request or response in memory would preclude the use of that implementation from
14 being able to work with large message bodies. The StreamInterface is used in
15 order to hide the implementation details of where a stream of data is read from
16 or written to.
17
18 Guzzle's StreamInterface exposes several methods that enable streams to be read
19 from, written to, and traversed effectively.
20
21 Streams expose their capabilities using three methods: ``isReadable()``,
22 ``isWritable()``, and ``isSeekable()``. These methods can be used by stream
23 collaborators to determine if a stream is capable of their requirements.
24
25 Each stream instance has various capabilities: they can be read-only,
26 write-only, read-write, allow arbitrary random access (seeking forwards or
27 backwards to any location), or only allow sequential access (for example in the
28 case of a socket or pipe).
29
30 Creating Streams
31 ================
32
33 The best way to create a stream is using the static factory method,
34 ``GuzzleHttp\Stream\Stream::factory()``. This factory accepts strings,
35 resources returned from ``fopen()``, an object that implements
36 ``__toString()``, and an object that implements
37 ``GuzzleHttp\Stream\StreamInterface``.
38
39 .. code-block:: php
40
41 use GuzzleHttp\Stream\Stream;
42
43 $stream = Stream::factory('string data');
44 echo $stream;
45 // string data
46 echo $stream->read(3);
47 // str
48 echo $stream->getContents();
49 // ing data
50 var_export($stream->eof());
51 // true
52 var_export($stream->tell());
53 // 11
54
55 Metadata
56 ========
57
58 Guzzle streams expose stream metadata through the ``getMetadata()`` method.
59 This method provides the data you would retrieve when calling PHP's
60 `stream_get_meta_data() function <http://php.net/manual/en/function.stream-get-meta-data.php>`_,
61 and can optionally expose other custom data.
62
63 .. code-block:: php
64
65 use GuzzleHttp\Stream\Stream;
66
67 $resource = fopen('/path/to/file', 'r');
68 $stream = Stream::factory($resource);
69 echo $stream->getMetadata('uri');
70 // /path/to/file
71 var_export($stream->isReadable());
72 // true
73 var_export($stream->isWritable());
74 // false
75 var_export($stream->isSeekable());
76 // true
77
78 Stream Decorators
79 =================
80
81 With the small and focused interface, add custom functionality to streams is
82 very simple with stream decorators. Guzzle provides several built-in decorators
83 that provide additional stream functionality.
84
85 CachingStream
86 -------------
87
88 The CachingStream is used to allow seeking over previously read bytes on
89 non-seekable streams. This can be useful when transferring a non-seekable
90 entity body fails due to needing to rewind the stream (for example, resulting
91 from a redirect). Data that is read from the remote stream will be buffered in
92 a PHP temp stream so that previously read bytes are cached first in memory,
93 then on disk.
94
95 .. code-block:: php
96
97 use GuzzleHttp\Stream\Stream;
98 use GuzzleHttp\Stream\CachingStream;
99
100 $original = Stream::factory(fopen('http://www.google.com', 'r'));
101 $stream = new CachingStream($original);
102
103 $stream->read(1024);
104 echo $stream->tell();
105 // 1024
106
107 $stream->seek(0);
108 echo $stream->tell();
109 // 0
110
111 LimitStream
112 -----------
113
114 LimitStream can be used to read a subset or slice of an existing stream object.
115 This can be useful for breaking a large file into smaller pieces to be sent in
116 chunks (e.g. Amazon S3's multipart upload API).
117
118 .. code-block:: php
119
120 use GuzzleHttp\Stream\Stream;
121 use GuzzleHttp\Stream\LimitStream;
122
123 $original = Stream::factory(fopen('/tmp/test.txt', 'r+'));
124 echo $original->getSize();
125 // >>> 1048576
126
127 // Limit the size of the body to 1024 bytes and start reading from byte 2048
128 $stream = new LimitStream($original, 1024, 2048);
129 echo $stream->getSize();
130 // >>> 1024
131 echo $stream->tell();
132 // >>> 0
133
134 NoSeekStream
135 ------------
136
137 NoSeekStream wraps a stream and does not allow seeking.
138
139 .. code-block:: php
140
141 use GuzzleHttp\Stream\Stream;
142 use GuzzleHttp\Stream\LimitStream;
143
144 $original = Stream::factory('foo');
145 $noSeek = new NoSeekStream($original);
146
147 echo $noSeek->read(3);
148 // foo
149 var_export($noSeek->isSeekable());
150 // false
151 $noSeek->seek(0);
152 var_export($noSeek->read(3));
153 // NULL
154
155 Creating Custom Decorators
156 --------------------------
157
158 Creating a stream decorator is very easy thanks to the
159 ``GuzzleHttp\Stream\StreamDecoratorTrait``. This trait provides methods that
160 implement ``GuzzleHttp\Stream\StreamInterface`` by proxying to an underlying
161 stream. Just ``use`` the ``StreamDecoratorTrait`` and implement your custom
162 methods.
163
164 For example, let's say we wanted to call a specific function each time the last
165 byte is read from a stream. This could be implemented by overriding the
166 ``read()`` method.
167
168 .. code-block:: php
169
170 use GuzzleHttp\Stream\StreamDecoratorTrait;
171
172 class EofCallbackStream implements StreamInterface
173 {
174 use StreamDecoratorTrait;
175
176 private $callback;
177
178 public function __construct(StreamInterface $stream, callable $callback)
179 {
180 $this->stream = $stream;
181 $this->callback = $callback;
182 }
183
184 public function read($length)
185 {
186 $result = $this->stream->read($length);
187
188 // Invoke the callback when EOF is hit.
189 if ($this->eof()) {
190 call_user_func($this->callback);
191 }
192
193 return $result;
194 }
195 }
196
197 This decorator could be added to any existing stream and used like so:
198
199 .. code-block:: php
200
201 use GuzzleHttp\Stream\Stream;
202
203 $original = Stream::factory('foo');
204 $eofStream = new EofCallbackStream($original, function () {
205 echo 'EOF!';
206 });
207
208 $eofStream->read(2);
209 $eofStream->read(1);
210 // echoes "EOF!"
211 $eofStream->seek(0);
212 $eofStream->read(3);
213 // echoes "EOF!"
1 ======================
2 Testing Guzzle Clients
3 ======================
4
5 Guzzle provides several tools that will enable you to easily mock the HTTP
6 layer without needing to send requests over the internet.
7
8 * Mock subscriber
9 * Mock handler
10 * Node.js web server for integration testing
11
12 Mock Subscriber
13 ===============
14
15 When testing HTTP clients, you often need to simulate specific scenarios like
16 returning a successful response, returning an error, or returning specific
17 responses in a certain order. Because unit tests need to be predictable, easy
18 to bootstrap, and fast, hitting an actual remote API is a test smell.
19
20 Guzzle provides a mock subscriber that can be attached to clients or requests
21 that allows you to queue up a list of responses to use rather than hitting a
22 remote API.
23
24 .. code-block:: php
25
26 use GuzzleHttp\Client;
27 use GuzzleHttp\Subscriber\Mock;
28 use GuzzleHttp\Message\Response;
29
30 $client = new Client();
31
32 // Create a mock subscriber and queue two responses.
33 $mock = new Mock([
34 new Response(200, ['X-Foo' => 'Bar']), // Use response object
35 "HTTP/1.1 202 OK\r\nContent-Length: 0\r\n\r\n" // Use a response string
36 ]);
37
38 // Add the mock subscriber to the client.
39 $client->getEmitter()->attach($mock);
40 // The first request is intercepted with the first response.
41 echo $client->get('/')->getStatusCode();
42 //> 200
43 // The second request is intercepted with the second response.
44 echo $client->get('/')->getStatusCode();
45 //> 202
46
47 When no more responses are in the queue and a request is sent, an
48 ``OutOfBoundsException`` is thrown.
49
50 History Subscriber
51 ==================
52
53 When using things like the ``Mock`` subscriber, you often need to know if the
54 requests you expected to send were sent exactly as you intended. While the mock
55 subscriber responds with mocked responses, the ``GuzzleHttp\Subscriber\History``
56 subscriber maintains a history of the requests that were sent by a client.
57
58 .. code-block:: php
59
60 use GuzzleHttp\Client;
61 use GuzzleHttp\Subscriber\History;
62
63 $client = new Client();
64 $history = new History();
65
66 // Add the history subscriber to the client.
67 $client->getEmitter()->attach($history);
68
69 $client->get('http://httpbin.org/get');
70 $client->head('http://httpbin.org/get');
71
72 // Count the number of transactions
73 echo count($history);
74 //> 2
75 // Get the last request
76 $lastRequest = $history->getLastRequest();
77 // Get the last response
78 $lastRequest = $history->getLastResponse();
79
80 // Iterate over the transactions that were sent
81 foreach ($history as $transaction) {
82 echo $transaction['request']->getMethod();
83 //> GET, HEAD
84 echo $transaction['response']->getStatusCode();
85 //> 200, 200
86 }
87
88 The history subscriber can also be printed, revealing the requests and
89 responses that were sent as a string, in order.
90
91 .. code-block:: php
92
93 echo $history;
94
95 ::
96
97 > GET /get HTTP/1.1
98 Host: httpbin.org
99 User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8
100
101 < HTTP/1.1 200 OK
102 Access-Control-Allow-Origin: *
103 Content-Type: application/json
104 Date: Tue, 25 Mar 2014 03:53:27 GMT
105 Server: gunicorn/0.17.4
106 Content-Length: 270
107 Connection: keep-alive
108
109 {
110 "headers": {
111 "Connection": "close",
112 "X-Request-Id": "3d0f7d5c-c937-4394-8248-2b8e03fcccdb",
113 "User-Agent": "Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8",
114 "Host": "httpbin.org"
115 },
116 "origin": "76.104.247.1",
117 "args": {},
118 "url": "http://httpbin.org/get"
119 }
120
121 > HEAD /get HTTP/1.1
122 Host: httpbin.org
123 User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8
124
125 < HTTP/1.1 200 OK
126 Access-Control-Allow-Origin: *
127 Content-length: 270
128 Content-Type: application/json
129 Date: Tue, 25 Mar 2014 03:53:27 GMT
130 Server: gunicorn/0.17.4
131 Connection: keep-alive
132
133 Mock Adapter
134 ============
135
136 In addition to using the Mock subscriber, you can use the
137 ``GuzzleHttp\Ring\Client\MockAdapter`` as the handler of a client to return the
138 same response over and over or return the result of a callable function.
139
140 Test Web Server
141 ===============
142
143 Using mock responses is almost always enough when testing a web service client.
144 When implementing custom :doc:`HTTP handlers <handlers>`, you'll need to send
145 actual HTTP requests in order to sufficiently test the handler. However, a
146 best practice is to contact a local web server rather than a server over the
147 internet.
148
149 - Tests are more reliable
150 - Tests do not require a network connection
151 - Tests have no external dependencies
152
153 Using the test server
154 ---------------------
155
156 .. warning::
157
158 The following functionality is provided to help developers of Guzzle
159 develop HTTP handlers. There is no promise of backwards compatibility
160 when it comes to the node.js test server or the ``GuzzleHttp\Tests\Server``
161 class. If you are using the test server or ``Server`` class outside of
162 guzzlehttp/guzzle, then you will need to configure autoloading and
163 ensure the web server is started manually.
164
165 .. hint::
166
167 You almost never need to use this test web server. You should only ever
168 consider using it when developing HTTP handlers. The test web server
169 is not necessary for mocking requests. For that, please use the
170 Mock subcribers and History subscriber.
171
172 Guzzle ships with a node.js test server that receives requests and returns
173 responses from a queue. The test server exposes a simple API that is used to
174 enqueue responses and inspect the requests that it has received.
175
176 Any operation on the ``Server`` object will ensure that
177 the server is running and wait until it is able to receive requests before
178 returning.
179
180 .. code-block:: php
181
182 use GuzzleHttp\Client;
183 use GuzzleHttp\Tests\Server;
184
185 // Start the server and queue a response
186 Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n");
187
188 $client = new Client(['base_url' => Server::$url]);
189 echo $client->get('/foo')->getStatusCode();
190 // 200
191
192 ``GuzzleHttp\Tests\Server`` provides a static interface to the test server. You
193 can queue an HTTP response or an array of responses by calling
194 ``Server::enqueue()``. This method accepts a string representing an HTTP
195 response message, a ``GuzzleHttp\Message\ResponseInterface``, or an array of
196 HTTP message strings / ``GuzzleHttp\Message\ResponseInterface`` objects.
197
198 .. code-block:: php
199
200 // Queue single response
201 Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n");
202
203 // Clear the queue and queue an array of responses
204 Server::enqueue([
205 "HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n",
206 "HTTP/1.1 404 Not Found\r\n\Content-Length: 0r\n\r\n"
207 ]);
208
209 When a response is queued on the test server, the test server will remove any
210 previously queued responses. As the server receives requests, queued responses
211 are dequeued and returned to the request. When the queue is empty, the server
212 will return a 500 response.
213
214 You can inspect the requests that the server has retrieved by calling
215 ``Server::received()``. This method accepts an optional ``$hydrate`` parameter
216 that specifies if you are retrieving an array of HTTP requests as strings or an
217 array of ``GuzzleHttp\Message\RequestInterface`` objects.
218
219 .. code-block:: php
220
221 foreach (Server::received() as $response) {
222 echo $response;
223 }
224
225 You can clear the list of received requests from the web server using the
226 ``Server::flush()`` method.
227
228 .. code-block:: php
229
230 Server::flush();
231 echo count(Server::received());
232 // 0
1 <?xml version="1.0" encoding="UTF-8"?>
2 <phpunit bootstrap="./tests/bootstrap.php"
3 colors="true">
4 <testsuites>
5 <testsuite>
6 <directory>tests</directory>
7 </testsuite>
8 </testsuites>
9 <filter>
10 <whitelist>
11 <directory suffix=".php">src</directory>
12 <exclude>
13 <directory suffix="Interface.php">src/</directory>
14 </exclude>
15 </whitelist>
16 </filter>
17 </phpunit>
1 <?php
2 namespace GuzzleHttp;
3
4 /**
5 * Represents the result of a batch operation. This result container is
6 * iterable, countable, and you can can get a result by value using the
7 * getResult function.
8 *
9 * Successful results are anything other than exceptions. Failure results are
10 * exceptions.
11 *
12 * @package GuzzleHttp
13 */
14 class BatchResults implements \Countable, \IteratorAggregate, \ArrayAccess
15 {
16 private $hash;
17
18 /**
19 * @param \SplObjectStorage $hash Hash of key objects to result values.
20 */
21 public function __construct(\SplObjectStorage $hash)
22 {
23 $this->hash = $hash;
24 }
25
26 /**
27 * Get the keys that are available on the batch result.
28 *
29 * @return array
30 */
31 public function getKeys()
32 {
33 return iterator_to_array($this->hash);
34 }
35
36 /**
37 * Gets a result from the container for the given object. When getting
38 * results for a batch of requests, provide the request object.
39 *
40 * @param object $forObject Object to retrieve the result for.
41 *
42 * @return mixed|null
43 */
44 public function getResult($forObject)
45 {
46 return isset($this->hash[$forObject]) ? $this->hash[$forObject] : null;
47 }
48
49 /**
50 * Get an array of successful results.
51 *
52 * @return array
53 */
54 public function getSuccessful()
55 {
56 $results = [];
57 foreach ($this->hash as $key) {
58 if (!($this->hash[$key] instanceof \Exception)) {
59 $results[] = $this->hash[$key];
60 }
61 }
62
63 return $results;
64 }
65
66 /**
67 * Get an array of failed results.
68 *
69 * @return array
70 */
71 public function getFailures()
72 {
73 $results = [];
74 foreach ($this->hash as $key) {
75 if ($this->hash[$key] instanceof \Exception) {
76 $results[] = $this->hash[$key];
77 }
78 }
79
80 return $results;
81 }
82
83 /**
84 * Allows iteration over all batch result values.
85 *
86 * @return \ArrayIterator
87 */
88 public function getIterator()
89 {
90 $results = [];
91 foreach ($this->hash as $key) {
92 $results[] = $this->hash[$key];
93 }
94
95 return new \ArrayIterator($results);
96 }
97
98 /**
99 * Counts the number of elements in the batch result.
100 *
101 * @return int
102 */
103 public function count()
104 {
105 return count($this->hash);
106 }
107
108 /**
109 * Checks if the batch contains a specific numerical array index.
110 *
111 * @param int $key Index to access
112 *
113 * @return bool
114 */
115 public function offsetExists($key)
116 {
117 return $key < count($this->hash);
118 }
119
120 /**
121 * Allows access of the batch using a numerical array index.
122 *
123 * @param int $key Index to access.
124 *
125 * @return mixed|null
126 */
127 public function offsetGet($key)
128 {
129 $i = -1;
130 foreach ($this->hash as $obj) {
131 if ($key === ++$i) {
132 return $this->hash[$obj];
133 }
134 }
135
136 return null;
137 }
138
139 public function offsetUnset($key)
140 {
141 throw new \RuntimeException('Not implemented');
142 }
143
144 public function offsetSet($key, $value)
145 {
146 throw new \RuntimeException('Not implemented');
147 }
148 }
1 <?php
2 namespace GuzzleHttp;
3
4 use GuzzleHttp\Event\HasEmitterInterface;
5 use GuzzleHttp\Exception\RequestException;
6 use GuzzleHttp\Message\RequestInterface;
7 use GuzzleHttp\Message\ResponseInterface;
8
9 /**
10 * Client interface for sending HTTP requests
11 */
12 interface ClientInterface extends HasEmitterInterface
13 {
14 const VERSION = '5.2.0';
15
16 /**
17 * Create and return a new {@see RequestInterface} object.
18 *
19 * Use an absolute path to override the base path of the client, or a
20 * relative path to append to the base path of the client. The URL can
21 * contain the query string as well. Use an array to provide a URL
22 * template and additional variables to use in the URL template expansion.
23 *
24 * @param string $method HTTP method
25 * @param string|array|Url $url URL or URI template
26 * @param array $options Array of request options to apply.
27 *
28 * @return RequestInterface
29 */
30 public function createRequest($method, $url = null, array $options = []);
31
32 /**
33 * Send a GET request
34 *
35 * @param string|array|Url $url URL or URI template
36 * @param array $options Array of request options to apply.
37 *
38 * @return ResponseInterface
39 * @throws RequestException When an error is encountered
40 */
41 public function get($url = null, $options = []);
42
43 /**
44 * Send a HEAD request
45 *
46 * @param string|array|Url $url URL or URI template
47 * @param array $options Array of request options to apply.
48 *
49 * @return ResponseInterface
50 * @throws RequestException When an error is encountered
51 */
52 public function head($url = null, array $options = []);
53
54 /**
55 * Send a DELETE request
56 *
57 * @param string|array|Url $url URL or URI template
58 * @param array $options Array of request options to apply.
59 *
60 * @return ResponseInterface
61 * @throws RequestException When an error is encountered
62 */
63 public function delete($url = null, array $options = []);
64
65 /**
66 * Send a PUT request
67 *
68 * @param string|array|Url $url URL or URI template
69 * @param array $options Array of request options to apply.
70 *
71 * @return ResponseInterface
72 * @throws RequestException When an error is encountered
73 */
74 public function put($url = null, array $options = []);
75
76 /**
77 * Send a PATCH request
78 *
79 * @param string|array|Url $url URL or URI template
80 * @param array $options Array of request options to apply.
81 *
82 * @return ResponseInterface
83 * @throws RequestException When an error is encountered
84 */
85 public function patch($url = null, array $options = []);
86
87 /**
88 * Send a POST request
89 *
90 * @param string|array|Url $url URL or URI template
91 * @param array $options Array of request options to apply.
92 *
93 * @return ResponseInterface
94 * @throws RequestException When an error is encountered
95 */
96 public function post($url = null, array $options = []);
97
98 /**
99 * Send an OPTIONS request
100 *
101 * @param string|array|Url $url URL or URI template
102 * @param array $options Array of request options to apply.
103 *
104 * @return ResponseInterface
105 * @throws RequestException When an error is encountered
106 */
107 public function options($url = null, array $options = []);
108
109 /**
110 * Sends a single request
111 *
112 * @param RequestInterface $request Request to send
113 *
114 * @return \GuzzleHttp\Message\ResponseInterface
115 * @throws \LogicException When the handler does not populate a response
116 * @throws RequestException When an error is encountered
117 */
118 public function send(RequestInterface $request);
119
120 /**
121 * Get default request options of the client.
122 *
123 * @param string|null $keyOrPath The Path to a particular default request
124 * option to retrieve or pass null to retrieve all default request
125 * options. The syntax uses "/" to denote a path through nested PHP
126 * arrays. For example, "headers/content-type".
127 *
128 * @return mixed
129 */
130 public function getDefaultOption($keyOrPath = null);
131
132 /**
133 * Set a default request option on the client so that any request created
134 * by the client will use the provided default value unless overridden
135 * explicitly when creating a request.
136 *
137 * @param string|null $keyOrPath The Path to a particular configuration
138 * value to set. The syntax uses a path notation that allows you to
139 * specify nested configuration values (e.g., 'headers/content-type').
140 * @param mixed $value Default request option value to set
141 */
142 public function setDefaultOption($keyOrPath, $value);
143
144 /**
145 * Get the base URL of the client.
146 *
147 * @return string Returns the base URL if present
148 */
149 public function getBaseUrl();
150 }
1 <?php
2 namespace GuzzleHttp;
3
4 /**
5 * Key value pair collection object
6 */
7 class Collection implements
8 \ArrayAccess,
9 \IteratorAggregate,
10 \Countable,
11 ToArrayInterface
12 {
13 use HasDataTrait;
14
15 /**
16 * @param array $data Associative array of data to set
17 */
18 public function __construct(array $data = [])
19 {
20 $this->data = $data;
21 }
22
23 /**
24 * Create a new collection from an array, validate the keys, and add default
25 * values where missing
26 *
27 * @param array $config Configuration values to apply.
28 * @param array $defaults Default parameters
29 * @param array $required Required parameter names
30 *
31 * @return self
32 * @throws \InvalidArgumentException if a parameter is missing
33 */
34 public static function fromConfig(
35 array $config = [],
36 array $defaults = [],
37 array $required = []
38 ) {
39 $data = $config + $defaults;
40
41 if ($missing = array_diff($required, array_keys($data))) {
42 throw new \InvalidArgumentException(
43 'Config is missing the following keys: ' .
44 implode(', ', $missing));
45 }
46
47 return new self($data);
48 }
49
50 /**
51 * Removes all key value pairs
52 */
53 public function clear()
54 {
55 $this->data = [];
56 }
57
58 /**
59 * Get a specific key value.
60 *
61 * @param string $key Key to retrieve.
62 *
63 * @return mixed|null Value of the key or NULL
64 */
65 public function get($key)
66 {
67 return isset($this->data[$key]) ? $this->data[$key] : null;
68 }
69
70 /**
71 * Set a key value pair
72 *
73 * @param string $key Key to set
74 * @param mixed $value Value to set
75 */
76 public function set($key, $value)
77 {
78 $this->data[$key] = $value;
79 }
80
81 /**
82 * Add a value to a key. If a key of the same name has already been added,
83 * the key value will be converted into an array and the new value will be
84 * pushed to the end of the array.
85 *
86 * @param string $key Key to add
87 * @param mixed $value Value to add to the key
88 */
89 public function add($key, $value)
90 {
91 if (!array_key_exists($key, $this->data)) {
92 $this->data[$key] = $value;
93 } elseif (is_array($this->data[$key])) {
94 $this->data[$key][] = $value;
95 } else {
96 $this->data[$key] = array($this->data[$key], $value);
97 }
98 }
99
100 /**
101 * Remove a specific key value pair
102 *
103 * @param string $key A key to remove
104 */
105 public function remove($key)
106 {
107 unset($this->data[$key]);
108 }
109
110 /**
111 * Get all keys in the collection
112 *
113 * @return array
114 */
115 public function getKeys()
116 {
117 return array_keys($this->data);
118 }
119
120 /**
121 * Returns whether or not the specified key is present.
122 *
123 * @param string $key The key for which to check the existence.
124 *
125 * @return bool
126 */
127 public function hasKey($key)
128 {
129 return array_key_exists($key, $this->data);
130 }
131
132 /**
133 * Checks if any keys contains a certain value
134 *
135 * @param string $value Value to search for
136 *
137 * @return mixed Returns the key if the value was found FALSE if the value
138 * was not found.
139 */
140 public function hasValue($value)
141 {
142 return array_search($value, $this->data, true);
143 }
144
145 /**
146 * Replace the data of the object with the value of an array
147 *
148 * @param array $data Associative array of data
149 */
150 public function replace(array $data)
151 {
152 $this->data = $data;
153 }
154
155 /**
156 * Add and merge in a Collection or array of key value pair data.
157 *
158 * @param Collection|array $data Associative array of key value pair data
159 */
160 public function merge($data)
161 {
162 foreach ($data as $key => $value) {
163 $this->add($key, $value);
164 }
165 }
166
167 /**
168 * Overwrite key value pairs in this collection with all of the data from
169 * an array or collection.
170 *
171 * @param array|\Traversable $data Values to override over this config
172 */
173 public function overwriteWith($data)
174 {
175 if (is_array($data)) {
176 $this->data = $data + $this->data;
177 } elseif ($data instanceof Collection) {
178 $this->data = $data->toArray() + $this->data;
179 } else {
180 foreach ($data as $key => $value) {
181 $this->data[$key] = $value;
182 }
183 }
184 }
185
186 /**
187 * Returns a Collection containing all the elements of the collection after
188 * applying the callback function to each one.
189 *
190 * The callable should accept three arguments:
191 * - (string) $key
192 * - (string) $value
193 * - (array) $context
194 *
195 * The callable must return a the altered or unaltered value.
196 *
197 * @param callable $closure Map function to apply
198 * @param array $context Context to pass to the callable
199 *
200 * @return Collection
201 */
202 public function map(callable $closure, array $context = [])
203 {
204 $collection = new static();
205 foreach ($this as $key => $value) {
206 $collection[$key] = $closure($key, $value, $context);
207 }
208
209 return $collection;
210 }
211
212 /**
213 * Iterates over each key value pair in the collection passing them to the
214 * callable. If the callable returns true, the current value from input is
215 * returned into the result Collection.
216 *
217 * The callable must accept two arguments:
218 * - (string) $key
219 * - (string) $value
220 *
221 * @param callable $closure Evaluation function
222 *
223 * @return Collection
224 */
225 public function filter(callable $closure)
226 {
227 $collection = new static();
228 foreach ($this->data as $key => $value) {
229 if ($closure($key, $value)) {
230 $collection[$key] = $value;
231 }
232 }
233
234 return $collection;
235 }
236 }
1 <?php
2 namespace GuzzleHttp\Cookie;
3
4 use GuzzleHttp\Message\RequestInterface;
5 use GuzzleHttp\Message\ResponseInterface;
6 use GuzzleHttp\ToArrayInterface;
7
8 /**
9 * Cookie jar that stores cookies an an array
10 */
11 class CookieJar implements CookieJarInterface, ToArrayInterface
12 {
13 /** @var SetCookie[] Loaded cookie data */
14 private $cookies = [];
15
16 /** @var bool */
17 private $strictMode;
18
19 /**
20 * @param bool $strictMode Set to true to throw exceptions when invalid
21 * cookies are added to the cookie jar.
22 * @param array $cookieArray Array of SetCookie objects or a hash of arrays
23 * that can be used with the SetCookie constructor
24 */
25 public function __construct($strictMode = false, $cookieArray = [])
26 {
27 $this->strictMode = $strictMode;
28
29 foreach ($cookieArray as $cookie) {
30 if (!($cookie instanceof SetCookie)) {
31 $cookie = new SetCookie($cookie);
32 }
33 $this->setCookie($cookie);
34 }
35 }
36
37 /**
38 * Create a new Cookie jar from an associative array and domain.
39 *
40 * @param array $cookies Cookies to create the jar from
41 * @param string $domain Domain to set the cookies to
42 *
43 * @return self
44 */
45 public static function fromArray(array $cookies, $domain)
46 {
47 $cookieJar = new self();
48 foreach ($cookies as $name => $value) {
49 $cookieJar->setCookie(new SetCookie([
50 'Domain' => $domain,
51 'Name' => $name,
52 'Value' => $value,
53 'Discard' => true
54 ]));
55 }
56
57 return $cookieJar;
58 }
59
60 /**
61 * Quote the cookie value if it is not already quoted and it contains
62 * problematic characters.
63 *
64 * @param string $value Value that may or may not need to be quoted
65 *
66 * @return string
67 */
68 public static function getCookieValue($value)
69 {
70 if (substr($value, 0, 1) !== '"' &&
71 substr($value, -1, 1) !== '"' &&
72 strpbrk($value, ';,')
73 ) {
74 $value = '"' . $value . '"';
75 }
76
77 return $value;
78 }
79
80 public function toArray()
81 {
82 return array_map(function (SetCookie $cookie) {
83 return $cookie->toArray();
84 }, $this->getIterator()->getArrayCopy());
85 }
86
87 public function clear($domain = null, $path = null, $name = null)
88 {
89 if (!$domain) {
90 $this->cookies = [];
91 return;
92 } elseif (!$path) {
93 $this->cookies = array_filter(
94 $this->cookies,
95 function (SetCookie $cookie) use ($path, $domain) {
96 return !$cookie->matchesDomain($domain);
97 }
98 );
99 } elseif (!$name) {
100 $this->cookies = array_filter(
101 $this->cookies,
102 function (SetCookie $cookie) use ($path, $domain) {
103 return !($cookie->matchesPath($path) &&
104 $cookie->matchesDomain($domain));
105 }
106 );
107 } else {
108 $this->cookies = array_filter(
109 $this->cookies,
110 function (SetCookie $cookie) use ($path, $domain, $name) {
111 return !($cookie->getName() == $name &&
112 $cookie->matchesPath($path) &&
113 $cookie->matchesDomain($domain));
114 }
115 );
116 }
117 }
118
119 public function clearSessionCookies()
120 {
121 $this->cookies = array_filter(
122 $this->cookies,
123 function (SetCookie $cookie) {
124 return !$cookie->getDiscard() && $cookie->getExpires();
125 }
126 );
127 }
128
129 public function setCookie(SetCookie $cookie)
130 {
131 // Only allow cookies with set and valid domain, name, value
132 $result = $cookie->validate();
133 if ($result !== true) {
134 if ($this->strictMode) {
135 throw new \RuntimeException('Invalid cookie: ' . $result);
136 } else {
137 $this->removeCookieIfEmpty($cookie);
138 return false;
139 }
140 }
141
142 // Resolve conflicts with previously set cookies
143 foreach ($this->cookies as $i => $c) {
144
145 // Two cookies are identical, when their path, and domain are
146 // identical.
147 if ($c->getPath() != $cookie->getPath() ||
148 $c->getDomain() != $cookie->getDomain() ||
149 $c->getName() != $cookie->getName()
150 ) {
151 continue;
152 }
153
154 // The previously set cookie is a discard cookie and this one is
155 // not so allow the new cookie to be set
156 if (!$cookie->getDiscard() && $c->getDiscard()) {
157 unset($this->cookies[$i]);
158 continue;
159 }
160
161 // If the new cookie's expiration is further into the future, then
162 // replace the old cookie
163 if ($cookie->getExpires() > $c->getExpires()) {
164 unset($this->cookies[$i]);
165 continue;
166 }
167
168 // If the value has changed, we better change it
169 if ($cookie->getValue() !== $c->getValue()) {
170 unset($this->cookies[$i]);
171 continue;
172 }
173
174 // The cookie exists, so no need to continue
175 return false;
176 }
177
178 $this->cookies[] = $cookie;
179
180 return true;
181 }
182
183 public function count()
184 {
185 return count($this->cookies);
186 }
187
188 public function getIterator()
189 {
190 return new \ArrayIterator(array_values($this->cookies));
191 }
192
193 public function extractCookies(
194 RequestInterface $request,
195 ResponseInterface $response
196 ) {
197 if ($cookieHeader = $response->getHeaderAsArray('Set-Cookie')) {
198 foreach ($cookieHeader as $cookie) {
199 $sc = SetCookie::fromString($cookie);
200 if (!$sc->getDomain()) {
201 $sc->setDomain($request->getHost());
202 }
203 $this->setCookie($sc);
204 }
205 }
206 }
207
208 public function addCookieHeader(RequestInterface $request)
209 {
210 $values = [];
211 $scheme = $request->getScheme();
212 $host = $request->getHost();
213 $path = $request->getPath();
214
215 foreach ($this->cookies as $cookie) {
216 if ($cookie->matchesPath($path) &&
217 $cookie->matchesDomain($host) &&
218 !$cookie->isExpired() &&
219 (!$cookie->getSecure() || $scheme == 'https')
220 ) {
221 $values[] = $cookie->getName() . '='
222 . self::getCookieValue($cookie->getValue());
223 }
224 }
225
226 if ($values) {
227 $request->setHeader('Cookie', implode('; ', $values));
228 }
229 }
230
231 /**
232 * If a cookie already exists and the server asks to set it again with a
233 * null value, the cookie must be deleted.
234 *
235 * @param SetCookie $cookie
236 */
237 private function removeCookieIfEmpty(SetCookie $cookie)
238 {
239 $cookieValue = $cookie->getValue();
240 if ($cookieValue === null || $cookieValue === '') {
241 $this->clear(
242 $cookie->getDomain(),
243 $cookie->getPath(),
244 $cookie->getName()
245 );
246 }
247 }
248 }
1 <?php
2 namespace GuzzleHttp\Cookie;
3
4 use GuzzleHttp\Message\RequestInterface;
5 use GuzzleHttp\Message\ResponseInterface;
6
7 /**
8 * Stores HTTP cookies.
9 *
10 * It extracts cookies from HTTP requests, and returns them in HTTP responses.
11 * CookieJarInterface instances automatically expire contained cookies when
12 * necessary. Subclasses are also responsible for storing and retrieving
13 * cookies from a file, database, etc.
14 *
15 * @link http://docs.python.org/2/library/cookielib.html Inspiration
16 */
17 interface CookieJarInterface extends \Countable, \IteratorAggregate
18 {
19 /**
20 * Add a Cookie header to a request.
21 *
22 * If no matching cookies are found in the cookie jar, then no Cookie
23 * header is added to the request.
24 *
25 * @param RequestInterface $request Request object to update
26 */
27 public function addCookieHeader(RequestInterface $request);
28
29 /**
30 * Extract cookies from an HTTP response and store them in the CookieJar.
31 *
32 * @param RequestInterface $request Request that was sent
33 * @param ResponseInterface $response Response that was received
34 */
35 public function extractCookies(
36 RequestInterface $request,
37 ResponseInterface $response
38 );
39
40 /**
41 * Sets a cookie in the cookie jar.
42 *
43 * @param SetCookie $cookie Cookie to set.
44 *
45 * @return bool Returns true on success or false on failure
46 */
47 public function setCookie(SetCookie $cookie);
48
49 /**
50 * Remove cookies currently held in the cookie jar.
51 *
52 * Invoking this method without arguments will empty the whole cookie jar.
53 * If given a $domain argument only cookies belonging to that domain will
54 * be removed. If given a $domain and $path argument, cookies belonging to
55 * the specified path within that domain are removed. If given all three
56 * arguments, then the cookie with the specified name, path and domain is
57 * removed.
58 *
59 * @param string $domain Clears cookies matching a domain
60 * @param string $path Clears cookies matching a domain and path
61 * @param string $name Clears cookies matching a domain, path, and name
62 *
63 * @return CookieJarInterface
64 */
65 public function clear($domain = null, $path = null, $name = null);
66
67 /**
68 * Discard all sessions cookies.
69 *
70 * Removes cookies that don't have an expire field or a have a discard
71 * field set to true. To be called when the user agent shuts down according
72 * to RFC 2965.
73 */
74 public function clearSessionCookies();
75 }
1 <?php
2 namespace GuzzleHttp\Cookie;
3
4 use GuzzleHttp\Utils;
5
6 /**
7 * Persists non-session cookies using a JSON formatted file
8 */
9 class FileCookieJar extends CookieJar
10 {
11 /** @var string filename */
12 private $filename;
13
14 /**
15 * Create a new FileCookieJar object
16 *
17 * @param string $cookieFile File to store the cookie data
18 *
19 * @throws \RuntimeException if the file cannot be found or created
20 */
21 public function __construct($cookieFile)
22 {
23 $this->filename = $cookieFile;
24
25 if (file_exists($cookieFile)) {
26 $this->load($cookieFile);
27 }
28 }
29
30 /**
31 * Saves the file when shutting down
32 */
33 public function __destruct()
34 {
35 $this->save($this->filename);
36 }
37
38 /**
39 * Saves the cookies to a file.
40 *
41 * @param string $filename File to save
42 * @throws \RuntimeException if the file cannot be found or created
43 */
44 public function save($filename)
45 {
46 $json = [];
47 foreach ($this as $cookie) {
48 if ($cookie->getExpires() && !$cookie->getDiscard()) {
49 $json[] = $cookie->toArray();
50 }
51 }
52
53 if (false === file_put_contents($filename, json_encode($json))) {
54 // @codeCoverageIgnoreStart
55 throw new \RuntimeException("Unable to save file {$filename}");
56 // @codeCoverageIgnoreEnd
57 }
58 }
59
60 /**
61 * Load cookies from a JSON formatted file.
62 *
63 * Old cookies are kept unless overwritten by newly loaded ones.
64 *
65 * @param string $filename Cookie file to load.
66 * @throws \RuntimeException if the file cannot be loaded.
67 */
68 public function load($filename)
69 {
70 $json = file_get_contents($filename);
71 if (false === $json) {
72 // @codeCoverageIgnoreStart
73 throw new \RuntimeException("Unable to load file {$filename}");
74 // @codeCoverageIgnoreEnd
75 }
76
77 $data = Utils::jsonDecode($json, true);
78 if (is_array($data)) {
79 foreach (Utils::jsonDecode($json, true) as $cookie) {
80 $this->setCookie(new SetCookie($cookie));
81 }
82 } elseif (strlen($data)) {
83 throw new \RuntimeException("Invalid cookie file: {$filename}");
84 }
85 }
86 }
1 <?php
2 namespace GuzzleHttp\Cookie;
3
4 use GuzzleHttp\Utils;
5
6 /**
7 * Persists cookies in the client session
8 */
9 class SessionCookieJar extends CookieJar
10 {
11 /** @var string session key */
12 private $sessionKey;
13
14 /**
15 * Create a new SessionCookieJar object
16 *
17 * @param string $sessionKey Session key name to store the cookie data in session
18 */
19 public function __construct($sessionKey)
20 {
21 $this->sessionKey = $sessionKey;
22 $this->load();
23 }
24
25 /**
26 * Saves cookies to session when shutting down
27 */
28 public function __destruct()
29 {
30 $this->save();
31 }
32
33 /**
34 * Save cookies to the client session
35 */
36 public function save()
37 {
38 $json = [];
39 foreach ($this as $cookie) {
40 if ($cookie->getExpires() && !$cookie->getDiscard()) {
41 $json[] = $cookie->toArray();
42 }
43 }
44
45 $_SESSION[$this->sessionKey] = json_encode($json);
46 }
47
48 /**
49 * Load the contents of the client session into the data array
50 */
51 protected function load()
52 {
53 $cookieJar = isset($_SESSION[$this->sessionKey])
54 ? $_SESSION[$this->sessionKey]
55 : null;
56
57 $data = Utils::jsonDecode($cookieJar, true);
58 if (is_array($data)) {
59 foreach ($data as $cookie) {
60 $this->setCookie(new SetCookie($cookie));
61 }
62 } elseif (strlen($data)) {
63 throw new \RuntimeException("Invalid cookie data");
64 }
65 }
66 }
1 <?php
2 namespace GuzzleHttp\Cookie;
3
4 use GuzzleHttp\ToArrayInterface;
5
6 /**
7 * Set-Cookie object
8 */
9 class SetCookie implements ToArrayInterface
10 {
11 /** @var array */
12 private static $defaults = [
13 'Name' => null,
14 'Value' => null,
15 'Domain' => null,
16 'Path' => '/',
17 'Max-Age' => null,
18 'Expires' => null,
19 'Secure' => false,
20 'Discard' => false,
21 'HttpOnly' => false
22 ];
23
24 /** @var array Cookie data */
25 private $data;
26
27 /**
28 * Create a new SetCookie object from a string
29 *
30 * @param string $cookie Set-Cookie header string
31 *
32 * @return self
33 */
34 public static function fromString($cookie)
35 {
36 // Create the default return array
37 $data = self::$defaults;
38 // Explode the cookie string using a series of semicolons
39 $pieces = array_filter(array_map('trim', explode(';', $cookie)));
40 // The name of the cookie (first kvp) must include an equal sign.
41 if (empty($pieces) || !strpos($pieces[0], '=')) {
42 return new self($data);
43 }
44
45 // Add the cookie pieces into the parsed data array
46 foreach ($pieces as $part) {
47
48 $cookieParts = explode('=', $part, 2);
49 $key = trim($cookieParts[0]);
50 $value = isset($cookieParts[1])
51 ? trim($cookieParts[1], " \n\r\t\0\x0B\"")
52 : true;
53
54 // Only check for non-cookies when cookies have been found
55 if (empty($data['Name'])) {
56 $data['Name'] = $key;
57 $data['Value'] = $value;
58 } else {
59 foreach (array_keys(self::$defaults) as $search) {
60 if (!strcasecmp($search, $key)) {
61 $data[$search] = $value;
62 continue 2;
63 }
64 }
65 $data[$key] = $value;
66 }
67 }
68
69 return new self($data);
70 }
71
72 /**
73 * @param array $data Array of cookie data provided by a Cookie parser
74 */
75 public function __construct(array $data = [])
76 {
77 $this->data = array_replace(self::$defaults, $data);
78 // Extract the Expires value and turn it into a UNIX timestamp if needed
79 if (!$this->getExpires() && $this->getMaxAge()) {
80 // Calculate the Expires date
81 $this->setExpires(time() + $this->getMaxAge());
82 } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
83 $this->setExpires($this->getExpires());
84 }
85 }
86
87 public function __toString()
88 {
89 $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
90 foreach ($this->data as $k => $v) {
91 if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) {
92 if ($k == 'Expires') {
93 $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
94 } else {
95 $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
96 }
97 }
98 }
99
100 return rtrim($str, '; ');
101 }
102
103 public function toArray()
104 {
105 return $this->data;
106 }
107
108 /**
109 * Get the cookie name
110 *
111 * @return string
112 */
113 public function getName()
114 {
115 return $this->data['Name'];
116 }
117
118 /**
119 * Set the cookie name
120 *
121 * @param string $name Cookie name
122 */
123 public function setName($name)
124 {
125 $this->data['Name'] = $name;
126 }
127
128 /**
129 * Get the cookie value
130 *
131 * @return string
132 */
133 public function getValue()
134 {
135 return $this->data['Value'];
136 }
137
138 /**
139 * Set the cookie value
140 *
141 * @param string $value Cookie value
142 */
143 public function setValue($value)
144 {
145 $this->data['Value'] = $value;
146 }
147
148 /**
149 * Get the domain
150 *
151 * @return string|null
152 */
153 public function getDomain()
154 {
155 return $this->data['Domain'];
156 }
157
158 /**
159 * Set the domain of the cookie
160 *
161 * @param string $domain
162 */
163 public function setDomain($domain)
164 {
165 $this->data['Domain'] = $domain;
166 }
167
168 /**
169 * Get the path
170 *
171 * @return string
172 */
173 public function getPath()
174 {
175 return $this->data['Path'];
176 }
177
178 /**
179 * Set the path of the cookie
180 *
181 * @param string $path Path of the cookie
182 */
183 public function setPath($path)
184 {
185 $this->data['Path'] = $path;
186 }
187
188 /**
189 * Maximum lifetime of the cookie in seconds
190 *
191 * @return int|null
192 */
193 public function getMaxAge()
194 {
195 return $this->data['Max-Age'];
196 }
197
198 /**
199 * Set the max-age of the cookie
200 *
201 * @param int $maxAge Max age of the cookie in seconds
202 */
203 public function setMaxAge($maxAge)
204 {
205 $this->data['Max-Age'] = $maxAge;
206 }
207
208 /**
209 * The UNIX timestamp when the cookie Expires
210 *
211 * @return mixed
212 */
213 public function getExpires()
214 {
215 return $this->data['Expires'];
216 }
217
218 /**
219 * Set the unix timestamp for which the cookie will expire
220 *
221 * @param int $timestamp Unix timestamp
222 */
223 public function setExpires($timestamp)
224 {
225 $this->data['Expires'] = is_numeric($timestamp)
226 ? (int) $timestamp
227 : strtotime($timestamp);
228 }
229
230 /**
231 * Get whether or not this is a secure cookie
232 *
233 * @return null|bool
234 */
235 public function getSecure()
236 {
237 return $this->data['Secure'];
238 }
239
240 /**
241 * Set whether or not the cookie is secure
242 *
243 * @param bool $secure Set to true or false if secure
244 */
245 public function setSecure($secure)
246 {
247 $this->data['Secure'] = $secure;
248 }
249
250 /**
251 * Get whether or not this is a session cookie
252 *
253 * @return null|bool
254 */
255 public function getDiscard()
256 {
257 return $this->data['Discard'];
258 }
259
260 /**
261 * Set whether or not this is a session cookie
262 *
263 * @param bool $discard Set to true or false if this is a session cookie
264 */
265 public function setDiscard($discard)
266 {
267 $this->data['Discard'] = $discard;
268 }
269
270 /**
271 * Get whether or not this is an HTTP only cookie
272 *
273 * @return bool
274 */
275 public function getHttpOnly()
276 {
277 return $this->data['HttpOnly'];
278 }
279
280 /**
281 * Set whether or not this is an HTTP only cookie
282 *
283 * @param bool $httpOnly Set to true or false if this is HTTP only
284 */
285 public function setHttpOnly($httpOnly)
286 {
287 $this->data['HttpOnly'] = $httpOnly;
288 }
289
290 /**
291 * Check if the cookie matches a path value
292 *
293 * @param string $path Path to check against
294 *
295 * @return bool
296 */
297 public function matchesPath($path)
298 {
299 return !$this->getPath() || 0 === stripos($path, $this->getPath());
300 }
301
302 /**
303 * Check if the cookie matches a domain value
304 *
305 * @param string $domain Domain to check against
306 *
307 * @return bool
308 */
309 public function matchesDomain($domain)
310 {
311 // Remove the leading '.' as per spec in RFC 6265.
312 // http://tools.ietf.org/html/rfc6265#section-5.2.3
313 $cookieDomain = ltrim($this->getDomain(), '.');
314
315 // Domain not set or exact match.
316 if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
317 return true;
318 }
319
320 // Matching the subdomain according to RFC 6265.
321 // http://tools.ietf.org/html/rfc6265#section-5.1.3
322 if (filter_var($domain, FILTER_VALIDATE_IP)) {
323 return false;
324 }
325
326 return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/i', $domain);
327 }
328
329 /**
330 * Check if the cookie is expired
331 *
332 * @return bool
333 */
334 public function isExpired()
335 {
336 return $this->getExpires() && time() > $this->getExpires();
337 }
338
339 /**
340 * Check if the cookie is valid according to RFC 6265
341 *
342 * @return bool|string Returns true if valid or an error message if invalid
343 */
344 public function validate()
345 {
346 // Names must not be empty, but can be 0
347 $name = $this->getName();
348 if (empty($name) && !is_numeric($name)) {
349 return 'The cookie name must not be empty';
350 }
351
352 // Check if any of the invalid characters are present in the cookie name
353 if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
354 return "Cookie name must not cannot invalid characters: =,; \\t\\r\\n\\013\\014";
355 }
356
357 // Value must not be empty, but can be 0
358 $value = $this->getValue();
359 if (empty($value) && !is_numeric($value)) {
360 return 'The cookie value must not be empty';
361 }
362
363 // Domains must not be empty, but can be 0
364 // A "0" is not a valid internet domain, but may be used as server name
365 // in a private network.
366 $domain = $this->getDomain();
367 if (empty($domain) && !is_numeric($domain)) {
368 return 'The cookie domain must not be empty';
369 }
370
371 return true;
372 }
373 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Basic event class that can be extended.
6 */
7 abstract class AbstractEvent implements EventInterface
8 {
9 private $propagationStopped = false;
10
11 public function isPropagationStopped()
12 {
13 return $this->propagationStopped;
14 }
15
16 public function stopPropagation()
17 {
18 $this->propagationStopped = true;
19 }
20 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 use GuzzleHttp\Transaction;
5 use GuzzleHttp\ClientInterface;
6 use GuzzleHttp\Message\RequestInterface;
7
8 /**
9 * Base class for request events, providing a request and client getter.
10 */
11 abstract class AbstractRequestEvent extends AbstractEvent
12 {
13 /** @var Transaction */
14 protected $transaction;
15
16 /**
17 * @param Transaction $transaction
18 */
19 public function __construct(Transaction $transaction)
20 {
21 $this->transaction = $transaction;
22 }
23
24 /**
25 * Get the HTTP client associated with the event.
26 *
27 * @return ClientInterface
28 */
29 public function getClient()
30 {
31 return $this->transaction->client;
32 }
33
34 /**
35 * Get the request object
36 *
37 * @return RequestInterface
38 */
39 public function getRequest()
40 {
41 return $this->transaction->request;
42 }
43
44 /**
45 * Get the number of transaction retries.
46 *
47 * @return int
48 */
49 public function getRetryCount()
50 {
51 return $this->transaction->retries;
52 }
53
54 /**
55 * @return Transaction
56 */
57 protected function getTransaction()
58 {
59 return $this->transaction;
60 }
61 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Abstract request event that can be retried.
6 */
7 class AbstractRetryableEvent extends AbstractTransferEvent
8 {
9 /**
10 * Mark the request as needing a retry and stop event propagation.
11 *
12 * This action allows you to retry a request without emitting the "end"
13 * event multiple times for a given request. When retried, the request
14 * emits a before event and is then sent again using the client that sent
15 * the original request.
16 *
17 * When retrying, it is important to limit the number of retries you allow
18 * to prevent infinite loops.
19 *
20 * This action can only be taken during the "complete" and "error" events.
21 *
22 * @param int $afterDelay If specified, the amount of time in milliseconds
23 * to delay before retrying. Note that this must
24 * be supported by the underlying RingPHP handler
25 * to work properly. Set to 0 or provide no value
26 * to retry immediately.
27 */
28 public function retry($afterDelay = 0)
29 {
30 // Setting the transition state to 'retry' will cause the next state
31 // transition of the transaction to retry the request.
32 $this->transaction->state = 'retry';
33
34 if ($afterDelay) {
35 $this->transaction->request->getConfig()->set('delay', $afterDelay);
36 }
37
38 $this->stopPropagation();
39 }
40 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 use GuzzleHttp\Message\ResponseInterface;
5 use GuzzleHttp\Ring\Future\FutureInterface;
6
7 /**
8 * Event that contains transfer statistics, and can be intercepted.
9 */
10 abstract class AbstractTransferEvent extends AbstractRequestEvent
11 {
12 /**
13 * Get all transfer information as an associative array if no $name
14 * argument is supplied, or gets a specific transfer statistic if
15 * a $name attribute is supplied (e.g., 'total_time').
16 *
17 * @param string $name Name of the transfer stat to retrieve
18 *
19 * @return mixed|null|array
20 */
21 public function getTransferInfo($name = null)
22 {
23 if (!$name) {
24 return $this->transaction->transferInfo;
25 }
26
27 return isset($this->transaction->transferInfo[$name])
28 ? $this->transaction->transferInfo[$name]
29 : null;
30 }
31
32 /**
33 * Returns true/false if a response is available.
34 *
35 * @return bool
36 */
37 public function hasResponse()
38 {
39 return !($this->transaction->response instanceof FutureInterface);
40 }
41
42 /**
43 * Get the response.
44 *
45 * @return ResponseInterface|null
46 */
47 public function getResponse()
48 {
49 return $this->hasResponse() ? $this->transaction->response : null;
50 }
51
52 /**
53 * Intercept the request and associate a response
54 *
55 * @param ResponseInterface $response Response to set
56 */
57 public function intercept(ResponseInterface $response)
58 {
59 $this->transaction->response = $response;
60 $this->transaction->exception = null;
61 $this->stopPropagation();
62 }
63 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 use GuzzleHttp\Message\ResponseInterface;
5
6 /**
7 * Event object emitted before a request is sent.
8 *
9 * This event MAY be emitted multiple times (i.e., if a request is retried).
10 * You MAY change the Response associated with the request using the
11 * intercept() method of the event.
12 */
13 class BeforeEvent extends AbstractRequestEvent
14 {
15 /**
16 * Intercept the request and associate a response
17 *
18 * @param ResponseInterface $response Response to set
19 */
20 public function intercept(ResponseInterface $response)
21 {
22 $this->transaction->response = $response;
23 $this->transaction->exception = null;
24 $this->stopPropagation();
25 }
26 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Event object emitted after a request has been completed.
6 *
7 * This event MAY be emitted multiple times for a single request. You MAY
8 * change the Response associated with the request using the intercept()
9 * method of the event.
10 *
11 * This event allows the request to be retried if necessary using the retry()
12 * method of the event.
13 */
14 class CompleteEvent extends AbstractRetryableEvent {}
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Guzzle event emitter.
6 *
7 * Some of this class is based on the Symfony EventDispatcher component, which
8 * ships with the following license:
9 *
10 * This file is part of the Symfony package.
11 *
12 * (c) Fabien Potencier <fabien@symfony.com>
13 *
14 * For the full copyright and license information, please view the LICENSE
15 * file that was distributed with this source code.
16 *
17 * @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher
18 */
19 class Emitter implements EmitterInterface
20 {
21 /** @var array */
22 private $listeners = [];
23
24 /** @var array */
25 private $sorted = [];
26
27 public function on($eventName, callable $listener, $priority = 0)
28 {
29 if ($priority === 'first') {
30 $priority = isset($this->listeners[$eventName])
31 ? max(array_keys($this->listeners[$eventName])) + 1
32 : 1;
33 } elseif ($priority === 'last') {
34 $priority = isset($this->listeners[$eventName])
35 ? min(array_keys($this->listeners[$eventName])) - 1
36 : -1;
37 }
38
39 $this->listeners[$eventName][$priority][] = $listener;
40 unset($this->sorted[$eventName]);
41 }
42
43 public function once($eventName, callable $listener, $priority = 0)
44 {
45 $onceListener = function (
46 EventInterface $event,
47 $eventName
48 ) use (&$onceListener, $eventName, $listener, $priority) {
49 $this->removeListener($eventName, $onceListener);
50 $listener($event, $eventName, $this);
51 };
52
53 $this->on($eventName, $onceListener, $priority);
54 }
55
56 public function removeListener($eventName, callable $listener)
57 {
58 if (empty($this->listeners[$eventName])) {
59 return;
60 }
61
62 foreach ($this->listeners[$eventName] as $priority => $listeners) {
63 if (false !== ($key = array_search($listener, $listeners, true))) {
64 unset(
65 $this->listeners[$eventName][$priority][$key],
66 $this->sorted[$eventName]
67 );
68 }
69 }
70 }
71
72 public function listeners($eventName = null)
73 {
74 // Return all events in a sorted priority order
75 if ($eventName === null) {
76 foreach (array_keys($this->listeners) as $eventName) {
77 if (empty($this->sorted[$eventName])) {
78 $this->listeners($eventName);
79 }
80 }
81 return $this->sorted;
82 }
83
84 // Return the listeners for a specific event, sorted in priority order
85 if (empty($this->sorted[$eventName])) {
86 $this->sorted[$eventName] = [];
87 if (isset($this->listeners[$eventName])) {
88 krsort($this->listeners[$eventName], SORT_NUMERIC);
89 foreach ($this->listeners[$eventName] as $listeners) {
90 foreach ($listeners as $listener) {
91 $this->sorted[$eventName][] = $listener;
92 }
93 }
94 }
95 }
96
97 return $this->sorted[$eventName];
98 }
99
100 public function hasListeners($eventName)
101 {
102 return !empty($this->listeners[$eventName]);
103 }
104
105 public function emit($eventName, EventInterface $event)
106 {
107 if (isset($this->listeners[$eventName])) {
108 foreach ($this->listeners($eventName) as $listener) {
109 $listener($event, $eventName);
110 if ($event->isPropagationStopped()) {
111 break;
112 }
113 }
114 }
115
116 return $event;
117 }
118
119 public function attach(SubscriberInterface $subscriber)
120 {
121 foreach ($subscriber->getEvents() as $eventName => $listeners) {
122 if (is_array($listeners[0])) {
123 foreach ($listeners as $listener) {
124 $this->on(
125 $eventName,
126 [$subscriber, $listener[0]],
127 isset($listener[1]) ? $listener[1] : 0
128 );
129 }
130 } else {
131 $this->on(
132 $eventName,
133 [$subscriber, $listeners[0]],
134 isset($listeners[1]) ? $listeners[1] : 0
135 );
136 }
137 }
138 }
139
140 public function detach(SubscriberInterface $subscriber)
141 {
142 foreach ($subscriber->getEvents() as $eventName => $listener) {
143 $this->removeListener($eventName, [$subscriber, $listener[0]]);
144 }
145 }
146 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Guzzle event emitter.
6 */
7 interface EmitterInterface
8 {
9 /**
10 * Binds a listener to a specific event.
11 *
12 * @param string $eventName Name of the event to bind to.
13 * @param callable $listener Listener to invoke when triggered.
14 * @param int|string $priority The higher this value, the earlier an event
15 * listener will be triggered in the chain (defaults to 0). You can
16 * pass "first" or "last" to dynamically specify the event priority
17 * based on the current event priorities associated with the given
18 * event name in the emitter. Use "first" to set the priority to the
19 * current highest priority plus one. Use "last" to set the priority to
20 * the current lowest event priority minus one.
21 */
22 public function on($eventName, callable $listener, $priority = 0);
23
24 /**
25 * Binds a listener to a specific event. After the listener is triggered
26 * once, it is removed as a listener.
27 *
28 * @param string $eventName Name of the event to bind to.
29 * @param callable $listener Listener to invoke when triggered.
30 * @param int $priority The higher this value, the earlier an event
31 * listener will be triggered in the chain (defaults to 0)
32 */
33 public function once($eventName, callable $listener, $priority = 0);
34
35 /**
36 * Removes an event listener from the specified event.
37 *
38 * @param string $eventName The event to remove a listener from
39 * @param callable $listener The listener to remove
40 */
41 public function removeListener($eventName, callable $listener);
42
43 /**
44 * Gets the listeners of a specific event or all listeners if no event is
45 * specified.
46 *
47 * @param string $eventName The name of the event. Pass null (the default)
48 * to retrieve all listeners.
49 *
50 * @return array The event listeners for the specified event, or all event
51 * listeners by event name. The format of the array when retrieving a
52 * specific event list is an array of callables. The format of the array
53 * when retrieving all listeners is an associative array of arrays of
54 * callables.
55 */
56 public function listeners($eventName = null);
57
58 /**
59 * Checks if the emitter has listeners by the given name.
60 *
61 * @param string $eventName The name of the event to check.
62 *
63 * @return bool
64 */
65 public function hasListeners($eventName);
66
67 /**
68 * Emits an event to all registered listeners.
69 *
70 * Each event that is bound to the emitted eventName receives a
71 * EventInterface, the name of the event, and the event emitter.
72 *
73 * @param string $eventName The name of the event to dispatch.
74 * @param EventInterface $event The event to pass to the event handlers/listeners.
75 *
76 * @return EventInterface Returns the provided event object
77 */
78 public function emit($eventName, EventInterface $event);
79
80 /**
81 * Attaches an event subscriber.
82 *
83 * The subscriber is asked for all the events it is interested in and added
84 * as an event listener for each event.
85 *
86 * @param SubscriberInterface $subscriber Subscriber to attach.
87 */
88 public function attach(SubscriberInterface $subscriber);
89
90 /**
91 * Detaches an event subscriber.
92 *
93 * @param SubscriberInterface $subscriber Subscriber to detach.
94 */
95 public function detach(SubscriberInterface $subscriber);
96 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * A terminal event that is emitted when a request transaction has ended.
6 *
7 * This event is emitted for both successful responses and responses that
8 * encountered an exception. You need to check if an exception is present
9 * in your listener to know the difference.
10 *
11 * You MAY intercept the response associated with the event if needed, but keep
12 * in mind that the "complete" event will not be triggered as a result.
13 */
14 class EndEvent extends AbstractTransferEvent
15 {
16 /**
17 * Get the exception that was encountered (if any).
18 *
19 * This method should be used to check if the request was sent successfully
20 * or if it encountered errors.
21 *
22 * @return \Exception|null
23 */
24 public function getException()
25 {
26 return $this->transaction->exception;
27 }
28 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 use GuzzleHttp\Exception\RequestException;
5
6 /**
7 * Event emitted when an error occurs while sending a request.
8 *
9 * This event MAY be emitted multiple times. You MAY intercept the exception
10 * and inject a response into the event to rescue the request using the
11 * intercept() method of the event.
12 *
13 * This event allows the request to be retried using the "retry" method of the
14 * event.
15 */
16 class ErrorEvent extends AbstractRetryableEvent
17 {
18 /**
19 * Get the exception that was encountered
20 *
21 * @return RequestException
22 */
23 public function getException()
24 {
25 return $this->transaction->exception;
26 }
27 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Base event interface used when dispatching events to listeners using an
6 * event emitter.
7 */
8 interface EventInterface
9 {
10 /**
11 * Returns whether or not stopPropagation was called on the event.
12 *
13 * @return bool
14 * @see Event::stopPropagation
15 */
16 public function isPropagationStopped();
17
18 /**
19 * Stops the propagation of the event, preventing subsequent listeners
20 * registered to the same event from being invoked.
21 */
22 public function stopPropagation();
23 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Holds an event emitter
6 */
7 interface HasEmitterInterface
8 {
9 /**
10 * Get the event emitter of the object
11 *
12 * @return EmitterInterface
13 */
14 public function getEmitter();
15 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Trait that implements the methods of HasEmitterInterface
6 */
7 trait HasEmitterTrait
8 {
9 /** @var EmitterInterface */
10 private $emitter;
11
12 public function getEmitter()
13 {
14 if (!$this->emitter) {
15 $this->emitter = new Emitter();
16 }
17
18 return $this->emitter;
19 }
20 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Trait that provides methods for extract event listeners specified in an array
6 * and attaching them to an emitter owned by the object or one of its direct
7 * dependencies.
8 */
9 trait ListenerAttacherTrait
10 {
11 /**
12 * Attaches event listeners and properly sets their priorities and whether
13 * or not they are are only executed once.
14 *
15 * @param HasEmitterInterface $object Object that has the event emitter.
16 * @param array $listeners Array of hashes representing event
17 * event listeners. Each item contains
18 * "name", "fn", "priority", & "once".
19 */
20 private function attachListeners(HasEmitterInterface $object, array $listeners)
21 {
22 $emitter = $object->getEmitter();
23 foreach ($listeners as $el) {
24 if ($el['once']) {
25 $emitter->once($el['name'], $el['fn'], $el['priority']);
26 } else {
27 $emitter->on($el['name'], $el['fn'], $el['priority']);
28 }
29 }
30 }
31
32 /**
33 * Extracts the allowed events from the provided array, and ignores anything
34 * else in the array. The event listener must be specified as a callable or
35 * as an array of event listener data ("name", "fn", "priority", "once").
36 *
37 * @param array $source Array containing callables or hashes of data to be
38 * prepared as event listeners.
39 * @param array $events Names of events to look for in the provided $source
40 * array. Other keys are ignored.
41 * @return array
42 */
43 private function prepareListeners(array $source, array $events)
44 {
45 $listeners = [];
46 foreach ($events as $name) {
47 if (isset($source[$name])) {
48 $this->buildListener($name, $source[$name], $listeners);
49 }
50 }
51
52 return $listeners;
53 }
54
55 /**
56 * Creates a complete event listener definition from the provided array of
57 * listener data. Also works recursively if more than one listeners are
58 * contained in the provided array.
59 *
60 * @param string $name Name of the event the listener is for.
61 * @param array|callable $data Event listener data to prepare.
62 * @param array $listeners Array of listeners, passed by reference.
63 *
64 * @throws \InvalidArgumentException if the event data is malformed.
65 */
66 private function buildListener($name, $data, &$listeners)
67 {
68 static $defaults = ['priority' => 0, 'once' => false];
69
70 // If a callable is provided, normalize it to the array format.
71 if (is_callable($data)) {
72 $data = ['fn' => $data];
73 }
74
75 // Prepare the listener and add it to the array, recursively.
76 if (isset($data['fn'])) {
77 $data['name'] = $name;
78 $listeners[] = $data + $defaults;
79 } elseif (is_array($data)) {
80 foreach ($data as $listenerData) {
81 $this->buildListener($name, $listenerData, $listeners);
82 }
83 } else {
84 throw new \InvalidArgumentException('Each event listener must be a '
85 . 'callable or an associative array containing a "fn" key.');
86 }
87 }
88 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 use GuzzleHttp\Transaction;
5
6 /**
7 * Event object emitted when upload or download progress is made.
8 *
9 * You can access the progress values using their corresponding public
10 * properties:
11 *
12 * - $downloadSize: The number of bytes that will be downloaded (if known)
13 * - $downloaded: The number of bytes that have been downloaded
14 * - $uploadSize: The number of bytes that will be uploaded (if known)
15 * - $uploaded: The number of bytes that have been uploaded
16 */
17 class ProgressEvent extends AbstractRequestEvent
18 {
19 /** @var int Amount of data to be downloaded */
20 public $downloadSize;
21
22 /** @var int Amount of data that has been downloaded */
23 public $downloaded;
24
25 /** @var int Amount of data to upload */
26 public $uploadSize;
27
28 /** @var int Amount of data that has been uploaded */
29 public $uploaded;
30
31 /**
32 * @param Transaction $transaction Transaction being sent.
33 * @param int $downloadSize Amount of data to download (if known)
34 * @param int $downloaded Amount of data that has been downloaded
35 * @param int $uploadSize Amount of data to upload (if known)
36 * @param int $uploaded Amount of data that had been uploaded
37 */
38 public function __construct(
39 Transaction $transaction,
40 $downloadSize,
41 $downloaded,
42 $uploadSize,
43 $uploaded
44 ) {
45 parent::__construct($transaction);
46 $this->downloadSize = $downloadSize;
47 $this->downloaded = $downloaded;
48 $this->uploadSize = $uploadSize;
49 $this->uploaded = $uploaded;
50 }
51 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * Contains methods used to manage the request event lifecycle.
6 */
7 final class RequestEvents
8 {
9 // Generic event priorities
10 const EARLY = 10000;
11 const LATE = -10000;
12
13 // "before" priorities
14 const PREPARE_REQUEST = -100;
15 const SIGN_REQUEST = -10000;
16
17 // "complete" and "error" response priorities
18 const VERIFY_RESPONSE = 100;
19 const REDIRECT_RESPONSE = 200;
20
21 /**
22 * Converts an array of event options into a formatted array of valid event
23 * configuration.
24 *
25 * @param array $options Event array to convert
26 * @param array $events Event names to convert in the options array.
27 * @param mixed $handler Event handler to utilize
28 *
29 * @return array
30 * @throws \InvalidArgumentException if the event config is invalid
31 * @internal
32 */
33 public static function convertEventArray(
34 array $options,
35 array $events,
36 $handler
37 ) {
38 foreach ($events as $name) {
39 if (!isset($options[$name])) {
40 $options[$name] = [$handler];
41 } elseif (is_callable($options[$name])) {
42 $options[$name] = [$options[$name], $handler];
43 } elseif (is_array($options[$name])) {
44 if (isset($options[$name]['fn'])) {
45 $options[$name] = [$options[$name], $handler];
46 } else {
47 $options[$name][] = $handler;
48 }
49 } else {
50 throw new \InvalidArgumentException('Invalid event format');
51 }
52 }
53
54 return $options;
55 }
56 }
1 <?php
2 namespace GuzzleHttp\Event;
3
4 /**
5 * SubscriberInterface provides an array of events to an
6 * EventEmitterInterface when it is registered. The emitter then binds the
7 * listeners specified by the EventSubscriber.
8 *
9 * This interface is based on the SubscriberInterface of the Symfony.
10 * @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher
11 */
12 interface SubscriberInterface
13 {
14 /**
15 * Returns an array of event names this subscriber wants to listen to.
16 *
17 * The returned array keys MUST map to an event name. Each array value
18 * MUST be an array in which the first element is the name of a function
19 * on the EventSubscriber OR an array of arrays in the aforementioned
20 * format. The second element in the array is optional, and if specified,
21 * designates the event priority.
22 *
23 * For example, the following are all valid:
24 *
25 * - ['eventName' => ['methodName']]
26 * - ['eventName' => ['methodName', $priority]]
27 * - ['eventName' => [['methodName'], ['otherMethod']]
28 * - ['eventName' => [['methodName'], ['otherMethod', $priority]]
29 * - ['eventName' => [['methodName', $priority], ['otherMethod', $priority]]
30 *
31 * @return array
32 */
33 public function getEvents();
34 }
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 /**
5 * Exception when an HTTP error occurs (4xx or 5xx error)
6 */
7 class BadResponseException extends RequestException {}
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 /**
5 * Exception when a client error is encountered (4xx codes)
6 */
7 class ClientException extends BadResponseException {}
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 class ConnectException extends RequestException {}
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 class CouldNotRewindStreamException extends RequestException {}
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 use GuzzleHttp\Message\ResponseInterface;
5
6 /**
7 * Exception when a client is unable to parse the response body as XML or JSON
8 */
9 class ParseException extends TransferException
10 {
11 /** @var ResponseInterface */
12 private $response;
13
14 public function __construct(
15 $message = '',
16 ResponseInterface $response = null,
17 \Exception $previous = null
18 ) {
19 parent::__construct($message, 0, $previous);
20 $this->response = $response;
21 }
22 /**
23 * Get the associated response
24 *
25 * @return ResponseInterface|null
26 */
27 public function getResponse()
28 {
29 return $this->response;
30 }
31 }
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 use GuzzleHttp\Message\RequestInterface;
5 use GuzzleHttp\Message\ResponseInterface;
6 use GuzzleHttp\Ring\Exception\ConnectException;
7 use GuzzleHttp\Exception\ConnectException as HttpConnectException;
8 use GuzzleHttp\Ring\Future\FutureInterface;
9
10 /**
11 * HTTP Request exception
12 */
13 class RequestException extends TransferException
14 {
15 /** @var RequestInterface */
16 private $request;
17
18 /** @var ResponseInterface */
19 private $response;
20
21 public function __construct(
22 $message,
23 RequestInterface $request,
24 ResponseInterface $response = null,
25 \Exception $previous = null
26 ) {
27 // Set the code of the exception if the response is set and not future.
28 $code = $response && !($response instanceof FutureInterface)
29 ? $response->getStatusCode()
30 : 0;
31 parent::__construct($message, $code, $previous);
32 $this->request = $request;
33 $this->response = $response;
34 }
35
36 /**
37 * Wrap non-RequesExceptions with a RequestException
38 *
39 * @param RequestInterface $request
40 * @param \Exception $e
41 *
42 * @return RequestException
43 */
44 public static function wrapException(RequestInterface $request, \Exception $e)
45 {
46 if ($e instanceof RequestException) {
47 return $e;
48 } elseif ($e instanceof ConnectException) {
49 return new HttpConnectException($e->getMessage(), $request, null, $e);
50 } else {
51 return new RequestException($e->getMessage(), $request, null, $e);
52 }
53 }
54
55 /**
56 * Factory method to create a new exception with a normalized error message
57 *
58 * @param RequestInterface $request Request
59 * @param ResponseInterface $response Response received
60 * @param \Exception $previous Previous exception
61 *
62 * @return self
63 */
64 public static function create(
65 RequestInterface $request,
66 ResponseInterface $response = null,
67 \Exception $previous = null
68 ) {
69 if (!$response) {
70 return new self('Error completing request', $request, null, $previous);
71 }
72
73 $level = floor($response->getStatusCode() / 100);
74 if ($level == '4') {
75 $label = 'Client error response';
76 $className = __NAMESPACE__ . '\\ClientException';
77 } elseif ($level == '5') {
78 $label = 'Server error response';
79 $className = __NAMESPACE__ . '\\ServerException';
80 } else {
81 $label = 'Unsuccessful response';
82 $className = __CLASS__;
83 }
84
85 $message = $label . ' [url] ' . $request->getUrl()
86 . ' [status code] ' . $response->getStatusCode()
87 . ' [reason phrase] ' . $response->getReasonPhrase();
88
89 return new $className($message, $request, $response, $previous);
90 }
91
92 /**
93 * Get the request that caused the exception
94 *
95 * @return RequestInterface
96 */
97 public function getRequest()
98 {
99 return $this->request;
100 }
101
102 /**
103 * Get the associated response
104 *
105 * @return ResponseInterface|null
106 */
107 public function getResponse()
108 {
109 return $this->response;
110 }
111
112 /**
113 * Check if a response was received
114 *
115 * @return bool
116 */
117 public function hasResponse()
118 {
119 return $this->response !== null;
120 }
121 }
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 /**
5 * Exception when a server error is encountered (5xx codes)
6 */
7 class ServerException extends BadResponseException {}
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 class StateException extends TransferException {};
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 class TooManyRedirectsException extends RequestException {}
1 <?php
2 namespace GuzzleHttp\Exception;
3
4 class TransferException extends \RuntimeException {}
1 <?php
2
3 namespace GuzzleHttp\Exception;
4
5 use GuzzleHttp\Message\ResponseInterface;
6
7 /**
8 * Exception when a client is unable to parse the response body as XML
9 */
10 class XmlParseException extends ParseException
11 {
12 /** @var \LibXMLError */
13 protected $error;
14
15 public function __construct(
16 $message = '',
17 ResponseInterface $response = null,
18 \Exception $previous = null,
19 \LibXMLError $error = null
20 ) {
21 parent::__construct($message, $response, $previous);
22 $this->error = $error;
23 }
24
25 /**
26 * Get the associated error
27 *
28 * @return \LibXMLError|null
29 */
30 public function getError()
31 {
32 return $this->error;
33 }
34 }
1 <?php
2 namespace GuzzleHttp;
3
4 /**
5 * Trait implementing ToArrayInterface, \ArrayAccess, \Countable,
6 * \IteratorAggregate, and some path style methods.
7 */
8 trait HasDataTrait
9 {
10 /** @var array */
11 protected $data = [];
12
13 public function getIterator()
14 {
15 return new \ArrayIterator($this->data);
16 }
17
18 public function offsetGet($offset)
19 {
20 return isset($this->data[$offset]) ? $this->data[$offset] : null;
21 }
22
23 public function offsetSet($offset, $value)
24 {
25 $this->data[$offset] = $value;
26 }
27
28 public function offsetExists($offset)
29 {
30 return isset($this->data[$offset]);
31 }
32
33 public function offsetUnset($offset)
34 {
35 unset($this->data[$offset]);
36 }
37
38 public function toArray()
39 {
40 return $this->data;
41 }
42
43 public function count()
44 {
45 return count($this->data);
46 }
47
48 /**
49 * Get a value from the collection using a path syntax to retrieve nested
50 * data.
51 *
52 * @param string $path Path to traverse and retrieve a value from
53 *
54 * @return mixed|null
55 */
56 public function getPath($path)
57 {
58 return Utils::getPath($this->data, $path);
59 }
60
61 /**
62 * Set a value into a nested array key. Keys will be created as needed to
63 * set the value.
64 *
65 * @param string $path Path to set
66 * @param mixed $value Value to set at the key
67 *
68 * @throws \RuntimeException when trying to setPath using a nested path
69 * that travels through a scalar value
70 */
71 public function setPath($path, $value)
72 {
73 Utils::setPath($this->data, $path, $value);
74 }
75 }
1 <?php
2 namespace GuzzleHttp\Message;
3
4 use GuzzleHttp\Stream\StreamInterface;
5
6 abstract class AbstractMessage implements MessageInterface
7 {
8 /** @var array HTTP header collection */
9 private $headers = [];
10
11 /** @var array mapping a lowercase header name to its name over the wire */
12 private $headerNames = [];
13
14 /** @var StreamInterface Message body */
15 private $body;
16
17 /** @var string HTTP protocol version of the message */
18 private $protocolVersion = '1.1';
19
20 public function __toString()
21 {
22 return static::getStartLineAndHeaders($this)
23 . "\r\n\r\n" . $this->getBody();
24 }
25
26 public function getProtocolVersion()
27 {
28 return $this->protocolVersion;
29 }
30
31 public function getBody()
32 {
33 return $this->body;
34 }
35
36 public function setBody(StreamInterface $body = null)
37 {
38 if ($body === null) {
39 // Setting a null body will remove the body of the request
40 $this->removeHeader('Content-Length');
41 $this->removeHeader('Transfer-Encoding');
42 }
43
44 $this->body = $body;
45 }
46
47 public function addHeader($header, $value)
48 {
49 if (is_array($value)) {
50 $current = array_merge($this->getHeaderAsArray($header), $value);
51 } else {
52 $current = $this->getHeaderAsArray($header);
53 $current[] = (string) $value;
54 }
55
56 $this->setHeader($header, $current);
57 }
58
59 public function addHeaders(array $headers)
60 {
61 foreach ($headers as $name => $header) {
62 $this->addHeader($name, $header);
63 }
64 }
65
66 public function getHeader($header)
67 {
68 $name = strtolower($header);
69 return isset($this->headers[$name])
70 ? implode(', ', $this->headers[$name])
71 : '';
72 }
73
74 public function getHeaderAsArray($header)
75 {
76 $name = strtolower($header);
77 return isset($this->headers[$name]) ? $this->headers[$name] : [];
78 }
79
80 public function getHeaders()
81 {
82 $headers = [];
83 foreach ($this->headers as $name => $values) {
84 $headers[$this->headerNames[$name]] = $values;
85 }
86
87 return $headers;
88 }
89
90 public function setHeader($header, $value)
91 {
92 $header = trim($header);
93 $name = strtolower($header);
94 $this->headerNames[$name] = $header;
95
96 if (is_array($value)) {
97 foreach ($value as &$v) {
98 $v = trim($v);
99 }
100 $this->headers[$name] = $value;
101 } else {
102 $this->headers[$name] = [trim($value)];
103 }
104 }
105
106 public function setHeaders(array $headers)
107 {
108 $this->headers = $this->headerNames = [];
109 foreach ($headers as $key => $value) {
110 $this->setHeader($key, $value);
111 }
112 }
113
114 public function hasHeader($header)
115 {
116 return isset($this->headers[strtolower($header)]);
117 }
118
119 public function removeHeader($header)
120 {
121 $name = strtolower($header);
122 unset($this->headers[$name], $this->headerNames[$name]);
123 }
124
125 /**
126 * Parse an array of header values containing ";" separated data into an
127 * array of associative arrays representing the header key value pair
128 * data of the header. When a parameter does not contain a value, but just
129 * contains a key, this function will inject a key with a '' string value.
130 *
131 * @param MessageInterface $message That contains the header
132 * @param string $header Header to retrieve from the message
133 *
134 * @return array Returns the parsed header values.
135 */
136 public static function parseHeader(MessageInterface $message, $header)
137 {
138 static $trimmed = "\"' \n\t\r";
139 $params = $matches = [];
140
141 foreach (self::normalizeHeader($message, $header) as $val) {
142 $part = [];
143 foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
144 if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
145 $m = $matches[0];
146 if (isset($m[1])) {
147 $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
148 } else {
149 $part[] = trim($m[0], $trimmed);
150 }
151 }
152 }
153 if ($part) {
154 $params[] = $part;
155 }
156 }
157
158 return $params;
159 }
160
161 /**
162 * Converts an array of header values that may contain comma separated
163 * headers into an array of headers with no comma separated values.
164 *
165 * @param MessageInterface $message That contains the header
166 * @param string $header Header to retrieve from the message
167 *
168 * @return array Returns the normalized header field values.
169 */
170 public static function normalizeHeader(MessageInterface $message, $header)
171 {
172 $h = $message->getHeaderAsArray($header);
173 for ($i = 0, $total = count($h); $i < $total; $i++) {
174 if (strpos($h[$i], ',') === false) {
175 continue;
176 }
177 foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $h[$i]) as $v) {
178 $h[] = trim($v);
179 }
180 unset($h[$i]);
181 }
182
183 return $h;
184 }
185
186 /**
187 * Gets the start-line and headers of a message as a string
188 *
189 * @param MessageInterface $message
190 *
191 * @return string
192 */
193 public static function getStartLineAndHeaders(MessageInterface $message)
194 {
195 return static::getStartLine($message)
196 . self::getHeadersAsString($message);
197 }
198
199 /**
200 * Gets the headers of a message as a string
201 *
202 * @param MessageInterface $message
203 *
204 * @return string
205 */
206 public static function getHeadersAsString(MessageInterface $message)
207 {
208 $result = '';
209 foreach ($message->getHeaders() as $name => $values) {
210 $result .= "\r\n{$name}: " . implode(', ', $values);
211 }
212
213 return $result;
214 }
215
216 /**
217 * Gets the start line of a message
218 *
219 * @param MessageInterface $message
220 *
221 * @return string
222 * @throws \InvalidArgumentException
223 */
224 public static function getStartLine(MessageInterface $message)
225 {
226 if ($message instanceof RequestInterface) {
227 return trim($message->getMethod() . ' '
228 . $message->getResource())
229 . ' HTTP/' . $message->getProtocolVersion();
230 } elseif ($message instanceof ResponseInterface) {
231 return 'HTTP/' . $message->getProtocolVersion() . ' '
232 . $message->getStatusCode() . ' '
233 . $message->getReasonPhrase();
234 } else {
235 throw new \InvalidArgumentException('Unknown message type');
236 }
237 }
238
239 /**
240 * Accepts and modifies the options provided to the message in the
241 * constructor.
242 *
243 * Can be overridden in subclasses as necessary.
244 *
245 * @param array $options Options array passed by reference.
246 */
247 protected function handleOptions(array &$options)
248 {
249 if (isset($options['protocol_version'])) {
250 $this->protocolVersion = $options['protocol_version'];
251 }
252 }
253 }
1 <?php
2 namespace GuzzleHttp\Message;
3
4 /**
5 * Applies headers to a request.
6 *
7 * This interface can be used with Guzzle streams to apply body specific
8 * headers to a request during the PREPARE_REQUEST priority of the before event
9 *
10 * NOTE: a body that implements this interface will prevent a default
11 * content-type from being added to a request during the before event. If you
12 * want a default content-type to be added, then it will need to be done
13 * manually (e.g., using {@see GuzzleHttp\Mimetypes}).
14 */
15 interface AppliesHeadersInterface
16 {
17 /**
18 * Apply headers to a request appropriate for the current state of the
19 * object.
20 *
21 * @param RequestInterface $request Request
22 */
23 public function applyRequestHeaders(RequestInterface $request);
24 }
1 <?php
2 namespace GuzzleHttp\Message;
3
4 use GuzzleHttp\Ring\Future\MagicFutureTrait;
5 use GuzzleHttp\Ring\Future\FutureInterface;
6 use GuzzleHttp\Stream\StreamInterface;
7
8 /**
9 * Represents a response that has not been fulfilled.
10 *
11 * When created, you must provide a function that is used to dereference the
12 * future result and return it's value. The function has no arguments and MUST
13 * return an instance of a {@see GuzzleHttp\Message\ResponseInterface} object.
14 *
15 * You can optionally provide a function in the constructor that can be used to
16 * cancel the future from completing if possible. This function has no
17 * arguments and returns a boolean value representing whether or not the
18 * response could be cancelled.
19 *
20 * @property ResponseInterface $_value
21 */
22 class FutureResponse implements ResponseInterface, FutureInterface
23 {
24 use MagicFutureTrait;
25
26 /**
27 * Returns a FutureResponse that wraps another future.
28 *
29 * @param FutureInterface $future Future to wrap with a new future
30 * @param callable $onFulfilled Invoked when the future fulfilled
31 * @param callable $onRejected Invoked when the future rejected
32 * @param callable $onProgress Invoked when the future progresses
33 *
34 * @return FutureResponse
35 */
36 public static function proxy(
37 FutureInterface $future,
38 callable $onFulfilled = null,
39 callable $onRejected = null,
40 callable $onProgress = null
41 ) {
42 return new FutureResponse(
43 $future->then($onFulfilled, $onRejected, $onProgress),
44 [$future, 'wait'],
45 [$future, 'cancel']
46 );
47 }
48
49 public function getStatusCode()
50 {
51 return $this->_value->getStatusCode();
52 }
53
54 public function setStatusCode($code)
55 {
56 $this->_value->setStatusCode($code);
57 }
58
59 public function getReasonPhrase()
60 {
61 return $this->_value->getReasonPhrase();
62 }
63
64 public function setReasonPhrase($phrase)
65 {
66 $this->_value->setReasonPhrase($phrase);
67 }
68
69 public function getEffectiveUrl()
70 {
71 return $this->_value->getEffectiveUrl();
72 }
73
74 public function setEffectiveUrl($url)
75 {
76 $this->_value->setEffectiveUrl($url);
77 }
78
79 public function json(array $config = [])
80 {
81 return $this->_value->json($config);
82 }
83
84 public function xml(array $config = [])
85 {
86 return $this->_value->xml($config);
87 }
88
89 public function __toString()
90 {
91 try {
92 return $this->_value->__toString();
93 } catch (\Exception $e) {
94 trigger_error($e->getMessage(), E_USER_WARNING);
95 return '';
96 }
97 }
98
99 public function getProtocolVersion()
100 {
101 return $this->_value->getProtocolVersion();
102 }
103
104 public function setBody(StreamInterface $body = null)
105 {
106 $this->_value->setBody($body);
107 }
108
109 public function getBody()
110 {
111 return $this->_value->getBody();
112 }
113
114 public function getHeaders()
115 {
116 return $this->_value->getHeaders();
117 }
118
119 public function getHeader($header)
120 {
121 return $this->_value->getHeader($header);
122 }
123
124 public function getHeaderAsArray($header)
125 {
126 return $this->_value->getHeaderAsArray($header);
127 }
128
129 public function hasHeader($header)
130 {
131 return $this->_value->hasHeader($header);
132 }
133
134 public function removeHeader($header)
135 {
136 $this->_value->removeHeader($header);
137 }
138
139 public function addHeader($header, $value)
140 {
141 $this->_value->addHeader($header, $value);
142 }
143
144 public function addHeaders(array $headers)
145 {
146 $this->_value->addHeaders($headers);
147 }
148
149 public function setHeader($header, $value)
150 {
151 $this->_value->setHeader($header, $value);
152 }
153
154 public function setHeaders(array $headers)
155 {
156 $this->_value->setHeaders($headers);
157 }
158 }
1 <?php
2 namespace GuzzleHttp\Message;
3
4 use GuzzleHttp\Url;
5
6 /**
7 * Request and response factory
8 */
9 interface MessageFactoryInterface
10 {
11 /**
12 * Creates a response
13 *
14 * @param string $statusCode HTTP status code
15 * @param array $headers Response headers
16 * @param mixed $body Response body
17 * @param array $options Response options
18 * - protocol_version: HTTP protocol version
19 * - header_factory: Factory used to create headers
20 * - And any other options used by a concrete message implementation
21 *
22 * @return ResponseInterface
23 */
24 public function createResponse(
25 $statusCode,
26 array $headers = [],
27 $body = null,
28 array $options = []
29 );
30
31 /**
32 * Create a new request based on the HTTP method.
33 *
34 * This method accepts an associative array of request options. Below is a
35 * brief description of each parameter. See
36 * http://docs.guzzlephp.org/clients.html#request-options for a much more
37 * in-depth description of each parameter.
38 *
39 * - headers: Associative array of headers to add to the request
40 * - body: string|resource|array|StreamInterface request body to send
41 * - json: mixed Uploads JSON encoded data using an application/json Content-Type header.
42 * - query: Associative array of query string values to add to the request
43 * - auth: array|string HTTP auth settings (user, pass[, type="basic"])
44 * - version: The HTTP protocol version to use with the request
45 * - cookies: true|false|CookieJarInterface To enable or disable cookies
46 * - allow_redirects: true|false|array Controls HTTP redirects
47 * - save_to: string|resource|StreamInterface Where the response is saved
48 * - events: Associative array of event names to callables or arrays
49 * - subscribers: Array of event subscribers to add to the request
50 * - exceptions: Specifies whether or not exceptions are thrown for HTTP protocol errors
51 * - timeout: Timeout of the request in seconds. Use 0 to wait indefinitely
52 * - connect_timeout: Number of seconds to wait while trying to connect. (0 to wait indefinitely)
53 * - verify: SSL validation. True/False or the path to a PEM file
54 * - cert: Path a SSL cert or array of (path, pwd)
55 * - ssl_key: Path to a private SSL key or array of (path, pwd)
56 * - proxy: Specify an HTTP proxy or hash of protocols to proxies
57 * - debug: Set to true or a resource to view handler specific debug info
58 * - stream: Set to true to stream a response body rather than download it all up front
59 * - expect: true/false/integer Controls the "Expect: 100-Continue" header
60 * - config: Associative array of request config collection options
61 * - decode_content: true/false/string to control decoding content-encoding responses
62 *
63 * @param string $method HTTP method (GET, POST, PUT, etc.)
64 * @param string|Url $url HTTP URL to connect to
65 * @param array $options Array of options to apply to the request
66 *
67 * @return RequestInterface
68 * @link http://docs.guzzlephp.org/clients.html#request-options
69 */
70 public function createRequest($method, $url, array $options = []);
71 }
1 <?php
2 namespace GuzzleHttp\Message;
3
4 use GuzzleHttp\Stream\StreamInterface;
5
6 /**
7 * Request and response message interface
8 */
9 interface MessageInterface
10 {
11 /**
12 * Get a string representation of the message
13 *
14 * @return string
15 */
16 public function __toString();
17
18 /**
19 * Get the HTTP protocol version of the message
20 *
21 * @return string
22 */
23 public function getProtocolVersion();
24
25 /**
26 * Sets the body of the message.
27 *
28 * The body MUST be a StreamInterface object. Setting the body to null MUST
29 * remove the existing body.
30 *
31 * @param StreamInterface|null $body Body.
32 */
33 public function setBody(StreamInterface $body = null);
34
35 /**
36 * Get the body of the message
37 *
38 * @return StreamInterface|null
39 */
40 public function getBody();
41
42 /**
43 * Gets all message headers.
44 *
45 * The keys represent the header name as it will be sent over the wire, and
46 * each value is an array of strings associated with the header.
47 *
48 * // Represent the headers as a string
49 * foreach ($message->getHeaders() as $name => $values) {
50 * echo $name . ": " . implode(", ", $values);
51 * }
52 *
53 * @return array Returns an associative array of the message's headers.
54 */
55 public function getHeaders();
56
57 /**
58 * Retrieve a header by the given case-insensitive name.
59 *
60 * @param string $header Case-insensitive header name.
61 *
62 * @return string
63 */
64 public function getHeader($header);
65
66 /**
67 * Retrieves a header by the given case-insensitive name as an array of strings.
68 *
69 * @param string $header Case-insensitive header name.
70 *
71 * @return string[]
72 */
73 public function getHeaderAsArray($header);
74
75 /**
76 * Checks if a header exists by the given case-insensitive name.
77 *
78 * @param string $header Case-insensitive header name.
79 *
80 * @return bool Returns true if any header names match the given header
81 * name using a case-insensitive string comparison. Returns false if
82 * no matching header name is found in the message.
83 */
84 public function hasHeader($header);
85
86 /**
87 * Remove a specific header by case-insensitive name.
88 *
89 * @param string $header Case-insensitive header name.
90 */
91 public function removeHeader($header);
92
93 /**
94 * Appends a header value to any existing values associated with the
95 * given header name.
96 *
97 * @param string $header Header name to add
98 * @param string $value Value of the header
99 */
100 public function addHeader($header, $value);
101
102 /**
103 * Merges in an associative array of headers.
104 *
105 * Each array key MUST be a string representing the case-insensitive name
106 * of a header. Each value MUST be either a string or an array of strings.
107 * For each value, the value is appended to any existing header of the same
108 * name, or, if a header does not already exist by the given name, then the
109 * header is added.
110 *
111 * @param array $headers Associative array of headers to add to the message
112 */
113 public function addHeaders(array $headers);
114
115 /**
116 * Sets a header, replacing any existing values of any headers with the
117 * same case-insensitive name.
118 *
119 * The header values MUST be a string or an array of strings.
120 *
121 * @param string $header Header name
122 * @param string|array $value Header value(s)
123 */
124 public function setHeader($header, $value);
125
126 /**
127 * Sets headers, replacing any headers that have already been set on the
128 * message.
129 *
130 * The array keys MUST be a string. The array values must be either a
131 * string or an array of strings.
132 *
133 * @param array $headers Headers to set.
134 */
135 public function setHeaders(array $headers);
136 }
1 <?php
2 namespace GuzzleHttp\Message;
3
4 /**
5 * Request and response parser used by Guzzle
6 */
7 class MessageParser
8 {
9 /**
10 * Parse an HTTP request message into an associative array of parts.
11 *
12 * @param string $message HTTP request to parse
13 *
14 * @return array|bool Returns false if the message is invalid
15 */
16 public function parseRequest($message)
17 {
18 if (!($parts = $this->parseMessage($message))) {
19 return false;
20 }
21
22 // Parse the protocol and protocol version
23 if (isset($parts['start_line'][2])) {
24 $startParts = explode('/', $parts['start_line'][2]);
25 $protocol = strtoupper($startParts[0]);
26 $version = isset($startParts[1]) ? $startParts[1] : '1.1';
27 } else {
28 $protocol = 'HTTP';
29 $version = '1.1';
30 }
31
32 $parsed = [
33 'method' => strtoupper($parts['start_line'][0]),
34 'protocol' => $protocol,
35 'protocol_version' => $version,
36 'headers' => $parts['headers'],
37 'body' => $parts['body']
38 ];
39
40 $parsed['request_url'] = $this->getUrlPartsFromMessage(
41 (isset($parts['start_line'][1]) ? $parts['start_line'][1] : ''), $parsed);
42
43 return $parsed;
44 }
45
46 /**
47 * Parse an HTTP response message into an associative array of parts.
48 *
49 * @param string $message HTTP response to parse
50 *
51 * @return array|bool Returns false if the message is invalid
52 */
53 public function parseResponse($message)
54 {
55 if (!($parts = $this->parseMessage($message))) {
56 return false;
57 }
58
59 list($protocol, $version) = explode('/', trim($parts['start_line'][0]));
60
61 return [
62 'protocol' => $protocol,
63 'protocol_version' => $version,
64 'code' => $parts['start_line'][1],
65 'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '',
66 'headers' => $parts['headers'],
67 'body' => $parts['body']
68 ];
69 }
70
71 /**
72 * Parse a message into parts
73 *
74 * @param string $message Message to parse
75 *
76 * @return array|bool
77 */
78 private function parseMessage($message)
79 {
80 if (!$message) {
81 return false;
82 }
83
84 $startLine = null;
85 $headers = [];
86 $body = '';
87
88 // Iterate over each line in the message, accounting for line endings
89 $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
90 for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
91
92 $line = $lines[$i];
93
94 // If two line breaks were encountered, then this is the end of body
95 if (empty($line)) {
96 if ($i < $totalLines - 1) {
97 $body = implode('', array_slice($lines, $i + 2));
98 }
99 break;
100 }
101
102 // Parse message headers
103 if (!$startLine) {
104 $startLine = explode(' ', $line, 3);
105 } elseif (strpos($line, ':')) {
106 $parts = explode(':', $line, 2);
107 $key = trim($parts[0]);
108 $value = isset($parts[1]) ? trim($parts[1]) : '';
109 if (!isset($headers[$key])) {
110 $headers[$key] = $value;
111 } elseif (!is_array($headers[$key])) {
112 $headers[$key] = [$headers[$key], $value];
113 } else {
114 $headers[$key][] = $value;
115 }
116 }
117 }
118
119 return [
120 'start_line' => $startLine,
121 'headers' => $headers,
122 'body' => $body
123 ];
124 }
125
126 /**
127 * Create URL parts from HTTP message parts
128 *
129 * @param string $requestUrl Associated URL
130 * @param array $parts HTTP message parts
131 *
132 * @return array
133 */
134 private function getUrlPartsFromMessage($requestUrl, array $parts)
135 {
136 // Parse the URL information from the message
137 $urlParts = ['path' => $requestUrl, 'scheme' => 'http'];
138
139 // Check for the Host header
140 if (isset($parts['headers']['Host'])) {
141 $urlParts['host'] = $parts['headers']['Host'];
142 } elseif (isset($parts['headers']['host'])) {
143 $urlParts['host'] = $parts['headers']['host'];
144 } else {
145 $urlParts['host'] = null;
146 }
147
148 if (false === strpos($urlParts['host'], ':')) {
149 $urlParts['port'] = '';
150 } else {
151 $hostParts = explode(':', $urlParts['host']);
152 $urlParts['host'] = trim($hostParts[0]);
153 $urlParts['port'] = (int) trim($hostParts[1]);
154 if ($urlParts['port'] == 443) {
155 $urlParts['scheme'] = 'https';
156 }
157 }
158
159 // Check if a query is present
160 $path = $urlParts['path'];
161 $qpos = strpos($path, '?');
162 if ($qpos) {
163 $urlParts['query'] = substr($path, $qpos + 1);
164 $urlParts['path'] = substr($path, 0, $qpos);
165 } else {
166 $urlParts['query'] = '';
167 }
168
169 return $urlParts;
170 }
171 }
1 <?php
2 namespace GuzzleHttp\Message;
3
4 use GuzzleHttp\Collection;
5 use GuzzleHttp\Event\HasEmitterTrait;
6 use GuzzleHttp\Subscriber\Prepare;
7 use GuzzleHttp\Url;
8
9 /**
10 * HTTP request class to send requests
11 */
12 class Request extends AbstractMessage implements RequestInterface
13 {
14 use HasEmitterTrait;
15
16 /** @var Url HTTP Url */
17 private $url;
18
19 /** @var string HTTP method */
20 private $method;
21
22 /** @var Collection Transfer options */
23 private $transferOptions;
24
25 /**
26 * @param string $method HTTP method
27 * @param string|Url $url HTTP URL to connect to. The URI scheme,
28 * host header, and URI are parsed from the full URL. If query string
29 * parameters are present they will be parsed as well.
30 * @param array|Collection $headers HTTP headers
31 * @param mixed $body Body to send with the request
32 * @param array $options Array of options to use with the request
33 * - emitter: Event emitter to use with the request
34 */
35 public function __construct(
36 $method,
37 $url,
38 $headers = [],
39 $body = null,
40 array $options = []
41 ) {
42 $this->setUrl($url);
43 $this->method = strtoupper($method);
44 $this->handleOptions($options);
45 $this->transferOptions = new Collection($options);
46 $this->addPrepareEvent();
47
48 if ($body !== null) {
49 $this->setBody($body);
50 }
51
52 if ($headers) {
53 foreach ($headers as $key => $value) {
54 $this->setHeader($key, $value);
55 }
56 }
57 }
58
59 public function __clone()
60 {
61 if ($this->emitter) {
62 $this->emitter = clone $this->emitter;
63 }
64 $this->transferOptions = clone $this->transferOptions;
65 $this->url = clone $this->url;
66 }
67
68 public function setUrl($url)
69 {
70 $this->url = $url instanceof Url ? $url : Url::fromString($url);
71 $this->updateHostHeaderFromUrl();
72 }
73
74 public function getUrl()
75 {
76 return (string) $this->url;
77 }
78
79 public function setQuery($query)
80 {
81 $this->url->setQuery($query);
82 }
83
84 public function getQuery()
85 {
86 return $this->url->getQuery();
87 }
88
89 public function setMethod($method)
90 {
91 $this->method = strtoupper($method);
92 }
93
94 public function getMethod()
95 {
96 return $this->method;
97 }
98
99 public function getScheme()
100 {
101 return $this->url->getScheme();
102 }
103
104 public function setScheme($scheme)
105 {
106 $this->url->setScheme($scheme);
107 }
108
109 public function getPort()
110 {
111 return $this->url->getPort();
112 }
113
114 public function setPort($port)
115 {
116 $this->url->setPort($port);
117 $this->updateHostHeaderFromUrl();
118 }
119
120 public function getHost()
121 {
122 return $this->url->getHost();
123 }
124
125 public function setHost($host)
126 {
127 $this->url->setHost($host);
128 $this->updateHostHeaderFromUrl();
129 }
130
131 public function getPath()
132 {
133 return '/' . ltrim($this->url->getPath(), '/');
134 }
135
136 public function setPath($path)
137 {
138 $this->url->setPath($path);
139 }
140
141 public function getResource()
142 {
143 $resource = $this->getPath();
144 if ($query = (string) $this->url->getQuery()) {
145 $resource .= '?' . $query;
146 }
147
148 return $resource;
149 }
150
151 public function getConfig()
152 {
153 return $this->transferOptions;
154 }
155
156 protected function handleOptions(array &$options)
157 {
158 parent::handleOptions($options);
159 // Use a custom emitter if one is specified, and remove it from
160 // options that are exposed through getConfig()
161 if (isset($options['emitter'])) {
162 $this->emitter = $options['emitter'];
163 unset($options['emitter']);
164 }
165 }
166
167 /**
168 * Adds a subscriber that ensures a request's body is prepared before
169 * sending.
170 */
171 private function addPrepareEvent()
172 {
173 static $subscriber;
174 if (!$subscriber) {
175 $subscriber = new Prepare();
176 }
177
178 $this->getEmitter()->attach($subscriber);
179 }
180
181 private function updateHostHeaderFromUrl()
182 {
183 $port = $this->url->getPort();
184 $scheme = $this->url->getScheme();
185 if ($host = $this->url->getHost()) {
186 if (($port == 80 && $scheme == 'http') ||
187 ($port == 443 && $scheme == 'https')
188 ) {
189 $this->setHeader('Host', $host);
190 } else {
191 $this->setHeader('Host', "{$host}:{$port}");
192 }
193 }
194 }
195 }
silex @ ddcec079
This diff is collapsed. Click to expand it.