scratch

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 86  →  ?path2? @ 87
/vendor/guzzlehttp/guzzle/src/Adapter/AdapterInterface.php
@@ -0,0 +1,23 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
use GuzzleHttp\Message\ResponseInterface;
 
/**
* Adapter interface used to transfer HTTP requests.
*
* @link http://docs.guzzlephp.org/en/guzzle4/adapters.html for a full
* explanation of adapters and their responsibilities.
*/
interface AdapterInterface
{
/**
* Transfers an HTTP request and populates a response
*
* @param TransactionInterface $transaction Transaction abject to populate
*
* @return ResponseInterface
*/
public function send(TransactionInterface $transaction);
}
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/BatchContext.php
@@ -0,0 +1,179 @@
<?php
namespace GuzzleHttp\Adapter\Curl;
 
use GuzzleHttp\Adapter\TransactionInterface;
use GuzzleHttp\Exception\AdapterException;
 
/**
* Provides context for a Curl transaction, including active handles,
* pending transactions, and whether or not this is a batch or single
* transaction.
*/
class BatchContext
{
/** @var resource Curl multi resource */
private $multi;
 
/** @var \SplObjectStorage Map of transactions to curl resources */
private $handles;
 
/** @var \Iterator Yields pending transactions */
private $pending;
 
/** @var bool Whether or not to throw transactions */
private $throwsExceptions;
 
/**
* @param resource $multiHandle Initialized curl_multi resource
* @param bool $throwsExceptions Whether or not exceptions are thrown
* @param \Iterator $pending Iterator yielding pending transactions
*/
public function __construct(
$multiHandle,
$throwsExceptions,
\Iterator $pending = null
) {
$this->multi = $multiHandle;
$this->handles = new \SplObjectStorage();
$this->throwsExceptions = $throwsExceptions;
$this->pending = $pending;
}
 
/**
* Closes all of the requests associated with the underlying multi handle.
*/
public function removeAll()
{
foreach ($this->handles as $transaction) {
$ch = $this->handles[$transaction];
curl_multi_remove_handle($this->multi, $ch);
curl_close($ch);
unset($this->handles[$transaction]);
}
}
 
/**
* Find a transaction for a given curl handle
*
* @param resource $handle Curl handle
*
* @return TransactionInterface
* @throws AdapterException if a transaction is not found
*/
public function findTransaction($handle)
{
foreach ($this->handles as $transaction) {
if ($this->handles[$transaction] === $handle) {
return $transaction;
}
}
 
throw new AdapterException('No curl handle was found');
}
 
/**
* Returns true if there are any active requests.
*
* @return bool
*/
public function isActive()
{
return count($this->handles) > 0;
}
 
/**
* Returns true if there are any remaining pending transactions
*
* @return bool
*/
public function hasPending()
{
return $this->pending && $this->pending->valid();
}
 
/**
* Pop the next transaction from the transaction queue
*
* @return TransactionInterface|null
*/
public function nextPending()
{
if (!$this->hasPending()) {
return null;
}
 
$current = $this->pending->current();
$this->pending->next();
 
return $current;
}
 
/**
* Checks if the batch is to throw exceptions on error
*
* @return bool
*/
public function throwsExceptions()
{
return $this->throwsExceptions;
}
 
/**
* Get the curl_multi handle
*
* @return resource
*/
public function getMultiHandle()
{
return $this->multi;
}
 
/**
* Add a transaction to the multi handle
*
* @param TransactionInterface $transaction Transaction to add
* @param resource $handle Resource to use with the handle
*
* @throws AdapterException If the handle is already registered
*/
public function addTransaction(TransactionInterface $transaction, $handle)
{
if (isset($this->handles[$transaction])) {
throw new AdapterException('Transaction already registered');
}
 
$code = curl_multi_add_handle($this->multi, $handle);
if ($code != CURLM_OK) {
MultiAdapter::throwMultiError($code);
}
 
$this->handles[$transaction] = $handle;
}
 
/**
* Remove a transaction and associated handle from the context
*
* @param TransactionInterface $transaction Transaction to remove
*
* @return array Returns the curl_getinfo array
* @throws AdapterException if the transaction is not found
*/
public function removeTransaction(TransactionInterface $transaction)
{
if (!isset($this->handles[$transaction])) {
throw new AdapterException('Transaction not registered');
}
 
$handle = $this->handles[$transaction];
$this->handles->detach($transaction);
$info = curl_getinfo($handle);
$code = curl_multi_remove_handle($this->multi, $handle);
curl_close($handle);
 
if ($code !== CURLM_OK) {
MultiAdapter::throwMultiError($code);
}
 
return $info;
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/CurlAdapter.php
@@ -0,0 +1,152 @@
<?php
 
namespace GuzzleHttp\Adapter\Curl;
 
use GuzzleHttp\Adapter\AdapterInterface;
use GuzzleHttp\Adapter\TransactionInterface;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Exception\AdapterException;
use GuzzleHttp\Message\MessageFactoryInterface;
 
/**
* HTTP adapter that uses cURL easy handles as a transport layer.
*
* Requires PHP 5.5+
*
* When using the CurlAdapter, custom curl options can be specified as an
* associative array of curl option constants mapping to values in the
* **curl** key of a request's configuration options.
*/
class CurlAdapter implements AdapterInterface
{
/** @var CurlFactory */
private $curlFactory;
 
/** @var MessageFactoryInterface */
private $messageFactory;
 
/** @var array Array of curl easy handles */
private $handles = [];
 
/** @var array Array of owned curl easy handles */
private $ownedHandles = [];
 
/** @var int Total number of idle handles to keep in cache */
private $maxHandles;
 
/**
* Accepts an associative array of options:
*
* - handle_factory: Optional callable factory used to create cURL handles.
* The callable is invoked with the following arguments:
* TransactionInterface, MessageFactoryInterface, and an optional cURL
* handle to modify. The factory method must then return a cURL resource.
* - max_handles: Maximum number of idle handles (defaults to 5).
*
* @param MessageFactoryInterface $messageFactory
* @param array $options Array of options to use with the adapter
*/
public function __construct(
MessageFactoryInterface $messageFactory,
array $options = []
) {
$this->handles = $this->ownedHandles = [];
$this->messageFactory = $messageFactory;
$this->curlFactory = isset($options['handle_factory'])
? $options['handle_factory']
: new CurlFactory();
$this->maxHandles = isset($options['max_handles'])
? $options['max_handles']
: 5;
}
 
public function __destruct()
{
foreach ($this->handles as $handle) {
if (is_resource($handle)) {
curl_close($handle);
}
}
}
 
public function send(TransactionInterface $transaction)
{
RequestEvents::emitBefore($transaction);
if ($response = $transaction->getResponse()) {
return $response;
}
 
$factory = $this->curlFactory;
$handle = $factory(
$transaction,
$this->messageFactory,
$this->checkoutEasyHandle()
);
 
curl_exec($handle);
$info = curl_getinfo($handle);
$info['curl_result'] = curl_errno($handle);
 
if ($info['curl_result']) {
$this->handleError($transaction, $info, $handle);
} else {
$this->releaseEasyHandle($handle);
if ($body = $transaction->getResponse()->getBody()) {
$body->seek(0);
}
RequestEvents::emitComplete($transaction, $info);
}
 
return $transaction->getResponse();
}
 
private function handleError(
TransactionInterface $transaction,
$info,
$handle
) {
$error = curl_error($handle);
$this->releaseEasyHandle($handle);
RequestEvents::emitError(
$transaction,
new AdapterException("cURL error {$info['curl_result']}: {$error}"),
$info
);
}
 
private function checkoutEasyHandle()
{
// Find an unused handle in the cache
if (false !== ($key = array_search(false, $this->ownedHandles, true))) {
$this->ownedHandles[$key] = true;
return $this->handles[$key];
}
 
// Add a new handle
$handle = curl_init();
$id = (int) $handle;
$this->handles[$id] = $handle;
$this->ownedHandles[$id] = true;
 
return $handle;
}
 
private function releaseEasyHandle($handle)
{
$id = (int) $handle;
if (count($this->ownedHandles) > $this->maxHandles) {
curl_close($this->handles[$id]);
unset($this->handles[$id], $this->ownedHandles[$id]);
} else {
// curl_reset doesn't clear these out for some reason
curl_setopt_array($handle, [
CURLOPT_HEADERFUNCTION => null,
CURLOPT_WRITEFUNCTION => null,
CURLOPT_READFUNCTION => null,
CURLOPT_PROGRESSFUNCTION => null
]);
curl_reset($handle);
$this->ownedHandles[$id] = false;
}
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/CurlFactory.php
@@ -0,0 +1,369 @@
<?php
namespace GuzzleHttp\Adapter\Curl;
 
use GuzzleHttp\Adapter\TransactionInterface;
use GuzzleHttp\Exception\AdapterException;
use GuzzleHttp\Message\MessageFactoryInterface;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Stream\LazyOpenStream;
use GuzzleHttp\Stream\Stream;
 
/**
* Creates curl resources from a request and response object
*/
class CurlFactory
{
/**
* Creates a cURL handle based on a transaction.
*
* @param TransactionInterface $transaction Holds a request and response
* @param MessageFactoryInterface $messageFactory Used to create responses
* @param null|resource $handle Optionally provide a curl handle to modify
*
* @return resource Returns a prepared cURL handle
* @throws AdapterException when an option cannot be applied
*/
public function __invoke(
TransactionInterface $transaction,
MessageFactoryInterface $messageFactory,
$handle = null
) {
$request = $transaction->getRequest();
$mediator = new RequestMediator($transaction, $messageFactory);
$options = $this->getDefaultOptions($request, $mediator);
$this->applyMethod($request, $options);
$this->applyTransferOptions($request, $mediator, $options);
$this->applyHeaders($request, $options);
unset($options['_headers']);
 
// Add adapter options from the request's configuration options
if ($config = $request->getConfig()['curl']) {
$options = $this->applyCustomCurlOptions($config, $options);
}
 
if (!$handle) {
$handle = curl_init();
}
 
curl_setopt_array($handle, $options);
 
return $handle;
}
 
protected function getDefaultOptions(
RequestInterface $request,
RequestMediator $mediator
) {
$url = $request->getUrl();
 
// Strip fragment from URL. See:
// https://github.com/guzzle/guzzle/issues/453
if (($pos = strpos($url, '#')) !== false) {
$url = substr($url, 0, $pos);
}
 
$config = $request->getConfig();
$options = [
CURLOPT_URL => $url,
CURLOPT_CONNECTTIMEOUT => $config['connect_timeout'] ?: 150,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
CURLOPT_WRITEFUNCTION => [$mediator, 'writeResponseBody'],
CURLOPT_HEADERFUNCTION => [$mediator, 'receiveResponseHeader'],
CURLOPT_READFUNCTION => [$mediator, 'readRequestBody'],
CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0'
? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
CURLOPT_SSL_VERIFYPEER => 1,
CURLOPT_SSL_VERIFYHOST => 2,
'_headers' => $request->getHeaders()
];
 
if (defined('CURLOPT_PROTOCOLS')) {
// Allow only HTTP and HTTPS protocols
$options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
}
 
// cURL sometimes adds a content-type by default. Prevent this.
if (!$request->hasHeader('Content-Type')) {
$options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
}
 
return $options;
}
 
private function applyMethod(RequestInterface $request, array &$options)
{
$method = $request->getMethod();
if ($method == 'HEAD') {
$options[CURLOPT_NOBODY] = true;
unset($options[CURLOPT_WRITEFUNCTION], $options[CURLOPT_READFUNCTION]);
} else {
$options[CURLOPT_CUSTOMREQUEST] = $method;
if (!$request->getBody()) {
unset($options[CURLOPT_READFUNCTION]);
} else {
$this->applyBody($request, $options);
}
}
}
 
private function applyBody(RequestInterface $request, array &$options)
{
if ($request->hasHeader('Content-Length')) {
$size = (int) $request->getHeader('Content-Length');
} else {
$size = null;
}
 
$request->getBody()->seek(0);
 
// You can send the body as a string using curl's CURLOPT_POSTFIELDS
if (($size !== null && $size < 32768) ||
isset($request->getConfig()['curl']['body_as_string'])
) {
$options[CURLOPT_POSTFIELDS] = $request->getBody()->getContents();
// Don't duplicate the Content-Length header
$this->removeHeader('Content-Length', $options);
$this->removeHeader('Transfer-Encoding', $options);
} else {
$options[CURLOPT_UPLOAD] = true;
// Let cURL handle setting the Content-Length header
if ($size !== null) {
$options[CURLOPT_INFILESIZE] = $size;
$this->removeHeader('Content-Length', $options);
}
}
 
// If the Expect header is not present, prevent curl from adding it
if (!$request->hasHeader('Expect')) {
$options[CURLOPT_HTTPHEADER][] = 'Expect:';
}
}
 
private function applyHeaders(RequestInterface $request, array &$options)
{
foreach ($options['_headers'] as $name => $values) {
$options[CURLOPT_HTTPHEADER][] = $name . ': ' . implode(', ', $values);
}
 
// Remove the Expect header if one was not set
if (!$request->hasHeader('Accept')) {
$options[CURLOPT_HTTPHEADER][] = 'Accept:';
}
}
 
private function applyTransferOptions(
RequestInterface $request,
RequestMediator $mediator,
array &$options
) {
static $methods;
if (!$methods) {
$methods = array_flip(get_class_methods(__CLASS__));
}
 
foreach ($request->getConfig()->toArray() as $key => $value) {
$method = "add_{$key}";
if (isset($methods[$method])) {
$this->{$method}($request, $mediator, $options, $value);
}
}
}
 
private function add_debug(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
if (!$value) {
return;
}
 
if (!is_resource($value)) {
$value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w');
}
 
$options[CURLOPT_STDERR] = $value;
$options[CURLOPT_VERBOSE] = true;
}
 
private function add_proxy(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
if (!is_array($value)) {
$options[CURLOPT_PROXY] = $value;
} else {
$scheme = $request->getScheme();
if (isset($value[$scheme])) {
$options[CURLOPT_PROXY] = $value[$scheme];
}
}
}
 
private function add_timeout(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
$options[CURLOPT_TIMEOUT_MS] = $value * 1000;
}
 
private function add_connect_timeout(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
$options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
}
 
private function add_verify(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
if ($value === false) {
unset($options[CURLOPT_CAINFO]);
$options[CURLOPT_SSL_VERIFYHOST] = 0;
$options[CURLOPT_SSL_VERIFYPEER] = false;
} elseif ($value === true || is_string($value)) {
$options[CURLOPT_SSL_VERIFYHOST] = 2;
$options[CURLOPT_SSL_VERIFYPEER] = true;
if ($value !== true) {
if (!file_exists($value)) {
throw new AdapterException('SSL certificate authority file'
. " not found: {$value}");
}
$options[CURLOPT_CAINFO] = $value;
}
}
}
 
private function add_cert(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
if (!file_exists($value)) {
throw new AdapterException("SSL certificate not found: {$value}");
}
 
$options[CURLOPT_SSLCERT] = $value;
}
 
private function add_ssl_key(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
if (is_array($value)) {
$options[CURLOPT_SSLKEYPASSWD] = $value[1];
$value = $value[0];
}
 
if (!file_exists($value)) {
throw new AdapterException("SSL private key not found: {$value}");
}
 
$options[CURLOPT_SSLKEY] = $value;
}
 
private function add_stream(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
if ($value === false) {
return;
}
 
throw new AdapterException('cURL adapters do not support the "stream"'
. ' request option. This error is typically encountered when trying'
. ' to send requests with the "stream" option set to true in '
. ' parallel. You will either need to send these one at a time or'
. ' implement a custom ParallelAdapterInterface that supports'
. ' sending these types of requests in parallel. This error can'
. ' also occur if the StreamAdapter is not available on your'
. ' system (e.g., allow_url_fopen is disabled in your php.ini).');
}
 
private function add_save_to(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
$mediator->setResponseBody(is_string($value)
? new LazyOpenStream($value, 'w')
: Stream::factory($value));
}
 
private function add_decode_content(
RequestInterface $request,
RequestMediator $mediator,
&$options,
$value
) {
if (!$request->hasHeader('Accept-Encoding')) {
$options[CURLOPT_ENCODING] = '';
// Don't let curl send the header over the wire
$options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
} else {
$options[CURLOPT_ENCODING] = $request->getHeader('Accept-Encoding');
}
}
 
/**
* Takes an array of curl options specified in the 'curl' option of a
* request's configuration array and maps them to CURLOPT_* options.
*
* This method is only called when a request has a 'curl' config setting.
* Array key strings that start with CURL that have a matching constant
* value will be automatically converted to the matching constant.
*
* @param array $config Configuration array of custom curl option
* @param array $options Array of existing curl options
*
* @return array Returns a new array of curl options
*/
private function applyCustomCurlOptions(array $config, array $options)
{
unset($config['body_as_string']);
$curlOptions = [];
 
// Map curl constant strings to defined values
foreach ($config as $key => $value) {
if (defined($key) && substr($key, 0, 4) === 'CURL') {
$key = constant($key);
}
$curlOptions[$key] = $value;
}
 
return $curlOptions + $options;
}
 
/**
* Remove a header from the options array
*
* @param string $name Case-insensitive header to remove
* @param array $options Array of options to modify
*/
private function removeHeader($name, array &$options)
{
foreach (array_keys($options['_headers']) as $key) {
if (!strcasecmp($key, $name)) {
unset($options['_headers'][$key]);
return;
}
}
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/MultiAdapter.php
@@ -0,0 +1,377 @@
<?php
 
namespace GuzzleHttp\Adapter\Curl;
 
use GuzzleHttp\Adapter\AdapterInterface;
use GuzzleHttp\Adapter\ParallelAdapterInterface;
use GuzzleHttp\Adapter\TransactionInterface;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Exception\AdapterException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Message\MessageFactoryInterface;
 
/**
* HTTP adapter that uses cURL multi as a transport layer
*
* When using the CurlAdapter, custom curl options can be specified as an
* associative array of curl option constants mapping to values in the
* **curl** key of a request's configuration options.
*
* In addition to being able to supply configuration options via the curl
* request config, you can also specify the select_timeout variable using the
* `GUZZLE_CURL_SELECT_TIMEOUT` environment variable.
*/
class MultiAdapter implements AdapterInterface, ParallelAdapterInterface
{
const ERROR_STR = 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html for an explanation of cURL errors';
const ENV_SELECT_TIMEOUT = 'GUZZLE_CURL_SELECT_TIMEOUT';
 
/** @var CurlFactory */
private $curlFactory;
 
/** @var MessageFactoryInterface */
private $messageFactory;
 
/** @var array Array of curl multi handles */
private $multiHandles = [];
 
/** @var array Array of curl multi handles */
private $multiOwned = [];
 
/** @var int Total number of idle handles to keep in cache */
private $maxHandles;
 
/** @var double */
private $selectTimeout;
 
/**
* Accepts an associative array of options:
*
* - handle_factory: Optional callable factory used to create cURL handles.
* The callable is invoked with the following arguments:
* TransactionInterface, MessageFactoryInterface, and an optional cURL
* handle to modify. The factory method must then return a cURL resource.
* - select_timeout: Specify a float in seconds to use for a
* curl_multi_select timeout.
* - max_handles: Maximum number of idle handles (defaults to 3).
*
* @param MessageFactoryInterface $messageFactory
* @param array $options Array of options to use with the adapter:
*/
public function __construct(
MessageFactoryInterface $messageFactory,
array $options = []
) {
$this->messageFactory = $messageFactory;
$this->curlFactory = isset($options['handle_factory'])
? $options['handle_factory']
: new CurlFactory();
 
if (isset($options['select_timeout'])) {
$this->selectTimeout = $options['select_timeout'];
} elseif (isset($_SERVER[self::ENV_SELECT_TIMEOUT])) {
$this->selectTimeout = $_SERVER[self::ENV_SELECT_TIMEOUT];
} else {
$this->selectTimeout = 1;
}
 
$this->maxHandles = isset($options['max_handles'])
? $options['max_handles']
: 3;
}
 
public function __destruct()
{
foreach ($this->multiHandles as $handle) {
if (is_resource($handle)) {
curl_multi_close($handle);
}
}
}
 
/**
* Throw an exception for a cURL multi response
*
* @param int $code Curl response code
* @throws AdapterException
*/
public static function throwMultiError($code)
{
$buffer = function_exists('curl_multi_strerror')
? curl_multi_strerror($code)
: self::ERROR_STR;
 
throw new AdapterException(sprintf('cURL error %s: %s', $code, $buffer));
}
 
public function send(TransactionInterface $transaction)
{
$context = new BatchContext($this->checkoutMultiHandle(), true);
$this->addHandle($transaction, $context);
$this->perform($context);
 
return $transaction->getResponse();
}
 
public function sendAll(\Iterator $transactions, $parallel)
{
$context = new BatchContext(
$this->checkoutMultiHandle(),
false,
$transactions
);
 
foreach (new \LimitIterator($transactions, 0, $parallel) as $trans) {
$this->addHandle($trans, $context);
}
 
$this->perform($context);
}
 
private function perform(BatchContext $context)
{
// The first curl_multi_select often times out no matter what, but is
// usually required for fast transfers.
$active = false;
$multi = $context->getMultiHandle();
 
do {
do {
$mrc = curl_multi_exec($multi, $active);
} while ($mrc === CURLM_CALL_MULTI_PERFORM);
 
if ($mrc != CURLM_OK) {
self::throwMultiError($mrc);
}
 
$this->processMessages($context);
 
if ($active &&
curl_multi_select($multi, $this->selectTimeout) === -1
) {
// Perform a usleep if a select returns -1.
// See: https://bugs.php.net/bug.php?id=61141
usleep(250);
}
 
} while ($context->isActive() || $active);
 
$this->releaseMultiHandle($multi, $this->maxHandles);
}
 
private function processMessages(BatchContext $context)
{
$multi = $context->getMultiHandle();
 
while ($done = curl_multi_info_read($multi)) {
$transaction = $context->findTransaction($done['handle']);
$this->processResponse($transaction, $done, $context);
// Add the next transaction if there are more in the queue
if ($next = $context->nextPending()) {
$this->addHandle($next, $context);
}
}
}
 
private function processResponse(
TransactionInterface $transaction,
array $curl,
BatchContext $context
) {
$info = $context->removeTransaction($transaction);
 
try {
if (!$this->isCurlException($transaction, $curl, $context, $info) &&
$this->validateResponseWasSet($transaction, $context)
) {
if ($body = $transaction->getResponse()->getBody()) {
$body->seek(0);
}
RequestEvents::emitComplete($transaction, $info);
}
} catch (\Exception $e) {
$this->throwException($e, $context);
}
}
 
private function addHandle(
TransactionInterface $transaction,
BatchContext $context
) {
try {
RequestEvents::emitBefore($transaction);
// Only transfer if the request was not intercepted
if (!$transaction->getResponse()) {
$factory = $this->curlFactory;
$context->addTransaction(
$transaction,
$factory($transaction, $this->messageFactory)
);
}
} catch (RequestException $e) {
$this->throwException($e, $context);
}
}
 
private function isCurlException(
TransactionInterface $transaction,
array $curl,
BatchContext $context,
array $info
) {
if (CURLM_OK == $curl['result'] ||
CURLM_CALL_MULTI_PERFORM == $curl['result']
) {
return false;
}
 
$request = $transaction->getRequest();
try {
// Send curl stats along if they are available
$stats = ['curl_result' => $curl['result']] + $info;
RequestEvents::emitError(
$transaction,
new RequestException(
sprintf(
'[curl] (#%s) %s [url] %s',
$curl['result'],
function_exists('curl_strerror')
? curl_strerror($curl['result'])
: self::ERROR_STR,
$request->getUrl()
),
$request
),
$stats
);
} catch (\Exception $e) {
$this->throwException($e, $context);
}
 
return true;
}
 
private function throwException(\Exception $e, BatchContext $context)
{
if ($context->throwsExceptions()
|| ($e instanceof RequestException && $e->getThrowImmediately())
) {
$context->removeAll();
$this->releaseMultiHandle($context->getMultiHandle(), -1);
throw $e;
}
}
 
/**
* Returns a curl_multi handle from the cache or creates a new one
*
* @return resource
*/
private function checkoutMultiHandle()
{
// Find an unused handle in the cache
$key = array_search(false, $this->multiOwned, true);
if (false !== $key) {
$this->multiOwned[$key] = true;
return $this->multiHandles[$key];
}
 
// Add a new handle
$handle = curl_multi_init();
$id = (int) $handle;
$this->multiHandles[$id] = $handle;
$this->multiOwned[$id] = true;
 
return $handle;
}
 
/**
* Releases a curl_multi handle back into the cache and removes excess cache
*
* @param resource $handle Curl multi handle to remove
* @param int $maxHandles (Optional) Maximum number of existing multiHandles to allow before pruning.
*/
private function releaseMultiHandle($handle, $maxHandles)
{
$id = (int) $handle;
 
if (count($this->multiHandles) <= $maxHandles) {
$this->multiOwned[$id] = false;
} elseif (isset($this->multiHandles[$id], $this->multiOwned[$id])) {
// Prune excessive handles
curl_multi_close($this->multiHandles[$id]);
unset($this->multiHandles[$id], $this->multiOwned[$id]);
}
}
 
/**
* This function ensures that a response was set on a transaction. If one
* was not set, then the request is retried if possible. This error
* typically means you are sending a payload, curl encountered a
* "Connection died, retrying a fresh connect" error, tried to rewind the
* stream, and then encountered a "necessary data rewind wasn't possible"
* error, causing the request to be sent through curl_multi_info_read()
* without an error status.
*
* @param TransactionInterface $transaction
* @param BatchContext $context
*
* @return bool Returns true if it's OK, and false if it failed.
* @throws \GuzzleHttp\Exception\RequestException If it failed and cannot
* recover.
*/
private function validateResponseWasSet(
TransactionInterface $transaction,
BatchContext $context
) {
if ($transaction->getResponse()) {
return true;
}
 
$body = $transaction->getRequest()->getBody();
 
if (!$body) {
// This is weird and should probably never happen.
RequestEvents::emitError(
$transaction,
new RequestException(
'No response was received for a request with no body. This'
. ' could mean that you are saturating your network.',
$transaction->getRequest()
)
);
} elseif (!$body->isSeekable() || !$body->seek(0)) {
// Nothing we can do with this. Sorry!
RequestEvents::emitError(
$transaction,
new RequestException(
'The connection was unexpectedly closed. The request would'
. ' have been retried, but attempting to rewind the'
. ' request body failed. Consider wrapping your request'
. ' body in a CachingStream decorator to work around this'
. ' issue if necessary.',
$transaction->getRequest()
)
);
} else {
$this->retryFailedConnection($transaction, $context);
}
 
return false;
}
 
private function retryFailedConnection(
TransactionInterface $transaction,
BatchContext $context
) {
// Add the request back to the batch to retry automatically.
$context->addTransaction(
$transaction,
call_user_func(
$this->curlFactory,
$transaction,
$this->messageFactory
)
);
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/Curl/RequestMediator.php
@@ -0,0 +1,130 @@
<?php
 
namespace GuzzleHttp\Adapter\Curl;
 
use GuzzleHttp\Adapter\TransactionInterface;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Message\MessageFactoryInterface;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Stream\StreamInterface;
 
/**
* Mediator between curl handles and request objects
*/
class RequestMediator
{
/** @var TransactionInterface */
private $transaction;
/** @var MessageFactoryInterface */
private $messageFactory;
private $statusCode;
private $reasonPhrase;
private $body;
private $protocolVersion;
private $headers;
 
/**
* @param TransactionInterface $transaction Transaction to populate
* @param MessageFactoryInterface $messageFactory Creates responses
*/
public function __construct(
TransactionInterface $transaction,
MessageFactoryInterface $messageFactory
) {
$this->transaction = $transaction;
$this->messageFactory = $messageFactory;
}
 
/**
* Set the body that will hold the response body
*
* @param StreamInterface $body Response body
*/
public function setResponseBody(StreamInterface $body = null)
{
$this->body = $body;
}
 
/**
* 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 = ["\r", "\n"];
$length = strlen($header);
$header = str_replace($normalize, '', $header);
 
if (strpos($header, 'HTTP/') === 0) {
$startLine = explode(' ', $header, 3);
// Only download the body to a target body when a successful
// response is received.
if ($startLine[1][0] != '2') {
$this->body = null;
}
$this->statusCode = $startLine[1];
$this->reasonPhrase = isset($startLine[2]) ? $startLine[2] : null;
$this->protocolVersion = substr($startLine[0], -3);
$this->headers = [];
} elseif ($pos = strpos($header, ':')) {
$this->headers[substr($header, 0, $pos)][] = substr($header, $pos + 1);
} elseif ($header == '' && $this->statusCode >= 200) {
$response = $this->messageFactory->createResponse(
$this->statusCode,
$this->headers,
$this->body,
[
'protocol_version' => $this->protocolVersion,
'reason_phrase' => $this->reasonPhrase
]
);
$this->headers = $this->body = null;
$this->transaction->setResponse($response);
// Allows events to react before downloading any of the body
RequestEvents::emitHeaders($this->transaction);
}
 
return $length;
}
 
/**
* Write data to the response body of a request
*
* @param resource $curl
* @param string $write
*
* @return int
*/
public function writeResponseBody($curl, $write)
{
if (!($response = $this->transaction->getResponse())) {
return 0;
}
 
// Add a default body on the response if one was not found
if (!($body = $response->getBody())) {
$body = new Stream(fopen('php://temp', 'r+'));
$response->setBody($body);
}
 
return $body->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)
{
return (string) $this->transaction->getRequest()->getBody()->read($length);
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/FakeParallelAdapter.php
@@ -0,0 +1,35 @@
<?php
namespace GuzzleHttp\Adapter;
 
use GuzzleHttp\Exception\RequestException;
 
/**
* Decorates a regular AdapterInterface object and creates a
* ParallelAdapterInterface object that sends multiple HTTP requests serially.
*/
class FakeParallelAdapter implements ParallelAdapterInterface
{
/** @var AdapterInterface */
private $adapter;
 
/**
* @param AdapterInterface $adapter Adapter used to send requests
*/
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
 
public function sendAll(\Iterator $transactions, $parallel)
{
foreach ($transactions as $transaction) {
try {
$this->adapter->send($transaction);
} catch (RequestException $e) {
if ($e->getThrowImmediately()) {
throw $e;
}
}
}
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/MockAdapter.php
@@ -0,0 +1,60 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Message\ResponseInterface;
 
/**
* Adapter that can be used to associate mock responses with a transaction
* while still emulating the event workflow of real adapters.
*/
class MockAdapter implements AdapterInterface
{
private $response;
 
/**
* @param ResponseInterface|callable $response Response to serve or function
* to invoke that handles a transaction
*/
public function __construct($response = null)
{
$this->setResponse($response);
}
 
/**
* Set the response that will be served by the adapter
*
* @param ResponseInterface|callable $response Response to serve or
* function to invoke that handles a transaction
*/
public function setResponse($response)
{
$this->response = $response;
}
 
public function send(TransactionInterface $transaction)
{
RequestEvents::emitBefore($transaction);
if (!$transaction->getResponse()) {
 
$response = is_callable($this->response)
? call_user_func($this->response, $transaction)
: $this->response;
if (!$response instanceof ResponseInterface) {
throw new \RuntimeException('Invalid mocked response');
}
 
// Read the request body if it is present
if ($transaction->getRequest()->getBody()) {
$transaction->getRequest()->getBody()->__toString();
}
 
$transaction->setResponse($response);
RequestEvents::emitHeaders($transaction);
RequestEvents::emitComplete($transaction);
}
 
return $transaction->getResponse();
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/ParallelAdapterInterface.php
@@ -0,0 +1,23 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
/**
* Adapter interface used to transfer multiple HTTP requests in parallel.
*
* Parallel adapters follow the same rules as AdapterInterface except that
* RequestExceptions are never thrown in a parallel transfer and parallel
* adapters do not return responses.
*/
interface ParallelAdapterInterface
{
/**
* Transfers multiple HTTP requests in parallel.
*
* RequestExceptions MUST not be thrown from a parallel transfer.
*
* @param \Iterator $transactions Iterator that yields TransactionInterface
* @param int $parallel Max number of requests to send in parallel
*/
public function sendAll(\Iterator $transactions, $parallel);
}
/vendor/guzzlehttp/guzzle/src/Adapter/StreamAdapter.php
@@ -0,0 +1,373 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Exception\AdapterException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Message\AbstractMessage;
use GuzzleHttp\Message\MessageFactoryInterface;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Stream\InflateStream;
use GuzzleHttp\Stream\LazyOpenStream;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Stream\StreamInterface;
use GuzzleHttp\Stream\Utils;
 
/**
* HTTP adapter that uses PHP's HTTP stream wrapper.
*
* When using the StreamAdapter, custom stream context options can be specified
* using the **stream_context** option in a request's **config** option. The
* structure of the "stream_context" option is an associative array where each
* key is a transport name and each option is an associative array of options.
*/
class StreamAdapter implements AdapterInterface
{
/** @var MessageFactoryInterface */
private $messageFactory;
 
/**
* @param MessageFactoryInterface $messageFactory
*/
public function __construct(MessageFactoryInterface $messageFactory)
{
$this->messageFactory = $messageFactory;
}
 
public function send(TransactionInterface $transaction)
{
// HTTP/1.1 streams using the PHP stream wrapper require a
// Connection: close header. Setting here so that it is added before
// emitting the request.before_send event.
$request = $transaction->getRequest();
if ($request->getProtocolVersion() == '1.1' &&
!$request->hasHeader('Connection')
) {
$transaction->getRequest()->setHeader('Connection', 'close');
}
 
RequestEvents::emitBefore($transaction);
if (!$transaction->getResponse()) {
$this->createResponse($transaction);
RequestEvents::emitComplete($transaction);
}
 
return $transaction->getResponse();
}
 
private function createResponse(TransactionInterface $transaction)
{
$request = $transaction->getRequest();
$stream = $this->createStream($request, $http_response_header);
$this->createResponseObject(
$request,
$http_response_header,
$transaction,
new Stream($stream)
);
}
 
private function createResponseObject(
RequestInterface $request,
array $headers,
TransactionInterface $transaction,
StreamInterface $stream
) {
$parts = explode(' ', array_shift($headers), 3);
$options = ['protocol_version' => substr($parts[0], -3)];
 
if (isset($parts[2])) {
$options['reason_phrase'] = $parts[2];
}
 
$response = $this->messageFactory->createResponse(
$parts[1],
$this->headersFromLines($headers),
null,
$options
);
 
// Automatically decode responses when instructed.
if ($request->getConfig()->get('decode_content')) {
switch ($response->getHeader('Content-Encoding')) {
case 'gzip':
case 'deflate':
$stream = new InflateStream($stream);
break;
}
}
 
// Drain the stream immediately if 'stream' was not enabled.
if (!$request->getConfig()['stream']) {
$stream = $this->getSaveToBody($request, $stream);
}
 
$response->setBody($stream);
$transaction->setResponse($response);
RequestEvents::emitHeaders($transaction);
 
return $response;
}
 
/**
* Drain the stream into the destination stream
*/
private function getSaveToBody(
RequestInterface $request,
StreamInterface $stream
) {
if ($saveTo = $request->getConfig()['save_to']) {
// Stream the response into the destination stream
$saveTo = is_string($saveTo)
? new Stream(Utils::open($saveTo, 'r+'))
: Stream::factory($saveTo);
} else {
// Stream into the default temp stream
$saveTo = Stream::factory();
}
 
Utils::copyToStream($stream, $saveTo);
$saveTo->seek(0);
$stream->close();
 
return $saveTo;
}
 
private function headersFromLines(array $lines)
{
$responseHeaders = [];
 
foreach ($lines as $line) {
$headerParts = explode(':', $line, 2);
$responseHeaders[$headerParts[0]][] = isset($headerParts[1])
? trim($headerParts[1])
: '';
}
 
return $responseHeaders;
}
 
/**
* Create a resource and check to ensure it was created successfully
*
* @param callable $callback Callable that returns stream resource
* @param RequestInterface $request Request used when throwing exceptions
* @param array $options Options used when throwing exceptions
*
* @return resource
* @throws RequestException on error
*/
private function createResource(callable $callback, RequestInterface $request, $options)
{
// Turn off error reporting while we try to initiate the request
$level = error_reporting(0);
$resource = call_user_func($callback);
error_reporting($level);
 
// If the resource could not be created, then grab the last error and
// throw an exception.
if (!is_resource($resource)) {
$message = 'Error creating resource. [url] ' . $request->getUrl() . ' ';
if (isset($options['http']['proxy'])) {
$message .= "[proxy] {$options['http']['proxy']} ";
}
foreach ((array) error_get_last() as $key => $value) {
$message .= "[{$key}] {$value} ";
}
throw new RequestException(trim($message), $request);
}
 
return $resource;
}
 
/**
* Create the stream for the request with the context options.
*
* @param RequestInterface $request Request being sent
* @param mixed $http_response_header Populated by stream wrapper
*
* @return resource
*/
private function createStream(
RequestInterface $request,
&$http_response_header
) {
static $methods;
if (!$methods) {
$methods = array_flip(get_class_methods(__CLASS__));
}
 
$params = [];
$options = $this->getDefaultOptions($request);
foreach ($request->getConfig()->toArray() as $key => $value) {
$method = "add_{$key}";
if (isset($methods[$method])) {
$this->{$method}($request, $options, $value, $params);
}
}
 
$this->applyCustomOptions($request, $options);
$context = $this->createStreamContext($request, $options, $params);
 
return $this->createStreamResource(
$request,
$options,
$context,
$http_response_header
);
}
 
private function getDefaultOptions(RequestInterface $request)
{
$headers = AbstractMessage::getHeadersAsString($request);
 
$context = [
'http' => [
'method' => $request->getMethod(),
'header' => trim($headers),
'protocol_version' => $request->getProtocolVersion(),
'ignore_errors' => true,
'follow_location' => 0
]
];
 
if ($body = $request->getBody()) {
$context['http']['content'] = (string) $body;
// Prevent the HTTP adapter from adding a Content-Type header.
if (!$request->hasHeader('Content-Type')) {
$context['http']['header'] .= "\r\nContent-Type:";
}
}
 
return $context;
}
 
private function add_proxy(RequestInterface $request, &$options, $value, &$params)
{
if (!is_array($value)) {
$options['http']['proxy'] = $value;
} else {
$scheme = $request->getScheme();
if (isset($value[$scheme])) {
$options['http']['proxy'] = $value[$scheme];
}
}
}
 
private function add_timeout(RequestInterface $request, &$options, $value, &$params)
{
$options['http']['timeout'] = $value;
}
 
private function add_verify(RequestInterface $request, &$options, $value, &$params)
{
if ($value === true || is_string($value)) {
$options['http']['verify_peer'] = true;
if ($value !== true) {
if (!file_exists($value)) {
throw new \RuntimeException("SSL certificate authority file not found: {$value}");
}
$options['http']['allow_self_signed'] = true;
$options['http']['cafile'] = $value;
}
} elseif ($value === false) {
$options['http']['verify_peer'] = false;
}
}
 
private function add_cert(RequestInterface $request, &$options, $value, &$params)
{
if (is_array($value)) {
$options['http']['passphrase'] = $value[1];
$value = $value[0];
}
 
if (!file_exists($value)) {
throw new \RuntimeException("SSL certificate not found: {$value}");
}
 
$options['http']['local_cert'] = $value;
}
 
private function add_debug(RequestInterface $request, &$options, $value, &$params)
{
static $map = [
STREAM_NOTIFY_CONNECT => 'CONNECT',
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
STREAM_NOTIFY_PROGRESS => 'PROGRESS',
STREAM_NOTIFY_FAILURE => 'FAILURE',
STREAM_NOTIFY_COMPLETED => 'COMPLETED',
STREAM_NOTIFY_RESOLVE => 'RESOLVE'
];
 
static $args = ['severity', 'message', 'message_code',
'bytes_transferred', 'bytes_max'];
 
if (!is_resource($value)) {
$value = defined('STDOUT') ? STDOUT : fopen('php://output', 'w');
}
 
$params['notification'] = function () use ($request, $value, $map, $args) {
$passed = func_get_args();
$code = array_shift($passed);
fprintf($value, '<%s> [%s] ', $request->getUrl(), $map[$code]);
foreach (array_filter($passed) as $i => $v) {
fwrite($value, $args[$i] . ': "' . $v . '" ');
}
fwrite($value, "\n");
};
}
 
private function applyCustomOptions(
RequestInterface $request,
array &$options
) {
// Overwrite any generated options with custom options
if ($custom = $request->getConfig()['stream_context']) {
if (!is_array($custom)) {
throw new AdapterException('stream_context must be an array');
}
$options = array_replace_recursive($options, $custom);
}
}
 
private function createStreamContext(
RequestInterface $request,
array $options,
array $params
) {
return $this->createResource(
function () use ($request, $options, $params) {
return stream_context_create($options, $params);
},
$request,
$options
);
}
 
private function createStreamResource(
RequestInterface $request,
array $options,
$context,
&$http_response_header
) {
$url = $request->getUrl();
 
return $this->createResource(
function () use ($url, &$http_response_header, $context) {
if (false === strpos($url, 'http')) {
trigger_error("URL is invalid: {$url}", E_USER_WARNING);
return null;
}
return fopen($url, 'r', null, $context);
},
$request,
$options
);
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/StreamingProxyAdapter.php
@@ -0,0 +1,36 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
/**
* Sends streaming requests to a streaming compatible adapter while sending all
* other requests to a default adapter.
*
* This, for example, could be useful for taking advantage of the performance
* benefits of the CurlAdapter while still supporting true streaming through
* the StreamAdapter.
*/
class StreamingProxyAdapter implements AdapterInterface
{
private $defaultAdapter;
private $streamingAdapter;
 
/**
* @param AdapterInterface $defaultAdapter Adapter used for non-streaming responses
* @param AdapterInterface $streamingAdapter Adapter used for streaming responses
*/
public function __construct(
AdapterInterface $defaultAdapter,
AdapterInterface $streamingAdapter
) {
$this->defaultAdapter = $defaultAdapter;
$this->streamingAdapter = $streamingAdapter;
}
 
public function send(TransactionInterface $transaction)
{
return $transaction->getRequest()->getConfig()['stream']
? $this->streamingAdapter->send($transaction)
: $this->defaultAdapter->send($transaction);
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/Transaction.php
@@ -0,0 +1,49 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
 
class Transaction implements TransactionInterface
{
/** @var ClientInterface */
private $client;
/** @var RequestInterface */
private $request;
/** @var ResponseInterface */
private $response;
 
/**
* @param ClientInterface $client Client that is used to send the requests
* @param RequestInterface $request
*/
public function __construct(
ClientInterface $client,
RequestInterface $request
) {
$this->client = $client;
$this->request = $request;
}
 
public function getRequest()
{
return $this->request;
}
 
public function getResponse()
{
return $this->response;
}
 
public function setResponse(ResponseInterface $response)
{
$this->response = $response;
}
 
public function getClient()
{
return $this->client;
}
}
/vendor/guzzlehttp/guzzle/src/Adapter/TransactionInterface.php
@@ -0,0 +1,35 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
 
/**
* Represents a transactions that consists of a request, response, and client
*/
interface TransactionInterface
{
/**
* @return RequestInterface
*/
public function getRequest();
 
/**
* @return ResponseInterface|null
*/
public function getResponse();
 
/**
* Set a response on the transaction
*
* @param ResponseInterface $response Response to set
*/
public function setResponse(ResponseInterface $response);
 
/**
* @return ClientInterface
*/
public function getClient();
}
/vendor/guzzlehttp/guzzle/src/Adapter/TransactionIterator.php
@@ -0,0 +1,78 @@
<?php
 
namespace GuzzleHttp\Adapter;
 
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Event\ListenerAttacherTrait;
use GuzzleHttp\Message\RequestInterface;
 
/**
* Converts a sequence of request objects into a transaction.
* @internal
*/
class TransactionIterator implements \Iterator
{
use ListenerAttacherTrait;
 
/** @var \Iterator */
private $source;
 
/** @var ClientInterface */
private $client;
 
/** @var array Listeners to attach to each request */
private $eventListeners = [];
 
public function __construct(
$source,
ClientInterface $client,
array $options
) {
$this->client = $client;
$this->eventListeners = $this->prepareListeners(
$options,
['before', 'complete', 'error']
);
if ($source instanceof \Iterator) {
$this->source = $source;
} elseif (is_array($source)) {
$this->source = new \ArrayIterator($source);
} else {
throw new \InvalidArgumentException('Expected an Iterator or array');
}
}
 
public function current()
{
$request = $this->source->current();
if (!$request instanceof RequestInterface) {
throw new \RuntimeException('All must implement RequestInterface');
}
 
$this->attachListeners($request, $this->eventListeners);
 
return new Transaction($this->client, $request);
}
 
public function next()
{
$this->source->next();
}
 
public function key()
{
return $this->source->key();
}
 
public function valid()
{
return $this->source->valid();
}
 
public function rewind()
{
if (!($this->source instanceof \Generator)) {
$this->source->rewind();
}
}
}