Initial commit

This commit is contained in:
2020-10-07 10:37:15 +02:00
commit ce5f440392
28157 changed files with 4429172 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?php
namespace GuzzleHttp\Event;
/**
* Basic event class that can be extended.
*/
abstract class AbstractEvent implements EventInterface
{
private $propagationStopped = false;
public function isPropagationStopped()
{
return $this->propagationStopped;
}
public function stopPropagation()
{
$this->propagationStopped = true;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace GuzzleHttp\Event;
use GuzzleHttp\Transaction;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Message\RequestInterface;
/**
* Base class for request events, providing a request and client getter.
*/
abstract class AbstractRequestEvent extends AbstractEvent
{
/** @var Transaction */
protected $transaction;
/**
* @param Transaction $transaction
*/
public function __construct(Transaction $transaction)
{
$this->transaction = $transaction;
}
/**
* Get the HTTP client associated with the event.
*
* @return ClientInterface
*/
public function getClient()
{
return $this->transaction->client;
}
/**
* Get the request object
*
* @return RequestInterface
*/
public function getRequest()
{
return $this->transaction->request;
}
/**
* Get the number of transaction retries.
*
* @return int
*/
public function getRetryCount()
{
return $this->transaction->retries;
}
/**
* @return Transaction
*/
public function getTransaction()
{
return $this->transaction;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace GuzzleHttp\Event;
/**
* Abstract request event that can be retried.
*/
class AbstractRetryableEvent extends AbstractTransferEvent
{
/**
* Mark the request as needing a retry and stop event propagation.
*
* This action allows you to retry a request without emitting the "end"
* event multiple times for a given request. When retried, the request
* emits a before event and is then sent again using the client that sent
* the original request.
*
* When retrying, it is important to limit the number of retries you allow
* to prevent infinite loops.
*
* This action can only be taken during the "complete" and "error" events.
*
* @param int $afterDelay If specified, the amount of time in milliseconds
* to delay before retrying. Note that this must
* be supported by the underlying RingPHP handler
* to work properly. Set to 0 or provide no value
* to retry immediately.
*/
public function retry($afterDelay = 0)
{
// Setting the transition state to 'retry' will cause the next state
// transition of the transaction to retry the request.
$this->transaction->state = 'retry';
if ($afterDelay) {
$this->transaction->request->getConfig()->set('delay', $afterDelay);
}
$this->stopPropagation();
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace GuzzleHttp\Event;
use GuzzleHttp\Message\ResponseInterface;
use GuzzleHttp\Ring\Future\FutureInterface;
/**
* Event that contains transfer statistics, and can be intercepted.
*/
abstract class AbstractTransferEvent extends AbstractRequestEvent
{
/**
* Get all transfer information as an associative array if no $name
* argument is supplied, or gets a specific transfer statistic if
* a $name attribute is supplied (e.g., 'total_time').
*
* @param string $name Name of the transfer stat to retrieve
*
* @return mixed|null|array
*/
public function getTransferInfo($name = null)
{
if (!$name) {
return $this->transaction->transferInfo;
}
return isset($this->transaction->transferInfo[$name])
? $this->transaction->transferInfo[$name]
: null;
}
/**
* Returns true/false if a response is available.
*
* @return bool
*/
public function hasResponse()
{
return !($this->transaction->response instanceof FutureInterface);
}
/**
* Get the response.
*
* @return ResponseInterface|null
*/
public function getResponse()
{
return $this->hasResponse() ? $this->transaction->response : null;
}
/**
* Intercept the request and associate a response
*
* @param ResponseInterface $response Response to set
*/
public function intercept(ResponseInterface $response)
{
$this->transaction->response = $response;
$this->transaction->exception = null;
$this->stopPropagation();
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace GuzzleHttp\Event;
use GuzzleHttp\Message\ResponseInterface;
/**
* Event object emitted before a request is sent.
*
* This event MAY be emitted multiple times (i.e., if a request is retried).
* You MAY change the Response associated with the request using the
* intercept() method of the event.
*/
class BeforeEvent extends AbstractRequestEvent
{
/**
* Intercept the request and associate a response
*
* @param ResponseInterface $response Response to set
*/
public function intercept(ResponseInterface $response)
{
$this->transaction->response = $response;
$this->transaction->exception = null;
$this->stopPropagation();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace GuzzleHttp\Event;
/**
* Event object emitted after a request has been completed.
*
* This event MAY be emitted multiple times for a single request. You MAY
* change the Response associated with the request using the intercept()
* method of the event.
*
* This event allows the request to be retried if necessary using the retry()
* method of the event.
*/
class CompleteEvent extends AbstractRetryableEvent {}

View File

@@ -0,0 +1,145 @@
<?php
namespace GuzzleHttp\Event;
/**
* Guzzle event emitter.
*
* Some of this class is based on the Symfony EventDispatcher component, which
* ships with the following license:
*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher
*/
class Emitter implements EmitterInterface
{
/** @var array */
private $listeners = [];
/** @var array */
private $sorted = [];
public function on($eventName, callable $listener, $priority = 0)
{
if ($priority === 'first') {
$priority = isset($this->listeners[$eventName])
? max(array_keys($this->listeners[$eventName])) + 1
: 1;
} elseif ($priority === 'last') {
$priority = isset($this->listeners[$eventName])
? min(array_keys($this->listeners[$eventName])) - 1
: -1;
}
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName]);
}
public function once($eventName, callable $listener, $priority = 0)
{
$onceListener = function (
EventInterface $event
) use (&$onceListener, $eventName, $listener, $priority) {
$this->removeListener($eventName, $onceListener);
$listener($event, $eventName);
};
$this->on($eventName, $onceListener, $priority);
}
public function removeListener($eventName, callable $listener)
{
if (empty($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $priority => $listeners) {
if (false !== ($key = array_search($listener, $listeners, true))) {
unset(
$this->listeners[$eventName][$priority][$key],
$this->sorted[$eventName]
);
}
}
}
public function listeners($eventName = null)
{
// Return all events in a sorted priority order
if ($eventName === null) {
foreach (array_keys($this->listeners) as $eventName) {
if (empty($this->sorted[$eventName])) {
$this->listeners($eventName);
}
}
return $this->sorted;
}
// Return the listeners for a specific event, sorted in priority order
if (empty($this->sorted[$eventName])) {
$this->sorted[$eventName] = [];
if (isset($this->listeners[$eventName])) {
krsort($this->listeners[$eventName], SORT_NUMERIC);
foreach ($this->listeners[$eventName] as $listeners) {
foreach ($listeners as $listener) {
$this->sorted[$eventName][] = $listener;
}
}
}
}
return $this->sorted[$eventName];
}
public function hasListeners($eventName)
{
return !empty($this->listeners[$eventName]);
}
public function emit($eventName, EventInterface $event)
{
if (isset($this->listeners[$eventName])) {
foreach ($this->listeners($eventName) as $listener) {
$listener($event, $eventName);
if ($event->isPropagationStopped()) {
break;
}
}
}
return $event;
}
public function attach(SubscriberInterface $subscriber)
{
foreach ($subscriber->getEvents() as $eventName => $listeners) {
if (is_array($listeners[0])) {
foreach ($listeners as $listener) {
$this->on(
$eventName,
[$subscriber, $listener[0]],
isset($listener[1]) ? $listener[1] : 0
);
}
} else {
$this->on(
$eventName,
[$subscriber, $listeners[0]],
isset($listeners[1]) ? $listeners[1] : 0
);
}
}
}
public function detach(SubscriberInterface $subscriber)
{
foreach ($subscriber->getEvents() as $eventName => $listener) {
$this->removeListener($eventName, [$subscriber, $listener[0]]);
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace GuzzleHttp\Event;
/**
* Guzzle event emitter.
*/
interface EmitterInterface
{
/**
* Binds a listener to a specific event.
*
* @param string $eventName Name of the event to bind to.
* @param callable $listener Listener to invoke when triggered.
* @param int|string $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0). You can
* pass "first" or "last" to dynamically specify the event priority
* based on the current event priorities associated with the given
* event name in the emitter. Use "first" to set the priority to the
* current highest priority plus one. Use "last" to set the priority to
* the current lowest event priority minus one.
*/
public function on($eventName, callable $listener, $priority = 0);
/**
* Binds a listener to a specific event. After the listener is triggered
* once, it is removed as a listener.
*
* @param string $eventName Name of the event to bind to.
* @param callable $listener Listener to invoke when triggered.
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function once($eventName, callable $listener, $priority = 0);
/**
* Removes an event listener from the specified event.
*
* @param string $eventName The event to remove a listener from
* @param callable $listener The listener to remove
*/
public function removeListener($eventName, callable $listener);
/**
* Gets the listeners of a specific event or all listeners if no event is
* specified.
*
* @param string $eventName The name of the event. Pass null (the default)
* to retrieve all listeners.
*
* @return array The event listeners for the specified event, or all event
* listeners by event name. The format of the array when retrieving a
* specific event list is an array of callables. The format of the array
* when retrieving all listeners is an associative array of arrays of
* callables.
*/
public function listeners($eventName = null);
/**
* Checks if the emitter has listeners by the given name.
*
* @param string $eventName The name of the event to check.
*
* @return bool
*/
public function hasListeners($eventName);
/**
* Emits an event to all registered listeners.
*
* Each event that is bound to the emitted eventName receives a
* EventInterface, the name of the event, and the event emitter.
*
* @param string $eventName The name of the event to dispatch.
* @param EventInterface $event The event to pass to the event handlers/listeners.
*
* @return EventInterface Returns the provided event object
*/
public function emit($eventName, EventInterface $event);
/**
* Attaches an event subscriber.
*
* The subscriber is asked for all the events it is interested in and added
* as an event listener for each event.
*
* @param SubscriberInterface $subscriber Subscriber to attach.
*/
public function attach(SubscriberInterface $subscriber);
/**
* Detaches an event subscriber.
*
* @param SubscriberInterface $subscriber Subscriber to detach.
*/
public function detach(SubscriberInterface $subscriber);
}

View File

@@ -0,0 +1,28 @@
<?php
namespace GuzzleHttp\Event;
/**
* A terminal event that is emitted when a request transaction has ended.
*
* This event is emitted for both successful responses and responses that
* encountered an exception. You need to check if an exception is present
* in your listener to know the difference.
*
* You MAY intercept the response associated with the event if needed, but keep
* in mind that the "complete" event will not be triggered as a result.
*/
class EndEvent extends AbstractTransferEvent
{
/**
* Get the exception that was encountered (if any).
*
* This method should be used to check if the request was sent successfully
* or if it encountered errors.
*
* @return \Exception|null
*/
public function getException()
{
return $this->transaction->exception;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace GuzzleHttp\Event;
use GuzzleHttp\Exception\RequestException;
/**
* Event emitted when an error occurs while sending a request.
*
* This event MAY be emitted multiple times. You MAY intercept the exception
* and inject a response into the event to rescue the request using the
* intercept() method of the event.
*
* This event allows the request to be retried using the "retry" method of the
* event.
*/
class ErrorEvent extends AbstractRetryableEvent
{
/**
* Get the exception that was encountered
*
* @return RequestException
*/
public function getException()
{
return $this->transaction->exception;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace GuzzleHttp\Event;
/**
* Base event interface used when dispatching events to listeners using an
* event emitter.
*/
interface EventInterface
{
/**
* Returns whether or not stopPropagation was called on the event.
*
* @return bool
* @see Event::stopPropagation
*/
public function isPropagationStopped();
/**
* Stops the propagation of the event, preventing subsequent listeners
* registered to the same event from being invoked.
*/
public function stopPropagation();
}

View File

@@ -0,0 +1,15 @@
<?php
namespace GuzzleHttp\Event;
/**
* Holds an event emitter
*/
interface HasEmitterInterface
{
/**
* Get the event emitter of the object
*
* @return EmitterInterface
*/
public function getEmitter();
}

View File

@@ -0,0 +1,20 @@
<?php
namespace GuzzleHttp\Event;
/**
* Trait that implements the methods of HasEmitterInterface
*/
trait HasEmitterTrait
{
/** @var EmitterInterface */
private $emitter;
public function getEmitter()
{
if (!$this->emitter) {
$this->emitter = new Emitter();
}
return $this->emitter;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace GuzzleHttp\Event;
/**
* Trait that provides methods for extract event listeners specified in an array
* and attaching them to an emitter owned by the object or one of its direct
* dependencies.
*/
trait ListenerAttacherTrait
{
/**
* Attaches event listeners and properly sets their priorities and whether
* or not they are are only executed once.
*
* @param HasEmitterInterface $object Object that has the event emitter.
* @param array $listeners Array of hashes representing event
* event listeners. Each item contains
* "name", "fn", "priority", & "once".
*/
private function attachListeners(HasEmitterInterface $object, array $listeners)
{
$emitter = $object->getEmitter();
foreach ($listeners as $el) {
if ($el['once']) {
$emitter->once($el['name'], $el['fn'], $el['priority']);
} else {
$emitter->on($el['name'], $el['fn'], $el['priority']);
}
}
}
/**
* Extracts the allowed events from the provided array, and ignores anything
* else in the array. The event listener must be specified as a callable or
* as an array of event listener data ("name", "fn", "priority", "once").
*
* @param array $source Array containing callables or hashes of data to be
* prepared as event listeners.
* @param array $events Names of events to look for in the provided $source
* array. Other keys are ignored.
* @return array
*/
private function prepareListeners(array $source, array $events)
{
$listeners = [];
foreach ($events as $name) {
if (isset($source[$name])) {
$this->buildListener($name, $source[$name], $listeners);
}
}
return $listeners;
}
/**
* Creates a complete event listener definition from the provided array of
* listener data. Also works recursively if more than one listeners are
* contained in the provided array.
*
* @param string $name Name of the event the listener is for.
* @param array|callable $data Event listener data to prepare.
* @param array $listeners Array of listeners, passed by reference.
*
* @throws \InvalidArgumentException if the event data is malformed.
*/
private function buildListener($name, $data, &$listeners)
{
static $defaults = ['priority' => 0, 'once' => false];
// If a callable is provided, normalize it to the array format.
if (is_callable($data)) {
$data = ['fn' => $data];
}
// Prepare the listener and add it to the array, recursively.
if (isset($data['fn'])) {
$data['name'] = $name;
$listeners[] = $data + $defaults;
} elseif (is_array($data)) {
foreach ($data as $listenerData) {
$this->buildListener($name, $listenerData, $listeners);
}
} else {
throw new \InvalidArgumentException('Each event listener must be a '
. 'callable or an associative array containing a "fn" key.');
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace GuzzleHttp\Event;
use GuzzleHttp\Transaction;
/**
* Event object emitted when upload or download progress is made.
*
* You can access the progress values using their corresponding public
* properties:
*
* - $downloadSize: The number of bytes that will be downloaded (if known)
* - $downloaded: The number of bytes that have been downloaded
* - $uploadSize: The number of bytes that will be uploaded (if known)
* - $uploaded: The number of bytes that have been uploaded
*/
class ProgressEvent extends AbstractRequestEvent
{
/** @var int Amount of data to be downloaded */
public $downloadSize;
/** @var int Amount of data that has been downloaded */
public $downloaded;
/** @var int Amount of data to upload */
public $uploadSize;
/** @var int Amount of data that has been uploaded */
public $uploaded;
/**
* @param Transaction $transaction Transaction being sent.
* @param int $downloadSize Amount of data to download (if known)
* @param int $downloaded Amount of data that has been downloaded
* @param int $uploadSize Amount of data to upload (if known)
* @param int $uploaded Amount of data that had been uploaded
*/
public function __construct(
Transaction $transaction,
$downloadSize,
$downloaded,
$uploadSize,
$uploaded
) {
parent::__construct($transaction);
$this->downloadSize = $downloadSize;
$this->downloaded = $downloaded;
$this->uploadSize = $uploadSize;
$this->uploaded = $uploaded;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace GuzzleHttp\Event;
/**
* Contains methods used to manage the request event lifecycle.
*/
final class RequestEvents
{
// Generic event priorities
const EARLY = 10000;
const LATE = -10000;
// "before" priorities
const PREPARE_REQUEST = -100;
const SIGN_REQUEST = -10000;
// "complete" and "error" response priorities
const VERIFY_RESPONSE = 100;
const REDIRECT_RESPONSE = 200;
/**
* Converts an array of event options into a formatted array of valid event
* configuration.
*
* @param array $options Event array to convert
* @param array $events Event names to convert in the options array.
* @param mixed $handler Event handler to utilize
*
* @return array
* @throws \InvalidArgumentException if the event config is invalid
* @internal
*/
public static function convertEventArray(
array $options,
array $events,
$handler
) {
foreach ($events as $name) {
if (!isset($options[$name])) {
$options[$name] = [$handler];
} elseif (is_callable($options[$name])) {
$options[$name] = [$options[$name], $handler];
} elseif (is_array($options[$name])) {
if (isset($options[$name]['fn'])) {
$options[$name] = [$options[$name], $handler];
} else {
$options[$name][] = $handler;
}
} else {
throw new \InvalidArgumentException('Invalid event format');
}
}
return $options;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace GuzzleHttp\Event;
/**
* SubscriberInterface provides an array of events to an
* EventEmitterInterface when it is registered. The emitter then binds the
* listeners specified by the EventSubscriber.
*
* This interface is based on the SubscriberInterface of the Symfony.
* @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher
*/
interface SubscriberInterface
{
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The returned array keys MUST map to an event name. Each array value
* MUST be an array in which the first element is the name of a function
* on the EventSubscriber OR an array of arrays in the aforementioned
* format. The second element in the array is optional, and if specified,
* designates the event priority.
*
* For example, the following are all valid:
*
* - ['eventName' => ['methodName']]
* - ['eventName' => ['methodName', $priority]]
* - ['eventName' => [['methodName'], ['otherMethod']]
* - ['eventName' => [['methodName'], ['otherMethod', $priority]]
* - ['eventName' => [['methodName', $priority], ['otherMethod', $priority]]
*
* @return array
*/
public function getEvents();
}