Missed the Vendor folder

This commit is contained in:
Travis Swientek 2013-07-17 22:26:09 -05:00
parent c0e37a689d
commit c8802175be
488 changed files with 55663 additions and 1 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
/vendor/

7
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,7 @@
<?php
// autoload.php generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitc3bba688adf807132d48cc1ebc896d16::getLoader();

246
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,246 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0 class loader
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ClassLoader
{
private $prefixes = array();
private $fallbackDirs = array();
private $useIncludePath = false;
private $classMap = array();
public function getPrefixes()
{
return call_user_func_array('array_merge', $this->prefixes);
}
public function getFallbackDirs()
{
return $this->fallbackDirs;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of classes, merging with any others previously set.
*
* @param string $prefix The classes prefix
* @param array|string $paths The location(s) of the classes
* @param bool $prepend Prepend the location(s)
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirs = array_merge(
(array) $paths,
$this->fallbackDirs
);
} else {
$this->fallbackDirs = array_merge(
$this->fallbackDirs,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixes[$first][$prefix])) {
$this->prefixes[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixes[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixes[$first][$prefix]
);
} else {
$this->prefixes[$first][$prefix] = array_merge(
$this->prefixes[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of classes, replacing any others previously set.
*
* @param string $prefix The classes prefix
* @param array|string $paths The location(s) of the classes
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirs = (array) $paths;
return;
}
$this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths;
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
include $file;
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$className = substr($class, $pos + 1);
} else {
// PEAR-like class name
$classPath = null;
$className = $class;
}
$classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php';
$first = $class[0];
if (isset($this->prefixes[$first])) {
foreach ($this->prefixes[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
return $dir . DIRECTORY_SEPARATOR . $classPath;
}
}
}
}
}
foreach ($this->fallbackDirs as $dir) {
if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
return $dir . DIRECTORY_SEPARATOR . $classPath;
}
}
if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) {
return $file;
}
return $this->classMap[$class] = false;
}
}

9
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,9 @@
<?php
// autoload_classmap.php generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

12
vendor/composer/autoload_namespaces.php vendored Normal file
View File

@ -0,0 +1,12 @@
<?php
// autoload_namespaces.php generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'),
'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'),
);

43
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,43 @@
<?php
// autoload_real.php generated by Composer
class ComposerAutoloaderInitc3bba688adf807132d48cc1ebc896d16
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitc3bba688adf807132d48cc1ebc896d16', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitc3bba688adf807132d48cc1ebc896d16', 'loadClassLoader'));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
$loader->register(true);
return $loader;
}
}

152
vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,152 @@
[
{
"name": "symfony/event-dispatcher",
"version": "v2.3.2",
"version_normalized": "2.3.2.0",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
"reference": "v2.3.2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.3.2",
"reference": "v2.3.2",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/dependency-injection": "~2.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"time": "2013-05-13 14:36:40",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Symfony\\Component\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com"
},
{
"name": "guzzle/guzzle",
"version": "v3.7.1",
"version_normalized": "3.7.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "v3.7.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/v3.7.1",
"reference": "v3.7.1",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.3.2",
"symfony/event-dispatcher": ">=2.1"
},
"replace": {
"guzzle/batch": "self.version",
"guzzle/cache": "self.version",
"guzzle/common": "self.version",
"guzzle/http": "self.version",
"guzzle/inflection": "self.version",
"guzzle/iterator": "self.version",
"guzzle/log": "self.version",
"guzzle/parser": "self.version",
"guzzle/plugin": "self.version",
"guzzle/plugin-async": "self.version",
"guzzle/plugin-backoff": "self.version",
"guzzle/plugin-cache": "self.version",
"guzzle/plugin-cookie": "self.version",
"guzzle/plugin-curlauth": "self.version",
"guzzle/plugin-error-response": "self.version",
"guzzle/plugin-history": "self.version",
"guzzle/plugin-log": "self.version",
"guzzle/plugin-md5": "self.version",
"guzzle/plugin-mock": "self.version",
"guzzle/plugin-oauth": "self.version",
"guzzle/service": "self.version",
"guzzle/stream": "self.version"
},
"require-dev": {
"doctrine/cache": "*",
"monolog/monolog": "1.*",
"phpunit/phpunit": "3.7.*",
"psr/log": "1.0.*",
"symfony/class-loader": "*",
"zendframework/zend-cache": "2.0.*",
"zendframework/zend-log": "2.0.*"
},
"time": "2013-07-05 20:17:54",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Guzzle\\Tests": "tests/",
"Guzzle": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Guzzle Community",
"homepage": "https://github.com/guzzle/guzzle/contributors"
}
],
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
]
}
]

24
vendor/guzzle/guzzle/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Ingore common cruft
.DS_STORE
coverage
.idea
# Ignore binary files
guzzle.phar
guzzle-min.phar
# Ignore potentially sensitive phpunit file
phpunit.xml
# Ignore composer generated files
composer.phar
composer.lock
composer-test.lock
vendor/
# Ignore build files
build/
phing/build.properties
# Ignore subsplit working directory
.subsplit

31
vendor/guzzle/guzzle/.travis.yml vendored Normal file
View File

@ -0,0 +1,31 @@
language: php
php:
- 5.3
- 5.4
- 5.5
before_script:
- curl --version
- pear config-set php_ini ~/.phpenv/versions/`php -r 'echo phpversion();'`/etc/php.ini
- echo 'Installing pecl_http'
- wget --quiet http://pecl.php.net/get/pecl_http-1.7.6.tgz
- tar -xzf pecl_http-1.7.6.tgz
- sh -c "cd pecl_http-1.7.6 && phpize && ./configure && make && sudo make install" > /dev/null
- echo "extension=http.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
- pecl install uri_template-beta
- pear config-set auto_discover 1
- pear install pear.phing.info/phing
- pear install pear.phpunit.de/phploc
- pear install pear/PHP_CodeSniffer
- pear install pear.pdepend.org/PHP_Depend
- pear install bartlett.laurent-laville.org/PHP_CompatInfo
- pear install pear/PEAR_PackageFileManager2 pear/PEAR_PackageFileManager2_Plugins pear/XML_Serializer-beta
- pear install VersionControl_Git-alpha
- phpenv rehash
- composer install --dev
- cp phing/build.properties.travis phing/build.properties
- echo 'Ensuring the correct version of node is running'
- ~/.nvm/nvm.sh install v0.6.14
script: phing travisci

678
vendor/guzzle/guzzle/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,678 @@
CHANGELOG
=========
3.7.1 (2013-07-05)
------------------
* Bug fix: Setting default options on a client now works
* Bug fix: Setting options on HEAD requests now works. See #352
* Bug fix: Moving stream factory before send event to before building the stream. See #353
* Bug fix: Cookies no longer match on IP addresses per RFC 6265
* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
* Added `cert` and `ssl_key` as request options
* `Host` header can now diverge from the host part of a URL if the header is set manually
* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
* OAuth parameters are only added via the plugin if they aren't already set
* Exceptions are now thrown when a URL cannot be parsed
* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
3.7.0 (2013-06-10)
------------------
* See UPGRADING.md for more information on how to upgrade.
* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
request. You can pass a 'request.options' configuration setting to a client to apply default request options to
every request created by a client (e.g. default query string variables, headers, curl options, etc).
* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
See `Guzzle\Http\StaticClient::mount`.
* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
created by a command (e.g. custom headers, query string variables, timeout settings, etc).
* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
headers of a response
* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
(e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
* ServiceBuilders now support storing and retrieving arbitrary data
* CachePlugin can now purge all resources for a given URI
* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
* CachePlugin now uses the Vary header to determine if a resource is a cache hit
* `Guzzle\Http\Message\Response` now implements `\Serializable`
* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
Symfony users can still use the old version of Monolog.
* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
* Several performance improvements to `Guzzle\Common\Collection`
* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
createRequest, head, delete, put, patch, post, options, prepareRequest
* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
`Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
resource, string, or EntityBody into the $options parameter to specify the download location of the response.
* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
default `array()`
* Added `Guzzle\Stream\StreamInterface::isRepeatable`
* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
* Removed `Guzzle\Http\Message\RequestInterface::canCache`
* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
`Guzzle\Common\Version::$emitWarnings` to true.
* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
`$request->getResponseBody()->isRepeatable()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
`Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
`Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
These will work through Guzzle 4.0
* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
* Marked `Guzzle\Common\Collection::inject()` as deprecated.
* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
CacheStorageInterface. These two objects and interface will be removed in a future version.
* Always setting X-cache headers on cached responses
* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
$request, Response $response);`
* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
* Added `CacheStorageInterface::purge($url)`
* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
$plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
CanCacheStrategyInterface $canCache = null)`
* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
3.6.0 (2013-05-29)
------------------
* ServiceDescription now implements ToArrayInterface
* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
* Guzzle can now correctly parse incomplete URLs
* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
CacheControl header implementation.
* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
Guzzle\Http\Curl\RequestMediator
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle
directly via interfaces
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
but are a no-op until removed.
* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
`Guzzle\Service\Command\ArrayCommandInterface`.
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
on a request while the request is still being transferred
* The ability to case-insensitively search for header values
* Guzzle\Http\Message\Header::hasExactHeader
* Guzzle\Http\Message\Header::raw. Use getAll()
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
instead.
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
* Added the ability to cast Model objects to a string to view debug information.
3.5.0 (2013-05-13)
------------------
* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove
itself from the EventDispatcher)
* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
non-existent key
* Bug: All __call() method arguments are now required (helps with mocking frameworks)
* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
to help with refcount based garbage collection of resources created by sending a request
* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the
HistoryPlugin for a history.
* Added a `responseBody` alias for the `response_body` location
* Refactored internals to no longer rely on Response::getRequest()
* HistoryPlugin can now be cast to a string
* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
and responses that are sent over the wire
* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
3.4.3 (2013-04-30)
------------------
* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
* Added a check to re-extract the temp cacert bundle from the phar before sending each request
3.4.2 (2013-04-29)
------------------
* Bug fix: Stream objects now work correctly with "a" and "a+" modes
* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
* Bug fix: AsyncPlugin no longer forces HEAD requests
* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
* Setting a response on a request will write to the custom request body from the response body if one is specified
* LogPlugin now writes to php://output when STDERR is undefined
* Added the ability to set multiple POST files for the same key in a single call
* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
* Added the ability to queue CurlExceptions to the MockPlugin
* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
* Configuration loading now allows remote files
3.4.1 (2013-04-16)
------------------
* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
* Exceptions are now properly grouped when sending requests in parallel
* Redirects are now properly aggregated when a multi transaction fails
* Redirects now set the response on the original object even in the event of a failure
* Bug fix: Model names are now properly set even when using $refs
* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
* Added support for oauth_callback in OAuth signatures
* Added support for oauth_verifier in OAuth signatures
* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
3.4.0 (2013-04-11)
------------------
* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
* Bug fix: Added `number` type to service descriptions.
* Bug fix: empty parameters are removed from an OAuth signature
* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
* Bug fix: Fixed "array to string" error when validating a union of types in a service description
* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
the Content-Type can be determined based on the entity body or the path of the request.
* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
* Added support for a PSR-3 LogAdapter.
* Added a `command.after_prepare` event
* Added `oauth_callback` parameter to the OauthPlugin
* Added the ability to create a custom stream class when using a stream factory
* Added a CachingEntityBody decorator
* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
POST fields or files (the latter is only used when emulating a form POST in the browser).
* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
3.3.1 (2013-03-10)
------------------
* Added the ability to create PHP streaming responses from HTTP requests
* Bug fix: Running any filters when parsing response headers with service descriptions
* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
response location visitors.
* Bug fix: Removed the possibility of creating configuration files with circular dependencies
* RequestFactory::create() now uses the key of a POST file when setting the POST file name
* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
3.3.0 (2013-03-03)
------------------
* A large number of performance optimizations have been made
* Bug fix: Added 'wb' as a valid write mode for streams
* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
* BC: Removed `Guzzle\Http\Utils` class
* BC: Setting a service description on a client will no longer modify the client's command factories.
* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
lowercase
* Operation parameter objects are now lazy loaded internally
* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
* Added support for instantiating responseType=class responseClass classes. Classes must implement
`Guzzle\Service\Command\ResponseClassInterface`
* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
additional properties also support locations and can be used to parse JSON responses where the outermost part of the
JSON is an array
* Added support for nested renaming of JSON models (rename sentAs to name)
* CachePlugin
* Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
* Debug headers can now added to cached response in the CachePlugin
3.2.0 (2013-02-14)
------------------
* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
* URLs with no path no longer contain a "/" by default
* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
* BadResponseException no longer includes the full request and response message
* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
* xmlEncoding can now be customized for the XML declaration of a XML service description operation
* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
aggregation and no longer uses callbacks
* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
* Bug fix: Filters were not always invoked for array service description parameters
* Bug fix: Redirects now use a target response body rather than a temporary response body
* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
3.1.2 (2013-01-27)
------------------
* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
3.1.1 (2013-01-20)
------------------
* Adding wildcard support to Guzzle\Common\Collection::getPath()
* Adding alias support to ServiceBuilder configs
* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
3.1.0 (2013-01-12)
------------------
* BC: CurlException now extends from RequestException rather than BadResponseException
* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
* Added getData to ServiceDescriptionInterface
* Added context array to RequestInterface::setState()
* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
* Bug: Adding required content-type when JSON request visitor adds JSON to a command
* Bug: Fixing the serialization of a service description with custom data
* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
an array of successful and failed responses
* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
* Added Guzzle\Http\IoEmittingEntityBody
* Moved command filtration from validators to location visitors
* Added `extends` attributes to service description parameters
* Added getModels to ServiceDescriptionInterface
3.0.7 (2012-12-19)
------------------
* Fixing phar detection when forcing a cacert to system if null or true
* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
* Cleaning up `Guzzle\Common\Collection::inject` method
* Adding a response_body location to service descriptions
3.0.6 (2012-12-09)
------------------
* CurlMulti performance improvements
* Adding setErrorResponses() to Operation
* composer.json tweaks
3.0.5 (2012-11-18)
------------------
* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
* Bug: Response body can now be a string containing "0"
* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
* Added support for XML attributes in service description responses
* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
* Added better mimetype guessing to requests and post files
3.0.4 (2012-11-11)
------------------
* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
* Bug: Cookies can now be added that have a name, domain, or value set to "0"
* Bug: Using the system cacert bundle when using the Phar
* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
* Enhanced cookie jar de-duplication
* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
3.0.3 (2012-11-04)
------------------
* Implementing redirects in PHP rather than cURL
* Added PECL URI template extension and using as default parser if available
* Bug: Fixed Content-Length parsing of Response factory
* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
* Adding ToArrayInterface throughout library
* Fixing OauthPlugin to create unique nonce values per request
3.0.2 (2012-10-25)
------------------
* Magic methods are enabled by default on clients
* Magic methods return the result of a command
* Service clients no longer require a base_url option in the factory
* Bug: Fixed an issue with URI templates where null template variables were being expanded
3.0.1 (2012-10-22)
------------------
* Models can now be used like regular collection objects by calling filter, map, etc
* Models no longer require a Parameter structure or initial data in the constructor
* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
3.0.0 (2012-10-15)
------------------
* Rewrote service description format to be based on Swagger
* Now based on JSON schema
* Added nested input structures and nested response models
* Support for JSON and XML input and output models
* Renamed `commands` to `operations`
* Removed dot class notation
* Removed custom types
* Broke the project into smaller top-level namespaces to be more component friendly
* Removed support for XML configs and descriptions. Use arrays or JSON files.
* Removed the Validation component and Inspector
* Moved all cookie code to Guzzle\Plugin\Cookie
* Magic methods on a Guzzle\Service\Client now return the command un-executed.
* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
* Now shipping with cURL's CA certs and using it by default
* Added previousResponse() method to response objects
* No longer sending Accept and Accept-Encoding headers on every request
* Only sending an Expect header by default when a payload is greater than 1MB
* Added/moved client options:
* curl.blacklist to curl.option.blacklist
* Added ssl.certificate_authority
* Added a Guzzle\Iterator component
* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
* Added a more robust caching plugin
* Added setBody to response objects
* Updating LogPlugin to use a more flexible MessageFormatter
* Added a completely revamped build process
* Cleaning up Collection class and removing default values from the get method
* Fixed ZF2 cache adapters
2.8.8 (2012-10-15)
------------------
* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
2.8.7 (2012-09-30)
------------------
* Bug: Fixed config file aliases for JSON includes
* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
* Bug: Hardening request and response parsing to account for missing parts
* Bug: Fixed PEAR packaging
* Bug: Fixed Request::getInfo
* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
* Adding the ability for the namespace Iterator factory to look in multiple directories
* Added more getters/setters/removers from service descriptions
* Added the ability to remove POST fields from OAuth signatures
* OAuth plugin now supports 2-legged OAuth
2.8.6 (2012-09-05)
------------------
* Added the ability to modify and build service descriptions
* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
* Added a `json` parameter location
* Now allowing dot notation for classes in the CacheAdapterFactory
* Using the union of two arrays rather than an array_merge when extending service builder services and service params
* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
in service builder config files.
* Services defined in two different config files that include one another will by default replace the previously
defined service, but you can now create services that extend themselves and merge their settings over the previous
* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
'_default' with a default JSON configuration file.
2.8.5 (2012-08-29)
------------------
* Bug: Suppressed empty arrays from URI templates
* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
* Added support for HTTP responses that do not contain a reason phrase in the start-line
* AbstractCommand commands are now invokable
* Added a way to get the data used when signing an Oauth request before a request is sent
2.8.4 (2012-08-15)
------------------
* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
* Added additional response status codes
* Removed SSL information from the default User-Agent header
* DELETE requests can now send an entity body
* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
* Added the ability of the MockPlugin to consume mocked request bodies
* LogPlugin now exposes request and response objects in the extras array
2.8.3 (2012-07-30)
------------------
* Bug: Fixed a case where empty POST requests were sent as GET requests
* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
* Added multiple inheritance to service description commands
* Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()``
* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
2.8.2 (2012-07-24)
------------------
* Bug: Query string values set to 0 are no longer dropped from the query string
* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()``
* Bug: ``+`` is now treated as an encoded space when parsing query strings
* QueryString and Collection performance improvements
* Allowing dot notation for class paths in filters attribute of a service descriptions
2.8.1 (2012-07-16)
------------------
* Loosening Event Dispatcher dependency
* POST redirects can now be customized using CURLOPT_POSTREDIR
2.8.0 (2012-07-15)
------------------
* BC: Guzzle\Http\Query
* Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
* Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
* Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
* Changed the aggregation functions of QueryString to be static methods
* Can now use fromString() with querystrings that have a leading ?
* cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters
* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
* Cookies are no longer URL decoded by default
* Bug: URI template variables set to null are no longer expanded
2.7.2 (2012-07-02)
------------------
* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
* CachePlugin now allows for a custom request parameter function to check if a request can be cached
* Bug fix: CachePlugin now only caches GET and HEAD requests by default
* Bug fix: Using header glue when transferring headers over the wire
* Allowing deeply nested arrays for composite variables in URI templates
* Batch divisors can now return iterators or arrays
2.7.1 (2012-06-26)
------------------
* Minor patch to update version number in UA string
* Updating build process
2.7.0 (2012-06-25)
------------------
* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
* BC: Removed magic setX methods from commands
* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
* Added the ability to set POST fields and files in a service description
* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
* Adding a command.before_prepare event to clients
* Added BatchClosureTransfer and BatchClosureDivisor
* BatchTransferException now includes references to the batch divisor and transfer strategies
* Fixed some tests so that they pass more reliably
* Added Guzzle\Common\Log\ArrayLogAdapter
2.6.6 (2012-06-10)
------------------
* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
* BC: Removing Guzzle\Service\Command\CommandSet
* Adding generic batching system (replaces the batch queue plugin and command set)
* Updating ZF cache and log adapters and now using ZF's composer repository
* Bug: Setting the name of each ApiParam when creating through an ApiCommand
* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
* Bug: Changed the default cookie header casing back to 'Cookie'
2.6.5 (2012-06-03)
------------------
* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
* BC: Renaming methods in the CookieJarInterface
* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
* Making the default glue for HTTP headers ';' instead of ','
* Adding a removeValue to Guzzle\Http\Message\Header
* Adding getCookies() to request interface.
* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
2.6.4 (2012-05-30)
------------------
* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
* Bug: Fixing magic method command calls on clients
* Bug: Email constraint only validates strings
* Bug: Aggregate POST fields when POST files are present in curl handle
* Bug: Fixing default User-Agent header
* Bug: Only appending or prepending parameters in commands if they are specified
* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
* Allowing the use of dot notation for class namespaces when using instance_of constraint
* Added any_match validation constraint
* Added an AsyncPlugin
* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
* Allowing the result of a command object to be changed
* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
2.6.3 (2012-05-23)
------------------
* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
* You can now use an array of data when creating PUT request bodies in the request factory.
* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
* [Http] Adding support for Content-Type in multipart POST uploads per upload
* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
* Adding more POST data operations for easier manipulation of POST data.
* You can now set empty POST fields.
* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
* CS updates
2.6.2 (2012-05-19)
------------------
* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
2.6.1 (2012-05-19)
------------------
* [BC] Removing 'path' support in service descriptions. Use 'uri'.
* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
* [BC] Removing Guzzle\Common\XmlElement.
* All commands, both dynamic and concrete, have ApiCommand objects.
* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
2.6.0 (2012-05-15)
------------------
* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
* [BC] Executing a Command returns the result of the command rather than the command
* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
* [BC] Guzzle\Guzzle is now deprecated
* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
* Adding Guzzle\Version class to give version information about Guzzle
* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
* ServiceDescription and ServiceBuilder are now cacheable using similar configs
* Changing the format of XML and JSON service builder configs. Backwards compatible.
* Cleaned up Cookie parsing
* Trimming the default Guzzle User-Agent header
* Adding a setOnComplete() method to Commands that is called when a command completes
* Keeping track of requests that were mocked in the MockPlugin
* Fixed a caching bug in the CacheAdapterFactory
* Inspector objects can be injected into a Command object
* Refactoring a lot of code and tests to be case insensitive when dealing with headers
* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
* Adding the ability to set global option overrides to service builder configs
* Adding the ability to include other service builder config files from within XML and JSON files
* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
2.5.0 (2012-05-08)
------------------
* Major performance improvements
* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
* Added the ability to passed parameters to all requests created by a client
* Added callback functionality to the ExponentialBackoffPlugin
* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
* Rewinding request stream bodies when retrying requests
* Exception is thrown when JSON response body cannot be decoded
* Added configurable magic method calls to clients and commands. This is off by default.
* Fixed a defect that added a hash to every parsed URL part
* Fixed duplicate none generation for OauthPlugin.
* Emitting an event each time a client is generated by a ServiceBuilder
* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
* cache.* request parameters should be renamed to params.cache.*
* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc). See CurlHandle.
* Added the ability to disable type validation of service descriptions
* ServiceDescriptions and ServiceBuilders are now Serializable

19
vendor/guzzle/guzzle/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2011 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

210
vendor/guzzle/guzzle/README.md vendored Normal file
View File

@ -0,0 +1,210 @@
Guzzle, PHP HTTP client and webservice framework
================================================
[![Latest Stable Version](https://poser.pugx.org/guzzle/guzzle/version.png)](https://packagist.org/packages/guzzle/guzzle) [![Composer Downloads](https://poser.pugx.org/guzzle/guzzle/d/total.png)](https://packagist.org/packages/guzzle/guzzle) [![Build Status](https://secure.travis-ci.org/guzzle/guzzle.png?branch=master)](http://travis-ci.org/guzzle/guzzle)
Guzzle is a PHP HTTP client and framework for building RESTful web service clients.
- Extremely powerful API provides all the power of cURL with a simple interface.
- Truly take advantage of HTTP/1.1 with persistent connections, connection pooling, and parallel requests.
- Service description DSL allows you build awesome web service clients faster.
- Symfony2 event-based plugin system allows you to completely modify the behavior of a request.
Get answers with: [Documentation](http://www.guzzlephp.org/), [Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle), IRC ([#guzzlephp](irc://irc.freenode.net/#guzzlephp) @ irc.freenode.net)
```php
// Really simple using a static facade
Guzzle\Http\StaticClient::mount();
$response = Guzzle::get('http://guzzlephp.org');
// More control using a client class
$client = new \Guzzle\Http\Client('http://guzzlephp.org');
$request = $client->get('/');
$response = $request->send();
```
### Installing via Composer
The recommended way to install Guzzle is through [Composer](http://getcomposer.org).
```bash
# Install Composer
curl -sS https://getcomposer.org/installer | php
# Add Guzzle as a dependency
php composer.phar require guzzle/guzzle:~3.7
```
After installing, you need to require Composer's autoloader:
```php
require 'vendor/autoload.php';
```
### Installing via phar
[Download the phar](http://guzzlephp.org/guzzle.phar) and include it in your project
([minimal phar](http://guzzlephp.org/guzzle-min.phar))
Features
--------
- Supports GET, HEAD, POST, DELETE, PUT, PATCH, OPTIONS, and any other custom HTTP method
- Allows full access to request and response headers
- Persistent connections are implicitly managed by Guzzle, resulting in huge performance benefits
- [Send requests in parallel](http://guzzlephp.org/tour/http.html#send-http-requests-in-parallel)
- Cookie sessions can be maintained between requests using the
[CookiePlugin](http://guzzlephp.org/tour/http.html#cookie-session-plugin)
- Allows custom [entity bodies](http://guzzlephp.org/tour/http.html#entity-bodies), including sending data from a PHP
stream and downloading data to a PHP stream
- Responses can be cached and served from cache using the
[caching forward proxy plugin](http://guzzlephp.org/tour/http.html#php-based-caching-forward-proxy)
- Failed requests can be retried using
[truncated exponential backoff](http://guzzlephp.org/tour/http.html#truncated-exponential-backoff) with custom retry
policies
- Entity bodies can be validated automatically using Content-MD5 headers and the
[MD5 hash validator plugin](http://guzzlephp.org/tour/http.html#md5-hash-validator-plugin)
- All data sent over the wire can be logged using the
[LogPlugin](http://guzzlephp.org/tour/http.html#over-the-wire-logging)
- Subject/Observer signal slot system for unobtrusively
[modifying request behavior](http://guzzlephp.org/guide/http/creating_plugins.html)
- Supports all of the features of libcurl including authentication, compression, redirects, SSL, proxies, etc
- Web service client framework for building future-proof interfaces to web services
- Includes a [service description DSL](http://guzzlephp.org/guide/service/service_descriptions.html) for quickly
building webservice clients
- Full support for [URI templates](http://tools.ietf.org/html/rfc6570)
- Advanced batching functionality to efficiently send requests or commands in parallel with customizable batch sizes
and transfer strategies
HTTP basics
-----------
```php
<?php
use Guzzle\Http\Client;
$client = new Client('http://www.example.com/api/v1/key/{key}', [
'key' => '***'
]);
// Issue a path using a relative URL to the client's base URL
// Sends to http://www.example.com/api/v1/key/***/users
$request = $client->get('users');
$response = $request->send();
// Relative URL that overwrites the path of the base URL
$request = $client->get('/test/123.php?a=b');
// Issue a head request on the base URL
$response = $client->head()->send();
// Delete user 123
$response = $client->delete('users/123')->send();
// Send a PUT request with custom headers
$response = $client->put('upload/text', [
'X-Header' => 'My Header'
], 'body of the request')->send();
// Send a PUT request using the contents of a PHP stream as the body
// Send using an absolute URL (overrides the base URL)
$response = $client->put('http://www.example.com/upload', [
'X-Header' => 'My Header'
], fopen('http://www.test.com/', 'r'));
// Create a POST request with a file upload (notice the @ symbol):
$request = $client->post('http://localhost:8983/solr/update', null, [
'custom_field' => 'my value',
'file' => '@/path/to/documents.xml'
]);
// Create a POST request and add the POST files manually
$request = $client->post('http://localhost:8983/solr/update')
->addPostFiles(['file' => '/path/to/documents.xml']);
// Responses are objects
echo $response->getStatusCode() . ' ' . $response->getReasonPhrase() . "\n";
// Requests and responses can be cast to a string to show the raw HTTP message
echo $request . "\n\n" . $response;
// Create a request based on an HTTP message
$request = RequestFactory::fromMessage(
"PUT / HTTP/1.1\r\n" .
"Host: test.com:8081\r\n" .
"Content-Type: text/plain" .
"Transfer-Encoding: chunked\r\n" .
"\r\n" .
"this is the body"
);
```
Using the static client facade
------------------------------
You can use Guzzle through a static client to make it even easier to send simple HTTP requests.
```php
<?php
// Use the static client directly:
$response = Guzzle\Http\StaticClient::get('http://www.google.com');
// Or, mount the client to \Guzzle to make it easier to use
Guzzle\Http\StaticClient::mount();
$response = Guzzle::get('http://guzzlephp.org');
// Custom options can be passed into requests created by the static client
$response = Guzzle::post('http://guzzlephp.org', [
'headers' => ['X-Foo' => 'Bar']
'body' => ['Foo' => 'Bar'],
'query' => ['Test' => 123],
'timeout' => 10,
'debug' => true,
'save_to' => '/path/to/file.html'
]);
```
### Available request options:
* headers: Associative array of headers
* query: Associative array of query string values to add to the request
* body: Body of a request, including an EntityBody, string, or array when sending POST requests. Setting a body for a
GET request will set where the response body is downloaded.
* auth: Array of HTTP authentication parameters to use with the request. The array must contain the
username in index [0], the password in index [2], and can optionally contain the authentication type in index [3].
The authentication types are: "Basic", "Digest". The default auth type is "Basic".
* cookies: Associative array of cookies
* allow_redirects: Set to false to disable redirects
* save_to: String, fopen resource, or EntityBody object used to store the body of the response
* events: Associative array mapping event names to a closure or array of (priority, closure)
* plugins: Array of plugins to add to the request
* exceptions: Set to false to disable throwing exceptions on an HTTP level error (e.g. 404, 500, etc)
* timeout: Float describing the timeout of the request in seconds
* connect_timeout: Float describing the number of seconds to wait while trying to connect. Use 0 to wait
indefinitely.
* verify: Set to true to enable SSL cert validation (the default), false to disable, or supply the path to a CA
bundle to enable verification using a custom certificate.
* proxy: Specify an HTTP proxy (e.g. "http://username:password@192.168.16.1:10")
* debug: Set to true to display all data sent over the wire
These options can also be used when creating requests using a standard client:
```php
$client = new Guzzle\Http\Client();
// Create a request with a timeout of 10 seconds
$request = $client->get('http://guzzlephp.org', [], ['timeout' => 10]);
$response = $request->send();
```
Unit testing
------------
Guzzle uses PHPUnit for unit testing. In order to run the unit tests, you'll first need
to install the dependencies of the project using Composer: `php composer.phar install --dev`.
You can then run the tests using `vendor/bin/phpunit`.
If you are running the tests with xdebug enabled, you may encounter the following issue: 'Fatal error: Maximum function nesting level of '100' reached, aborting!'. This can be resolved by adding 'xdebug.max_nesting_level = 200' to your php.ini file.
The PECL extensions, uri_template and pecl_http will be required to ensure all the tests can run.

539
vendor/guzzle/guzzle/UPGRADING.md vendored Normal file
View File

@ -0,0 +1,539 @@
Guzzle Upgrade Guide
====================
3.6 to 3.7
----------
### Deprecations
- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
```php
\Guzzle\Common\Version::$emitWarnings = true;
```
The following APIs and options have been marked as deprecated:
- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
- Marked `Guzzle\Common\Collection::inject()` as deprecated.
- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
`$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
`$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
request methods. When paired with a client's configuration settings, these options allow you to specify default settings
for various aspects of a request. Because these options make other previous configuration options redundant, several
configuration options and methods of a client and AbstractCommand have been deprecated.
- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
$command = $client->getCommand('foo', array(
'command.headers' => array('Test' => '123'),
'command.response_body' => '/path/to/file'
));
// Should be changed to:
$command = $client->getCommand('foo', array(
'command.request_options' => array(
'headers' => array('Test' => '123'),
'save_as' => '/path/to/file'
)
));
### Interface changes
Additions and changes (you will need to update any implementations or subclasses you may have created):
- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
createRequest, head, delete, put, patch, post, options, prepareRequest
- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
`Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
resource, string, or EntityBody into the $options parameter to specify the download location of the response.
- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
default `array()`
- Added `Guzzle\Stream\StreamInterface::isRepeatable`
- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
The following methods were removed from interfaces. All of these methods are still available in the concrete classes
that implement them, but you should update your code to use alternative methods:
- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
`$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
`$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
`$client->setDefaultOption('headers/{header_name}', 'value')`. or
`$client->setDefaultOption('headers', array('header_name' => 'value'))`.
- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
### Cache plugin breaking changes
- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
CacheStorageInterface. These two objects and interface will be removed in a future version.
- Always setting X-cache headers on cached responses
- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
$request, Response $response);`
- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
- Added `CacheStorageInterface::purge($url)`
- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
$plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
CanCacheStrategyInterface $canCache = null)`
- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
3.5 to 3.6
----------
* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
CacheControl header implementation.
* Moved getLinks() from Response to just be used on a Link header object.
If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
HeaderInterface (e.g. toArray(), getAll(), etc).
### Interface changes
* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
Guzzle\Http\Curl\RequestMediator
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
### Removed deprecated functions
* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
### Deprecations
* The ability to case-insensitively search for header values
* Guzzle\Http\Message\Header::hasExactHeader
* Guzzle\Http\Message\Header::raw. Use getAll()
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
instead.
### Other changes
* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle
directly via interfaces
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
but are a no-op until removed.
* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
`Guzzle\Service\Command\ArrayCommandInterface`.
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
on a request while the request is still being transferred
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
3.3 to 3.4
----------
Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
3.2 to 3.3
----------
### Response::getEtag() quote stripping removed
`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
### Removed `Guzzle\Http\Utils`
The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
### Stream wrapper and type
`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to lowercase.
### curl.emit_io became emit_io
Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
3.1 to 3.2
----------
### CurlMulti is no longer reused globally
Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
to a single client can pollute requests dispatched from other clients.
If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
created.
```php
$multi = new Guzzle\Http\Curl\CurlMulti();
$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
$event['client']->setCurlMulti($multi);
}
});
```
### No default path
URLs no longer have a default path value of '/' if no path was specified.
Before:
```php
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com/
```
After:
```php
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com
```
### Less verbose BadResponseException
The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
response information. You can, however, get access to the request and response object by calling `getRequest()` or
`getResponse()` on the exception object.
### Query parameter aggregation
Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
2.8 to 3.x
----------
### Guzzle\Service\Inspector
Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
**Before**
```php
use Guzzle\Service\Inspector;
class YourClient extends \Guzzle\Service\Client
{
public static function factory($config = array())
{
$default = array();
$required = array('base_url', 'username', 'api_key');
$config = Inspector::fromConfig($config, $default, $required);
$client = new self(
$config->get('base_url'),
$config->get('username'),
$config->get('api_key')
);
$client->setConfig($config);
$client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
return $client;
}
```
**After**
```php
use Guzzle\Common\Collection;
class YourClient extends \Guzzle\Service\Client
{
public static function factory($config = array())
{
$default = array();
$required = array('base_url', 'username', 'api_key');
$config = Collection::fromConfig($config, $default, $required);
$client = new self(
$config->get('base_url'),
$config->get('username'),
$config->get('api_key')
);
$client->setConfig($config);
$client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
return $client;
}
```
### Convert XML Service Descriptions to JSON
**Before**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<client>
<commands>
<!-- Groups -->
<command name="list_groups" method="GET" uri="groups.json">
<doc>Get a list of groups</doc>
</command>
<command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
<doc>Uses a search query to get a list of groups</doc>
<param name="query" type="string" required="true" />
</command>
<command name="create_group" method="POST" uri="groups.json">
<doc>Create a group</doc>
<param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
<param name="Content-Type" location="header" static="application/json"/>
</command>
<command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
<doc>Delete a group by ID</doc>
<param name="id" type="integer" required="true"/>
</command>
<command name="get_group" method="GET" uri="groups/{{id}}.json">
<param name="id" type="integer" required="true"/>
</command>
<command name="update_group" method="PUT" uri="groups/{{id}}.json">
<doc>Update a group</doc>
<param name="id" type="integer" required="true"/>
<param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
<param name="Content-Type" location="header" static="application/json"/>
</command>
</commands>
</client>
```
**After**
```json
{
"name": "Zendesk REST API v2",
"apiVersion": "2012-12-31",
"description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
"operations": {
"list_groups": {
"httpMethod":"GET",
"uri": "groups.json",
"summary": "Get a list of groups"
},
"search_groups":{
"httpMethod":"GET",
"uri": "search.json?query=\"{query} type:group\"",
"summary": "Uses a search query to get a list of groups",
"parameters":{
"query":{
"location": "uri",
"description":"Zendesk Search Query",
"type": "string",
"required": true
}
}
},
"create_group": {
"httpMethod":"POST",
"uri": "groups.json",
"summary": "Create a group",
"parameters":{
"data": {
"type": "array",
"location": "body",
"description":"Group JSON",
"filters": "json_encode",
"required": true
},
"Content-Type":{
"type": "string",
"location":"header",
"static": "application/json"
}
}
},
"delete_group": {
"httpMethod":"DELETE",
"uri": "groups/{id}.json",
"summary": "Delete a group",
"parameters":{
"id":{
"location": "uri",
"description":"Group to delete by ID",
"type": "integer",
"required": true
}
}
},
"get_group": {
"httpMethod":"GET",
"uri": "groups/{id}.json",
"summary": "Get a ticket",
"parameters":{
"id":{
"location": "uri",
"description":"Group to get by ID",
"type": "integer",
"required": true
}
}
},
"update_group": {
"httpMethod":"PUT",
"uri": "groups/{id}.json",
"summary": "Update a group",
"parameters":{
"id": {
"location": "uri",
"description":"Group to update by ID",
"type": "integer",
"required": true
},
"data": {
"type": "array",
"location": "body",
"description":"Group JSON",
"filters": "json_encode",
"required": true
},
"Content-Type":{
"type": "string",
"location":"header",
"static": "application/json"
}
}
}
}
```
### Guzzle\Service\Description\ServiceDescription
Commands are now called Operations
**Before**
```php
use Guzzle\Service\Description\ServiceDescription;
$sd = new ServiceDescription();
$sd->getCommands(); // @returns ApiCommandInterface[]
$sd->hasCommand($name);
$sd->getCommand($name); // @returns ApiCommandInterface|null
$sd->addCommand($command); // @param ApiCommandInterface $command
```
**After**
```php
use Guzzle\Service\Description\ServiceDescription;
$sd = new ServiceDescription();
$sd->getOperations(); // @returns OperationInterface[]
$sd->hasOperation($name);
$sd->getOperation($name); // @returns OperationInterface|null
$sd->addOperation($operation); // @param OperationInterface $operation
```
### Guzzle\Common\Inflection\Inflector
Namespace is now `Guzzle\Inflection\Inflector`
### Guzzle\Http\Plugin
Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
**Before**
```php
use Guzzle\Common\Log\ClosureLogAdapter;
use Guzzle\Http\Plugin\LogPlugin;
/** @var \Guzzle\Http\Client */
$client;
// $verbosity is an integer indicating desired message verbosity level
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
```
**After**
```php
use Guzzle\Log\ClosureLogAdapter;
use Guzzle\Log\MessageFormatter;
use Guzzle\Plugin\Log\LogPlugin;
/** @var \Guzzle\Http\Client */
$client;
// $format is a string indicating desired message format -- @see MessageFormatter
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
```
### Guzzle\Http\Plugin\CurlAuthPlugin
Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
### Guzzle\Http\Plugin\ExponentialBackoffPlugin
Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
**Before**
```php
use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
));
$client->addSubscriber($backoffPlugin);
```
**After**
```php
use Guzzle\Plugin\Backoff\BackoffPlugin;
use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
// Use convenient factory method instead -- see implementation for ideas of what
// you can do with chaining backoff strategies
$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
));
$client->addSubscriber($backoffPlugin);
```
### Known Issues
#### [BUG] Accept-Encoding header behavior changed unintentionally.
(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
See issue #217 for a workaround, or use a version containing the fix.

54
vendor/guzzle/guzzle/build.xml vendored Normal file
View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="guzzle" default="test">
<!-- set local values, like git location -->
<property file="phing/build.properties" override="true" />
<property name="dir.output" value="${project.basedir}/build/artifacts" />
<property name="dir.imports" value="${project.basedir}/phing/imports" />
<property name="dir.bin" value="${project.basedir}/bin" />
<property name="repo.dir" value="${project.basedir}" />
<import file="${dir.imports}/dependencies.xml"/>
<import file="${dir.imports}/test.xml"/>
<import file="${dir.imports}/deploy.xml"/>
<!-- <import file="${dir.imports}/metrics.xml"/> -->
<target name="test" description="Run unit tests" depends="test-init">
<trycatch>
<try>
<exec passthru="true" command="vendor/bin/phpunit" checkReturn="true" />
</try>
<finally>
<testserver cmd="${cmd.testserver}" action="stop" />
</finally>
</trycatch>
</target>
<target name="test-init" depends="install-dependencies" description="Initialize test dependencies">
<testserver cmd="${cmd.testserver}" action="start" />
<copy file="phpunit.xml.dist" tofile="phpunit.xml" overwrite="false" />
</target>
<target name="build-init" description="Initialize local phing properties">
<copy file="phing/build.properties.dist" tofile="phing/build.properties" overwrite="false" />
</target>
<target name="clean">
<delete dir="${dir.output}"/>
<delete dir="${project.basedir}/build/pearwork"/>
</target>
<target name="prepare" depends="clean,test-init,build-init">
<mkdir dir="${dir.output}"/>
<mkdir dir="${dir.output}/logs" />
</target>
<target name="coverage" depends="prepare">
<exec passthru="true" command="vendor/bin/phpunit --coverage-html=${dir.output}/coverage" />
</target>
<target name="view-coverage">
<exec passthru="true" command="open ${dir.output}/coverage/index.html" />
</target>
</project>

74
vendor/guzzle/guzzle/composer.json vendored Normal file
View File

@ -0,0 +1,74 @@
{
"name": "guzzle/guzzle",
"type": "library",
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
"homepage": "http://guzzlephp.org/",
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Guzzle Community",
"homepage": "https://github.com/guzzle/guzzle/contributors"
}
],
"replace": {
"guzzle/batch": "self.version",
"guzzle/cache": "self.version",
"guzzle/common": "self.version",
"guzzle/http": "self.version",
"guzzle/inflection": "self.version",
"guzzle/iterator": "self.version",
"guzzle/log": "self.version",
"guzzle/parser": "self.version",
"guzzle/plugin": "self.version",
"guzzle/plugin-async": "self.version",
"guzzle/plugin-backoff": "self.version",
"guzzle/plugin-cache": "self.version",
"guzzle/plugin-cookie": "self.version",
"guzzle/plugin-curlauth": "self.version",
"guzzle/plugin-error-response": "self.version",
"guzzle/plugin-history": "self.version",
"guzzle/plugin-log": "self.version",
"guzzle/plugin-md5": "self.version",
"guzzle/plugin-mock": "self.version",
"guzzle/plugin-oauth": "self.version",
"guzzle/service": "self.version",
"guzzle/stream": "self.version"
},
"require": {
"php": ">=5.3.2",
"ext-curl": "*",
"symfony/event-dispatcher": ">=2.1"
},
"autoload": {
"psr-0": {
"Guzzle\\Tests": "tests/",
"Guzzle": "src/"
}
},
"require-dev": {
"doctrine/cache": "*",
"symfony/class-loader": "*",
"monolog/monolog": "1.*",
"psr/log": "1.0.*",
"zendframework/zend-cache": "2.0.*",
"zendframework/zend-log": "2.0.*",
"phpunit/phpunit": "3.7.*"
},
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

16
vendor/guzzle/guzzle/phar-stub.php vendored Normal file
View File

@ -0,0 +1,16 @@
<?php
Phar::mapPhar('guzzle.phar');
require_once 'phar://guzzle.phar/vendor/symfony/class-loader/Symfony/Component/ClassLoader/UniversalClassLoader.php';
$classLoader = new Symfony\Component\ClassLoader\UniversalClassLoader();
$classLoader->registerNamespaces(array(
'Guzzle' => 'phar://guzzle.phar/src',
'Symfony\\Component\\EventDispatcher' => 'phar://guzzle.phar/vendor/symfony/event-dispatcher',
'Doctrine' => 'phar://guzzle.phar/vendor/doctrine/common/lib',
'Monolog' => 'phar://guzzle.phar/vendor/monolog/monolog/src'
));
$classLoader->register();
__HALT_COMPILER();

View File

@ -0,0 +1,16 @@
# you may need to update this if you're working on a fork.
guzzle.remote=git@github.com:guzzle/guzzle.git
# github credentials -- only used by GitHub API calls to create subtree repos
github.basicauth=username:password
# for the subtree split and testing
github.org=guzzle
# your git path
cmd.git=git
# your composer command
cmd.composer=composer
# test server start
cmd.testserver=node

View File

@ -0,0 +1,16 @@
# update this if you're working on a fork.
guzzle.remote=git@github.com:guzzle/guzzle.git
# github credentials -- only used by GitHub API calls to create subtree repos
github.basicauth=username:password
# for the subtree split and testing
github.org=guzzle
# your git path
cmd.git=/usr/bin/git
# your composer command
cmd.composer=composer
# test server start
cmd.testserver="~/.nvm/nvm.sh run v0.6.14"

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="../../" default="install-dependencies">
<property name="cmd.composer" value="" />
<property name="cmd.git" value="" />
<property name="cmd.testserver" value="" />
<!--
Our custom tasks
-->
<taskdef name="composerlint" classname="phing.tasks.ComposerLintTask" />
<taskdef name="testserver" classname="phing.tasks.NodeServerTask" />
<taskdef name="guzzlesubsplit" classname="phing.tasks.GuzzleSubSplitTask" />
<taskdef name="guzzlepear" classname="phing.tasks.GuzzlePearPharPackageTask" />
<!--
Handle locating composer (global) or composer.phar, and
if necessary, installing composer.phar locally.
-->
<target name="install-composer" description="Installs composer locally if it can't be found in build.properties or global install.">
<if>
<contains string="${cmd.composer}" substring="composer" />
<then>
<echo>Using composer at ${cmd.composer}</echo>
</then>
<else>
<exec command="which composer" outputProperty="cmd.composer" />
<if>
<contains string="${cmd.composer}" substring="composer" />
<then>
<echo>Using composer at ${cmd.composer}</echo>
</then>
<elseif>
<available file="${project.basedir}/composer.phar" />
<then>
<echo>Composer is installed locally</echo>
<property name="cmd.composer" value="${php.interpreter} ${project.basedir}/composer.phar" override="true"/>
</then>
</elseif>
<else>
<echo message="Installing composer locally" />
<exec command="curl -s http://getcomposer.org/installer | php" passthru="true" />
<property name="cmd.composer" value="${php.interpreter} ${project.basedir}/composer.phar" override="true"/>
</else>
</if>
</else>
</if>
<echo message="cmd.composer is ${cmd.composer}"/>
</target>
<target name="find-git">
<if>
<contains string="${cmd.git}" substring="git" />
<then>
<echo>using git at ${cmd.git}</echo>
</then>
<else>
<exec command="which git" outputProperty="cmd.git" />
<echo>found git at ${cmd.git}</echo>
</else>
</if>
</target>
<target name="install-dependencies" depends="install-composer">
<exec command="${cmd.composer} install --dev" passthru="true" />
</target>
<target name="update-dependencies" depends="install-composer">
<exec command="${cmd.composer} update --dev" passthru="true" />
</target>
<target name="clean-dependencies">
<delete dir="${project.basedir}/vendor"/>
<delete file="${project.basedir}/composer.lock" />
</target>
</project>

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="../../" default="deploy">
<property name="git.status" value=""/>
<property name="git.currentbranch" value=""/>
<target name="check-git-branch-status">
<exec command="git status -s -b" outputProperty="git.currentbranch" />
<if>
<equals arg1="${git.currentbranch}" arg2="## ${head}" trim="true"/>
<then>
<echo>On branch ${head}</echo>
</then>
<else>
<fail message="-Dhead=${head} arg did not match ${git.currentbranch}"/>
</else>
</if>
<exec command="git status -s" outputProperty="git.status" />
<if>
<equals arg1="${git.status}" arg2="" trim="true"/>
<then>
<echo>working directory clean</echo>
</then>
<else>
<echo>${git.status}</echo>
<fail message="Working directory isn't clean." />
</else>
</if>
</target>
<property name="version.changelog" value=""/>
<property name="version.version" value=""/>
<target name="check-changelog-version">
<exec executable="fgrep" outputProperty="version.changelog">
<arg value="${new.version} ("/>
<arg value="${project.basedir}/CHANGELOG.md"/>
</exec>
<if>
<equals arg1="${version.changelog}" arg2="" trim="true"/>
<then>
<fail message="${new.version} not mentioned in CHANGELOG"/>
</then>
</if>
<exec executable="fgrep" outputProperty="version.version">
<arg value="const VERSION = '${new.version}'"/>
<arg value="${project.basedir}/src/Guzzle/Common/Version.php"/>
</exec>
<if>
<equals arg1="${version.version}" arg2="" trim="true"/>
<then>
<fail message="${new.version} not mentioned in Guzzle\Common\Version"/>
</then>
</if>
<echo>ChangeLog Match: ${version.changelog}</echo>
<echo>Guzzle\Common\Version Match: ${version.version}</echo>
</target>
<target name="help" description="HELP AND REMINDERS about what you can do with this project">
<echo>releasing: phing -Dnew.version=3.0.x -Dhead=master release</echo>
<echo>--</echo>
<exec command="phing -l" passthru="true"/>
</target>
<target name="release" depends="check-changelog-version,check-git-branch-status"
description="tag, subtree split, package, deploy: Use: phing -Dnew.version=[TAG] -Dhead=[BRANCH] release">
<if>
<isset property="new.version" />
<then>
<if>
<contains string="${new.version}" substring="v" casesensitive="false" />
<then>
<fail message="Please specify version as [0-9].[0-9].[0-9]. (I'll add v for you.)"/>
</then>
<else>
<echo>BEGINNING RELEASE FOR ${new.version}</echo>
<!-- checkout the specified branch -->
<!-- <gitcheckout repository="${repo.dir}" branchname="${head}" gitPath="${cmd.git}" /> -->
<!-- Ensure that the tag exists -->
<!-- push the tag up so subsplit will get it -->
<!--gitpush repository="${repo.dir}" tags="true" gitPath="${cmd.git}" /-->
<!-- now do the subsplits -->
<guzzlesubsplit
repository="${repo.dir}"
remote="${guzzle.remote}"
heads="${head}"
tags="v${new.version}"
base="src"
subIndicatorFile="composer.json"
gitPath="${cmd.git}" />
<!-- Copy .md files into the PEAR package -->
<copy file="${repo.dir}/LICENSE" tofile=".subsplit/src/Guzzle/LICENSE.md" />
<copy file="${repo.dir}/README.md" tofile=".subsplit/src/Guzzle/README.md" />
<copy file="${repo.dir}/CHANGELOG.md" tofile=".subsplit/src/Guzzle/CHANGELOG.md" />
<!-- and now the pear packages -->
<guzzlepear
version="${new.version}"
makephar="true"
/>
</else>
</if>
</then>
<else>
<echo>Tip: to create a new release, do: phing -Dnew.version=[TAG] -Dhead=[BRANCH] release</echo>
</else>
</if>
</target>
<target name="peartest">
<guzzlepear
version="${new.version}"
deploy="false"
makephar="false"
/>
</target>
<target name="package-phar" depends="test-init" description="Create a phar with an autoloader">
<pharpackage
destfile="${dir.output}/guzzle.phar"
basedir="${project.basedir}/.subsplit"
stub="phar-stub.php"
signature="md5">
<fileset dir="${project.basedir}/.subsplit">
<include name="src/**/*.php" />
<include name="src/**/*.pem" />
<include name="src/**/*.md5" />
<include name="vendor/symfony/class-loader/Symfony/Component/ClassLoader/UniversalClassLoader.php" />
<include name="vendor/symfony/event-dispatcher/**/*.php" />
<include name="vendor/doctrine/common/lib/Doctrine/Common/Cache/*.php" />
<include name="vendor/monolog/monolog/src/**/*.php" />
</fileset>
<metadata>
<element name="author" value="Michael Dowling" />
</metadata>
</pharpackage>
<exec command="php -d guzzle_phar=${dir.output}/guzzle.phar `which phpunit`" passthru="true" />
</target>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="../../" default="metrics">
<target name="metrics" depends="phploc,pdepend"/>
<target name="phploc" description="Measure project size using PHPLOC">
<exec command="phploc --log-csv ${dir.output}/logs/phploc.csv ${project.basedir}/src" passthru="true"/>
</target>
<target name="pdepend" description="Calculate software metrics using PHP_Depend">
<mkdir dir="${dir.output}/pdepend" />
<phpdepend>
<fileset dir="${project.basedir}/src">
<include name="**/*.php" />
</fileset>
<logger type="jdepend-xml" outfile="${dir.output}/logs/jdepend.xml" />
<logger type="jdepend-chart" outfile="${dir.output}/pdepend/dependencies.svg" />
<logger type="overview-pyramid" outfile="${dir.output}/pdepend/overview-pyramid.svg" />
<analyzer type="coderank-mode" value="method" />
</phpdepend>
</target>
</project>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="../../" default="preflight">
<!-- instead of phingcall sub-tasks, use an empty target with deps -->
<target name="preflight" depends="test-init,lint"/>
<target name="travisci" depends="install-dependencies,composer-lint,php-lint,test"/>
<target name="lint" depends="install-composer,composer-lint,php-lint" description="lint-check PHP and JSON" />
<target name="composer-lint" description="lint-check composer.json only">
<composerlint dir="${project.basedir}/src" file="{$project.basedir}/composer.json" />
</target>
<target name="php-lint" description="lint-check just the PHP source">
<phplint level="info">
<fileset dir="${project.basedir}/src">
<include name="**/*.php" />
</fileset>
</phplint>
<phplint level="info">
<fileset dir="${project.basedir}/tests">
<include name="**/*.php" />
</fileset>
</phplint>
</target>
<target name="sniff" description="Check code for PSR-2 compliance">
<phpcodesniffer
standard="PSR2"
format="summary" />
</target>
</project>

View File

@ -0,0 +1,152 @@
<?php
/**
* Phing task for composer validation.
*
* @copyright 2012 Clay Loveless <clay@php.net>
* @license http://claylo.mit-license.org/2012/ MIT License
*/
require_once 'phing/Task.php';
class ComposerLintTask extends Task
{
protected $dir = null;
protected $file = null;
protected $passthru = false;
protected $composer = null;
/**
* The setter for the dir
*
* @param string $str Directory to crawl recursively for composer files
*/
public function setDir($str)
{
$this->dir = $str;
}
/**
* The setter for the file
*
* @param string $str Individual file to validate
*/
public function setFile($str)
{
$this->file = $str;
}
/**
* Whether to use PHP's passthru() function instead of exec()
*
* @param boolean $passthru If passthru shall be used
*/
public function setPassthru($passthru)
{
$this->passthru = (bool) $passthru;
}
/**
* Composer to execute. If unset, will attempt composer.phar in project
* basedir, and if that fails, will attempt global composer
* installation.
*
* @param string $str Individual file to validate
*/
public function setComposer($str)
{
$this->file = $str;
}
/**
* The init method: do init steps
*/
public function init()
{
// nothing needed here
}
/**
* The main entry point
*/
public function main()
{
if ($this->composer === null) {
$this->findComposer();
}
$files = array();
if (!empty($this->file) && file_exists($this->file)) {
$files[] = $this->file;
}
if (!empty($this->dir)) {
$found = $this->findFiles();
foreach ($found as $file) {
$files[] = $this->dir . DIRECTORY_SEPARATOR . $file;
}
}
foreach ($files as $file) {
$cmd = $this->composer . ' validate ' . $file;
$cmd = escapeshellcmd($cmd);
if ($this->passthru) {
$retval = null;
passthru($cmd, $retval);
if ($retval == 1) {
throw new BuildException('invalid composer.json');
}
} else {
$out = array();
$retval = null;
exec($cmd, $out, $retval);
if ($retval == 1) {
$err = join("\n", $out);
throw new BuildException($err);
} else {
$this->log($out[0]);
}
}
}
}
/**
* Find the composer.json files using Phing's directory scanner
*
* @return array
*/
protected function findFiles()
{
$ds = new DirectoryScanner();
$ds->setBasedir($this->dir);
$ds->setIncludes(array('**/composer.json'));
$ds->scan();
return $ds->getIncludedFiles();
}
/**
* Find composer installation
*
*/
protected function findComposer()
{
$basedir = $this->project->getBasedir();
$php = $this->project->getProperty('php.interpreter');
if (file_exists($basedir . '/composer.phar')) {
$this->composer = "$php $basedir/composer.phar";
} else {
$out = array();
exec('which composer', $out);
if (empty($out)) {
throw new BuildException(
'Could not determine composer location.'
);
}
$this->composer = $out[0];
}
}
}

View File

@ -0,0 +1,339 @@
<?php
/**
* This file is part of Guzzle's build process.
*
* @copyright 2012 Clay Loveless <clay@php.net>
* @license http://claylo.mit-license.org/2012/ MIT License
*/
require_once 'phing/Task.php';
require_once 'PEAR/PackageFileManager2.php';
require_once 'PEAR/PackageFileManager/File.php';
require_once 'PEAR/Packager.php';
class GuzzlePearPharPackageTask extends Task
{
private $dir;
private $version;
private $deploy = true;
private $makephar = true;
private $subpackages = array();
public function setVersion($str)
{
$this->version = $str;
}
public function getVersion()
{
return $this->version;
}
public function setDeploy($deploy)
{
$this->deploy = (bool) $deploy;
}
public function getDeploy()
{
return $this->deploy;
}
public function setMakephar($makephar)
{
$this->makephar = (bool) $makephar;
}
public function getMakephar()
{
return $this->makephar;
}
private $basedir;
private $guzzleinfo;
private $changelog_release_date;
private $changelog_notes = '-';
public function main()
{
$this->basedir = $this->getProject()->getBasedir();
if (!is_dir((string) $this->basedir.'/.subsplit')) {
throw new BuildException('PEAR packaging requires .subsplit directory');
}
// main composer file
$composer_file = file_get_contents((string) $this->basedir.'/.subsplit/composer.json');
$this->guzzleinfo = json_decode($composer_file, true);
// make sure we have a target
$pearwork = (string) $this->basedir . '/build/pearwork';
if (!is_dir($pearwork)) {
mkdir($pearwork, 0777, true);
}
$pearlogs = (string) $this->basedir . '/build/artifacts/logs';
if (!is_dir($pearlogs)) {
mkdir($pearlogs, 0777, true);
}
$version = $this->getVersion();
$this->grabChangelog();
if ($version[0] == '2') {
$this->log('building single PEAR package');
$this->buildSinglePackage();
} else {
// $this->log("building PEAR subpackages");
// $this->createSubPackages();
// $this->log("building PEAR bundle package");
$this->buildSinglePackage();
}
if ($this->getMakephar()) {
$this->log("building PHAR");
$this->getProject()->executeTarget('package-phar');
}
if ($this->getDeploy()) {
$this->doDeployment();
}
}
public function doDeployment()
{
$basedir = (string) $this->basedir;
$this->log('beginning PEAR/PHAR deployment');
chdir($basedir . '/build/pearwork');
if (is_dir($basedir . '/build/pearwork/guzzle.github.com')) {
exec('rm -rf guzzle.github.com');
}
passthru('git clone git@github.com:guzzle/guzzle.github.com');
// add PEAR packages
foreach (scandir($basedir . '/build/pearwork') as $file) {
if (substr($file, -4) == '.tgz') {
passthru('pirum add guzzle.github.com/pear '.$file);
}
}
// if we have a new phar, add it
if ($this->getMakephar() && file_exists($basedir.'/build/artifacts/guzzle.phar')) {
rename($basedir.'/build/artifacts/guzzle.phar', $basedir.'/build/pearwork/guzzle.github.com/guzzle.phar');
}
// add and commit
chdir($basedir . '/build/pearwork/guzzle.github.com');
passthru('git add --all .');
passthru('git commit -m "Pushing PEAR/PHAR release for '.$this->getVersion().'" && git push');
}
public function buildSinglePackage()
{
$v = $this->getVersion();
$apiversion = $v[0] . '.0.0';
$opts = array(
'packagedirectory' => (string) $this->basedir . '/.subsplit/src/',
'filelistgenerator' => 'file',
'ignore' => array('*composer.json'),
'baseinstalldir' => '/',
'packagefile' => 'package.xml'
//'outputdirectory' => (string) $this->basedir . '/build/pearwork/'
);
$pfm = new PEAR_PackageFileManager2();
$e = $pfm->setOptions($opts);
$pfm->addRole('md', 'doc');
$pfm->addRole('pem', 'php');
$pfm->setPackage('Guzzle');
$pfm->setSummary("Object-oriented PHP HTTP Client for PHP 5.3+");
$pfm->setDescription($this->guzzleinfo['description']);
$pfm->setPackageType('php');
$pfm->setChannel('guzzlephp.org/pear');
$pfm->setAPIVersion($apiversion);
$pfm->setReleaseVersion($this->getVersion());
$pfm->setAPIStability('stable');
$pfm->setReleaseStability('stable');
$pfm->setNotes($this->changelog_notes);
$pfm->setPackageType('php');
$pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE');
$pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes');
$pfm->setDate($this->changelog_release_date);
$pfm->generateContents();
$phpdep = $this->guzzleinfo['require']['php'];
$phpdep = str_replace('>=', '', $phpdep);
$pfm->setPhpDep($phpdep);
$pfm->addExtensionDep('required', 'curl');
$pfm->setPearinstallerDep('1.4.6');
$pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0');
if (!empty($this->subpackages)) {
foreach ($this->subpackages as $package) {
$pkg = dirname($package);
$pkg = str_replace('/', '_', $pkg);
$pfm->addConflictingPackageDepWithChannel($pkg, 'guzzlephp.org/pear', false, $apiversion);
}
}
ob_start();
$startdir = getcwd();
chdir((string) $this->basedir . '/build/pearwork');
echo "DEBUGGING GENERATED PACKAGE FILE\n";
$result = $pfm->debugPackageFile();
if ($result) {
$out = $pfm->writePackageFile();
echo "\n\n\nWRITE PACKAGE FILE RESULT:\n";
var_dump($out);
// load up package file and build package
$packager = new PEAR_Packager();
echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n";
$dest_package = $packager->package($opts['packagedirectory'].'package.xml');
var_dump($dest_package);
} else {
echo "\n\n\nDEBUGGING RESULT:\n";
var_dump($result);
}
echo "removing package.xml";
unlink($opts['packagedirectory'].'package.xml');
$log = ob_get_clean();
file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package.log', $log);
chdir($startdir);
}
public function createSubPackages()
{
$version = $this->getVersion();
$this->findComponents();
foreach ($this->subpackages as $package) {
$baseinstalldir = dirname($package);
$dir = (string) $this->basedir.'/.subsplit/src/' . $baseinstalldir;
$composer_file = file_get_contents((string) $this->basedir.'/.subsplit/src/'. $package);
$package_info = json_decode($composer_file, true);
$this->log('building ' . $package_info['target-dir'] . ' subpackage');
$this->buildSubPackage($dir, $baseinstalldir, $package_info);
}
}
public function buildSubPackage($dir, $baseinstalldir, $info)
{
$package = str_replace('/', '_', $baseinstalldir);
$opts = array(
'packagedirectory' => $dir,
'filelistgenerator' => 'file',
'ignore' => array('*composer.json', '*package.xml'),
'baseinstalldir' => '/' . $info['target-dir'],
'packagefile' => 'package.xml'
);
$pfm = new PEAR_PackageFileManager2();
$e = $pfm->setOptions($opts);
$pfm->setPackage($package);
$pfm->setSummary($info['description']);
$pfm->setDescription($info['description']);
$pfm->setPackageType('php');
$pfm->setChannel('guzzlephp.org/pear');
$pfm->setAPIVersion('3.0.0');
$pfm->setReleaseVersion($this->getVersion());
$pfm->setAPIStability('stable');
$pfm->setReleaseStability('stable');
$pfm->setNotes($this->changelog_notes);
$pfm->setPackageType('php');
$pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE');
$pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes');
$pfm->setDate($this->changelog_release_date);
$pfm->generateContents();
$phpdep = $this->guzzleinfo['require']['php'];
$phpdep = str_replace('>=', '', $phpdep);
$pfm->setPhpDep($phpdep);
$pfm->setPearinstallerDep('1.4.6');
foreach ($info['require'] as $type => $version) {
if ($type == 'php') {
continue;
}
if ($type == 'symfony/event-dispatcher') {
$pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0');
}
if ($type == 'ext-curl') {
$pfm->addExtensionDep('required', 'curl');
}
if (substr($type, 0, 6) == 'guzzle') {
$gdep = str_replace('/', ' ', $type);
$gdep = ucwords($gdep);
$gdep = str_replace(' ', '_', $gdep);
$pfm->addPackageDepWithChannel('required', $gdep, 'guzzlephp.org/pear', $this->getVersion());
}
}
// can't have main Guzzle package AND sub-packages
$pfm->addConflictingPackageDepWithChannel('Guzzle', 'guzzlephp.org/pear', false, $apiversion);
ob_start();
$startdir = getcwd();
chdir((string) $this->basedir . '/build/pearwork');
echo "DEBUGGING GENERATED PACKAGE FILE\n";
$result = $pfm->debugPackageFile();
if ($result) {
$out = $pfm->writePackageFile();
echo "\n\n\nWRITE PACKAGE FILE RESULT:\n";
var_dump($out);
// load up package file and build package
$packager = new PEAR_Packager();
echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n";
$dest_package = $packager->package($opts['packagedirectory'].'/package.xml');
var_dump($dest_package);
} else {
echo "\n\n\nDEBUGGING RESULT:\n";
var_dump($result);
}
echo "removing package.xml";
unlink($opts['packagedirectory'].'/package.xml');
$log = ob_get_clean();
file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package_'.$package.'.log', $log);
chdir($startdir);
}
public function findComponents()
{
$ds = new DirectoryScanner();
$ds->setBasedir((string) $this->basedir.'/.subsplit/src');
$ds->setIncludes(array('**/composer.json'));
$ds->scan();
$files = $ds->getIncludedFiles();
$this->subpackages = $files;
}
public function grabChangelog()
{
$cl = file((string) $this->basedir.'/.subsplit/CHANGELOG.md');
$notes = '';
$in_version = false;
$release_date = null;
foreach ($cl as $line) {
$line = trim($line);
if (preg_match('/^\* '.$this->getVersion().' \(([0-9\-]+)\)$/', $line, $matches)) {
$release_date = $matches[1];
$in_version = true;
continue;
}
if ($in_version && empty($line) && empty($notes)) {
continue;
}
if ($in_version && ! empty($line)) {
$notes .= $line."\n";
}
if ($in_version && empty($line) && !empty($notes)) {
$in_version = false;
}
}
$this->changelog_release_date = $release_date;
if (! empty($notes)) {
$this->changelog_notes = $notes;
}
}
}

View File

@ -0,0 +1,385 @@
<?php
/**
* Phing wrapper around git subsplit.
*
* @see https://github.com/dflydev/git-subsplit
* @copyright 2012 Clay Loveless <clay@php.net>
* @license http://claylo.mit-license.org/2012/ MIT License
*/
require_once 'phing/tasks/ext/git/GitBaseTask.php';
// base - base of tree to split out
// subIndicatorFile - composer.json, package.xml?
class GuzzleSubSplitTask extends GitBaseTask
{
/**
* What git repository to pull from and publish to
*/
protected $remote = null;
/**
* Publish for comma-separated heads instead of all heads
*/
protected $heads = null;
/**
* Publish for comma-separated tags instead of all tags
*/
protected $tags = null;
/**
* Base of the tree RELATIVE TO .subsplit working dir
*/
protected $base = null;
/**
* The presence of this file will indicate that the directory it resides
* in is at the top level of a split.
*/
protected $subIndicatorFile = 'composer.json';
/**
* Do everything except actually send the update.
*/
protected $dryRun = null;
/**
* Do not sync any heads.
*/
protected $noHeads = false;
/**
* Do not sync any tags.
*/
protected $noTags = false;
/**
* The splits we found in the heads
*/
protected $splits;
public function setRemote($str)
{
$this->remote = $str;
}
public function getRemote()
{
return $this->remote;
}
public function setHeads($str)
{
$this->heads = explode(',', $str);
}
public function getHeads()
{
return $this->heads;
}
public function setTags($str)
{
$this->tags = explode(',', $str);
}
public function getTags()
{
return $this->tags;
}
public function setBase($str)
{
$this->base = $str;
}
public function getBase()
{
return $this->base;
}
public function setSubIndicatorFile($str)
{
$this->subIndicatorFile = $str;
}
public function getSubIndicatorFile()
{
return $this->subIndicatorFile;
}
public function setDryRun($bool)
{
$this->dryRun = (bool) $bool;
}
public function getDryRun()
{
return $this->dryRun;
}
public function setNoHeads($bool)
{
$this->noHeads = (bool) $bool;
}
public function getNoHeads()
{
return $this->noHeads;
}
public function setNoTags($bool)
{
$this->noTags = (bool) $bool;
}
public function getNoTags()
{
return $this->noTags;
}
/**
* GitClient from VersionControl_Git
*/
protected $client = null;
/**
* The main entry point
*/
public function main()
{
$repo = $this->getRepository();
if (empty($repo)) {
throw new BuildException('"repository" is a required parameter');
}
$remote = $this->getRemote();
if (empty($remote)) {
throw new BuildException('"remote" is a required parameter');
}
chdir($repo);
$this->client = $this->getGitClient(false, $repo);
// initalized yet?
if (!is_dir('.subsplit')) {
$this->subsplitInit();
} else {
// update
$this->subsplitUpdate();
}
// find all splits based on heads requested
$this->findSplits();
// check that GitHub has the repos
$this->verifyRepos();
// execute the subsplits
$this->publish();
}
public function publish()
{
$this->log('DRY RUN ONLY FOR NOW');
$base = $this->getBase();
$base = rtrim($base, '/') . '/';
$org = $this->getOwningTarget()->getProject()->getProperty('github.org');
$splits = array();
$heads = $this->getHeads();
foreach ($heads as $head) {
foreach ($this->splits[$head] as $component => $meta) {
$splits[] = $base . $component . ':git@github.com:'. $org.'/'.$meta['repo'];
}
$cmd = 'git subsplit publish ';
$cmd .= escapeshellarg(implode(' ', $splits));
if ($this->getNoHeads()) {
$cmd .= ' --no-heads';
} else {
$cmd .= ' --heads='.$head;
}
if ($this->getNoTags()) {
$cmd .= ' --no-tags';
} else {
if ($this->getTags()) {
$cmd .= ' --tags=' . escapeshellarg(implode(' ', $this->getTags()));
}
}
passthru($cmd);
}
}
/**
* Runs `git subsplit update`
*/
public function subsplitUpdate()
{
$repo = $this->getRepository();
$this->log('git-subsplit update...');
$cmd = $this->client->getCommand('subsplit');
$cmd->addArgument('update');
try {
$output = $cmd->execute();
} catch (Exception $e) {
throw new BuildException('git subsplit update failed'. $e);
}
chdir($repo . '/.subsplit');
passthru('php ../composer.phar update --dev');
chdir($repo);
}
/**
* Runs `git subsplit init` based on the remote repository.
*/
public function subsplitInit()
{
$remote = $this->getRemote();
$cmd = $this->client->getCommand('subsplit');
$this->log('running git-subsplit init ' . $remote);
$cmd->setArguments(array(
'init',
$remote
));
try {
$output = $cmd->execute();
} catch (Exception $e) {
throw new BuildException('git subsplit init failed'. $e);
}
$this->log(trim($output), Project::MSG_INFO);
$repo = $this->getRepository();
chdir($repo . '/.subsplit');
passthru('php ../composer.phar install --dev');
chdir($repo);
}
/**
* Find the composer.json files using Phing's directory scanner
*
* @return array
*/
protected function findSplits()
{
$this->log("checking heads for subsplits");
$repo = $this->getRepository();
$base = $this->getBase();
$splits = array();
$heads = $this->getHeads();
if (!empty($base)) {
$base = '/' . ltrim($base, '/');
} else {
$base = '/';
}
chdir($repo . '/.subsplit');
foreach ($heads as $head) {
$splits[$head] = array();
// check each head requested *BEFORE* the actual subtree split command gets it
passthru("git checkout '$head'");
$ds = new DirectoryScanner();
$ds->setBasedir($repo . '/.subsplit' . $base);
$ds->setIncludes(array('**/'.$this->subIndicatorFile));
$ds->scan();
$files = $ds->getIncludedFiles();
// Process the files we found
foreach ($files as $file) {
$pkg = file_get_contents($repo . '/.subsplit' . $base .'/'. $file);
$pkg_json = json_decode($pkg, true);
$name = $pkg_json['name'];
$component = str_replace('/composer.json', '', $file);
// keep this for split cmd
$tmpreponame = explode('/', $name);
$reponame = $tmpreponame[1];
$splits[$head][$component]['repo'] = $reponame;
$nscomponent = str_replace('/', '\\', $component);
$splits[$head][$component]['desc'] = "[READ ONLY] Subtree split of $nscomponent: " . $pkg_json['description'];
}
}
// go back to how we found it
passthru("git checkout master");
chdir($repo);
$this->splits = $splits;
}
/**
* Based on list of repositories we determined we *should* have, talk
* to GitHub and make sure they're all there.
*
*/
protected function verifyRepos()
{
$this->log('verifying GitHub target repos');
$github_org = $this->getOwningTarget()->getProject()->getProperty('github.org');
$github_creds = $this->getOwningTarget()->getProject()->getProperty('github.basicauth');
if ($github_creds == 'username:password') {
$this->log('Skipping GitHub repo checks. Update github.basicauth in build.properties to verify repos.', 1);
return;
}
$ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos?type=all');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERPWD, $github_creds);
// change this when we know we can use our bundled CA bundle!
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($ch);
curl_close($ch);
$repos = json_decode($result, true);
$existing_repos = array();
// parse out the repos we found on GitHub
foreach ($repos as $repo) {
$tmpreponame = explode('/', $repo['full_name']);
$reponame = $tmpreponame[1];
$existing_repos[$reponame] = $repo['description'];
}
$heads = $this->getHeads();
foreach ($heads as $head) {
foreach ($this->splits[$head] as $component => $meta) {
$reponame = $meta['repo'];
if (!isset($existing_repos[$reponame])) {
$this->log("Creating missing repo $reponame");
$payload = array(
'name' => $reponame,
'description' => $meta['desc'],
'homepage' => 'http://www.guzzlephp.org/',
'private' => true,
'has_issues' => false,
'has_wiki' => false,
'has_downloads' => true,
'auto_init' => false
);
$ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERPWD, $github_creds);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
// change this when we know we can use our bundled CA bundle!
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($ch);
echo "Response code: ".curl_getinfo($ch, CURLINFO_HTTP_CODE)."\n";
curl_close($ch);
} else {
$this->log("Repo $reponame exists", 2);
}
}
}
}
}

View File

@ -0,0 +1,118 @@
<?php
/**
* Phing task for node server checking
*
* @copyright 2012 Clay Loveless <clay@php.net>
* @license http://claylo.mit-license.org/2012/ MIT License
*/
require_once 'phing/Task.php';
class NodeServerTask extends Task
{
protected $cmd = null;
protected $action = 'start';
protected $serverfile = 'tests/Guzzle/Tests/Http/server.js';
/**
* The setter for the start command
*
* @param string $str How to start the node server
*/
public function setCmd($str)
{
$this->cmd = $str;
}
public function getCmd()
{
return $this->cmd;
}
/**
* The setter for the action
*
* @param string $str Start up or shutdown
*/
public function setAction($str)
{
$this->action = $str;
}
public function getAction()
{
return $this->action;
}
public function main()
{
$cmd = $this->getCmd();
$action = $this->getAction();
if (empty($cmd)) {
throw new BuildException('"cmd" is a required parameter');
}
if ($action == 'start') {
$this->startServer();
} else {
$this->stopServer();
}
}
protected function startServer()
{
chdir(__DIR__ . '/../..');
$serverfile = $this->serverfile;
$cmd = $this->getCmd();
// resolve $HOME directory
if ($cmd[0] == '~') {
$cmd = $_ENV["HOME"] . substr($cmd, 1);
}
$fp = @fsockopen('127.0.0.1', 8124, $errno, $errstr, 1);
if (! $fp) {
// need to start node server
$cmd = escapeshellcmd($cmd . ' ' . $serverfile);
$this->log('starting node test server with '.$cmd);
exec($cmd . ' &> /dev/null &');
sleep(2);
$fp = @fsockopen('127.0.0.1', 8124, $errno, $errstr, 1);
}
// test it again
if (! $fp) {
$this->log('could not start node server');
} else {
fclose($fp);
$this->log('node test server running');
}
}
protected function stopServer()
{
exec('ps axo "pid,command"', $out);
$nodeproc = false;
foreach ($out as $proc) {
if (strpos($proc, $this->serverfile) !== false) {
$nodeproc = $proc;
break;
}
}
if ($nodeproc) {
$proc = trim($nodeproc);
$space = strpos($proc, ' ');
$pid = substr($proc, 0, $space);
$killed = posix_kill($pid, 9);
if ($killed) {
$this->log('test server stopped');
} else {
$this->log('test server appears immortal');
}
} else {
$this->log('could not find test server in process list');
}
}
}

48
vendor/guzzle/guzzle/phpunit.xml.dist vendored Normal file
View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
colors="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader">
<testsuites>
<testsuite>
<directory>./tests/Guzzle/Tests</directory>
</testsuite>
</testsuites>
<logging>
<log type="junit" target="build/artifacts/logs/junit.xml" logIncompleteSkipped="false" />
</logging>
<filter>
<whitelist>
<directory suffix=".php">./src/Guzzle</directory>
<exclude>
<directory suffix="Interface.php">./src/Guzzle</directory>
<file>./src/Guzzle/Common/Exception/GuzzleException.php</file>
<file>./src/Guzzle/Http/Exception/HttpException.php</file>
<file>./src/Guzzle/Http/Exception/ServerErrorResponseException.php</file>
<file>./src/Guzzle/Http/Exception/ClientErrorResponseException.php</file>
<file>./src/Guzzle/Http/Exception/TooManyRedirectsException.php</file>
<file>./src/Guzzle/Http/Exception/CouldNotRewindStreamException.php</file>
<file>./src/Guzzle/Common/Exception/BadMethodCallException.php</file>
<file>./src/Guzzle/Common/Exception/InvalidArgumentException.php</file>
<file>./src/Guzzle/Common/Exception/RuntimeException.php</file>
<file>./src/Guzzle/Common/Exception/UnexpectedValueException.php</file>
<file>./src/Guzzle/Service/Exception/ClientNotFoundException.php</file>
<file>./src/Guzzle/Service/Exception/CommandException.php</file>
<file>./src/Guzzle/Service/Exception/DescriptionBuilderException.php</file>
<file>./src/Guzzle/Service/Exception/ServiceBuilderException.php</file>
<file>./src/Guzzle/Service/Exception/ServiceNotFoundException.php</file>
<file>./src/Guzzle/Service/Exception/ValidationException.php</file>
<file>./src/Guzzle/Service/Exception/JsonException.php</file>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,66 @@
<?php
namespace Guzzle\Batch;
/**
* Abstract decorator used when decorating a BatchInterface
*/
abstract class AbstractBatchDecorator implements BatchInterface
{
/** @var BatchInterface Decorated batch object */
protected $decoratedBatch;
/**
* @param BatchInterface $decoratedBatch BatchInterface that is being decorated
*/
public function __construct(BatchInterface $decoratedBatch)
{
$this->decoratedBatch = $decoratedBatch;
}
/**
* Allow decorators to implement custom methods
*
* @param string $method Missing method name
* @param array $args Method arguments
*
* @return mixed
* @codeCoverageIgnore
*/
public function __call($method, array $args)
{
return call_user_func_array(array($this->decoratedBatch, $method), $args);
}
public function add($item)
{
$this->decoratedBatch->add($item);
return $this;
}
public function flush()
{
return $this->decoratedBatch->flush();
}
public function isEmpty()
{
return $this->decoratedBatch->isEmpty();
}
/**
* Trace the decorators associated with the batch
*
* @return array
*/
public function getDecorators()
{
$found = array($this);
if (method_exists($this->decoratedBatch, 'getDecorators')) {
$found = array_merge($found, $this->decoratedBatch->getDecorators());
}
return $found;
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Batch\Exception\BatchTransferException;
/**
* Default batch implementation used to convert queued items into smaller chunks of batches using a
* {@see BatchDivisorIterface} and transfers each batch using a {@see BatchTransferInterface}.
*
* Any exception encountered during a flush operation will throw a {@see BatchTransferException} object containing the
* batch that failed. After an exception is encountered, you can flush the batch again to attempt to finish transferring
* any previously created batches or queued items.
*/
class Batch implements BatchInterface
{
/** @var \SplQueue Queue of items in the queue */
protected $queue;
/** @var array Divided batches to be transferred */
protected $dividedBatches;
/** @var BatchTransferInterface */
protected $transferStrategy;
/** @var BatchDivisorInterface */
protected $divisionStrategy;
/**
* @param BatchTransferInterface $transferStrategy Strategy used to transfer items
* @param BatchDivisorInterface $divisionStrategy Divisor used to create batches
*/
public function __construct(BatchTransferInterface $transferStrategy, BatchDivisorInterface $divisionStrategy)
{
$this->transferStrategy = $transferStrategy;
$this->divisionStrategy = $divisionStrategy;
$this->queue = new \SplQueue();
$this->queue->setIteratorMode(\SplQueue::IT_MODE_DELETE);
$this->dividedBatches = array();
}
public function add($item)
{
$this->queue->enqueue($item);
return $this;
}
public function flush()
{
$this->createBatches();
$items = array();
foreach ($this->dividedBatches as $batchIndex => $dividedBatch) {
while ($dividedBatch->valid()) {
$batch = $dividedBatch->current();
$dividedBatch->next();
try {
$this->transferStrategy->transfer($batch);
$items = array_merge($items, $batch);
} catch (\Exception $e) {
throw new BatchTransferException($batch, $items, $e, $this->transferStrategy, $this->divisionStrategy);
}
}
// Keep the divided batch down to a minimum in case of a later exception
unset($this->dividedBatches[$batchIndex]);
}
return $items;
}
public function isEmpty()
{
return count($this->queue) == 0 && count($this->dividedBatches) == 0;
}
/**
* Create batches for any queued items
*/
protected function createBatches()
{
if (count($this->queue)) {
if ($batches = $this->divisionStrategy->createBatches($this->queue)) {
// Convert arrays into iterators
if (is_array($batches)) {
$batches = new \ArrayIterator($batches);
}
$this->dividedBatches[] = $batches;
}
}
}
}

View File

@ -0,0 +1,199 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Exception\RuntimeException;
/**
* Builder used to create custom batch objects
*/
class BatchBuilder
{
/** @var bool Whether or not the batch should automatically flush*/
protected $autoFlush = false;
/** @var bool Whether or not to maintain a batch history */
protected $history = false;
/** @var bool Whether or not to buffer exceptions encountered in transfer */
protected $exceptionBuffering = false;
/** @var mixed Callable to invoke each time a flush completes */
protected $afterFlush;
/** @var BatchTransferInterface Object used to transfer items in the queue */
protected $transferStrategy;
/** @var BatchDivisorInterface Object used to divide the queue into batches */
protected $divisorStrategy;
/** @var array of Mapped transfer strategies by handle name */
protected static $mapping = array(
'request' => 'Guzzle\Batch\BatchRequestTransfer',
'command' => 'Guzzle\Batch\BatchCommandTransfer'
);
/**
* Create a new instance of the BatchBuilder
*
* @return BatchBuilder
*/
public static function factory()
{
return new self();
}
/**
* Automatically flush the batch when the size of the queue reaches a certain threshold. Adds {@see FlushingBatch}.
*
* @param $threshold Number of items to allow in the queue before a flush
*
* @return BatchBuilder
*/
public function autoFlushAt($threshold)
{
$this->autoFlush = $threshold;
return $this;
}
/**
* Maintain a history of all items that have been transferred using the batch. Adds {@see HistoryBatch}.
*
* @return BatchBuilder
*/
public function keepHistory()
{
$this->history = true;
return $this;
}
/**
* Buffer exceptions thrown during transfer so that you can transfer as much as possible, and after a transfer
* completes, inspect each exception that was thrown. Enables the {@see ExceptionBufferingBatch} decorator.
*
* @return BatchBuilder
*/
public function bufferExceptions()
{
$this->exceptionBuffering = true;
return $this;
}
/**
* Notify a callable each time a batch flush completes. Enables the {@see NotifyingBatch} decorator.
*
* @param mixed $callable Callable function to notify
*
* @return BatchBuilder
* @throws InvalidArgumentException if the argument is not callable
*/
public function notify($callable)
{
$this->afterFlush = $callable;
return $this;
}
/**
* Configures the batch to transfer batches of requests. Associates a {@see \Guzzle\Http\BatchRequestTransfer}
* object as both the transfer and divisor strategy.
*
* @param int $batchSize Batch size for each batch of requests
*
* @return BatchBuilder
*/
public function transferRequests($batchSize = 50)
{
$className = self::$mapping['request'];
$this->transferStrategy = new $className($batchSize);
$this->divisorStrategy = $this->transferStrategy;
return $this;
}
/**
* Configures the batch to transfer batches commands. Associates as
* {@see \Guzzle\Service\Command\BatchCommandTransfer} as both the transfer and divisor strategy.
*
* @param int $batchSize Batch size for each batch of commands
*
* @return BatchBuilder
*/
public function transferCommands($batchSize = 50)
{
$className = self::$mapping['command'];
$this->transferStrategy = new $className($batchSize);
$this->divisorStrategy = $this->transferStrategy;
return $this;
}
/**
* Specify the strategy used to divide the queue into an array of batches
*
* @param BatchDivisorInterface $divisorStrategy Strategy used to divide a batch queue into batches
*
* @return BatchBuilder
*/
public function createBatchesWith(BatchDivisorInterface $divisorStrategy)
{
$this->divisorStrategy = $divisorStrategy;
return $this;
}
/**
* Specify the strategy used to transport the items when flush is called
*
* @param BatchTransferInterface $transferStrategy How items are transferred
*
* @return BatchBuilder
*/
public function transferWith(BatchTransferInterface $transferStrategy)
{
$this->transferStrategy = $transferStrategy;
return $this;
}
/**
* Create and return the instantiated batch
*
* @return BatchInterface
* @throws RuntimeException if no transfer strategy has been specified
*/
public function build()
{
if (!$this->transferStrategy) {
throw new RuntimeException('No transfer strategy has been specified');
}
if (!$this->divisorStrategy) {
throw new RuntimeException('No divisor strategy has been specified');
}
$batch = new Batch($this->transferStrategy, $this->divisorStrategy);
if ($this->exceptionBuffering) {
$batch = new ExceptionBufferingBatch($batch);
}
if ($this->afterFlush) {
$batch = new NotifyingBatch($batch, $this->afterFlush);
}
if ($this->autoFlush) {
$batch = new FlushingBatch($batch, $this->autoFlush);
}
if ($this->history) {
$batch = new HistoryBatch($batch);
}
return $batch;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Common\Exception\InvalidArgumentException;
/**
* Divides batches using a callable
*/
class BatchClosureDivisor implements BatchDivisorInterface
{
/** @var callable Method used to divide the batches */
protected $callable;
/** @var mixed $context Context passed to the callable */
protected $context;
/**
* @param callable $callable Method used to divide the batches. The method must accept an \SplQueue and return an
* array of arrays containing the divided items.
* @param mixed $context Optional context to pass to the batch divisor
*
* @throws InvalidArgumentException if the callable is not callable
*/
public function __construct($callable, $context = null)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException('Must pass a callable');
}
$this->callable = $callable;
$this->context = $context;
}
public function createBatches(\SplQueue $queue)
{
return call_user_func($this->callable, $queue, $this->context);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Common\Exception\InvalidArgumentException;
/**
* Batch transfer strategy where transfer logic can be defined via a Closure.
* This class is to be used with {@see Guzzle\Batch\BatchInterface}
*/
class BatchClosureTransfer implements BatchTransferInterface
{
/** @var callable A closure that performs the transfer */
protected $callable;
/** @var mixed $context Context passed to the callable */
protected $context;
/**
* @param mixed $callable Callable that performs the transfer. This function should accept two arguments:
* (array $batch, mixed $context).
* @param mixed $context Optional context to pass to the batch divisor
*
* @throws InvalidArgumentException
*/
public function __construct($callable, $context = null)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException('Argument must be callable');
}
$this->callable = $callable;
$this->context = $context;
}
public function transfer(array $batch)
{
return empty($batch) ? null : call_user_func($this->callable, $batch, $this->context);
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Batch\BatchTransferInterface;
use Guzzle\Batch\BatchDivisorInterface;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Service\Command\CommandInterface;
use Guzzle\Service\Exception\InconsistentClientTransferException;
/**
* Efficiently transfers multiple commands in parallel per client
* This class is to be used with {@see Guzzle\Batch\BatchInterface}
*/
class BatchCommandTransfer implements BatchTransferInterface, BatchDivisorInterface
{
/** @var int Size of each command batch */
protected $batchSize;
/**
* @param int $batchSize Size of each batch
*/
public function __construct($batchSize = 50)
{
$this->batchSize = $batchSize;
}
/**
* Creates batches by grouping commands by their associated client
* {@inheritdoc}
*/
public function createBatches(\SplQueue $queue)
{
$groups = new \SplObjectStorage();
foreach ($queue as $item) {
if (!$item instanceof CommandInterface) {
throw new InvalidArgumentException('All items must implement Guzzle\Service\Command\CommandInterface');
}
$client = $item->getClient();
if (!$groups->contains($client)) {
$groups->attach($client, new \ArrayObject(array($item)));
} else {
$groups[$client]->append($item);
}
}
$batches = array();
foreach ($groups as $batch) {
$batches = array_merge($batches, array_chunk($groups[$batch]->getArrayCopy(), $this->batchSize));
}
return $batches;
}
public function transfer(array $batch)
{
if (empty($batch)) {
return;
}
// Get the client of the first found command
$client = reset($batch)->getClient();
// Keep a list of all commands with invalid clients
$invalid = array_filter($batch, function ($command) use ($client) {
return $command->getClient() !== $client;
});
if (!empty($invalid)) {
throw new InconsistentClientTransferException($invalid);
}
$client->execute($batch);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Guzzle\Batch;
/**
* Interface used for dividing a queue of items into an array of batches
*/
interface BatchDivisorInterface
{
/**
* Divide a queue of items into an array batches
*
* @param \SplQueue $queue Queue of items to divide into batches. Items are removed as they are iterated.
*
* @return array|\Traversable Returns an array or Traversable object that contains arrays of items to transfer
*/
public function createBatches(\SplQueue $queue);
}

View File

@ -0,0 +1,32 @@
<?php
namespace Guzzle\Batch;
/**
* Interface for efficiently transferring items in a queue using batches
*/
interface BatchInterface
{
/**
* Add an item to the queue
*
* @param mixed $item Item to add
*
* @return self
*/
public function add($item);
/**
* Flush the batch and transfer the items
*
* @return array Returns an array flushed items
*/
public function flush();
/**
* Check if the batch is empty and has further items to transfer
*
* @return bool
*/
public function isEmpty();
}

View File

@ -0,0 +1,65 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Batch\BatchTransferInterface;
use Guzzle\Batch\BatchDivisorInterface;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Http\Message\RequestInterface;
/**
* Batch transfer strategy used to efficiently transfer a batch of requests.
* This class is to be used with {@see Guzzle\Batch\BatchInterface}
*/
class BatchRequestTransfer implements BatchTransferInterface, BatchDivisorInterface
{
/** @var int Size of each command batch */
protected $batchSize;
/**
* Constructor used to specify how large each batch should be
*
* @param int $batchSize Size of each batch
*/
public function __construct($batchSize = 50)
{
$this->batchSize = $batchSize;
}
/**
* Creates batches of requests by grouping requests by their associated curl multi object.
* {@inheritdoc}
*/
public function createBatches(\SplQueue $queue)
{
// Create batches by client objects
$groups = new \SplObjectStorage();
foreach ($queue as $item) {
if (!$item instanceof RequestInterface) {
throw new InvalidArgumentException('All items must implement Guzzle\Http\Message\RequestInterface');
}
$client = $item->getClient();
if (!$groups->contains($client)) {
$groups->attach($client, array($item));
} else {
$current = $groups[$client];
$current[] = $item;
$groups[$client] = $current;
}
}
$batches = array();
foreach ($groups as $batch) {
$batches = array_merge($batches, array_chunk($groups[$batch], $this->batchSize));
}
return $batches;
}
public function transfer(array $batch)
{
if ($batch) {
reset($batch)->getClient()->send($batch);
}
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Guzzle\Batch;
/**
* Divides batches into smaller batches under a certain size
*/
class BatchSizeDivisor implements BatchDivisorInterface
{
/** @var int Size of each batch */
protected $size;
/** @param int $size Size of each batch */
public function __construct($size)
{
$this->size = $size;
}
/**
* Set the size of each batch
*
* @param int $size Size of each batch
*
* @return BatchSizeDivisor
*/
public function setSize($size)
{
$this->size = $size;
return $this;
}
/**
* Get the size of each batch
*
* @return int
*/
public function getSize()
{
return $this->size;
}
public function createBatches(\SplQueue $queue)
{
return array_chunk(iterator_to_array($queue, false), $this->size);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Guzzle\Batch;
/**
* Interface used for transferring batches of items
*/
interface BatchTransferInterface
{
/**
* Transfer an array of items
*
* @param array $batch Array of items to transfer
*/
public function transfer(array $batch);
}

View File

@ -0,0 +1,90 @@
<?php
namespace Guzzle\Batch\Exception;
use Guzzle\Common\Exception\GuzzleException;
use Guzzle\Batch\BatchTransferInterface as TransferStrategy;
use Guzzle\Batch\BatchDivisorInterface as DivisorStrategy;
/**
* Exception thrown during a batch transfer
*/
class BatchTransferException extends \Exception implements GuzzleException
{
/** @var array The batch being sent when the exception occurred */
protected $batch;
/** @var TransferStrategy The transfer strategy in use when the exception occurred */
protected $transferStrategy;
/** @var DivisorStrategy The divisor strategy in use when the exception occurred */
protected $divisorStrategy;
/** @var array Items transferred at the point in which the exception was encountered */
protected $transferredItems;
/**
* @param array $batch The batch being sent when the exception occurred
* @param array $transferredItems Items transferred at the point in which the exception was encountered
* @param \Exception $exception Exception encountered
* @param TransferStrategy $transferStrategy The transfer strategy in use when the exception occurred
* @param DivisorStrategy $divisorStrategy The divisor strategy in use when the exception occurred
*/
public function __construct(
array $batch,
array $transferredItems,
\Exception $exception,
TransferStrategy $transferStrategy = null,
DivisorStrategy $divisorStrategy = null
) {
$this->batch = $batch;
$this->transferredItems = $transferredItems;
$this->transferStrategy = $transferStrategy;
$this->divisorStrategy = $divisorStrategy;
parent::__construct(
'Exception encountered while transferring batch: ' . $exception->getMessage(),
$exception->getCode(),
$exception
);
}
/**
* Get the batch that we being sent when the exception occurred
*
* @return array
*/
public function getBatch()
{
return $this->batch;
}
/**
* Get the items transferred at the point in which the exception was encountered
*
* @return array
*/
public function getTransferredItems()
{
return $this->transferredItems;
}
/**
* Get the transfer strategy
*
* @return TransferStrategy
*/
public function getTransferStrategy()
{
return $this->transferStrategy;
}
/**
* Get the divisor strategy
*
* @return DivisorStrategy
*/
public function getDivisorStrategy()
{
return $this->divisorStrategy;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Batch\Exception\BatchTransferException;
/**
* BatchInterface decorator used to buffer exceptions encountered during a transfer. The exceptions can then later be
* processed after a batch flush has completed.
*/
class ExceptionBufferingBatch extends AbstractBatchDecorator
{
/** @var array Array of BatchTransferException exceptions */
protected $exceptions = array();
public function flush()
{
$items = array();
while (!$this->decoratedBatch->isEmpty()) {
try {
$transferredItems = $this->decoratedBatch->flush();
} catch (BatchTransferException $e) {
$this->exceptions[] = $e;
$transferredItems = $e->getTransferredItems();
}
$items = array_merge($items, $transferredItems);
}
return $items;
}
/**
* Get the buffered exceptions
*
* @return array Array of BatchTransferException objects
*/
public function getExceptions()
{
return $this->exceptions;
}
/**
* Clear the buffered exceptions
*/
public function clearExceptions()
{
$this->exceptions = array();
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Guzzle\Batch;
/**
* BatchInterface decorator used to add automatic flushing of the queue when the size of the queue reaches a threshold.
*/
class FlushingBatch extends AbstractBatchDecorator
{
/** @var int The threshold for which to automatically flush */
protected $threshold;
/** @var int Current number of items known to be in the queue */
protected $currentTotal = 0;
/**
* @param BatchInterface $decoratedBatch BatchInterface that is being decorated
* @param int $threshold Flush when the number in queue matches the threshold
*/
public function __construct(BatchInterface $decoratedBatch, $threshold)
{
$this->threshold = $threshold;
parent::__construct($decoratedBatch);
}
/**
* Set the auto-flush threshold
*
* @param int $threshold The auto-flush threshold
*
* @return FlushingBatch
*/
public function setThreshold($threshold)
{
$this->threshold = $threshold;
return $this;
}
/**
* Get the auto-flush threshold
*
* @return int
*/
public function getThreshold()
{
return $this->threshold;
}
public function add($item)
{
$this->decoratedBatch->add($item);
if (++$this->currentTotal >= $this->threshold) {
$this->currentTotal = 0;
$this->decoratedBatch->flush();
}
return $this;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Guzzle\Batch;
/**
* BatchInterface decorator used to keep a history of items that were added to the batch. You must clear the history
* manually to remove items from the history.
*/
class HistoryBatch extends AbstractBatchDecorator
{
/** @var array Items in the history */
protected $history = array();
public function add($item)
{
$this->history[] = $item;
$this->decoratedBatch->add($item);
return $this;
}
/**
* Get the batch history
*
* @return array
*/
public function getHistory()
{
return $this->history;
}
/**
* Clear the batch history
*/
public function clearHistory()
{
$this->history = array();
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Guzzle\Batch;
use Guzzle\Common\Exception\InvalidArgumentException;
/**
* BatchInterface decorator used to call a method each time flush is called
*/
class NotifyingBatch extends AbstractBatchDecorator
{
/** @var mixed Callable to call */
protected $callable;
/**
* @param BatchInterface $decoratedBatch Batch object to decorate
* @param mixed $callable Callable to call
*
* @throws InvalidArgumentException
*/
public function __construct(BatchInterface $decoratedBatch, $callable)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException('The passed argument is not callable');
}
$this->callable = $callable;
parent::__construct($decoratedBatch);
}
public function flush()
{
$items = $this->decoratedBatch->flush();
call_user_func($this->callable, $items);
return $items;
}
}

View File

@ -0,0 +1,31 @@
{
"name": "guzzle/batch",
"description": "Guzzle batch component for batching requests, commands, or custom transfers",
"homepage": "http://guzzlephp.org/",
"keywords": ["batch", "HTTP", "REST", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.3.2",
"guzzle/common": "self.version"
},
"autoload": {
"psr-0": { "Guzzle\\Batch": "" }
},
"suggest": {
"guzzle/http": "self.version",
"guzzle/service": "self.version"
},
"target-dir": "Guzzle/Batch",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Guzzle\Cache;
/**
* Abstract cache adapter
*/
abstract class AbstractCacheAdapter implements CacheAdapterInterface
{
protected $cache;
/**
* Get the object owned by the adapter
*
* @return mixed
*/
public function getCacheObject()
{
return $this->cache;
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace Guzzle\Cache;
use Doctrine\Common\Cache\Cache;
use Guzzle\Common\Version;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Common\FromConfigInterface;
use Zend\Cache\Storage\StorageInterface;
/**
* Generates cache adapters from any number of known cache implementations
*/
class CacheAdapterFactory implements FromConfigInterface
{
/**
* Create a Guzzle cache adapter based on an array of options
*
* @param mixed $cache Cache value
*
* @return CacheAdapterInterface
* @throws InvalidArgumentException
*/
public static function fromCache($cache)
{
if (!is_object($cache)) {
throw new InvalidArgumentException('Cache must be one of the known cache objects');
}
if ($cache instanceof CacheAdapterInterface) {
return $cache;
} elseif ($cache instanceof Cache) {
return new DoctrineCacheAdapter($cache);
} elseif ($cache instanceof StorageInterface) {
return new Zf2CacheAdapter($cache);
} else {
throw new InvalidArgumentException('Unknown cache type: ' . get_class($cache));
}
}
/**
* Create a Guzzle cache adapter based on an array of options
*
* @param array $config Array of configuration options
*
* @return CacheAdapterInterface
* @throws InvalidArgumentException
* @deprecated This will be removed in a future version
* @codeCoverageIgnore
*/
public static function factory($config = array())
{
Version::warn(__METHOD__ . ' is deprecated');
if (!is_array($config)) {
throw new InvalidArgumentException('$config must be an array');
}
if (!isset($config['cache.adapter']) && !isset($config['cache.provider'])) {
$config['cache.adapter'] = 'Guzzle\Cache\NullCacheAdapter';
$config['cache.provider'] = null;
} else {
// Validate that the options are valid
foreach (array('cache.adapter', 'cache.provider') as $required) {
if (!isset($config[$required])) {
throw new InvalidArgumentException("{$required} is a required CacheAdapterFactory option");
}
if (is_string($config[$required])) {
// Convert dot notation to namespaces
$config[$required] = str_replace('.', '\\', $config[$required]);
if (!class_exists($config[$required])) {
throw new InvalidArgumentException("{$config[$required]} is not a valid class for {$required}");
}
}
}
// Instantiate the cache provider
if (is_string($config['cache.provider'])) {
$args = isset($config['cache.provider.args']) ? $config['cache.provider.args'] : null;
$config['cache.provider'] = self::createObject($config['cache.provider'], $args);
}
}
// Instantiate the cache adapter using the provider and options
if (is_string($config['cache.adapter'])) {
$args = isset($config['cache.adapter.args']) ? $config['cache.adapter.args'] : array();
array_unshift($args, $config['cache.provider']);
$config['cache.adapter'] = self::createObject($config['cache.adapter'], $args);
}
return $config['cache.adapter'];
}
/**
* Create a class using an array of constructor arguments
*
* @param string $className Class name
* @param array $args Arguments for the class constructor
*
* @return mixed
* @throws RuntimeException
* @deprecated
* @codeCoverageIgnore
*/
private static function createObject($className, array $args = null)
{
try {
if (!$args) {
return new $className;
} else {
$c = new \ReflectionClass($className);
return $c->newInstanceArgs($args);
}
} catch (\Exception $e) {
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
}
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Guzzle\Cache;
/**
* Interface for cache adapters.
*
* Cache adapters allow Guzzle to utilize various frameworks for caching HTTP responses.
*
* @link http://www.doctrine-project.org/ Inspired by Doctrine 2
*/
interface CacheAdapterInterface
{
/**
* Test if an entry exists in the cache.
*
* @param string $id cache id The cache id of the entry to check for.
* @param array $options Array of cache adapter options
*
* @return bool Returns TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
public function contains($id, array $options = null);
/**
* Deletes a cache entry.
*
* @param string $id cache id
* @param array $options Array of cache adapter options
*
* @return bool TRUE on success, FALSE on failure
*/
public function delete($id, array $options = null);
/**
* Fetches an entry from the cache.
*
* @param string $id cache id The id of the cache entry to fetch.
* @param array $options Array of cache adapter options
*
* @return string The cached data or FALSE, if no cache entry exists for the given id.
*/
public function fetch($id, array $options = null);
/**
* Puts data into the cache.
*
* @param string $id The cache id
* @param string $data The cache entry/data
* @param int|bool $lifeTime The lifetime. If != false, sets a specific lifetime for this cache entry
* @param array $options Array of cache adapter options
*
* @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
public function save($id, $data, $lifeTime = false, array $options = null);
}

View File

@ -0,0 +1,57 @@
<?php
namespace Guzzle\Cache;
/**
* Cache adapter that defers to closures for implementation
*/
class ClosureCacheAdapter implements CacheAdapterInterface
{
/**
* @var array Mapping of method names to callables
*/
protected $callables;
/**
* The callables array is an array mapping the actions of the cache adapter to callables.
* - contains: Callable that accepts an $id and $options argument
* - delete: Callable that accepts an $id and $options argument
* - fetch: Callable that accepts an $id and $options argument
* - save: Callable that accepts an $id, $data, $lifeTime, and $options argument
*
* @param array $callables array of action names to callable
*
* @throws \InvalidArgumentException if the callable is not callable
*/
public function __construct(array $callables)
{
// Validate each key to ensure it exists and is callable
foreach (array('contains', 'delete', 'fetch', 'save') as $key) {
if (!array_key_exists($key, $callables) || !is_callable($callables[$key])) {
throw new \InvalidArgumentException("callables must contain a callable {$key} key");
}
}
$this->callables = $callables;
}
public function contains($id, array $options = null)
{
return call_user_func($this->callables['contains'], $id, $options);
}
public function delete($id, array $options = null)
{
return call_user_func($this->callables['delete'], $id, $options);
}
public function fetch($id, array $options = null)
{
return call_user_func($this->callables['fetch'], $id, $options);
}
public function save($id, $data, $lifeTime = false, array $options = null)
{
return call_user_func($this->callables['save'], $id, $data, $lifeTime, $options);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Guzzle\Cache;
use Doctrine\Common\Cache\Cache;
/**
* Doctrine 2 cache adapter
*
* @link http://www.doctrine-project.org/
*/
class DoctrineCacheAdapter extends AbstractCacheAdapter
{
/**
* @param Cache $cache Doctrine cache object
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
public function contains($id, array $options = null)
{
return $this->cache->contains($id);
}
public function delete($id, array $options = null)
{
return $this->cache->delete($id);
}
public function fetch($id, array $options = null)
{
return $this->cache->fetch($id);
}
public function save($id, $data, $lifeTime = false, array $options = null)
{
return $this->cache->save($id, $data, $lifeTime);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Guzzle\Cache;
/**
* Null cache adapter
*/
class NullCacheAdapter extends AbstractCacheAdapter
{
public function __construct() {}
public function contains($id, array $options = null)
{
return false;
}
public function delete($id, array $options = null)
{
return true;
}
public function fetch($id, array $options = null)
{
return false;
}
public function save($id, $data, $lifeTime = false, array $options = null)
{
return true;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Guzzle\Cache;
use Guzzle\Common\Version;
/**
* Zend Framework 1 cache adapter
*
* @link http://framework.zend.com/manual/en/zend.cache.html
* @deprecated
* @codeCoverageIgnore
*/
class Zf1CacheAdapter extends AbstractCacheAdapter
{
/**
* @param \Zend_Cache_Backend $cache Cache object to wrap
*/
public function __construct(\Zend_Cache_Backend $cache)
{
Version::warn(__CLASS__ . ' is deprecated. Upgrade to ZF2 or use PsrCacheAdapter');
$this->cache = $cache;
}
public function contains($id, array $options = null)
{
return $this->cache->test($id);
}
public function delete($id, array $options = null)
{
return $this->cache->remove($id);
}
public function fetch($id, array $options = null)
{
return $this->cache->load($id);
}
public function save($id, $data, $lifeTime = false, array $options = null)
{
return $this->cache->save($data, $id, array(), $lifeTime);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Guzzle\Cache;
use Zend\Cache\Storage\StorageInterface;
/**
* Zend Framework 2 cache adapter
*
* @link http://packages.zendframework.com/docs/latest/manual/en/zend.cache.html
*/
class Zf2CacheAdapter extends AbstractCacheAdapter
{
/**
* @param StorageInterface $cache Zend Framework 2 cache adapter
*/
public function __construct(StorageInterface $cache)
{
$this->cache = $cache;
}
public function contains($id, array $options = null)
{
return $this->cache->hasItem($id);
}
public function delete($id, array $options = null)
{
return $this->cache->removeItem($id);
}
public function fetch($id, array $options = null)
{
return $this->cache->getItem($id);
}
public function save($id, $data, $lifeTime = false, array $options = null)
{
return $this->cache->setItem($id, $data);
}
}

View File

@ -0,0 +1,27 @@
{
"name": "guzzle/cache",
"description": "Guzzle cache adapter component",
"homepage": "http://guzzlephp.org/",
"keywords": ["cache", "adapter", "zf", "doctrine", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.3.2",
"guzzle/common": "self.version"
},
"autoload": {
"psr-0": { "Guzzle\\Cache": "" }
},
"target-dir": "Guzzle/Cache",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Guzzle\Common;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class that holds an event dispatcher
*/
class AbstractHasDispatcher implements HasDispatcherInterface
{
/** @var EventDispatcherInterface */
protected $eventDispatcher;
public static function getAllEvents()
{
return array();
}
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
return $this;
}
public function getEventDispatcher()
{
if (!$this->eventDispatcher) {
$this->eventDispatcher = new EventDispatcher();
}
return $this->eventDispatcher;
}
public function dispatch($eventName, array $context = array())
{
$this->getEventDispatcher()->dispatch($eventName, new Event($context));
}
public function addSubscriber(EventSubscriberInterface $subscriber)
{
$this->getEventDispatcher()->addSubscriber($subscriber);
return $this;
}
}

View File

@ -0,0 +1,403 @@
<?php
namespace Guzzle\Common;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Exception\RuntimeException;
/**
* Key value pair collection object
*/
class Collection implements \ArrayAccess, \IteratorAggregate, \Countable, ToArrayInterface
{
/** @var array Data associated with the object. */
protected $data;
/**
* @param array $data Associative array of data to set
*/
public function __construct(array $data = array())
{
$this->data = $data;
}
/**
* Create a new collection from an array, validate the keys, and add default values where missing
*
* @param array $config Configuration values to apply.
* @param array $defaults Default parameters
* @param array $required Required parameter names
*
* @return self
* @throws InvalidArgumentException if a parameter is missing
*/
public static function fromConfig(array $config = array(), array $defaults = array(), array $required = array())
{
$data = $config + $defaults;
if ($missing = array_diff($required, array_keys($data))) {
throw new InvalidArgumentException('Config is missing the following keys: ' . implode(', ', $missing));
}
return new self($data);
}
public function count()
{
return count($this->data);
}
public function getIterator()
{
return new \ArrayIterator($this->data);
}
public function toArray()
{
return $this->data;
}
/**
* Removes all key value pairs
*
* @return Collection
*/
public function clear()
{
$this->data = array();
return $this;
}
/**
* Get all or a subset of matching key value pairs
*
* @param array $keys Pass an array of keys to retrieve only a subset of key value pairs
*
* @return array Returns an array of all matching key value pairs
*/
public function getAll(array $keys = null)
{
return $keys ? array_intersect_key($this->data, array_flip($keys)) : $this->data;
}
/**
* Get a specific key value.
*
* @param string $key Key to retrieve.
*
* @return mixed|null Value of the key or NULL
*/
public function get($key)
{
return isset($this->data[$key]) ? $this->data[$key] : null;
}
/**
* Set a key value pair
*
* @param string $key Key to set
* @param mixed $value Value to set
*
* @return Collection Returns a reference to the object
*/
public function set($key, $value)
{
$this->data[$key] = $value;
return $this;
}
/**
* Add a value to a key. If a key of the same name has already been added, the key value will be converted into an
* array and the new value will be pushed to the end of the array.
*
* @param string $key Key to add
* @param mixed $value Value to add to the key
*
* @return Collection Returns a reference to the object.
*/
public function add($key, $value)
{
if (!array_key_exists($key, $this->data)) {
$this->data[$key] = $value;
} elseif (is_array($this->data[$key])) {
$this->data[$key][] = $value;
} else {
$this->data[$key] = array($this->data[$key], $value);
}
return $this;
}
/**
* Remove a specific key value pair
*
* @param string $key A key to remove
*
* @return Collection
*/
public function remove($key)
{
unset($this->data[$key]);
return $this;
}
/**
* Get all keys in the collection
*
* @return array
*/
public function getKeys()
{
return array_keys($this->data);
}
/**
* Returns whether or not the specified key is present.
*
* @param string $key The key for which to check the existence.
*
* @return bool
*/
public function hasKey($key)
{
return array_key_exists($key, $this->data);
}
/**
* Case insensitive search the keys in the collection
*
* @param string $key Key to search for
*
* @return bool|string Returns false if not found, otherwise returns the key
*/
public function keySearch($key)
{
foreach (array_keys($this->data) as $k) {
if (!strcasecmp($k, $key)) {
return $k;
}
}
return false;
}
/**
* Checks if any keys contains a certain value
*
* @param string $value Value to search for
*
* @return mixed Returns the key if the value was found FALSE if the value was not found.
*/
public function hasValue($value)
{
return array_search($value, $this->data);
}
/**
* Replace the data of the object with the value of an array
*
* @param array $data Associative array of data
*
* @return Collection Returns a reference to the object
*/
public function replace(array $data)
{
$this->data = $data;
return $this;
}
/**
* Add and merge in a Collection or array of key value pair data.
*
* @param Collection|array $data Associative array of key value pair data
*
* @return Collection Returns a reference to the object.
*/
public function merge($data)
{
foreach ($data as $key => $value) {
$this->add($key, $value);
}
return $this;
}
/**
* Over write key value pairs in this collection with all of the data from an array or collection.
*
* @param array|\Traversable $data Values to override over this config
*
* @return self
*/
public function overwriteWith($data)
{
if (is_array($data)) {
$this->data = $data + $this->data;
} elseif ($data instanceof Collection) {
$this->data = $data->toArray() + $this->data;
} else {
foreach ($data as $key => $value) {
$this->data[$key] = $value;
}
}
return $this;
}
/**
* Returns a Collection containing all the elements of the collection after applying the callback function to each
* one. The Closure should accept three parameters: (string) $key, (string) $value, (array) $context and return a
* modified value
*
* @param \Closure $closure Closure to apply
* @param array $context Context to pass to the closure
* @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection
*
* @return Collection
*/
public function map(\Closure $closure, array $context = array(), $static = true)
{
$collection = $static ? new static() : new self();
foreach ($this as $key => $value) {
$collection->add($key, $closure($key, $value, $context));
}
return $collection;
}
/**
* Iterates over each key value pair in the collection passing them to the Closure. If the Closure function returns
* true, the current value from input is returned into the result Collection. The Closure must accept three
* parameters: (string) $key, (string) $value and return Boolean TRUE or FALSE for each value.
*
* @param \Closure $closure Closure evaluation function
* @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection
*
* @return Collection
*/
public function filter(\Closure $closure, $static = true)
{
$collection = ($static) ? new static() : new self();
foreach ($this->data as $key => $value) {
if ($closure($key, $value)) {
$collection->add($key, $value);
}
}
return $collection;
}
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
public function offsetGet($offset)
{
return isset($this->data[$offset]) ? $this->data[$offset] : null;
}
public function offsetSet($offset, $value)
{
$this->data[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
/**
* Set a value into a nested array key. Keys will be created as needed to set the value.
*
* @param string $path Path to set
* @param mixed $value Value to set at the key
*
* @return self
* @throws RuntimeException when trying to setPath using a nested path that travels through a scalar value
*/
public function setPath($path, $value)
{
$current =& $this->data;
$queue = explode('/', $path);
while (null !== ($key = array_shift($queue))) {
if (!is_array($current)) {
throw new RuntimeException("Trying to setPath {$path}, but {$key} is set and is not an array");
} elseif (!$queue) {
$current[$key] = $value;
} elseif (isset($current[$key])) {
$current =& $current[$key];
} else {
$current[$key] = array();
$current =& $current[$key];
}
}
return $this;
}
/**
* Gets a value from the collection using an array path (e.g. foo/baz/bar would retrieve bar from two nested arrays)
* Allows for wildcard searches which recursively combine matches up to the level at which the wildcard occurs. This
* can be useful for accepting any key of a sub-array and combining matching keys from each diverging path.
*
* @param string $path Path to traverse and retrieve a value from
* @param string $separator Character used to add depth to the search
* @param mixed $data Optional data to descend into (used when wildcards are encountered)
*
* @return mixed|null
*/
public function getPath($path, $separator = '/', $data = null)
{
if ($data === null) {
$data =& $this->data;
}
$path = is_array($path) ? $path : explode($separator, $path);
while (null !== ($part = array_shift($path))) {
if (!is_array($data)) {
return null;
} elseif (isset($data[$part])) {
$data =& $data[$part];
} elseif ($part != '*') {
return null;
} else {
// Perform a wildcard search by diverging and merging paths
$result = array();
foreach ($data as $value) {
if (!$path) {
$result = array_merge_recursive($result, (array) $value);
} elseif (null !== ($test = $this->getPath($path, $separator, $value))) {
$result = array_merge_recursive($result, (array) $test);
}
}
return $result;
}
}
return $data;
}
/**
* Inject configuration settings into an input string
*
* @param string $input Input to inject
*
* @return string
* @deprecated
*/
public function inject($input)
{
Version::warn(__METHOD__ . ' is deprecated');
$replace = array();
foreach ($this->data as $key => $val) {
$replace['{' . $key . '}'] = $val;
}
return strtr($input, $replace);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Guzzle\Common;
use Symfony\Component\EventDispatcher\Event as SymfonyEvent;
/**
* Default event for Guzzle notifications
*/
class Event extends SymfonyEvent implements ToArrayInterface, \ArrayAccess, \IteratorAggregate
{
/** @var array */
private $context;
/**
* @param array $context Contextual information
*/
public function __construct(array $context = array())
{
$this->context = $context;
}
public function getIterator()
{
return new \ArrayIterator($this->context);
}
public function offsetGet($offset)
{
return isset($this->context[$offset]) ? $this->context[$offset] : null;
}
public function offsetSet($offset, $value)
{
$this->context[$offset] = $value;
}
public function offsetExists($offset)
{
return isset($this->context[$offset]);
}
public function offsetUnset($offset)
{
unset($this->context[$offset]);
}
public function toArray()
{
return $this->context;
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace Guzzle\Common\Exception;
class BadMethodCallException extends \BadMethodCallException implements GuzzleException {}

View File

@ -0,0 +1,85 @@
<?php
namespace Guzzle\Common\Exception;
/**
* Collection of exceptions
*/
class ExceptionCollection extends \Exception implements GuzzleException, \IteratorAggregate, \Countable
{
/** @var array Array of Exceptions */
protected $exceptions = array();
/**
* Set all of the exceptions
*
* @param array $exceptions Array of exceptions
*
* @return self
*/
public function setExceptions(array $exceptions)
{
$this->exceptions = array();
foreach ($exceptions as $exception) {
$this->add($exception);
}
return $this;
}
/**
* Add exceptions to the collection
*
* @param ExceptionCollection|\Exception $e Exception to add
*
* @return ExceptionCollection;
*/
public function add($e)
{
if ($this->message) {
$this->message .= "\n";
}
if ($e instanceof self) {
$this->message .= '(' . get_class($e) . ")";
foreach (explode("\n", $e->getMessage()) as $message) {
$this->message .= "\n {$message}";
}
} elseif ($e instanceof \Exception) {
$this->exceptions[] = $e;
$this->message .= '(' . get_class($e) . ') ' . $e->getMessage();
}
return $this;
}
/**
* Get the total number of request exceptions
*
* @return int
*/
public function count()
{
return count($this->exceptions);
}
/**
* Allows array-like iteration over the request exceptions
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->exceptions);
}
/**
* Get the first exception in the collection
*
* @return \Exception
*/
public function getFirst()
{
return $this->exceptions ? $this->exceptions[0] : null;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Guzzle\Common\Exception;
/**
* Guzzle exception
*/
interface GuzzleException {}

View File

@ -0,0 +1,5 @@
<?php
namespace Guzzle\Common\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException {}

View File

@ -0,0 +1,5 @@
<?php
namespace Guzzle\Common\Exception;
class RuntimeException extends \RuntimeException implements GuzzleException {}

View File

@ -0,0 +1,5 @@
<?php
namespace Guzzle\Common\Exception;
class UnexpectedValueException extends \UnexpectedValueException implements GuzzleException {}

View File

@ -0,0 +1,18 @@
<?php
namespace Guzzle\Common;
/**
* Interfaces that adds a factory method which is used to instantiate a class from an array of configuration options.
*/
interface FromConfigInterface
{
/**
* Static factory method used to turn an array or collection of configuration data into an instantiated object.
*
* @param array|Collection $config Configuration data
*
* @return FromConfigInterface
*/
public static function factory($config = array());
}

View File

@ -0,0 +1,52 @@
<?php
namespace Guzzle\Common;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Holds an event dispatcher
*/
interface HasDispatcherInterface
{
/**
* Get a list of all of the events emitted from the class
*
* @return array
*/
public static function getAllEvents();
/**
* Set the EventDispatcher of the request
*
* @param EventDispatcherInterface $eventDispatcher
*
* @return self
*/
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher);
/**
* Get the EventDispatcher of the request
*
* @return EventDispatcherInterface
*/
public function getEventDispatcher();
/**
* Helper to dispatch Guzzle events and set the event name on the event
*
* @param string $eventName Name of the event to dispatch
* @param array $context Context of the event
*/
public function dispatch($eventName, array $context = array());
/**
* Add an event subscriber to the dispatcher
*
* @param EventSubscriberInterface $subscriber Event subscriber
*
* @return self
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
}

View File

@ -0,0 +1,16 @@
<?php
namespace Guzzle\Common;
/**
* An object that can be represented as an array
*/
interface ToArrayInterface
{
/**
* Get the array representation of an object
*
* @return array
*/
public function toArray();
}

View File

@ -0,0 +1,29 @@
<?php
namespace Guzzle\Common;
/**
* Guzzle version information
*/
class Version
{
const VERSION = '3.7.1';
/**
* @var bool Set this value to true to enable warnings for deprecated functionality use. This should be on in your
* unit tests, but probably not in production.
*/
public static $emitWarnings = false;
/**
* Emit a deprecation warning
*
* @param string $message Warning message
*/
public static function warn($message)
{
if (self::$emitWarnings) {
trigger_error('Deprecation warning: ' . $message, E_USER_DEPRECATED);
}
}
}

View File

@ -0,0 +1,20 @@
{
"name": "guzzle/common",
"homepage": "http://guzzlephp.org/",
"description": "Common libraries used by Guzzle",
"keywords": ["common", "event", "exception", "collection"],
"license": "MIT",
"require": {
"php": ">=5.3.2",
"symfony/event-dispatcher": ">=2.1"
},
"autoload": {
"psr-0": { "Guzzle\\Common": "" }
},
"target-dir": "Guzzle/Common",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

View File

@ -0,0 +1,221 @@
<?php
namespace Guzzle\Http;
use Guzzle\Stream\Stream;
/**
* Abstract decorator used to wrap entity bodies
*/
class AbstractEntityBodyDecorator implements EntityBodyInterface
{
/** @var EntityBodyInterface Decorated entity body */
protected $body;
/**
* @param EntityBodyInterface $body Entity body to decorate
*/
public function __construct(EntityBodyInterface $body)
{
$this->body = $body;
}
public function __toString()
{
return (string) $this->body;
}
/**
* Allow decorators to implement custom methods
*
* @param string $method Missing method name
* @param array $args Method arguments
*
* @return mixed
*/
public function __call($method, array $args)
{
return call_user_func_array(array($this->body, $method), $args);
}
public function close()
{
return $this->body->close();
}
public function setRewindFunction($callable)
{
$this->body->setRewindFunction($callable);
return $this;
}
public function rewind()
{
return $this->body->rewind();
}
public function compress($filter = 'zlib.deflate')
{
return $this->body->compress($filter);
}
public function uncompress($filter = 'zlib.inflate')
{
return $this->body->uncompress($filter);
}
public function getContentLength()
{
return $this->getSize();
}
public function getContentType()
{
return $this->body->getContentType();
}
public function getContentMd5($rawOutput = false, $base64Encode = false)
{
$hash = Stream::getHash($this, 'md5', $rawOutput);
return $hash && $base64Encode ? base64_encode($hash) : $hash;
}
public function getContentEncoding()
{
return $this->body->getContentEncoding();
}
public function getMetaData($key = null)
{
return $this->body->getMetaData($key);
}
public function getStream()
{
return $this->body->getStream();
}
public function setStream($stream, $size = 0)
{
$this->body->setStream($stream, $size);
return $this;
}
public function detachStream()
{
$this->body->detachStream();
return $this;
}
public function getWrapper()
{
return $this->body->getWrapper();
}
public function getWrapperData()
{
return $this->body->getWrapperData();
}
public function getStreamType()
{
return $this->body->getStreamType();
}
public function getUri()
{
return $this->body->getUri();
}
public function getSize()
{
return $this->body->getSize();
}
public function isReadable()
{
return $this->body->isReadable();
}
public function isRepeatable()
{
return $this->isSeekable() && $this->isReadable();
}
public function isWritable()
{
return $this->body->isWritable();
}
public function isConsumed()
{
return $this->body->isConsumed();
}
/**
* Alias of isConsumed()
* {@inheritdoc}
*/
public function feof()
{
return $this->isConsumed();
}
public function isLocal()
{
return $this->body->isLocal();
}
public function isSeekable()
{
return $this->body->isSeekable();
}
public function setSize($size)
{
$this->body->setSize($size);
return $this;
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->body->seek($offset, $whence);
}
public function read($length)
{
return $this->body->read($length);
}
public function write($string)
{
return $this->body->write($string);
}
public function readLine($maxLength = null)
{
return $this->body->readLine($maxLength);
}
public function ftell()
{
return $this->body->ftell();
}
public function getCustomData($key)
{
return $this->body->getCustomData($key);
}
public function setCustomData($key, $value)
{
$this->body->setCustomData($key, $value);
return $this;
}
}

View File

@ -0,0 +1,229 @@
<?php
namespace Guzzle\Http;
use Guzzle\Common\Exception\RuntimeException;
/**
* EntityBody decorator that can cache previously read bytes from a sequentially read tstream
*/
class CachingEntityBody extends AbstractEntityBodyDecorator
{
/** @var EntityBody Remote stream used to actually pull data onto the buffer */
protected $remoteStream;
/** @var int The number of bytes to skip reading due to a write on the temporary buffer */
protected $skipReadBytes = 0;
/**
* We will treat the buffer object as the body of the entity body
* {@inheritdoc}
*/
public function __construct(EntityBodyInterface $body)
{
$this->remoteStream = $body;
$this->body = new EntityBody(fopen('php://temp', 'r+'));
}
/**
* Will give the contents of the buffer followed by the exhausted remote stream.
*
* Warning: Loads the entire stream into memory
*
* @return string
*/
public function __toString()
{
$pos = $this->ftell();
$this->rewind();
$str = '';
while (!$this->isConsumed()) {
$str .= $this->read(16384);
}
$this->seek($pos);
return $str;
}
public function getSize()
{
return max($this->body->getSize(), $this->remoteStream->getSize());
}
/**
* {@inheritdoc}
* @throws RuntimeException When seeking with SEEK_END or when seeking past the total size of the buffer stream
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence == SEEK_SET) {
$byte = $offset;
} elseif ($whence == SEEK_CUR) {
$byte = $offset + $this->ftell();
} else {
throw new RuntimeException(__CLASS__ . ' supports only SEEK_SET and SEEK_CUR seek operations');
}
// You cannot skip ahead past where you've read from the remote stream
if ($byte > $this->body->getSize()) {
throw new RuntimeException(
"Cannot seek to byte {$byte} when the buffered stream only contains {$this->body->getSize()} bytes"
);
}
return $this->body->seek($byte);
}
public function rewind()
{
return $this->seek(0);
}
/**
* Does not support custom rewind functions
*
* @throws RuntimeException
*/
public function setRewindFunction($callable)
{
throw new RuntimeException(__CLASS__ . ' does not support custom stream rewind functions');
}
public function read($length)
{
// Perform a regular read on any previously read data from the buffer
$data = $this->body->read($length);
$remaining = $length - strlen($data);
// More data was requested so read from the remote stream
if ($remaining) {
// If data was written to the buffer in a position that would have been filled from the remote stream,
// then we must skip bytes on the remote stream to emulate overwriting bytes from that position. This
// mimics the behavior of other PHP stream wrappers.
$remoteData = $this->remoteStream->read($remaining + $this->skipReadBytes);
if ($this->skipReadBytes) {
$len = strlen($remoteData);
$remoteData = substr($remoteData, $this->skipReadBytes);
$this->skipReadBytes = max(0, $this->skipReadBytes - $len);
}
$data .= $remoteData;
$this->body->write($remoteData);
}
return $data;
}
public function write($string)
{
// When appending to the end of the currently read stream, you'll want to skip bytes from being read from
// the remote stream to emulate other stream wrappers. Basically replacing bytes of data of a fixed length.
$overflow = (strlen($string) + $this->ftell()) - $this->remoteStream->ftell();
if ($overflow > 0) {
$this->skipReadBytes += $overflow;
}
return $this->body->write($string);
}
/**
* {@inheritdoc}
* @link http://php.net/manual/en/function.fgets.php
*/
public function readLine($maxLength = null)
{
$buffer = '';
$size = 0;
while (!$this->isConsumed()) {
$byte = $this->read(1);
$buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached
if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
break;
}
}
return $buffer;
}
public function isConsumed()
{
return $this->body->isConsumed() && $this->remoteStream->isConsumed();
}
/**
* Close both the remote stream and buffer stream
*/
public function close()
{
return $this->remoteStream->close() && $this->body->close();
}
public function setStream($stream, $size = 0)
{
$this->remoteStream->setStream($stream, $size);
}
public function getContentType()
{
return $this->remoteStream->getContentType();
}
public function getContentEncoding()
{
return $this->remoteStream->getContentEncoding();
}
public function getMetaData($key = null)
{
return $this->remoteStream->getMetaData($key);
}
public function getStream()
{
return $this->remoteStream->getStream();
}
public function getWrapper()
{
return $this->remoteStream->getWrapper();
}
public function getWrapperData()
{
return $this->remoteStream->getWrapperData();
}
public function getStreamType()
{
return $this->remoteStream->getStreamType();
}
public function getUri()
{
return $this->remoteStream->getUri();
}
/**
* Always retrieve custom data from the remote stream
* {@inheritdoc}
*/
public function getCustomData($key)
{
return $this->remoteStream->getCustomData($key);
}
/**
* Always set custom data on the remote stream
* {@inheritdoc}
*/
public function setCustomData($key, $value)
{
$this->remoteStream->setCustomData($key, $value);
return $this;
}
}

View File

@ -0,0 +1,506 @@
<?php
namespace Guzzle\Http;
use Guzzle\Common\Collection;
use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Common\Exception\ExceptionCollection;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Exception\RuntimeException;
use Guzzle\Common\Version;
use Guzzle\Parser\ParserRegistry;
use Guzzle\Parser\UriTemplate\UriTemplateInterface;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\RequestFactory;
use Guzzle\Http\Message\RequestFactoryInterface;
use Guzzle\Http\Curl\CurlMultiInterface;
use Guzzle\Http\Curl\CurlMultiProxy;
use Guzzle\Http\Curl\CurlHandle;
use Guzzle\Http\Curl\CurlVersion;
/**
* HTTP client
*/
class Client extends AbstractHasDispatcher implements ClientInterface
{
/** @deprecated Use [request.options][params] */
const REQUEST_PARAMS = 'request.params';
const REQUEST_OPTIONS = 'request.options';
const CURL_OPTIONS = 'curl.options';
const SSL_CERT_AUTHORITY = 'ssl.certificate_authority';
const DISABLE_REDIRECTS = RedirectPlugin::DISABLE;
/** @var Collection Default HTTP headers to set on each request */
protected $defaultHeaders;
/** @var string The user agent string to set on each request */
protected $userAgent;
/** @var Collection Parameter object holding configuration data */
private $config;
/** @var Url Base URL of the client */
private $baseUrl;
/** @var CurlMultiInterface CurlMulti object used internally */
private $curlMulti;
/** @var UriTemplateInterface URI template owned by the client */
private $uriTemplate;
/** @var RequestFactoryInterface Request factory used by the client */
protected $requestFactory;
public static function getAllEvents()
{
return array(self::CREATE_REQUEST);
}
/**
* @param string $baseUrl Base URL of the web service
* @param array|Collection $config Configuration settings
*
* @throws RuntimeException if cURL is not installed
*/
public function __construct($baseUrl = '', $config = null)
{
if (!extension_loaded('curl')) {
// @codeCoverageIgnoreStart
throw new RuntimeException('The PHP cURL extension must be installed to use Guzzle.');
// @codeCoverageIgnoreEnd
}
$this->setConfig($config ?: new Collection());
$this->initSsl();
$this->setBaseUrl($baseUrl);
$this->defaultHeaders = new Collection();
$this->setRequestFactory(RequestFactory::getInstance());
$this->userAgent = $this->getDefaultUserAgent();
if (!$this->config[self::DISABLE_REDIRECTS]) {
$this->addSubscriber(new RedirectPlugin());
}
}
final public function setConfig($config)
{
if ($config instanceof Collection) {
$this->config = $config;
} elseif (is_array($config)) {
$this->config = new Collection($config);
} else {
throw new InvalidArgumentException('Config must be an array or Collection');
}
return $this;
}
final public function getConfig($key = false)
{
return $key ? $this->config[$key] : $this->config;
}
/**
* Set a default request option on the client that will be used as a default for each request
*
* @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo)
* @param mixed $value Value to set
*
* @return $this
*/
public function setDefaultOption($keyOrPath, $value)
{
$keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
$this->config->setPath($keyOrPath, $value);
return $this;
}
/**
* Retrieve a default request option from the client
*
* @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo)
*
* @return mixed|null
*/
public function getDefaultOption($keyOrPath)
{
$keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
return $this->config->getPath($keyOrPath);
}
final public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2)
{
$opts = $this->config[self::CURL_OPTIONS] ?: array();
if ($certificateAuthority === true) {
// use bundled CA bundle, set secure defaults
$opts[CURLOPT_CAINFO] = __DIR__ . '/Resources/cacert.pem';
$opts[CURLOPT_SSL_VERIFYPEER] = true;
$opts[CURLOPT_SSL_VERIFYHOST] = 2;
} elseif ($certificateAuthority === false) {
unset($opts[CURLOPT_CAINFO]);
$opts[CURLOPT_SSL_VERIFYPEER] = false;
$opts[CURLOPT_SSL_VERIFYHOST] = 2;
} elseif ($verifyPeer !== true && $verifyPeer !== false && $verifyPeer !== 1 && $verifyPeer !== 0) {
throw new InvalidArgumentException('verifyPeer must be 1, 0 or boolean');
} elseif ($verifyHost !== 0 && $verifyHost !== 1 && $verifyHost !== 2) {
throw new InvalidArgumentException('verifyHost must be 0, 1 or 2');
} else {
$opts[CURLOPT_SSL_VERIFYPEER] = $verifyPeer;
$opts[CURLOPT_SSL_VERIFYHOST] = $verifyHost;
if (is_file($certificateAuthority)) {
unset($opts[CURLOPT_CAPATH]);
$opts[CURLOPT_CAINFO] = $certificateAuthority;
} elseif (is_dir($certificateAuthority)) {
unset($opts[CURLOPT_CAINFO]);
$opts[CURLOPT_CAPATH] = $certificateAuthority;
} else {
throw new RuntimeException(
'Invalid option passed to ' . self::SSL_CERT_AUTHORITY . ': ' . $certificateAuthority
);
}
}
$this->config->set(self::CURL_OPTIONS, $opts);
return $this;
}
public function createRequest($method = 'GET', $uri = null, $headers = null, $body = null, array $options = array())
{
if (!$uri) {
$url = $this->getBaseUrl();
} else {
if (!is_array($uri)) {
$templateVars = null;
} else {
list($uri, $templateVars) = $uri;
}
if (substr($uri, 0, 4) === 'http') {
// Use absolute URLs as-is
$url = $this->expandTemplate($uri, $templateVars);
} else {
$url = Url::factory($this->getBaseUrl())->combine($this->expandTemplate($uri, $templateVars));
}
}
// If default headers are provided, then merge them under any explicitly provided headers for the request
if (count($this->defaultHeaders)) {
if (!$headers) {
$headers = $this->defaultHeaders->toArray();
} elseif (is_array($headers)) {
$headers += $this->defaultHeaders->toArray();
} elseif ($headers instanceof Collection) {
$headers = $headers->toArray() + $this->defaultHeaders->toArray();
}
}
return $this->prepareRequest($this->requestFactory->create($method, (string) $url, $headers, $body), $options);
}
public function getBaseUrl($expand = true)
{
return $expand ? $this->expandTemplate($this->baseUrl) : $this->baseUrl;
}
public function setBaseUrl($url)
{
$this->baseUrl = $url;
return $this;
}
public function setUserAgent($userAgent, $includeDefault = false)
{
if ($includeDefault) {
$userAgent .= ' ' . $this->getDefaultUserAgent();
}
$this->userAgent = $userAgent;
return $this;
}
/**
* Get the default User-Agent string to use with Guzzle
*
* @return string
*/
public function getDefaultUserAgent()
{
return 'Guzzle/' . Version::VERSION
. ' curl/' . CurlVersion::getInstance()->get('version')
. ' PHP/' . PHP_VERSION;
}
public function get($uri = null, $headers = null, $options = array())
{
// BC compat: $options can be a string, resource, etc to specify where the response body is downloaded
return is_array($options)
? $this->createRequest('GET', $uri, $headers, null, $options)
: $this->createRequest('GET', $uri, $headers, $options);
}
public function head($uri = null, $headers = null, array $options = array())
{
return $this->createRequest('HEAD', $uri, $headers, null, $options);
}
public function delete($uri = null, $headers = null, $body = null, array $options = array())
{
return $this->createRequest('DELETE', $uri, $headers, $body, $options);
}
public function put($uri = null, $headers = null, $body = null, array $options = array())
{
return $this->createRequest('PUT', $uri, $headers, $body, $options);
}
public function patch($uri = null, $headers = null, $body = null, array $options = array())
{
return $this->createRequest('PATCH', $uri, $headers, $body, $options);
}
public function post($uri = null, $headers = null, $postBody = null, array $options = array())
{
return $this->createRequest('POST', $uri, $headers, $postBody, $options);
}
public function options($uri = null, array $options = array())
{
return $this->createRequest('OPTIONS', $uri, $options);
}
public function send($requests)
{
if (!($requests instanceof RequestInterface)) {
return $this->sendMultiple($requests);
}
try {
/** @var $requests RequestInterface */
$this->getCurlMulti()->add($requests)->send();
return $requests->getResponse();
} catch (ExceptionCollection $e) {
throw $e->getFirst();
}
}
/**
* Set a curl multi object to be used internally by the client for transferring requests.
*
* @param CurlMultiInterface $curlMulti Multi object
*
* @return self
*/
public function setCurlMulti(CurlMultiInterface $curlMulti)
{
$this->curlMulti = $curlMulti;
return $this;
}
/**
* @return CurlMultiInterface|CurlMultiProxy
*/
public function getCurlMulti()
{
if (!$this->curlMulti) {
$this->curlMulti = new CurlMultiProxy();
}
return $this->curlMulti;
}
public function setRequestFactory(RequestFactoryInterface $factory)
{
$this->requestFactory = $factory;
return $this;
}
/**
* Set the URI template expander to use with the client
*
* @param UriTemplateInterface $uriTemplate URI template expander
*
* @return self
*/
public function setUriTemplate(UriTemplateInterface $uriTemplate)
{
$this->uriTemplate = $uriTemplate;
return $this;
}
/**
* Copy the cacert.pem file from the phar if it is not in the temp folder and validate the MD5 checksum
*
* @param bool $md5Check Set to false to not perform the MD5 validation
*
* @return string Returns the path to the extracted cacert
* @throws RuntimeException if the file cannot be copied or there is a MD5 mismatch
*/
public function preparePharCacert($md5Check = true)
{
$from = __DIR__ . '/Resources/cacert.pem';
$certFile = sys_get_temp_dir() . '/guzzle-cacert.pem';
if (!file_exists($certFile) && !copy($from, $certFile)) {
throw new RuntimeException("Could not copy {$from} to {$certFile}: " . var_export(error_get_last(), true));
} elseif ($md5Check) {
$actualMd5 = md5_file($certFile);
$expectedMd5 = trim(file_get_contents("{$from}.md5"));
if ($actualMd5 != $expectedMd5) {
throw new RuntimeException("{$certFile} MD5 mismatch: expected {$expectedMd5} but got {$actualMd5}");
}
}
return $certFile;
}
/**
* Expand a URI template while merging client config settings into the template variables
*
* @param string $template Template to expand
* @param array $variables Variables to inject
*
* @return string
*/
protected function expandTemplate($template, array $variables = null)
{
$expansionVars = $this->getConfig()->toArray();
if ($variables) {
$expansionVars = $variables + $expansionVars;
}
return $this->getUriTemplate()->expand($template, $expansionVars);
}
/**
* Get the URI template expander used by the client
*
* @return UriTemplateInterface
*/
protected function getUriTemplate()
{
if (!$this->uriTemplate) {
$this->uriTemplate = ParserRegistry::getInstance()->getParser('uri_template');
}
return $this->uriTemplate;
}
/**
* Send multiple requests in parallel
*
* @param array $requests Array of RequestInterface objects
*
* @return array Returns an array of Response objects
*/
protected function sendMultiple(array $requests)
{
$curlMulti = $this->getCurlMulti();
foreach ($requests as $request) {
$curlMulti->add($request);
}
$curlMulti->send();
/** @var $request RequestInterface */
$result = array();
foreach ($requests as $request) {
$result[] = $request->getResponse();
}
return $result;
}
/**
* Prepare a request to be sent from the Client by adding client specific behaviors and properties to the request.
*
* @param RequestInterface $request Request to prepare for the client
* @param array $options Options to apply to the request
*
* @return RequestInterface
*/
protected function prepareRequest(RequestInterface $request, array $options = array())
{
$request->setClient($this)->setEventDispatcher(clone $this->getEventDispatcher());
if ($curl = $this->config[self::CURL_OPTIONS]) {
$request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($curl));
}
if ($params = $this->config[self::REQUEST_PARAMS]) {
Version::warn('request.params is deprecated. Use request.options to add default request options.');
$request->getParams()->overwriteWith($params);
}
if ($this->userAgent && !$request->hasHeader('User-Agent')) {
$request->setHeader('User-Agent', $this->userAgent);
}
if ($defaults = $this->config[self::REQUEST_OPTIONS]) {
$this->requestFactory->applyOptions($request, $defaults, RequestFactoryInterface::OPTIONS_AS_DEFAULTS);
}
if ($options) {
$this->requestFactory->applyOptions($request, $options);
}
$this->dispatch('client.create_request', array('client' => $this, 'request' => $request));
return $request;
}
/**
* Initializes SSL settings
*/
protected function initSsl()
{
if ('system' == ($authority = $this->config[self::SSL_CERT_AUTHORITY])) {
return;
}
if ($authority === null) {
$authority = true;
}
if ($authority === true && substr(__FILE__, 0, 7) == 'phar://') {
$authority = $this->preparePharCacert();
$that = $this;
$this->getEventDispatcher()->addListener('request.before_send', function ($event) use ($authority, $that) {
if ($authority == $event['request']->getCurlOptions()->get(CURLOPT_CAINFO)) {
$that->preparePharCacert(false);
}
});
}
$this->setSslVerification($authority);
}
/**
* @deprecated
*/
public function getDefaultHeaders()
{
Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to retrieve default request options');
return $this->defaultHeaders;
}
/**
* @deprecated
*/
public function setDefaultHeaders($headers)
{
Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to specify default request options');
if ($headers instanceof Collection) {
$this->defaultHeaders = $headers;
} elseif (is_array($headers)) {
$this->defaultHeaders = new Collection($headers);
} else {
throw new InvalidArgumentException('Headers must be an array or Collection');
}
return $this;
}
}

View File

@ -0,0 +1,223 @@
<?php
namespace Guzzle\Http;
use Guzzle\Common\HasDispatcherInterface;
use Guzzle\Common\Collection;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
use Guzzle\Http\Message\RequestInterface;
/**
* Client interface for send HTTP requests
*/
interface ClientInterface extends HasDispatcherInterface
{
const CREATE_REQUEST = 'client.create_request';
/** @var string RFC 1123 HTTP-Date */
const HTTP_DATE = 'D, d M Y H:i:s \G\M\T';
/**
* Set the configuration object to use with the client
*
* @param array|Collection $config Parameters that define how the client behaves
*
* @return self
*/
public function setConfig($config);
/**
* Get a configuration setting or all of the configuration settings. The Collection result of this method can be
* modified to change the configuration settings of a client.
*
* A client should honor the following special values:
*
* - request.options: Associative array of default RequestFactory options to apply to each request
* - request.params: Associative array of request parameters (data values) to apply to each request
* - curl.options: Associative array of cURL configuration settings to apply to each request
* - ssl.certificate_authority: Path a CAINFO, CAPATH, true to use strict defaults, or false to disable verification
* - redirect.disable: Set to true to disable redirects
*
* @param bool|string $key Configuration value to retrieve. Set to FALSE to retrieve all values of the client.
* The object return can be modified, and modifications will affect the client's config.
* @return mixed|Collection
* @see \Guzzle\Http\Message\RequestFactoryInterface::applyOptions for a full list of request.options options
*/
public function getConfig($key = false);
/**
* Create and return a new {@see RequestInterface} configured for the client.
*
* Use an absolute path to override the base path of the client, or a relative path to append to the base path of
* the client. The URI can contain the query string as well. Use an array to provide a URI template and additional
* variables to use in the URI template expansion.
*
* @param string $method HTTP method. Defaults to GET
* @param string|array $uri Resource URI.
* @param array|Collection $headers HTTP headers
* @param string|resource|array|EntityBodyInterface $body Entity body of request (POST/PUT) or response (GET)
* @param array $options Array of options to apply to the request
*
* @return RequestInterface
* @throws InvalidArgumentException if a URI array is passed that does not contain exactly two elements: the URI
* followed by template variables
*/
public function createRequest(
$method = RequestInterface::GET,
$uri = null,
$headers = null,
$body = null,
array $options = array()
);
/**
* Create a GET request for the client
*
* @param string|array $uri Resource URI
* @param array|Collection $headers HTTP headers
* @param array $options Options to apply to the request. For BC compatibility, you can also pass a
* string to tell Guzzle to download the body of the response to a particular
* location. Use the 'body' option instead for forward compatibility.
* @return RequestInterface
* @see Guzzle\Http\ClientInterface::createRequest()
*/
public function get($uri = null, $headers = null, $options = array());
/**
* Create a HEAD request for the client
*
* @param string|array $uri Resource URI
* @param array|Collection $headers HTTP headers
* @param array $options Options to apply to the request
*
* @return RequestInterface
* @see Guzzle\Http\ClientInterface::createRequest()
*/
public function head($uri = null, $headers = null, array $options = array());
/**
* Create a DELETE request for the client
*
* @param string|array $uri Resource URI
* @param array|Collection $headers HTTP headers
* @param string|resource|EntityBodyInterface $body Body to send in the request
* @param array $options Options to apply to the request
*
* @return EntityEnclosingRequestInterface
* @see Guzzle\Http\ClientInterface::createRequest()
*/
public function delete($uri = null, $headers = null, $body = null, array $options = array());
/**
* Create a PUT request for the client
*
* @param string|array $uri Resource URI
* @param array|Collection $headers HTTP headers
* @param string|resource|EntityBodyInterface $body Body to send in the request
* @param array $options Options to apply to the request
*
* @return EntityEnclosingRequestInterface
* @see Guzzle\Http\ClientInterface::createRequest()
*/
public function put($uri = null, $headers = null, $body = null, array $options = array());
/**
* Create a PATCH request for the client
*
* @param string|array $uri Resource URI
* @param array|Collection $headers HTTP headers
* @param string|resource|EntityBodyInterface $body Body to send in the request
* @param array $options Options to apply to the request
*
* @return EntityEnclosingRequestInterface
* @see Guzzle\Http\ClientInterface::createRequest()
*/
public function patch($uri = null, $headers = null, $body = null, array $options = array());
/**
* Create a POST request for the client
*
* @param string|array $uri Resource URI
* @param array|Collection $headers HTTP headers
* @param array|Collection|string|EntityBodyInterface $postBody POST body. Can be a string, EntityBody, or
* associative array of POST fields to send in the body of the
* request. Prefix a value in the array with the @ symbol to
* reference a file.
* @param array $options Options to apply to the request
*
* @return EntityEnclosingRequestInterface
* @see Guzzle\Http\ClientInterface::createRequest()
*/
public function post($uri = null, $headers = null, $postBody = null, array $options = array());
/**
* Create an OPTIONS request for the client
*
* @param string|array $uri Resource URI
* @param array $options Options to apply to the request
*
* @return RequestInterface
* @see Guzzle\Http\ClientInterface::createRequest()
*/
public function options($uri = null, array $options = array());
/**
* Sends a single request or an array of requests in parallel
*
* @param array|RequestInterface $requests One or more RequestInterface objects to send
*
* @return \Guzzle\Http\Message\Response|array Returns a single Response or an array of Response objects
*/
public function send($requests);
/**
* Get the client's base URL as either an expanded or raw URI template
*
* @param bool $expand Set to FALSE to get the raw base URL without URI template expansion
*
* @return string|null
*/
public function getBaseUrl($expand = true);
/**
* Set the base URL of the client
*
* @param string $url The base service endpoint URL of the webservice
*
* @return self
*/
public function setBaseUrl($url);
/**
* Set the User-Agent header to be used on all requests from the client
*
* @param string $userAgent User agent string
* @param bool $includeDefault Set to true to prepend the value to Guzzle's default user agent string
*
* @return self
*/
public function setUserAgent($userAgent, $includeDefault = false);
/**
* Set SSL verification options.
*
* Setting $certificateAuthority to TRUE will result in the bundled cacert.pem being used to verify against the
* remote host.
*
* Alternate certificates to verify against can be specified with the $certificateAuthority option set to the full
* path to a certificate file, or the path to a directory containing certificates.
*
* Setting $certificateAuthority to FALSE will turn off peer verification, unset the bundled cacert.pem, and
* disable host verification. Please don't do this unless you really know what you're doing, and why you're doing
* it.
*
* @param string|bool $certificateAuthority bool, file path, or directory path
* @param bool $verifyPeer FALSE to stop from verifying the peer's certificate.
* @param int $verifyHost Set to 1 to check the existence of a common name in the SSL peer
* certificate. 2 to check the existence of a common name and also verify
* that it matches the hostname provided.
* @return self
*/
public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2);
}

View File

@ -0,0 +1,451 @@
<?php
namespace Guzzle\Http\Curl;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Exception\RuntimeException;
use Guzzle\Common\Collection;
use Guzzle\Http\Message\EntityEnclosingRequest;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Parser\ParserRegistry;
use Guzzle\Http\Url;
/**
* Immutable wrapper for a cURL handle
*/
class CurlHandle
{
const BODY_AS_STRING = 'body_as_string';
const PROGRESS = 'progress';
const DEBUG = 'debug';
/** @var Collection Curl options */
protected $options;
/** @var resource Curl resource handle */
protected $handle;
/** @var int CURLE_* error */
protected $errorNo = CURLE_OK;
/**
* Factory method to create a new curl handle based on an HTTP request.
*
* There are some helpful options you can set to enable specific behavior:
* - debug: Set to true to enable cURL debug functionality to track the actual headers sent over the wire.
* - progress: Set to true to enable progress function callbacks.
*
* @param RequestInterface $request Request
*
* @return CurlHandle
* @throws RuntimeException
*/
public static function factory(RequestInterface $request)
{
$requestCurlOptions = $request->getCurlOptions();
$mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io'));
$tempContentLength = null;
$method = $request->getMethod();
$bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING);
// Array of default cURL options.
$curlOptions = array(
CURLOPT_URL => $request->getUrl(),
CURLOPT_CONNECTTIMEOUT => 150,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
CURLOPT_PORT => $request->getPort(),
CURLOPT_HTTPHEADER => array(),
CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'),
CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'),
CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0'
? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
// Verifies the authenticity of the peer's certificate
CURLOPT_SSL_VERIFYPEER => 1,
// Certificate must indicate that the server is the server to which you meant to connect
CURLOPT_SSL_VERIFYHOST => 2
);
if (defined('CURLOPT_PROTOCOLS')) {
// Allow only HTTP and HTTPS protocols
$curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
}
// Add CURLOPT_ENCODING if Accept-Encoding header is provided
if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) {
$curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader;
// Let cURL set the Accept-Encoding header, prevents duplicate values
$request->removeHeader('Accept-Encoding');
}
// Enable curl debug information if the 'debug' param was set
if ($requestCurlOptions->get('debug')) {
$curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+');
// @codeCoverageIgnoreStart
if (false === $curlOptions[CURLOPT_STDERR]) {
throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR');
}
// @codeCoverageIgnoreEnd
$curlOptions[CURLOPT_VERBOSE] = true;
}
// Specify settings according to the HTTP method
if ($method == 'GET') {
$curlOptions[CURLOPT_HTTPGET] = true;
} elseif ($method == 'HEAD') {
$curlOptions[CURLOPT_NOBODY] = true;
// HEAD requests do not use a write function
unset($curlOptions[CURLOPT_WRITEFUNCTION]);
} elseif (!($request instanceof EntityEnclosingRequest)) {
$curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
} else {
$curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
// Handle sending raw bodies in a request
if ($request->getBody()) {
// You can send the body as a string using curl's CURLOPT_POSTFIELDS
if ($bodyAsString) {
$curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody();
// Allow curl to add the Content-Length for us to account for the times when
// POST redirects are followed by GET requests
if ($tempContentLength = $request->getHeader('Content-Length')) {
$tempContentLength = (int) (string) $tempContentLength;
}
// Remove the curl generated Content-Type header if none was set manually
if (!$request->hasHeader('Content-Type')) {
$curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:';
}
} else {
$curlOptions[CURLOPT_UPLOAD] = true;
// Let cURL handle setting the Content-Length header
if ($tempContentLength = $request->getHeader('Content-Length')) {
$tempContentLength = (int) (string) $tempContentLength;
$curlOptions[CURLOPT_INFILESIZE] = $tempContentLength;
}
// Add a callback for curl to read data to send with the request only if a body was specified
$curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody');
// Attempt to seek to the start of the stream
$request->getBody()->seek(0);
}
} else {
// Special handling for POST specific fields and files
$postFields = false;
if (count($request->getPostFiles())) {
$postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode();
foreach ($request->getPostFiles() as $key => $data) {
$prefixKeys = count($data) > 1;
foreach ($data as $index => $file) {
// Allow multiple files in the same key
$fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key;
$postFields[$fieldKey] = $file->getCurlValue();
}
}
} elseif (count($request->getPostFields())) {
$postFields = (string) $request->getPostFields()->useUrlEncoding(true);
}
if ($postFields !== false) {
if ($method == 'POST') {
unset($curlOptions[CURLOPT_CUSTOMREQUEST]);
$curlOptions[CURLOPT_POST] = true;
}
$curlOptions[CURLOPT_POSTFIELDS] = $postFields;
$request->removeHeader('Content-Length');
}
}
// If the Expect header is not present, prevent curl from adding it
if (!$request->hasHeader('Expect')) {
$curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:';
}
}
// If a Content-Length header was specified but we want to allow curl to set one for us
if (null !== $tempContentLength) {
$request->removeHeader('Content-Length');
}
// Set custom cURL options
foreach ($requestCurlOptions->toArray() as $key => $value) {
if (is_numeric($key)) {
$curlOptions[$key] = $value;
}
}
// Do not set an Accept header by default
if (!isset($curlOptions[CURLOPT_ENCODING])) {
$curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:';
}
// Add any custom headers to the request. Empty headers will cause curl to not send the header at all.
foreach ($request->getHeaderLines() as $line) {
$curlOptions[CURLOPT_HTTPHEADER][] = $line;
}
// Add the content-length header back if it was temporarily removed
if ($tempContentLength) {
$request->setHeader('Content-Length', $tempContentLength);
}
// Apply the options to a new cURL handle.
$handle = curl_init();
// Enable the progress function if the 'progress' param was set
if ($requestCurlOptions->get('progress')) {
// Wrap the function in a function that provides the curl handle to the mediator's progress function
// Using this rather than injecting the handle into the mediator prevents a circular reference
$curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) {
$args = func_get_args();
$args[] = $handle;
call_user_func_array(array($mediator, 'progress'), $args);
};
$curlOptions[CURLOPT_NOPROGRESS] = false;
}
curl_setopt_array($handle, $curlOptions);
return new static($handle, $curlOptions);
}
/**
* Construct a new CurlHandle object that wraps a cURL handle
*
* @param resource $handle Configured cURL handle resource
* @param Collection|array $options Curl options to use with the handle
*
* @throws InvalidArgumentException
*/
public function __construct($handle, $options)
{
if (!is_resource($handle)) {
throw new InvalidArgumentException('Invalid handle provided');
}
if (is_array($options)) {
$this->options = new Collection($options);
} elseif ($options instanceof Collection) {
$this->options = $options;
} else {
throw new InvalidArgumentException('Expected array or Collection');
}
$this->handle = $handle;
}
/**
* Destructor
*/
public function __destruct()
{
$this->close();
}
/**
* Close the curl handle
*/
public function close()
{
if (is_resource($this->handle)) {
curl_close($this->handle);
}
$this->handle = null;
}
/**
* Check if the handle is available and still OK
*
* @return bool
*/
public function isAvailable()
{
return is_resource($this->handle);
}
/**
* Get the last error that occurred on the cURL handle
*
* @return string
*/
public function getError()
{
return $this->isAvailable() ? curl_error($this->handle) : '';
}
/**
* Get the last error number that occurred on the cURL handle
*
* @return int
*/
public function getErrorNo()
{
if ($this->errorNo) {
return $this->errorNo;
}
return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK;
}
/**
* Set the curl error number
*
* @param int $error Error number to set
*
* @return CurlHandle
*/
public function setErrorNo($error)
{
$this->errorNo = $error;
return $this;
}
/**
* Get cURL curl_getinfo data
*
* @param int $option Option to retrieve. Pass null to retrieve all data as an array.
*
* @return array|mixed
*/
public function getInfo($option = null)
{
if (!is_resource($this->handle)) {
return null;
}
if (null !== $option) {
return curl_getinfo($this->handle, $option) ?: null;
}
return curl_getinfo($this->handle) ?: array();
}
/**
* Get the stderr output
*
* @param bool $asResource Set to TRUE to get an fopen resource
*
* @return string|resource|null
*/
public function getStderr($asResource = false)
{
$stderr = $this->getOptions()->get(CURLOPT_STDERR);
if (!$stderr) {
return null;
}
if ($asResource) {
return $stderr;
}
fseek($stderr, 0);
$e = stream_get_contents($stderr);
fseek($stderr, 0, SEEK_END);
return $e;
}
/**
* Get the URL that this handle is connecting to
*
* @return Url
*/
public function getUrl()
{
return Url::factory($this->options->get(CURLOPT_URL));
}
/**
* Get the wrapped curl handle
*
* @return resource|null Returns the cURL handle or null if it was closed
*/
public function getHandle()
{
return $this->isAvailable() ? $this->handle : null;
}
/**
* Get the cURL setopt options of the handle. Changing values in the return object will have no effect on the curl
* handle after it is created.
*
* @return Collection
*/
public function getOptions()
{
return $this->options;
}
/**
* Update a request based on the log messages of the CurlHandle
*
* @param RequestInterface $request Request to update
*/
public function updateRequestFromTransfer(RequestInterface $request)
{
if (!$request->getResponse()) {
return;
}
// Update the transfer stats of the response
$request->getResponse()->setInfo($this->getInfo());
if (!$log = $this->getStderr(true)) {
return;
}
// Parse the cURL stderr output for outgoing requests
$headers = '';
fseek($log, 0);
while (($line = fgets($log)) !== false) {
if ($line && $line[0] == '>') {
$headers = substr(trim($line), 2) . "\r\n";
while (($line = fgets($log)) !== false) {
if ($line[0] == '*' || $line[0] == '<') {
break;
} else {
$headers .= trim($line) . "\r\n";
}
}
}
}
// Add request headers to the request exactly as they were sent
if ($headers) {
$parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers);
if (!empty($parsed['headers'])) {
$request->setHeaders(array());
foreach ($parsed['headers'] as $name => $value) {
$request->setHeader($name, $value);
}
}
if (!empty($parsed['version'])) {
$request->setProtocolVersion($parsed['version']);
}
}
}
/**
* Parse the config and replace curl.* configurators into the constant based values so it can be used elsewhere
*
* @param array|Collection $config The configuration we want to parse
*
* @return array
*/
public static function parseCurlConfig($config)
{
$curlOptions = array();
foreach ($config as $key => $value) {
if (is_string($key) && defined($key)) {
// Convert constants represented as string to constant int values
$key = constant($key);
}
if (is_string($value) && defined($value)) {
$value = constant($value);
}
$curlOptions[$key] = $value;
}
return $curlOptions;
}
}

View File

@ -0,0 +1,390 @@
<?php
namespace Guzzle\Http\Curl;
use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Common\Event;
use Guzzle\Http\Exception\MultiTransferException;
use Guzzle\Http\Exception\CurlException;
use Guzzle\Http\Message\RequestInterface;
/**
* Send {@see RequestInterface} objects in parallel using curl_multi
*/
class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
{
/** @var resource cURL multi handle. */
protected $multiHandle;
/** @var array Attached {@see RequestInterface} objects. */
protected $requests;
/** @var \SplObjectStorage RequestInterface to CurlHandle hash */
protected $handles;
/** @var array Hash mapping curl handle resource IDs to request objects */
protected $resourceHash;
/** @var array Queued exceptions */
protected $exceptions = array();
/** @var array Requests that succeeded */
protected $successful = array();
/** @var array cURL multi error values and codes */
protected $multiErrors = array(
CURLM_BAD_HANDLE => array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'),
CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."),
CURLM_OUT_OF_MEMORY => array('CURLM_OUT_OF_MEMORY', 'You are doomed.'),
CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!')
);
public function __construct()
{
$this->multiHandle = curl_multi_init();
// @codeCoverageIgnoreStart
if ($this->multiHandle === false) {
throw new CurlException('Unable to create multi handle');
}
// @codeCoverageIgnoreEnd
$this->reset();
}
public function __destruct()
{
if (is_resource($this->multiHandle)) {
curl_multi_close($this->multiHandle);
}
}
public function add(RequestInterface $request)
{
$this->requests[] = $request;
// If requests are currently transferring and this is async, then the
// request must be prepared now as the send() method is not called.
$this->beforeSend($request);
$this->dispatch(self::ADD_REQUEST, array('request' => $request));
return $this;
}
public function all()
{
return $this->requests;
}
public function remove(RequestInterface $request)
{
$this->removeHandle($request);
foreach ($this->requests as $i => $r) {
if ($request === $r) {
unset($this->requests[$i]);
$this->requests = array_values($this->requests);
$this->dispatch(self::REMOVE_REQUEST, array('request' => $request));
return true;
}
}
return false;
}
public function reset($hard = false)
{
// Remove each request
if ($this->requests) {
foreach ($this->requests as $request) {
$this->remove($request);
}
}
$this->handles = new \SplObjectStorage();
$this->requests = $this->resourceHash = $this->exceptions = $this->successful = array();
}
public function send()
{
$this->perform();
$exceptions = $this->exceptions;
$successful = $this->successful;
$this->reset();
if ($exceptions) {
$this->throwMultiException($exceptions, $successful);
}
}
public function count()
{
return count($this->requests);
}
/**
* Build and throw a MultiTransferException
*
* @param array $exceptions Exceptions encountered
* @param array $successful Successful requests
* @throws MultiTransferException
*/
protected function throwMultiException(array $exceptions, array $successful)
{
$multiException = new MultiTransferException('Errors during multi transfer');
while ($e = array_shift($exceptions)) {
$multiException->add($e['exception']);
$multiException->addFailedRequest($e['request']);
}
// Add successful requests
foreach ($successful as $request) {
if (!$multiException->containsRequest($request)) {
$multiException->addSuccessfulRequest($request);
}
}
throw $multiException;
}
/**
* Prepare for sending
*
* @param RequestInterface $request Request to prepare
* @throws \Exception on error preparing the request
*/
protected function beforeSend(RequestInterface $request)
{
try {
$state = $request->setState(RequestInterface::STATE_TRANSFER);
if ($state == RequestInterface::STATE_TRANSFER) {
// Add the request curl handle to the multi handle
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $this->createCurlHandle($request)->getHandle()));
} else {
// Requests might decide they don't need to be sent just before transfer (e.g. CachePlugin)
$this->remove($request);
if ($state == RequestInterface::STATE_COMPLETE) {
$this->successful[] = $request;
}
}
} catch (\Exception $e) {
// Queue the exception to be thrown when sent
$this->removeErroredRequest($request, $e);
}
}
/**
* Create a curl handle for a request
*
* @param RequestInterface $request Request
*
* @return CurlHandle
*/
protected function createCurlHandle(RequestInterface $request)
{
$wrapper = CurlHandle::factory($request);
$this->handles[$request] = $wrapper;
$this->resourceHash[(int) $wrapper->getHandle()] = $request;
return $wrapper;
}
/**
* Get the data from the multi handle
*/
protected function perform()
{
if (!$this->requests) {
return;
}
// Initialize the handles with a very quick select timeout
$active = $mrc = null;
$this->executeHandles($active, $mrc, 0.001);
$event = new Event(array('curl_multi' => $this));
$this->processMessages();
while ($this->requests) {
// Notify each request as polling
$blocking = $total = 0;
foreach ($this->requests as $request) {
++$total;
$event['request'] = $request;
$request->getEventDispatcher()->dispatch(self::POLLING_REQUEST, $event);
// The blocking variable just has to be non-falsey to block the loop
if ($request->getParams()->hasKey(self::BLOCKING)) {
++$blocking;
}
}
if ($blocking == $total) {
// Sleep to prevent eating CPU because no requests are actually pending a select call
usleep(500);
} else {
do {
$this->executeHandles($active, $mrc, 1);
} while ($active);
}
$this->processMessages();
}
}
/**
* Process any received curl multi messages
*/
private function processMessages()
{
// Get messages from curl handles
while ($done = curl_multi_info_read($this->multiHandle)) {
try {
$request = $this->resourceHash[(int) $done['handle']];
$this->processResponse($request, $this->handles[$request], $done);
$this->successful[] = $request;
} catch (MultiTransferException $e) {
$this->removeErroredRequest($request, $e, false);
throw $e;
} catch (\Exception $e) {
$this->removeErroredRequest($request, $e);
}
}
}
/**
* Execute and select curl handles until there is activity
*
* @param int $active Active value to update
* @param int $mrc Multi result value to update
* @param int $timeout Select timeout in seconds
*/
private function executeHandles(&$active, &$mrc, $timeout = 1)
{
do {
$mrc = curl_multi_exec($this->multiHandle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM && $active);
$this->checkCurlResult($mrc);
// @codeCoverageIgnoreStart
// Select the curl handles until there is any activity on any of the open file descriptors
// See https://github.com/php/php-src/blob/master/ext/curl/multi.c#L170
if ($active && $mrc == CURLM_OK && curl_multi_select($this->multiHandle, $timeout) == -1) {
// Perform a usleep if a previously executed select returned -1
// @see https://bugs.php.net/bug.php?id=61141
usleep(100);
}
// @codeCoverageIgnoreEnd
}
/**
* Remove a request that encountered an exception
*
* @param RequestInterface $request Request to remove
* @param \Exception $e Exception encountered
* @param bool $buffer Set to false to not buffer the exception
*/
protected function removeErroredRequest(RequestInterface $request, \Exception $e = null, $buffer = true)
{
if ($buffer) {
$this->exceptions[] = array('request' => $request, 'exception' => $e);
}
$this->remove($request);
$this->dispatch(self::MULTI_EXCEPTION, array('exception' => $e, 'all_exceptions' => $this->exceptions));
}
/**
* Check for errors and fix headers of a request based on a curl response
*
* @param RequestInterface $request Request to process
* @param CurlHandle $handle Curl handle object
* @param array $curl Array returned from curl_multi_info_read
*
* @throws CurlException on Curl error
*/
protected function processResponse(RequestInterface $request, CurlHandle $handle, array $curl)
{
// Set the transfer stats on the response
$handle->updateRequestFromTransfer($request);
// Check if a cURL exception occurred, and if so, notify things
$curlException = $this->isCurlException($request, $handle, $curl);
// Always remove completed curl handles. They can be added back again
// via events if needed (e.g. ExponentialBackoffPlugin)
$this->removeHandle($request);
if (!$curlException) {
$state = $request->setState(RequestInterface::STATE_COMPLETE, array('handle' => $handle));
// Only remove the request if it wasn't resent as a result of the state change
if ($state != RequestInterface::STATE_TRANSFER) {
$this->remove($request);
}
} else {
// Set the state of the request to an error
$state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $curlException));
// Allow things to ignore the error if possible
if ($state != RequestInterface::STATE_TRANSFER) {
$this->remove($request);
}
// The error was not handled, so fail
if ($state == RequestInterface::STATE_ERROR) {
/** @var CurlException $curlException */
throw $curlException;
}
}
}
/**
* Remove a curl handle from the curl multi object
*
* @param RequestInterface $request Request that owns the handle
*/
protected function removeHandle(RequestInterface $request)
{
if (isset($this->handles[$request])) {
$handle = $this->handles[$request];
unset($this->handles[$request]);
unset($this->resourceHash[(int) $handle->getHandle()]);
curl_multi_remove_handle($this->multiHandle, $handle->getHandle());
$handle->close();
}
}
/**
* Check if a cURL transfer resulted in what should be an exception
*
* @param RequestInterface $request Request to check
* @param CurlHandle $handle Curl handle object
* @param array $curl Array returned from curl_multi_info_read
*
* @return CurlException|bool
*/
private function isCurlException(RequestInterface $request, CurlHandle $handle, array $curl)
{
if (CURLM_OK == $curl['result'] || CURLM_CALL_MULTI_PERFORM == $curl['result']) {
return false;
}
$handle->setErrorNo($curl['result']);
$e = new CurlException(sprintf('[curl] %s: %s [url] %s',
$handle->getErrorNo(), $handle->getError(), $handle->getUrl()));
$e->setCurlHandle($handle)
->setRequest($request)
->setCurlInfo($handle->getInfo())
->setError($handle->getError(), $handle->getErrorNo());
return $e;
}
/**
* Throw an exception for a cURL multi response if needed
*
* @param int $code Curl response code
* @throws CurlException
*/
private function checkCurlResult($code)
{
if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) {
throw new CurlException(isset($this->multiErrors[$code])
? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}"
: 'Unexpected cURL error: ' . $code
);
}
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Guzzle\Http\Curl;
use Guzzle\Common\HasDispatcherInterface;
use Guzzle\Common\Exception\ExceptionCollection;
use Guzzle\Http\Message\RequestInterface;
/**
* Interface for sending a pool of {@see RequestInterface} objects in parallel
*/
interface CurlMultiInterface extends \Countable, HasDispatcherInterface
{
const POLLING_REQUEST = 'curl_multi.polling_request';
const ADD_REQUEST = 'curl_multi.add_request';
const REMOVE_REQUEST = 'curl_multi.remove_request';
const MULTI_EXCEPTION = 'curl_multi.exception';
const BLOCKING = 'curl_multi.blocking';
/**
* Add a request to the pool.
*
* @param RequestInterface $request Request to add
*
* @return CurlMultiInterface
*/
public function add(RequestInterface $request);
/**
* Get an array of attached {@see RequestInterface} objects
*
* @return array
*/
public function all();
/**
* Remove a request from the pool.
*
* @param RequestInterface $request Request to remove
*
* @return bool Returns true on success or false on failure
*/
public function remove(RequestInterface $request);
/**
* Reset the state and remove any attached RequestInterface objects
*
* @param bool $hard Set to true to close and reopen any open multi handles
*/
public function reset($hard = false);
/**
* Send a pool of {@see RequestInterface} requests.
*
* @throws ExceptionCollection if any requests threw exceptions during the transfer.
*/
public function send();
}

View File

@ -0,0 +1,147 @@
<?php
namespace Guzzle\Http\Curl;
use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Http\Message\RequestInterface;
/**
* Proxies requests and connections to a pool of internal curl_multi handles. Each recursive call will add requests
* to the next available CurlMulti handle.
*/
class CurlMultiProxy extends AbstractHasDispatcher implements CurlMultiInterface
{
protected $handles = array();
protected $groups = array();
protected $queued = array();
protected $maxHandles;
/**
* @param int $maxHandles The maximum number of idle CurlMulti handles to allow to remain open
*/
public function __construct($maxHandles = 3)
{
$this->maxHandles = $maxHandles;
// You can get some weird "Too many open files" errors when sending a large amount of requests in parallel.
// These two statements autoload classes before a system runs out of file descriptors so that you can get back
// valuable error messages if you run out.
class_exists('Guzzle\Http\Message\Response');
class_exists('Guzzle\Http\Exception\CurlException');
}
public function add(RequestInterface $request)
{
$this->queued[] = $request;
return $this;
}
public function all()
{
$requests = $this->queued;
foreach ($this->handles as $handle) {
$requests = array_merge($requests, $handle->all());
}
return $requests;
}
public function remove(RequestInterface $request)
{
foreach ($this->queued as $i => $r) {
if ($request === $r) {
unset($this->queued[$i]);
return true;
}
}
foreach ($this->handles as $handle) {
if ($handle->remove($request)) {
return true;
}
}
return false;
}
public function reset($hard = false)
{
$this->queued = array();
$this->groups = array();
foreach ($this->handles as $handle) {
$handle->reset();
}
if ($hard) {
$this->handles = array();
}
return $this;
}
public function send()
{
if ($this->queued) {
$group = $this->getAvailableHandle();
// Add this handle to a list of handles than is claimed
$this->groups[] = $group;
while ($request = array_shift($this->queued)) {
$group->add($request);
}
try {
$group->send();
array_pop($this->groups);
$this->cleanupHandles();
} catch (\Exception $e) {
// Remove the group and cleanup if an exception was encountered and no more requests in group
if (!$group->count()) {
array_pop($this->groups);
$this->cleanupHandles();
}
throw $e;
}
}
}
public function count()
{
return count($this->all());
}
/**
* Get an existing available CurlMulti handle or create a new one
*
* @return CurlMulti
*/
protected function getAvailableHandle()
{
// Grab a handle that is not claimed
foreach ($this->handles as $h) {
if (!in_array($h, $this->groups, true)) {
return $h;
}
}
// All are claimed, so create one
$handle = new CurlMulti();
$handle->setEventDispatcher($this->getEventDispatcher());
$this->handles[] = $handle;
return $handle;
}
/**
* Trims down unused CurlMulti handles to limit the number of open connections
*/
protected function cleanupHandles()
{
if ($diff = max(0, count($this->handles) - $this->maxHandles)) {
for ($i = count($this->handles) - 1; $i > 0 && $diff > 0; $i--) {
if (!count($this->handles[$i])) {
unset($this->handles[$i]);
$diff--;
}
}
$this->handles = array_values($this->handles);
}
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Guzzle\Http\Curl;
/**
* Class used for querying curl_version data
*/
class CurlVersion
{
/** @var array curl_version() information */
protected $version;
/** @var CurlVersion */
protected static $instance;
/** @var string Default user agent */
protected $userAgent;
/**
* @return CurlVersion
*/
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Get all of the curl_version() data
*
* @return array
*/
public function getAll()
{
if (!$this->version) {
$this->version = curl_version();
}
return $this->version;
}
/**
* Get a specific type of curl information
*
* @param string $type Version information to retrieve. This value is one of:
* - version_number: cURL 24 bit version number
* - version: cURL version number, as a string
* - ssl_version_number: OpenSSL 24 bit version number
* - ssl_version: OpenSSL version number, as a string
* - libz_version: zlib version number, as a string
* - host: Information about the host where cURL was built
* - features: A bitmask of the CURL_VERSION_XXX constants
* - protocols: An array of protocols names supported by cURL
*
* @return string|float|bool if the $type is found, and false if not found
*/
public function get($type)
{
$version = $this->getAll();
return isset($version[$type]) ? $version[$type] : false;
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace Guzzle\Http\Curl;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\EntityBody;
use Guzzle\Http\Message\Response;
/**
* Mediator between curl handles and request objects
*/
class RequestMediator
{
/** @var RequestInterface */
protected $request;
/** @var bool Whether or not to emit read/write events */
protected $emitIo;
/**
* @param RequestInterface $request Request to mediate
* @param bool $emitIo Set to true to dispatch events on input and output
*/
public function __construct(RequestInterface $request, $emitIo = false)
{
$this->request = $request;
$this->emitIo = $emitIo;
}
/**
* Receive a response header from curl
*
* @param resource $curl Curl handle
* @param string $header Received header
*
* @return int
*/
public function receiveResponseHeader($curl, $header)
{
static $normalize = array("\r", "\n");
$length = strlen($header);
$header = str_replace($normalize, '', $header);
if (strpos($header, 'HTTP/') === 0) {
$startLine = explode(' ', $header, 3);
$code = $startLine[1];
$status = isset($startLine[2]) ? $startLine[2] : '';
// Only download the body of the response to the specified response
// body when a successful response is received.
if ($code >= 200 && $code < 300) {
$body = $this->request->getResponseBody();
} else {
$body = EntityBody::factory();
}
$response = new Response($code, null, $body);
$response->setStatus($code, $status);
$this->request->startResponse($response);
$this->request->dispatch('request.receive.status_line', array(
'request' => $this,
'line' => $header,
'status_code' => $code,
'reason_phrase' => $status
));
} elseif ($pos = strpos($header, ':')) {
$this->request->getResponse()->addHeader(
trim(substr($header, 0, $pos)),
trim(substr($header, $pos + 1))
);
}
return $length;
}
/**
* Received a progress notification
*
* @param int $downloadSize Total download size
* @param int $downloaded Amount of bytes downloaded
* @param int $uploadSize Total upload size
* @param int $uploaded Amount of bytes uploaded
* @param resource $handle CurlHandle object
*/
public function progress($downloadSize, $downloaded, $uploadSize, $uploaded, $handle = null)
{
$this->request->dispatch('curl.callback.progress', array(
'request' => $this->request,
'handle' => $handle,
'download_size' => $downloadSize,
'downloaded' => $downloaded,
'upload_size' => $uploadSize,
'uploaded' => $uploaded
));
}
/**
* Write data to the response body of a request
*
* @param resource $curl Curl handle
* @param string $write Data that was received
*
* @return int
*/
public function writeResponseBody($curl, $write)
{
if ($this->emitIo) {
$this->request->dispatch('curl.callback.write', array(
'request' => $this->request,
'write' => $write
));
}
return $this->request->getResponse()->getBody()->write($write);
}
/**
* Read data from the request body and send it to curl
*
* @param resource $ch Curl handle
* @param resource $fd File descriptor
* @param int $length Amount of data to read
*
* @return string
*/
public function readRequestBody($ch, $fd, $length)
{
if (!($body = $this->request->getBody())) {
return '';
}
$read = (string) $body->read($length);
if ($this->emitIo) {
$this->request->dispatch('curl.callback.read', array('request' => $this->request, 'read' => $read));
}
return $read;
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace Guzzle\Http;
use Guzzle\Common\Version;
use Guzzle\Stream\Stream;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Http\Mimetypes;
/**
* Entity body used with an HTTP request or response
*/
class EntityBody extends Stream implements EntityBodyInterface
{
/** @var bool Content-Encoding of the entity body if known */
protected $contentEncoding = false;
/** @var callable Method to invoke for rewinding a stream */
protected $rewindFunction;
/**
* Create a new EntityBody based on the input type
*
* @param resource|string|EntityBody $resource Entity body data
* @param int $size Size of the data contained in the resource
*
* @return EntityBody
* @throws InvalidArgumentException if the $resource arg is not a resource or string
*/
public static function factory($resource = '', $size = null)
{
if ($resource instanceof EntityBodyInterface) {
return $resource;
}
switch (gettype($resource)) {
case 'string':
return self::fromString($resource);
case 'resource':
return new static($resource, $size);
case 'object':
if (method_exists($resource, '__toString')) {
return self::fromString((string) $resource);
}
break;
case 'array':
return self::fromString(http_build_query($resource));
}
throw new InvalidArgumentException('Invalid resource type');
}
public function setRewindFunction($callable)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException('Must specify a callable');
}
$this->rewindFunction = $callable;
return $this;
}
public function rewind()
{
return $this->rewindFunction ? call_user_func($this->rewindFunction, $this) : parent::rewind();
}
/**
* Create a new EntityBody from a string
*
* @param string $string String of data
*
* @return EntityBody
*/
public static function fromString($string)
{
$stream = fopen('php://temp', 'r+');
if ($string !== '') {
fwrite($stream, $string);
rewind($stream);
}
return new static($stream);
}
public function compress($filter = 'zlib.deflate')
{
$result = $this->handleCompression($filter);
$this->contentEncoding = $result ? $filter : false;
return $result;
}
public function uncompress($filter = 'zlib.inflate')
{
$offsetStart = 0;
// When inflating gzipped data, the first 10 bytes must be stripped
// if a gzip header is present
if ($filter == 'zlib.inflate') {
// @codeCoverageIgnoreStart
if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) {
return false;
}
// @codeCoverageIgnoreEnd
if (stream_get_contents($this->stream, 3, 0) === "\x1f\x8b\x08") {
$offsetStart = 10;
}
}
$this->contentEncoding = false;
return $this->handleCompression($filter, $offsetStart);
}
public function getContentLength()
{
return $this->getSize();
}
public function getContentType()
{
return $this->getUri() ? Mimetypes::getInstance()->fromFilename($this->getUri()) : null;
}
public function getContentMd5($rawOutput = false, $base64Encode = false)
{
if ($hash = self::getHash($this, 'md5', $rawOutput)) {
return $hash && $base64Encode ? base64_encode($hash) : $hash;
} else {
return false;
}
}
/**
* Calculate the MD5 hash of an entity body
*
* @param EntityBodyInterface $body Entity body to calculate the hash for
* @param bool $rawOutput Whether or not to use raw output
* @param bool $base64Encode Whether or not to base64 encode raw output (only if raw output is true)
*
* @return bool|string Returns an MD5 string on success or FALSE on failure
* @deprecated This will be deprecated soon
* @codeCoverageIgnore
*/
public static function calculateMd5(EntityBodyInterface $body, $rawOutput = false, $base64Encode = false)
{
Version::warn(__CLASS__ . ' is deprecated. Use getContentMd5()');
return $body->getContentMd5($rawOutput, $base64Encode);
}
public function setStreamFilterContentEncoding($streamFilterContentEncoding)
{
$this->contentEncoding = $streamFilterContentEncoding;
return $this;
}
public function getContentEncoding()
{
return strtr($this->contentEncoding, array(
'zlib.deflate' => 'gzip',
'bzip2.compress' => 'compress'
)) ?: false;
}
protected function handleCompression($filter, $offsetStart = 0)
{
// @codeCoverageIgnoreStart
if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) {
return false;
}
// @codeCoverageIgnoreEnd
$handle = fopen('php://temp', 'r+');
$filter = @stream_filter_append($handle, $filter, STREAM_FILTER_WRITE);
if (!$filter) {
return false;
}
// Seek to the offset start if possible
$this->seek($offsetStart);
while ($data = fread($this->stream, 8096)) {
fwrite($handle, $data);
}
fclose($this->stream);
$this->stream = $handle;
stream_filter_remove($filter);
$stat = fstat($this->stream);
$this->size = $stat['size'];
$this->rebuildCache();
$this->seek(0);
// Remove any existing rewind function as the underlying stream has been replaced
$this->rewindFunction = null;
return true;
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace Guzzle\Http;
use Guzzle\Stream\StreamInterface;
/**
* Entity body used with an HTTP request or response
*/
interface EntityBodyInterface extends StreamInterface
{
/**
* Specify a custom callback used to rewind a non-seekable stream. This can be useful entity enclosing requests
* that are redirected.
*
* @param mixed $callable Callable to invoke to rewind a non-seekable stream. The callback must accept an
* EntityBodyInterface object, perform the rewind if possible, and return a boolean
* representing whether or not the rewind was successful.
* @return self
*/
public function setRewindFunction($callable);
/**
* If the stream is readable, compress the data in the stream using deflate compression. The uncompressed stream is
* then closed, and the compressed stream then becomes the wrapped stream.
*
* @param string $filter Compression filter
*
* @return bool Returns TRUE on success or FALSE on failure
*/
public function compress($filter = 'zlib.deflate');
/**
* Decompress a deflated string. Once uncompressed, the uncompressed string is then used as the wrapped stream.
*
* @param string $filter De-compression filter
*
* @return bool Returns TRUE on success or FALSE on failure
*/
public function uncompress($filter = 'zlib.inflate');
/**
* Get the Content-Length of the entity body if possible (alias of getSize)
*
* @return int|bool Returns the Content-Length or false on failure
*/
public function getContentLength();
/**
* Guess the Content-Type of a local stream
*
* @return string|null
* @see http://www.php.net/manual/en/function.finfo-open.php
*/
public function getContentType();
/**
* Get an MD5 checksum of the stream's contents
*
* @param bool $rawOutput Whether or not to use raw output
* @param bool $base64Encode Whether or not to base64 encode raw output (only if raw output is true)
*
* @return bool|string Returns an MD5 string on success or FALSE on failure
*/
public function getContentMd5($rawOutput = false, $base64Encode = false);
/**
* Get the Content-Encoding of the EntityBody
*
* @return bool|string
*/
public function getContentEncoding();
}

View File

@ -0,0 +1,70 @@
<?php
namespace Guzzle\Http\Exception;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* Http request exception thrown when a bad response is received
*/
class BadResponseException extends RequestException
{
/** @var Response */
private $response;
/**
* Factory method to create a new response exception based on the response code.
*
* @param RequestInterface $request Request
* @param Response $response Response received
*
* @return BadResponseException
*/
public static function factory(RequestInterface $request, Response $response)
{
if ($response->isClientError()) {
$label = 'Client error response';
$class = __NAMESPACE__ . '\\ClientErrorResponseException';
} elseif ($response->isServerError()) {
$label = 'Server error response';
$class = __NAMESPACE__ . '\\ServerErrorResponseException';
} else {
$label = 'Unsuccessful response';
$class = __CLASS__;
$e = new self();
}
$message = $label . PHP_EOL . implode(PHP_EOL, array(
'[status code] ' . $response->getStatusCode(),
'[reason phrase] ' . $response->getReasonPhrase(),
'[url] ' . $request->getUrl(),
));
$e = new $class($message);
$e->setResponse($response);
$e->setRequest($request);
return $e;
}
/**
* Set the response that caused the exception
*
* @param Response $response Response to set
*/
public function setResponse(Response $response)
{
$this->response = $response;
}
/**
* Get the response that caused the exception
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Guzzle\Http\Exception;
/**
* Exception when a client error is encountered (4xx codes)
*/
class ClientErrorResponseException extends BadResponseException {}

View File

@ -0,0 +1,7 @@
<?php
namespace Guzzle\Http\Exception;
use Guzzle\Common\Exception\RuntimeException;
class CouldNotRewindStreamException extends RuntimeException implements HttpException {}

View File

@ -0,0 +1,101 @@
<?php
namespace Guzzle\Http\Exception;
use Guzzle\Http\Curl\CurlHandle;
/**
* cURL request exception
*/
class CurlException extends RequestException
{
private $curlError;
private $curlErrorNo;
private $handle;
private $curlInfo = array();
/**
* Set the cURL error message
*
* @param string $error Curl error
* @param int $number Curl error number
*
* @return self
*/
public function setError($error, $number)
{
$this->curlError = $error;
$this->curlErrorNo = $number;
return $this;
}
/**
* Set the associated curl handle
*
* @param CurlHandle $handle Curl handle
*
* @return self
*/
public function setCurlHandle(CurlHandle $handle)
{
$this->handle = $handle;
return $this;
}
/**
* Get the associated cURL handle
*
* @return CurlHandle|null
*/
public function getCurlHandle()
{
return $this->handle;
}
/**
* Get the associated cURL error message
*
* @return string|null
*/
public function getError()
{
return $this->curlError;
}
/**
* Get the associated cURL error number
*
* @return int|null
*/
public function getErrorNo()
{
return $this->curlErrorNo;
}
/**
* Returns curl information about the transfer
*
* @return array
*/
public function getCurlInfo()
{
return $this->curlInfo;
}
/**
* Set curl transfer information
*
* @param array $info Array of curl transfer information
*
* @return self
* @link http://php.net/manual/en/function.curl-getinfo.php
*/
public function setCurlInfo(array $info)
{
$this->curlInfo = $info;
return $this;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Guzzle\Http\Exception;
use Guzzle\Common\Exception\GuzzleException;
/**
* Http exception interface
*/
interface HttpException extends GuzzleException {}

View File

@ -0,0 +1,113 @@
<?php
namespace Guzzle\Http\Exception;
use Guzzle\Common\Exception\ExceptionCollection;
use Guzzle\Http\Message\RequestInterface;
/**
* Exception encountered during a multi transfer
*/
class MultiTransferException extends ExceptionCollection
{
protected $successfulRequests = array();
protected $failedRequests = array();
/**
* Get all of the requests in the transfer
*
* @return array
*/
public function getAllRequests()
{
return array_merge($this->successfulRequests, $this->failedRequests);
}
/**
* Add to the array of successful requests
*
* @param RequestInterface $request Successful request
*
* @return self
*/
public function addSuccessfulRequest(RequestInterface $request)
{
$this->successfulRequests[] = $request;
return $this;
}
/**
* Add to the array of failed requests
*
* @param RequestInterface $request Failed request
*
* @return self
*/
public function addFailedRequest(RequestInterface $request)
{
$this->failedRequests[] = $request;
return $this;
}
/**
* Set all of the successful requests
*
* @param array Array of requests
*
* @return self
*/
public function setSuccessfulRequests(array $requests)
{
$this->successfulRequests = $requests;
return $this;
}
/**
* Set all of the failed requests
*
* @param array Array of requests
*
* @return self
*/
public function setFailedRequests(array $requests)
{
$this->failedRequests = $requests;
return $this;
}
/**
* Get an array of successful requests sent in the multi transfer
*
* @return array
*/
public function getSuccessfulRequests()
{
return $this->successfulRequests;
}
/**
* Get an array of failed requests sent in the multi transfer
*
* @return array
*/
public function getFailedRequests()
{
return $this->failedRequests;
}
/**
* Check if the exception object contains a request
*
* @param RequestInterface $request Request to check
*
* @return bool
*/
public function containsRequest(RequestInterface $request)
{
return in_array($request, $this->failedRequests, true) || in_array($request, $this->successfulRequests, true);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Guzzle\Http\Exception;
use Guzzle\Common\Exception\RuntimeException;
use Guzzle\Http\Message\RequestInterface;
/**
* Http request exception
*/
class RequestException extends RuntimeException implements HttpException
{
/** @var RequestInterface */
protected $request;
/**
* Set the request that caused the exception
*
* @param RequestInterface $request Request to set
*
* @return RequestException
*/
public function setRequest(RequestInterface $request)
{
$this->request = $request;
return $this;
}
/**
* Get the request that caused the exception
*
* @return RequestInterface
*/
public function getRequest()
{
return $this->request;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Guzzle\Http\Exception;
/**
* Exception when a server error is encountered (5xx codes)
*/
class ServerErrorResponseException extends BadResponseException {}

View File

@ -0,0 +1,5 @@
<?php
namespace Guzzle\Http\Exception;
class TooManyRedirectsException extends BadResponseException {}

View File

@ -0,0 +1,83 @@
<?php
namespace Guzzle\Http;
use Guzzle\Common\Event;
use Guzzle\Common\HasDispatcherInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* EntityBody decorator that emits events for read and write methods
*/
class IoEmittingEntityBody extends AbstractEntityBodyDecorator implements HasDispatcherInterface
{
/** @var EventDispatcherInterface */
protected $eventDispatcher;
public static function getAllEvents()
{
return array('body.read', 'body.write');
}
/**
* {@inheritdoc}
* @codeCoverageIgnore
*/
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
return $this;
}
public function getEventDispatcher()
{
if (!$this->eventDispatcher) {
$this->eventDispatcher = new EventDispatcher();
}
return $this->eventDispatcher;
}
public function dispatch($eventName, array $context = array())
{
$this->getEventDispatcher()->dispatch($eventName, new Event($context));
}
/**
* {@inheritdoc}
* @codeCoverageIgnore
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
$this->getEventDispatcher()->addSubscriber($subscriber);
return $this;
}
public function read($length)
{
$event = array(
'body' => $this,
'length' => $length,
'read' => $this->body->read($length)
);
$this->dispatch('body.read', $event);
return $event['read'];
}
public function write($string)
{
$event = array(
'body' => $this,
'write' => $string,
'result' => $this->body->write($string)
);
$this->dispatch('body.write', $event);
return $event['result'];
}
}

View File

@ -0,0 +1,220 @@
<?php
namespace Guzzle\Http\Message;
use Guzzle\Common\Version;
use Guzzle\Common\Collection;
use Guzzle\Http\Message\Header\HeaderCollection;
use Guzzle\Http\Message\Header\HeaderFactory;
use Guzzle\Http\Message\Header\HeaderFactoryInterface;
use Guzzle\Http\Message\Header\HeaderInterface;
/**
* Abstract HTTP request/response message
*/
abstract class AbstractMessage implements MessageInterface
{
/** @var array HTTP header collection */
protected $headers;
/** @var HeaderFactoryInterface $headerFactory */
protected $headerFactory;
/** @var Collection Custom message parameters that are extendable by plugins */
protected $params;
/** @var string Message protocol */
protected $protocol = 'HTTP';
/** @var string HTTP protocol version of the message */
protected $protocolVersion = '1.1';
public function __construct()
{
$this->params = new Collection();
$this->headerFactory = new HeaderFactory();
$this->headers = new HeaderCollection();
}
/**
* Set the header factory to use to create headers
*
* @param HeaderFactoryInterface $factory
*
* @return self
*/
public function setHeaderFactory(HeaderFactoryInterface $factory)
{
$this->headerFactory = $factory;
return $this;
}
public function getParams()
{
return $this->params;
}
public function addHeader($header, $value)
{
if (isset($this->headers[$header])) {
$this->headers[$header]->add($value);
} elseif ($value instanceof HeaderInterface) {
$this->headers[$header] = $value;
} else {
$this->headers[$header] = $this->headerFactory->createHeader($header, $value);
}
return $this;
}
public function addHeaders(array $headers)
{
foreach ($headers as $key => $value) {
$this->addHeader($key, $value);
}
return $this;
}
public function getHeader($header)
{
return $this->headers[$header];
}
public function getHeaders()
{
return $this->headers;
}
public function getHeaderLines()
{
$headers = array();
foreach ($this->headers as $value) {
$headers[] = $value->getName() . ': ' . $value;
}
return $headers;
}
public function setHeader($header, $value)
{
unset($this->headers[$header]);
$this->addHeader($header, $value);
return $this;
}
public function setHeaders(array $headers)
{
$this->headers->clear();
foreach ($headers as $key => $value) {
$this->addHeader($key, $value);
}
return $this;
}
public function hasHeader($header)
{
return isset($this->headers[$header]);
}
public function removeHeader($header)
{
unset($this->headers[$header]);
return $this;
}
/**
* @deprecated Use $message->getHeader()->parseParams()
* @codeCoverageIgnore
*/
public function getTokenizedHeader($header, $token = ';')
{
Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader()->parseParams()');
if ($this->hasHeader($header)) {
$data = new Collection();
foreach ($this->getHeader($header)->parseParams() as $values) {
foreach ($values as $key => $value) {
if ($value === '') {
$data->set($data->count(), $key);
} else {
$data->add($key, $value);
}
}
}
return $data;
}
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function setTokenizedHeader($header, $data, $token = ';')
{
Version::warn(__METHOD__ . ' is deprecated.');
return $this;
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function getCacheControlDirective($directive)
{
Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->getDirective()');
if (!($header = $this->getHeader('Cache-Control'))) {
return null;
}
return $header->getDirective($directive);
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function hasCacheControlDirective($directive)
{
Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->hasDirective()');
if ($header = $this->getHeader('Cache-Control')) {
return $header->hasDirective($directive);
} else {
return false;
}
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function addCacheControlDirective($directive, $value = true)
{
Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->addDirective()');
if (!($header = $this->getHeader('Cache-Control'))) {
$this->addHeader('Cache-Control', '');
$header = $this->getHeader('Cache-Control');
}
$header->addDirective($directive, $value);
return $this;
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function removeCacheControlDirective($directive)
{
Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->removeDirective()');
if ($header = $this->getHeader('Cache-Control')) {
$header->removeDirective($directive);
}
return $this;
}
}

View File

@ -0,0 +1,248 @@
<?php
namespace Guzzle\Http\Message;
use Guzzle\Http\EntityBody;
use Guzzle\Http\EntityBodyInterface;
use Guzzle\Http\QueryString;
use Guzzle\Http\RedirectPlugin;
use Guzzle\Http\Exception\RequestException;
use Guzzle\Http\Mimetypes;
/**
* HTTP request that sends an entity-body in the request message (POST, PUT, PATCH, DELETE)
*/
class EntityEnclosingRequest extends Request implements EntityEnclosingRequestInterface
{
/** @var int When the size of the body is greater than 1MB, then send Expect: 100-Continue */
protected $expectCutoff = 1048576;
/** @var EntityBodyInterface $body Body of the request */
protected $body;
/** @var QueryString POST fields to use in the EntityBody */
protected $postFields;
/** @var array POST files to send with the request */
protected $postFiles = array();
public function __construct($method, $url, $headers = array())
{
$this->postFields = new QueryString();
parent::__construct($method, $url, $headers);
}
/**
* @return string
*/
public function __toString()
{
// Only attempt to include the POST data if it's only fields
if (count($this->postFields) && empty($this->postFiles)) {
return parent::__toString() . (string) $this->postFields;
}
return parent::__toString() . $this->body;
}
public function setState($state, array $context = array())
{
parent::setState($state, $context);
if ($state == self::STATE_TRANSFER && !$this->body && !count($this->postFields) && !count($this->postFiles)) {
$this->setHeader('Content-Length', 0)->removeHeader('Transfer-Encoding');
}
return $this->state;
}
public function setBody($body, $contentType = null)
{
$this->body = EntityBody::factory($body);
// Auto detect the Content-Type from the path of the request if possible
if ($contentType === null && !$this->hasHeader('Content-Type')) {
$contentType = $this->body->getContentType() ?: Mimetypes::getInstance()->fromFilename($this->getPath());
}
if ($contentType) {
$this->setHeader('Content-Type', $contentType);
}
// Always add the Expect 100-Continue header if the body cannot be rewound. This helps with redirects.
if (!$this->body->isSeekable() && $this->expectCutoff !== false) {
$this->setHeader('Expect', '100-Continue');
}
// Set the Content-Length header if it can be determined
$size = $this->body->getContentLength();
if ($size !== null && $size !== false) {
$this->setHeader('Content-Length', $size);
if ($size > $this->expectCutoff) {
$this->setHeader('Expect', '100-Continue');
}
} elseif (!$this->hasHeader('Content-Length')) {
if ('1.1' == $this->protocolVersion) {
$this->setHeader('Transfer-Encoding', 'chunked');
} else {
throw new RequestException(
'Cannot determine Content-Length and cannot use chunked Transfer-Encoding when using HTTP/1.0'
);
}
}
return $this;
}
public function getBody()
{
return $this->body;
}
/**
* Set the size that the entity body of the request must exceed before adding the Expect: 100-Continue header.
*
* @param int|bool $size Cutoff in bytes. Set to false to never send the expect header (even with non-seekable data)
*
* @return self
*/
public function setExpectHeaderCutoff($size)
{
$this->expectCutoff = $size;
if ($size === false || !$this->body) {
$this->removeHeader('Expect');
} elseif ($this->body && $this->body->getSize() && $this->body->getSize() > $size) {
$this->setHeader('Expect', '100-Continue');
}
return $this;
}
public function configureRedirects($strict = false, $maxRedirects = 5)
{
$this->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, $strict);
if ($maxRedirects == 0) {
$this->getParams()->set(RedirectPlugin::DISABLE, true);
} else {
$this->getParams()->set(RedirectPlugin::MAX_REDIRECTS, $maxRedirects);
}
return $this;
}
public function getPostField($field)
{
return $this->postFields->get($field);
}
public function getPostFields()
{
return $this->postFields;
}
public function setPostField($key, $value)
{
$this->postFields->set($key, $value);
$this->processPostFields();
return $this;
}
public function addPostFields($fields)
{
$this->postFields->merge($fields);
$this->processPostFields();
return $this;
}
public function removePostField($field)
{
$this->postFields->remove($field);
$this->processPostFields();
return $this;
}
public function getPostFiles()
{
return $this->postFiles;
}
public function getPostFile($fieldName)
{
return isset($this->postFiles[$fieldName]) ? $this->postFiles[$fieldName] : null;
}
public function removePostFile($fieldName)
{
unset($this->postFiles[$fieldName]);
$this->processPostFields();
return $this;
}
public function addPostFile($field, $filename = null, $contentType = null)
{
$data = null;
if ($field instanceof PostFileInterface) {
$data = $field;
} elseif (is_array($filename)) {
// Allow multiple values to be set in a single key
foreach ($filename as $file) {
$this->addPostFile($field, $file, $contentType);
}
return $this;
} elseif (!is_string($filename)) {
throw new RequestException('The path to a file must be a string');
} elseif (!empty($filename)) {
// Adding an empty file will cause cURL to error out
$data = new PostFile($field, $filename, $contentType);
}
if ($data) {
if (!isset($this->postFiles[$data->getFieldName()])) {
$this->postFiles[$data->getFieldName()] = array($data);
} else {
$this->postFiles[$data->getFieldName()][] = $data;
}
$this->processPostFields();
}
return $this;
}
public function addPostFiles(array $files)
{
foreach ($files as $key => $file) {
if ($file instanceof PostFileInterface) {
$this->addPostFile($file, null, null, false);
} elseif (is_string($file)) {
// Convert non-associative array keys into 'file'
if (is_numeric($key)) {
$key = 'file';
}
$this->addPostFile($key, $file, null, false);
} else {
throw new RequestException('File must be a string or instance of PostFileInterface');
}
}
return $this;
}
/**
* Determine what type of request should be sent based on post fields
*/
protected function processPostFields()
{
if (!$this->postFiles) {
$this->removeHeader('Expect')->setHeader('Content-Type', self::URL_ENCODED);
} else {
$this->setHeader('Content-Type', self::MULTIPART);
if ($this->expectCutoff !== false) {
$this->setHeader('Expect', '100-Continue');
}
}
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace Guzzle\Http\Message;
use Guzzle\Http\Exception\RequestException;
use Guzzle\Http\EntityBodyInterface;
use Guzzle\Http\QueryString;
/**
* HTTP request that sends an entity-body in the request message (POST, PUT)
*/
interface EntityEnclosingRequestInterface extends RequestInterface
{
const URL_ENCODED = 'application/x-www-form-urlencoded; charset=utf-8';
const MULTIPART = 'multipart/form-data';
/**
* Set the body of the request
*
* @param string|resource|EntityBodyInterface $body Body to use in the entity body of the request
* @param string $contentType Content-Type to set. Leave null to use an existing
* Content-Type or to guess the Content-Type
* @return self
* @throws RequestException if the protocol is < 1.1 and Content-Length can not be determined
*/
public function setBody($body, $contentType = null);
/**
* Get the body of the request if set
*
* @return EntityBodyInterface|null
*/
public function getBody();
/**
* Get a POST field from the request
*
* @param string $field Field to retrieve
*
* @return mixed|null
*/
public function getPostField($field);
/**
* Get the post fields that will be used in the request
*
* @return QueryString
*/
public function getPostFields();
/**
* Set a POST field value
*
* @param string $key Key to set
* @param string $value Value to set
*
* @return self
*/
public function setPostField($key, $value);
/**
* Add POST fields to use in the request
*
* @param QueryString|array $fields POST fields
*
* @return self
*/
public function addPostFields($fields);
/**
* Remove a POST field or file by name
*
* @param string $field Name of the POST field or file to remove
*
* @return self
*/
public function removePostField($field);
/**
* Returns an associative array of POST field names to PostFileInterface objects
*
* @return array
*/
public function getPostFiles();
/**
* Get a POST file from the request
*
* @param string $fieldName POST fields to retrieve
*
* @return array|null Returns an array wrapping an array of PostFileInterface objects
*/
public function getPostFile($fieldName);
/**
* Remove a POST file from the request
*
* @param string $fieldName POST file field name to remove
*
* @return self
*/
public function removePostFile($fieldName);
/**
* Add a POST file to the upload
*
* @param string $field POST field to use (e.g. file). Used to reference content from the server.
* @param string $filename Full path to the file. Do not include the @ symbol.
* @param string $contentType Optional Content-Type to add to the Content-Disposition.
* Default behavior is to guess. Set to false to not specify.
* @return self
*/
public function addPostFile($field, $filename = null, $contentType = null);
/**
* Add POST files to use in the upload
*
* @param array $files An array of POST fields => filenames where filename can be a string or PostFileInterface
*
* @return self
*/
public function addPostFiles(array $files);
/**
* Configure how redirects are handled for the request
*
* @param bool $strict Set to true to follow strict RFC compliance when redirecting POST requests. Most
* browsers with follow a 301-302 redirect for a POST request with a GET request. This is
* the default behavior of Guzzle. Enable strict redirects to redirect these responses
* with a POST rather than a GET request.
* @param int $maxRedirects Specify the maximum number of allowed redirects. Set to 0 to disable redirects.
*
* @return self
*/
public function configureRedirects($strict = false, $maxRedirects = 5);
}

View File

@ -0,0 +1,177 @@
<?php
namespace Guzzle\Http\Message;
use Guzzle\Common\Version;
use Guzzle\Http\Message\Header\HeaderInterface;
/**
* Represents a header and all of the values stored by that header
*/
class Header implements HeaderInterface
{
protected $values = array();
protected $header;
protected $glue;
/**
* @param string $header Name of the header
* @param array|string $values Values of the header as an array or a scalar
* @param string $glue Glue used to combine multiple values into a string
*/
public function __construct($header, $values = array(), $glue = ',')
{
$this->header = trim($header);
$this->glue = $glue;
foreach ((array) $values as $value) {
foreach ((array) $value as $v) {
$this->values[] = $v;
}
}
}
public function __toString()
{
return implode($this->glue . ' ', $this->toArray());
}
public function add($value)
{
$this->values[] = $value;
return $this;
}
public function getName()
{
return $this->header;
}
public function setName($name)
{
$this->header = $name;
return $this;
}
public function setGlue($glue)
{
$this->glue = $glue;
return $this;
}
public function getGlue()
{
return $this->glue;
}
/**
* Normalize the header to be a single header with an array of values.
*
* If any values of the header contains the glue string value (e.g. ","), then the value will be exploded into
* multiple entries in the header.
*
* @return self
*/
public function normalize()
{
$values = $this->toArray();
for ($i = 0, $total = count($values); $i < $total; $i++) {
if (strpos($values[$i], $this->glue) !== false) {
foreach (explode($this->glue, $values[$i]) as $v) {
$values[] = trim($v);
}
unset($values[$i]);
}
}
$this->values = array_values($values);
return $this;
}
public function hasValue($searchValue)
{
return in_array($searchValue, $this->toArray());
}
public function removeValue($searchValue)
{
$this->values = array_values(array_filter($this->values, function ($value) use ($searchValue) {
return $value != $searchValue;
}));
return $this;
}
public function toArray()
{
return $this->values;
}
public function count()
{
return count($this->toArray());
}
public function getIterator()
{
return new \ArrayIterator($this->toArray());
}
public function parseParams()
{
$params = $matches = array();
$callback = array($this, 'trimHeader');
// Normalize the header into a single array and iterate over all values
foreach ($this->normalize()->toArray() as $val) {
$part = array();
foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches);
$pieces = array_map($callback, $matches[0]);
$part[$pieces[0]] = isset($pieces[1]) ? $pieces[1] : '';
}
$params[] = $part;
}
return $params;
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function hasExactHeader($header)
{
Version::warn(__METHOD__ . ' is deprecated');
return $this->header == $header;
}
/**
* @deprecated
* @codeCoverageIgnore
*/
public function raw()
{
Version::warn(__METHOD__ . ' is deprecated. Use toArray()');
return $this->toArray();
}
/**
* Trim a header by removing excess spaces and wrapping quotes
*
* @param $str
*
* @return string
*/
protected function trimHeader($str)
{
static $trimmed = "\"' \n\t";
return trim($str, $trimmed);
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace Guzzle\Http\Message\Header;
use Guzzle\Http\Message\Header;
/**
* Provides helpful functionality for Cache-Control headers
*/
class CacheControl extends Header
{
/** @var array */
protected $directives;
public function add($value)
{
parent::add($value);
$this->directives = null;
}
public function removeValue($searchValue)
{
parent::removeValue($searchValue);
$this->directives = null;
}
/**
* Check if a specific cache control directive exists
*
* @param string $param Directive to retrieve
*
* @return bool
*/
public function hasDirective($param)
{
$directives = $this->getDirectives();
return isset($directives[$param]);
}
/**
* Get a specific cache control directive
*
* @param string $param Directive to retrieve
*
* @return string|bool|null
*/
public function getDirective($param)
{
$directives = $this->getDirectives();
return isset($directives[$param]) ? $directives[$param] : null;
}
/**
* Add a cache control directive
*
* @param string $param Directive to add
* @param string $value Value to set
*
* @return self
*/
public function addDirective($param, $value)
{
$directives = $this->getDirectives();
$directives[$param] = $value;
$this->updateFromDirectives($directives);
return $this;
}
/**
* Remove a cache control directive by name
*
* @param string $param Directive to remove
*
* @return self
*/
public function removeDirective($param)
{
$directives = $this->getDirectives();
unset($directives[$param]);
$this->updateFromDirectives($directives);
return $this;
}
/**
* Get an associative array of cache control directives
*
* @return array
*/
public function getDirectives()
{
if ($this->directives === null) {
$this->directives = array();
foreach ($this->parseParams() as $collection) {
foreach ($collection as $key => $value) {
$this->directives[$key] = $value === '' ? true : $value;
}
}
}
return $this->directives;
}
/**
* Updates the header value based on the parsed directives
*
* @param array $directives Array of cache control directives
*/
protected function updateFromDirectives(array $directives)
{
$this->directives = $directives;
$this->values = array();
foreach ($directives as $key => $value) {
$this->values[] = $value === true ? $key : "{$key}={$value}";
}
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace Guzzle\Http\Message\Header;
use Guzzle\Common\Collection;
use Guzzle\Common\ToArrayInterface;
/**
* Provides a case-insensitive collection of headers
*/
class HeaderCollection implements \IteratorAggregate, \Countable, \ArrayAccess, ToArrayInterface
{
/** @var array */
protected $headers;
public function __construct($headers = array())
{
$this->headers = $headers;
}
public function __clone()
{
foreach ($this->headers as &$header) {
$header = clone $header;
}
}
/**
* Clears the header collection
*/
public function clear()
{
$this->headers = array();
}
/**
* Set a header on the collection
*
* @param HeaderInterface $header Header to add
*
* @return self
*/
public function add(HeaderInterface $header)
{
$this->headers[strtolower($header->getName())] = $header;
return $this;
}
/**
* Get an array of header objects
*
* @return array
*/
public function getAll()
{
return $this->headers;
}
/**
* Alias of offsetGet
*/
public function get($key)
{
return $this->offsetGet($key);
}
public function count()
{
return count($this->headers);
}
public function offsetExists($offset)
{
return isset($this->headers[strtolower($offset)]);
}
public function offsetGet($offset)
{
$l = strtolower($offset);
return isset($this->headers[$l]) ? $this->headers[$l] : null;
}
public function offsetSet($offset, $value)
{
$this->add($value);
}
public function offsetUnset($offset)
{
unset($this->headers[strtolower($offset)]);
}
public function getIterator()
{
return new \ArrayIterator($this->headers);
}
public function toArray()
{
$result = array();
foreach ($this->headers as $header) {
$result[$header->getName()] = $header->toArray();
}
return $result;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Guzzle\Http\Message\Header;
use Guzzle\Http\Message\Header;
/**
* Default header factory implementation
*/
class HeaderFactory implements HeaderFactoryInterface
{
/** @var array */
protected $mapping = array(
'cache-control' => 'Guzzle\Http\Message\Header\CacheControl',
'link' => 'Guzzle\Http\Message\Header\Link',
);
public function createHeader($header, $value = null)
{
$lowercase = strtolower($header);
return isset($this->mapping[$lowercase])
? new $this->mapping[$lowercase]($header, $value)
: new Header($header, $value);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Guzzle\Http\Message\Header;
/**
* Interface for creating headers
*/
interface HeaderFactoryInterface
{
/**
* Create a header from a header name and a single value
*
* @param string $header Name of the header to create
* @param string $value Value to set on the header
*
* @return HeaderInterface
*/
public function createHeader($header, $value = null);
}

View File

@ -0,0 +1,83 @@
<?php
namespace Guzzle\Http\Message\Header;
use Guzzle\Common\ToArrayInterface;
interface HeaderInterface extends ToArrayInterface, \Countable, \IteratorAggregate
{
/**
* Convert the header to a string
*
* @return string
*/
public function __toString();
/**
* Add a value to the list of header values
*
* @param string $value Value to add to the header
*
* @return self
*/
public function add($value);
/**
* Get the name of the header
*
* @return string
*/
public function getName();
/**
* Change the name of the header
*
* @param string $name Name to change to
*
* @return self
*/
public function setName($name);
/**
* Change the glue used to implode the values
*
* @param string $glue Glue used to implode multiple values
*
* @return self
*/
public function setGlue($glue);
/**
* Get the glue used to implode multiple values into a string
*
* @return string
*/
public function getGlue();
/**
* Check if the collection of headers has a particular value
*
* @param string $searchValue Value to search for
*
* @return bool
*/
public function hasValue($searchValue);
/**
* Remove a specific value from the header
*
* @param string $searchValue Value to remove
*
* @return self
*/
public function removeValue($searchValue);
/**
* Parse a header containing ";" separated data into an array of associative arrays representing the header
* key value pair data of the header. When a parameter does not contain a value, but just contains a key, this
* function will inject a key with a '' string value.
*
* @return array
*/
public function parseParams();
}

View File

@ -0,0 +1,93 @@
<?php
namespace Guzzle\Http\Message\Header;
use Guzzle\Http\Message\Header;
/**
* Provides helpful functionality for link headers
*/
class Link extends Header
{
/**
* Add a link to the header
*
* @param string $url Link URL
* @param string $rel Link rel
* @param array $params Other link parameters
*
* @return self
*/
public function addLink($url, $rel, array $params = array())
{
$values = array("<{$url}>", "rel=\"{$rel}\"");
foreach ($params as $k => $v) {
$values[] = "{$k}=\"{$v}\"";
}
return $this->add(implode('; ', $values));
}
/**
* Check if a specific link exists for a given rel attribute
*
* @param string $rel rel value
*
* @return bool
*/
public function hasLink($rel)
{
return $this->getLink($rel) !== null;
}
/**
* Get a specific link for a given rel attribute
*
* @param string $rel Rel value
*
* @return array|null
*/
public function getLink($rel)
{
foreach ($this->getLinks() as $link) {
if (isset($link['rel']) && $link['rel'] == $rel) {
return $link;
}
}
return null;
}
/**
* Get an associative array of links
*
* For example:
* Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg"
*
* <code>
* var_export($response->getLinks());
* array(
* array(
* 'url' => 'http:/.../front.jpeg',
* 'rel' => 'back',
* 'type' => 'image/jpeg',
* )
* )
* </code>
*
* @return array
*/
public function getLinks()
{
$links = $this->parseParams();
foreach ($links as &$link) {
$key = key($link);
unset($link[$key]);
$link['url'] = trim($key, '<> ');
}
return $links;
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace Guzzle\Http\Message;
/**
* Request and response message interface
*/
interface MessageInterface
{
/**
* Get application and plugin specific parameters set on the message.
*
* @return \Guzzle\Common\Collection
*/
public function getParams();
/**
* Add a header to an existing collection of headers.
*
* @param string $header Header name to add
* @param string $value Value of the header
*
* @return self
*/
public function addHeader($header, $value);
/**
* Add and merge in an array of HTTP headers.
*
* @param array $headers Associative array of header data.
*
* @return self
*/
public function addHeaders(array $headers);
/**
* Retrieve an HTTP header by name. Performs a case-insensitive search of all headers.
*
* @param string $header Header to retrieve.
*
* @return Header|null
*/
public function getHeader($header);
/**
* Get all headers as a collection
*
* @return \Guzzle\Http\Message\Header\HeaderCollection
*/
public function getHeaders();
/**
* Check if the specified header is present.
*
* @param string $header The header to check.
*
* @return bool
*/
public function hasHeader($header);
/**
* Remove a specific HTTP header.
*
* @param string $header HTTP header to remove.
*
* @return self
*/
public function removeHeader($header);
/**
* Set an HTTP header and overwrite any existing value for the header
*
* @param string $header Name of the header to set.
* @param mixed $value Value to set.
*
* @return self
*/
public function setHeader($header, $value);
/**
* Overwrite all HTTP headers with the supplied array of headers
*
* @param array $headers Associative array of header data.
*
* @return self
*/
public function setHeaders(array $headers);
/**
* Get an array of message header lines (e.g. ["Host: example.com", ...])
*
* @return array
*/
public function getHeaderLines();
/**
* Get the raw message headers as a string
*
* @return string
*/
public function getRawHeaders();
}

Some files were not shown because too many files have changed in this diff Show More