Skip to content

Latest commit

 

History

History
1031 lines (729 loc) · 34.9 KB

events.md

File metadata and controls

1031 lines (729 loc) · 34.9 KB

Events

If you use the Ivory\HttpAdapter\EventDispatcherHttpAdapter, then, when a request is sent, some events are triggered through the Symfony2 event dispatcher and so, you can hook into them pretty easily.

Available events

All available events are described by the constants wrapped in the Ivory\HttpAdapter\Event\Events class. There are two types of events:

  • Serial events
  • Parallel events

Request created

The Ivory\HttpAdapter\Event\Events::REQUEST_CREATED describes the event trigger just before a request is sent serially. It is represented by the Ivory\HttpAdapter\Event\RequestCreatedEvent and wraps the http adapter and the internal request. To get/set them, you can use:

use Ivory\HttpAdapter\Event\RequestCreatedEvent;

$requestCreatedEvent = new RequestCreatedEvent($httpAdapter, $request);

$httpAdapter = $requestCreatedEvent->getHttpAdapter();

$request = $requestCreatedEvent->getRequest();
$requestCreatedEvent->setRequest($request);

Request sent

The Ivory\HttpAdapter\Event\Events::REQUEST_SENT describes the event trigger just after the request is sent serially. It is described by the Ivory\HttpAdapter\Event\RequestSentEvent and wraps the http adapter, the internal request and the response. To get/set them, you can use:

use Ivory\HttpAdapter\Event\RequestSentEvent;

$requestSentEvent = new RequestSentEvent($httpAdapter, $request, $response);

$httpAdapter = $requestSentEvent->getHttpAdapter();

$request = $requestSentEvent->getRequest();
$requestSentEvent->setRequest($request);

$response = $requestSentEvent->getResponse();
$requestSentEvent->setResponse($response);

Request errored

The Ivory\HttpAdapter\Event\Events::REQUEST_ERRORED describes the event trigger if an error occurred in a serial request context. It is represented by the Ivory\HttpAdapter\Event\RequestErroredEvent and wraps the http adapter and the exception. To get/set them, you can use:

use Ivory\HttpAdapter\Event\RequestErroredEvent;

$requestErroredEvent = new RequestErroredEvent($httpAdapter, $request, $exception);

$httpAdapter = $requestErroredEvent->getHttpAdapter();

$exception = $requestErroredEvent->getException();
$requestErroredEvent->setException($exception);

$request = $exception->getRequest();
$response = $exception->getResponse();

Additionally, this event allows you to manage a response. That means if you want to by-pass the exception, just set the response on the event and it will be returned by the http adapter instead of throwing the exception. The following API is available:

$hasResponse = $requestErroredEvent->hasResponse();
$response = $requestErroredEvent->getResponse();
$requestErroredEvent->setResponse($response);

Multi request created

The Ivory\HttpAdapter\Event\Events::MULTI_REQUEST_CREATED describes the event trigger just before multiple requests are sent in parallel. It is represented by the Ivory\HttpAdapter\Event\MultiRequestCreatedEvent and wraps the http adapter and the internal requests. To get/set them, you can use:

use Ivory\HttpAdapter\Event\MultiRequestCreatedEvent;

$multiRequestCreatedEvent = new MultiRequestCreatedEvent($httpAdapter, $requests);

$httpAdapter = $multiRequestCreatedEvent->getHttpAdapter();

$requests = $multiRequestCreatedEvent->getRequests();
$multiRequestCreatedEvent->setRequests($requests);

Multi request sent

The Ivory\HttpAdapter\Event\Events::MULTI_REQUEST_SENT describes the event trigger just after multiple requests are sent fully successfully in parallel. To be even more precise, if only one request is errored, this event is not triggered. It is described by the Ivory\HttpAdapter\Event\MultiRequestSentEvent and wraps the http adapter and the responses. To get/set them, you can use:

use Ivory\HttpAdapter\Event\MultiRequestSentEvent;

$multiRequestSentEvent = new MultiRequestSentEvent($httpAdapter, $responses);

$httpAdapter = $multiRequestSentEvent->getHttpAdapter();

$responses = $multiRequestSentEvent->getResponses();
$multiRequestSentEvent->setResponses($responses);

All responses wrap the linked request as parameter:

foreach ($responses as $response) {
    $request = $response->getParameter('request');
}

Multi request errored

The Ivory\HttpAdapter\Event\Events::MULTI_REQUEST_ERRORED describes the event trigger if an error occurred in a parallel requests context. It is represented by the Ivory\HttpAdapter\Event\MultiRequestErroredEvent and wraps the http adapter exceptions. To get/set them, you can use:

use Ivory\HttpAdapter\Event\MultiRequestErroredEvent;

$multiRequestErroredEvent = new MultiRequestErroredEvent($httpAdapter, $multiException);

$httpAdapter = $multiRequestErroredEvent->getHttpAdapter();

$exceptions = $multiRequestErroredEvent->getExceptions();
$multiRequestErroredEvent->setExceptions($exceptions);

The exception is an instance of Ivory\HttpAdapter\HttpAdapterException which wraps the request and the responses if available. To get/set them, you can use:

foreach ($mutliRequestErroredEvent->getExceptions() as $exception) {
    $request = $exception->getRequest();

    if ($exception->hasResponses()) {
        $responses = $exception->getResponse();
    }
}

Available subscribers

The library provides some useful built-in subscribers you can directly use. Obviously, you can define your own and propose to add them in the core. The built-in ones are:

Some other event subscribers are available as dedicated packages:

Basic authentication

The basic authentication subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\BasicAuthSubscriber and allows you to do an HTTP basic authentication. To use it:

use Ivory\HttpAdapter\Event\BasicAuth\BasicAuth;
use Ivory\HttpAdapter\Event\Subscriber\BasicAuthSubscriber;

$basicAuthSubscriber = new BasicAuthSubscriber(new BasicAuth('username', 'password'));

Internally, the basic auth subscriber uses the Ivory\HttpAdapter\Event\BasicAuth\BasicAuth in order to do the authentication. Since the username and passord is mandatory, the basic auth is too. It takes respectively as first and second argument the username and the password. The basic authentication accepts a third argument known as matcher. A matcher is responsible to check if the request should be authenticated according to your rules. It can be either:

  • null: all requests are authenticated (default).
  • string: only requests with the url matching the string (regex pattern) are authenticated.
  • callable: only requests matching your callable are authenticated (the callable receives the event request as argument and should return true/false).

Finally, all constructor arguments can be updated at runtime:

$username = $basicAuth->getUsername();
$basicAuth->setUsername($username);

$password = $basicAuth->getPassword();
$basicAuth->setPassword($password);

$hasMatcher = $basicAuth->hasMatcher();
$matcher = $basicAuth->getMatcher();
$basicAuth->setMatcher($matcher);

Cache

The cache subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\CacheSubscriber and allow you to cache responses/exceptions. We are not talking about http cache mechanism but a cache layer which pick response/exceptions according to a request and save them the first time requests are sent. To use it:

use Doctrine\Common\Cache\FilesystemCache;
use Ivory\HttpAdapter\Event\Cache\Adapter\DoctrineCacheAdapter;
use Ivory\HttpAdapter\Event\Cache\Cache;
use Ivory\HttpAdapter\Event\Subscriber\CacheSubscriber;

$filesystemCache = new FilesystemCache(__DIR__.'/cache');
$doctrineCache = new DoctrineCacheAdapter($filesystemCache);
$cache = new Cache($doctrineCache);
$cacheSubscriber = new CacheSubscriber($cache);

At a first read, it seems complex but it is pretty easy and allows you to use any kind of caching system. Basically, the subscriber needs a cache system represented by the Ivory\HttpAdapter\Event\Cache\CacheInterface in order to get/save responses/exceptions. The default implementation is the Ivory\HttpAdapter\Event\Cache\Cache. The cache system needs a cache adapter defined by the Ivory\HttpAdapter\Event\Cache\Adapter\CacheAdapterInterface which is responsible to get/save scalar data. Currently, there are the Ivory\HttpAdapter\Event\Cache\Adapter\DoctrineCacheAdapter and the Ivory\HttpAdapter\Event\Cache\Adapter\StashCacheAdapter. Then, the cache adapters requires the underlying cache (doctrine, stash, ...).

The cache system serializes/unserializes responses/exceptions through the formatter represented by the Ivory\HttpAdapter\Event\Formatter\FormatterInterface. Its default implementation is the Ivory\HttpAdapter\Event\Formatter\Formatter and can be passed as second constructor argument:

use Doctrine\Common\Cache\ArrayCache;
use Ivory\HttpAdapter\Event\Cache\Adapter\DoctrineCacheAdapter;
use Ivory\HttpAdapter\Event\Cache\Cache;
use Ivory\HttpAdapter\Event\Formatter\Formatter;
use Ivory\HttpAdapter\Event\Subscriber\CacheSubscriber;

$arrayCache = new ArrayCache();
$doctrineCache = new DoctrineCacheAdapter($arrayCache);
$formatter = new Formatter();
$cache = new Cache($doctrineCache, $formatter);
$cacheSubscriber = new CacheSubscriber($cache);

You can specify the lifetime of the cache by passing it as third argument:

use Doctrine\Common\Cache\ArrayCache;
use Ivory\HttpAdapter\Event\Cache\Adapter\DoctrineCacheAdapter;
use Ivory\HttpAdapter\Event\Cache\Cache;
use Ivory\HttpAdapter\Event\Subscriber\CacheSubscriber;

$arrayCache = new ArrayCache();
$doctrineCache = new DoctrineCacheAdapter($arrayCache);
$cache = new Cache($doctrineCache, null, 100);
$cacheSubscriber = new CacheSubscriber($cache);

or at runtime:

$cache->setLifetime(100);

You can also disable exceptions caching only (keeping responses caching) by passing false as fourth argument:

use Doctrine\Common\Cache\ArrayCache;
use Ivory\HttpAdapter\Event\Cache\Adapter\DoctrineCacheAdapter;
use Ivory\HttpAdapter\Event\Cache\Cache;
use Ivory\HttpAdapter\Event\Subscriber\CacheSubscriber;

$arrayCache = new ArrayCache();
$doctrineCache = new DoctrineCacheAdapter($arrayCache);
$cache = new Cache($doctrineCache, null, null, false);
$cacheSubscriber = new CacheSubscriber($cache);

or at runtime:

$cache->cacheException(false);

If you prefer using stash, an other example:

use Ivory\HttpAdapter\Event\Cache\Adapter\StashCacheAdapter;
use Ivory\HttpAdapter\Event\Cache\Cache;
use Ivory\HttpAdapter\Event\Subscriber\CacheSubscriber;
use Stash\Pool

$pool = new Stash\Pool();
$stashCache = new StashCacheAdapter($pool);
$cache = new Cache($stashCache);
$cacheSubscriber = new CacheSubscriber($cache);

Cookie

The cookie subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\CookieSubscriber and allow you to manage cookies through a jar. To use it:

use Ivory\HttpAdapter\Event\Subscriber\CookieSubscriber;

$cookieSubscriber = new CookieSubscriber();

By default, a cookie jar is created by the subscriber but you can specify it in its constructor:

use Ivory\HttpAdapter\Event\Cookie\Jar\CookieJar;
use Ivory\HttpAdapter\Event\Subscriber\CookieSubscriber;

$cookieJar = new CookieJar();
$cookieSubscriber = new CookieSubscriber($cookieJar);

Finally, the cookie jar can be access or change at runtime with:

$cookieJar = $cookieSubscriber->getCookieJar();
$cookieSubscriber->setCookieJar($cookieJar);

Cookie Jar

A cookie jar is described by the Ivory\HttpAdapter\Event\Cookie\Jar\CookieJarInterface and its default implementation is Ivory\HttpAdapter\Event\Cookie\Jar\CookieJar. As there is an interface, you can define your own implementation.

First, a cookie jar stores/manages the cookies. You can use the following API:

$hasCookies = $cookieJar->hasCookies();
$cookies = $cookieJar->getCookies();
$cookieJar->setCookies($cookies);
$cookieJar->addCookies($cookies);
$cookieJar->removeCookies($cookies);

$hasCookie = $cookieJar->hasCookie($cookie);
$cookieJar->addCookie($cookie);
$cookieJar->removeCookie($cookie);

// Clean expired cookies
$cookieJar->clean();

// Clear cookies
$cookieJar->clear($domain, $path, $name);

Second, the cookie jar creates the cookies through the Ivory\HttpAdapter\Event\Cookie\CookieFactoryInterface and its default implementation is Ivory\HttpAdapter\Event\Cookie\CookieFactory. It can be configured via the constructor or getter/setter:

use Ivory\HttpAdapter\Event\Cookie\CookieFactory;
use Ivory\HttpAdapter\Event\Cookie\Jar\CookieJar;

$cookieJar = new CookieJar(new CookieFactory());

$cookieFactory = $cookieJar->getCookieFactory();
$cookieJar->setCookieFactory($cookieFactory);

Third, it is responsible to populate cookies in a request and extract them from a response with the following API:

$cookieJar->populate($request);
$cookieJar->extract($request, $response);

Fourth, the cookie jar implements the Countable interface, so if you wants to know how many cookies are in it, you can use:

$count = count($cookieJar);

Fifth, the cookies implements the IteratorAggregator interface, so, you can directly access cookies with the following code:

foreach ($cookieJar as $cookie) {
    // Do what you want with the cookie
}

// or

$cookies = iterator_to_array($cookieJar);

Persistent cookie jar

The persistent cookie jar is described by the Ivory\HttpAdapter\Event\Cookie\Jar\PersistentCookieJarInterface and its default implementation is the Ivory\HttpAdapter\Event\Cookie\Jar\AbstractPersistentCookieJar (not directly usable). Basically, it allows you to load/save cookies from/to somewhere. All persistent cookie jars share the following API:

// Loads the cookie jar from the underlying resource
$cookieJar->load();

// Saves the cookie jar on the underlying resource
$cookieJar->save();

Additionally, the persistent cookie jar will automatically try to load it when it is instantiated and will save it when it is destroyed.

File cookie jar

The file cookie jar is a persistent cookie jar which stores/retrieves cookies from a file. To use it:

use Ivory\HttpAdapter\Event\Cookie\Jar\FileCookieJar;

$cookieJar = new FileCookieJar('path/to/the/file');
Session cookie jar

The session cookie jar is a persistent cookie jar which stores/retrieves cookies from the session. To use it:

use Ivory\HttpAdapter\Event\Cookie\Jar\SessionCookieJar;

$cookieJar = new SessionCookieJar('session_key');

Cookie factory

As already explained, the cookie factory is defined by the Ivory\HttpAdapter\Event\Cookie\CookieFactoryInterface and its default implementation is Ivory\HttpAdapter\Event\Cookie\CookieFactory. It is responsible to instantiate a cookie. For that, you can directly instantiate it or parse an header:

$cookie = $cookieFactory->create($name, $value, $attributes, $createdAt);
// or
$cookie = $cookieFactory->parse($header);

Cookie

A cookie is describes by the Ivory\HttpAdapter\Event\Cookie\CookieInterface and its default implemenation is Ivory\HttpAdapter\Event\Cookie\Cookie. As there is an interface, you can define your own implementation.

A cookie stores all the informations related to it. You can get/set them with:

use Ivory\HttpAdapter\Event\Cookie\Cookie;

$cookie = new Cookie($name, $value, $attributes, $createdAt);

$hasName = $cookie->hasName();
$name = $cookie->getName();
$cookie->setName($name);

$hasValue = $cookie->hasValue();
$value = $cookie->getValue();
$cookie->setValue($value);

$hasAttributes = $cookie->hasAttributes();
$attributes = $cookie->getAttributes();
$cookie->setAttributes($attributes);
$cookie->addAttributes($attributes);
$cookie->removeAttributes($names);
$cookie->clearAttributes();

$hasAttribute = $cookie->hasAttributes($name);
$value = $cookie->getAttribute($name);
$cookie->setAttribute($name, $value);
$cookie->removeAttribute($name);

$createdAt = $cookie->getCreatedAt();
$cookie->setCreatedAt($createdAt);

All attribute names are described by the Ivory\HttpAdapter\Event\Cookie\Cookie::ATTR_* constants.

To check/get the expiration date according to the Expires pr Max-Age attribute, you can use the following method:

$expired = $cookie->isExpired();
$expires = $cookie->getExpires();

Additionally, you can check if the request matches a request or just matches a part of the request:

$match = $cookie->match($request);
$match = $cookie->matchDomain($domain);
$match = $cookie->matchPath($path);
$match = $cookie->matchScheme($scheme);

Furthermore, you can compare two cookies:

$compare = $cookie->compare($otherCookie);

You can also convert the cookie into an array or a string:

$array = $cookie->toArray();
$string = (string) $cookie;

History

The history subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\HistorySubscriber and allow you to maintain an history of all requests/responses sent through a journal. To use it:

use Ivory\HttpAdapter\Event\Subscriber\HistorySubscriber;

$historySubscriber = new HistorySubscriber();

By default, a journal is created by the subscriber but you can specify it in its constructor:

use Ivory\HttpAdapter\Event\Subscriber\History\Journal;
use Ivory\HttpAdapter\Event\Subscriber\HistorySubscriber;

$journal = new Journal();
$historySubscriber = new HistorySubscriber($journal);

Finally, the journal can be access or change at runtime with:

$journal = $historySubscriber->getJournal();
$historySubscriber->setJournal($journal);

Journal

A journal is described by the Ivory\HttpAdapter\Event\Subscriber\History\JournalInterface and its default implementation is Ivory\HttpAdapter\Event\Subscriber\History\Journal. As there is an interface, you can define your own implementation.

So, a journal wraps a limit which represents the maximum number of allowed entries in the journal (default: 10) but can be configured via getter/setter:

$limit = $journal->getLimit();
$journal->setLimit($limit);

Second, the journal wraps all entries of the history according to the limit (the last entries are kept in the journal and the last ones are dropped when a new one is added). The following API allows you to check/get/set/clear the entries:

$hasEntries = $journal->hasEntries();
$entries = $journal->getEntries();
$journal->setEntries($entries);
$journal->addEntries($entries);
$journal->removeEntries($entries);

$hasEntry = $journal->hasEntry($entry);
$journal->addEntry($entry);
$journal->removeEntry($entry);

$journal->clear();

Third, a journal creates the entries through the Ivory\HttpAdapter\Event\History\JournalEntryFactoryInterface and its default implementation is Ivory\HttpAdapter\Event\History\JournalEntryFactory. It can be configured via the constructor or getter/setter:

use Ivory\HttpAdapter\Event\Subscriber\History\Journal;
use Ivory\HttpAdapter\Event\Subscriber\History\JournalEntryFactory;

$journal = new Journal(new JournalEntryFactory());

$entryJournalFactory = $journal->getJournalEntryFactory();
$journal->setJournalEntryFactory($entryJournalFactory);

Fourth, the journal implements the Countable interface, so if you wants to know how many entries are in the journal, you can use:

$count = count($journal);

Fifth, the journal implements the IteratorAggregator interface, so, you can directly access entries with the following code but the entries are ordered from the most recent to the most old:

foreach ($journal as $entry) {
    // Do what you want with the entry
}

// or

$entries = iterator_to_array($journal);

Journal entry

A journal entry is described by the Ivory\HttpAdapter\Event\Subscriber\History\JournalEntryInterface and its default implementation is Ivory\HttpAdapter\Event\Subscriber\History\JournalEntry. As there is an interface, you can define your own implementation through the factory.

It wraps the request and the response. To get/set them, you can use the following API:

use Ivory\HttpAdapter\Event\Subscriber\History\JournalEntry;

$entry = new JournalEntry($request, $response);

$request = $entry->getRequest();
$entry->setRequest($request);

$response = $entry->getResponse();
$entry->setResponse($response);

Logger

The logger subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\LoggerSubscriber and allows you to log all requests sent/errorred through a PSR logger. As Monolog follows the PSR-3 Standard, here an example using it and its stream handler but you can use any PSR compliant logger:

use Ivory\HttpAdapter\Event\Subscriber\LoggerSubscriber;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$monolog = new Logger('name');
$monolog->pushHandler(new StreamHandler('path/to/your.log'));

$loggerSubscriber = new LoggerSubscriber($monolog);

You can also change the logger at runtime:

$logger = $loggerSubscriber->getLogger();
$loggerSubscriber->setLogger($logger);

Internally, the logger subscriber uses the Ivory\HttpAdapter\Event\Formatter\Formatter in order to format the log context and and the Ivory\HttpAdapter\Timer\Timer in order to time the request. By default, if you don't provide them, it will be created automatically. If you want to use your own, you can pass them via constructor or getter/setter:

use Ivory\HttpAdapter\Event\Formatter\Formatter;
use Ivory\HttpAdapter\Event\Subscriber\LoggerSubscriber;
use Ivory\HttpAdapter\Event\Timer\Timer;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$monolog = new Logger('name');
$monolog->pushHandler(new StreamHandler('path/to/your.log'));

$loggerSubscriber = new LoggerSubscriber($monolog, new Formatter(), new Timer());

$formatter = $loggerSubscriber->getFormatter();
$loggerSubscriber->setFormatter($formatter);

$timer = $loggerSubscriber->getTimer();
$loggerSubscriber->setTimer($timer);

Redirect

The redirect subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\RedirectSubscriber and allow you to follow redirects. Basically, by default, all http adapters don't follow the redirect and will give you the 3xx response. Then, if you want to follow redirect, just register the redirect subscriber:

use Ivory\HttpAdapter\Event\Subscriber\RedirectSubscriber;

$redirectSubscriber = new RedirectSubscriber();

When you use the redirect subscriber, some parameters are available on the response:

  • effective_url: The final url of the redirection.
  • redirect_count: The number of redirects which have been followed.

Internally, the redirect subscriber uses the Ivory\HttpAdapter\Event\Redirect\Redirect in order to do a redirect. By default, if you don't provide it, it will be created automatically. If you want to use your own, you can pass it via constructor or getter/setter:

use Ivory\HttpAdapter\Redirect\Redirect;
use Ivory\HttpAdapter\Event\Subscriber\RedirectSubscriber;

$redirectSubscriber = new RedirectSubscriber(new Redirect());

$redirect = $redirectSubscriber->getRedirect();
$redirectSubscriber->setRedirect($redirect);

The redirect is also configurable and allow you to modify its behavior.

First, by default, the redirect allows you to follow 5 redirects. If you want to increase or decrease it, you can specify it as first constructor argument or via getter/setter:

use Ivory\HttpAdapter\Event\Redirect\Redirect;

$redirect = new Redirect(10);

$max = $redirect->getMax();
$redirect->setMax($max);

Second, by default, the redirect does not follow strictly the RFC and will prefer to do what most browser does (convert to a GET request). If you want to follow strictly the RFC, you can specify it as second constructor argument or via getter/setter:

use Ivory\HttpAdapter\Event\Redirect\Redirect;

$redirect = new Redirect(5, true);

$strict = $redirect->isStrict();
$redirect->setStrict($strict);

Third, by default, the redirect will throw an exception when the maximum number of redirects is exceeded. If you want to just stop the redirection and return the redirect response reached, you can specify it as third constructor argument or via getter/setter:

use Ivory\HttpAdapter\Event\Redirect\Redirect;

$redirect = new Redirect(5, false, false);

$throwException = $redirect->getThrowException();
$redirect->setThrowException($throwException);

Retry

The retry subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\RetrySubscriber and allow you to retry an errored request (more precisely when an exception is thrown). To use it:

use Ivory\HttpAdapter\Event\Subscriber\RetrySubscriber;

$retrySubscriber = new RetrySubscriber();

When you use the retry subscriber, some parameters are available on the request:

  • retry_count: The number of retries which have been done.

Internally, the retry subscriber uses the Ivory\HttpAdapter\Event\Retry\Retry in order to do a retry. By default, if you don't provide it, it will be created automatically. If you want to use your own, you can pass it via constructor or getter/setter:

use Ivory\HttpAdapter\Event\Subscriber\RetrySubscriber;
use Ivory\HttpAdapter\Event\Retry\Retry;

$retrySubscriber = new RetrySubscriber(new Retry());

$retry = $retrySubscriber->getRetry();
$retrySubscriber->setRetry($retry);

The retry is also configurable and allows you to modify its behavior.

By default, the retry uses a limited retry strategy of 3 retries combined to an exponential delayed one. Basically, that means it will retry 3 times maximum and wait more and more between each retry (2 ^ retry_count). This strategy can be configured through the constructor or via getter/setter:

use Ivory\HttpAdapter\Event\Retry\Strategy\ExponentialDelayedRetryStrategy;
use Ivory\HttpAdapter\Event\Retry\Strategy\LimitedRetryStrategy;

$retry = new Retry(new LimitedRetryStrategy(3, new ExponentialDelayedRetryStrategy()));

$strategy = $retry->getStrategy();
$retry->setStrategy($strategy);

If you want to know more about the retry strategies, the next sections are for you.

Constant Delayed Strategy

The constant delayed retry strategy is defined by the Ivory\HttpAdapter\Event\Retry\Strategy\ConstantDelayedRetryStrategy and allow you to retry a request following the exact same delay between each retry (by default: 5 seconds).

use Ivory\HttpAdapter\Event\Retry\Strategy\ConstantDelayedRetryStrategy;

$retryStrategy = new ConstantDelayedRetryStrategy();

The delay can be configured through the constructor or via getter/setter:

use Ivory\HttpAdapter\Event\Retry\Strategy\ConstantDelayedRetryStrategy;

$retryStrategy = new ConstantDelayedRetryStrategy(2);

$delay = $retryStrategy->getDelay();
$retryStrategy->setDelay(2);

Linear Delayed Strategy

The linear delayed retry strategy is defined by the Ivory\HttpAdapter\Event\Retry\Strategy\LinearDelayedRetryStrategy and allow you to retry a request following a linear delay between each retry (delay = configured delay * retry count). By default, the configured delay is 5 seconds.

use Ivory\HttpAdapter\Event\Retry\Strategy\LinearDelayedRetryStrategy;

$retryStrategy = new LinearDelayedRetryStrategy();

The delay can be configured through the constructor or via getter/setter:

use Ivory\HttpAdapter\Event\Retry\Strategy\LinearDelayedRetryStrategy;

$retryStrategy = new LinearDelayedRetryStrategy(2);

$delay = $retryStrategy->getDelay();
$retryStrategy->setDelay(2);

Exponential Delayed Strategy

The exponential retry strategy is defined by the Ivory\HttpAdapter\Event\Retry\Strategy\ExponentialRetryStrategy and allow you to retry a request following an exponential delay between each retry (delay = 2 ^ redirect count).

use Ivory\HttpAdapter\Event\Retry\Strategy\ExponentialDelayedRetryStrategy;

$retryStrategy = new ExponentialDelayedRetryStrategy();

Limited Strategy

The limited retry strategy is defined by the Ivory\HttpAdapter\Event\Retry\Strategy\LimitedRetryStrategy and allow you to limit the number of retry (by default: 3).

use Ivory\HttpAdapter\Event\Retry\Strategy\LimitedRetryStrategy;

$retryStrategy = new LimitedRetryStrategy();

The limit can be configured through the constructor or via getter/setter:

use Ivory\HttpAdapter\Event\Retry\Strategy\LimitedRetryStrategy;

$retryStrategy = new LimitedRetryStrategy(5);

$limit = $retryStrategy->getLimit();
$retryStrategy->setLimit(5);

Callback Strategy

The callback retry strategy is defined by the Ivory\HttpAdapter\Event\Retry\Strategy\CallbackRetryStrategy and allow you to limit the number of retry or specify the delay between each retry through two callbacks. They can be configured through the constructor or via getter/setter:

use Ivory\HttpAdapter\Event\Retry\Strategy\CallbackRetryStrategy;
use Ivory\HttpAdapter\HttpAdapterException;
use Ivory\HttpAdapter\Message\InternalRequestInterface;

$retryStrategy = new CallbackRetryStrategy(
    // Verify callback
    function (InternalRequestInterface $request, HttpAdapterException $exception) {
        // Return TRUE if you want to retry the request otherwise FALSE.
    },
    // Delay callback
    function (InternalRequestInterface $request, HttpAdapterException $exception) {
        // Return the delay to wait before retrying the request.
    }
);

$hasVerifyCallback = $retryStrategy->hasVerifyCallback();
$verifyCallback = $retryStrategy->getVerifyCallback();
$retryStrategy->setVerifyCallback($verifyCallback);

$hasDelayCallback = $retryStrategy->hasDelayCallback();
$delayCallback = $retryStrategy->getDelayCallback();
$retryStrategy->setDelayCallback($delayCallback);

The two callbacks are not mandatory. If you don't provide the verify callback, it will consider it should retry the request and if you don't provide the delay callback, it will consider it should not wait before retrying the request.

Custom Strategy

All retry strategies implement the Ivory\HttpAdapter\Event\Retry\Strategy\RetryStrategyInterface. Then, if you want to define your own strategy, you will need to implement this interface. Here, an example which rely on information wraps in the header:

namespace My\Own\Namespace;

use Ivory\HttpAdapter\Event\Retry\Strategy\RetryStrategyInterface;
use Ivory\HttpAdapter\Message\InternalRequestInterface;

class MyOwnRetryStrategy implements RetryStrategyInterface
{
    public function verify(InternalRequestInterface $request)
    {
        return $request->getHeader('X-Can-Retry') === 'true';
    }

    public function delay(InternalRequestInterface $request)
    {
        return (float) $request->getHeader('X-Retry-Delay');
    }
}

Chain of Responsibility

All retry strategies implement a chain of responsibility. That means you can combine strategies together in order to archive more complex behavior. For example, by default, the retry subscriber combines the limited retry strategy with the exponential delayed one.

To do that, all retry strategies implement the Ivory\HttpAdapter\Event\Retry\Strategy\RetryStrategyChainInterface and extend the Ivory\HttpAdapter\Event\Retry\Strategy\AbstractRetryStrategyChain where the default chain of responsibility implementation is done. Then, a chained retry strategy wraps a next strategy which is involved when it decides if it should retry the request and for calculating the delay to wait if it should retry.

The behavior of the chain of responsibility is the following:

  • It retries a request if all retry strategies in the chain of responsibility agree about retrying it.
  • It waits the biggest delay stored in the chain of responsibility.

So, technically, all retry strategies accept the next strategy as second constructor argument. Here, an example on how you should instantiate them:

Ivory\HttpAdapter\Event\Retry\Strategy\LimitedRetryStrategy;
Ivory\HttpAdapter\Event\Retry\Strategy\LinearDelayedRetryStrategy;

$retryStrategy = new LimitedRetryStrategy(5, new LinearDelayedRetryStrategy(2));

Additionally, you can update the chain of responsibility at runtime with the following API:

$hasNext = $retryStrategy->hasNext();
$next = $retryStrategy->getNext();
$retryStrategy->setNext($next);

Status Code

The status code subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\StatusCodeSubscriber and allow you to detect errored response. Basically, by default, all http adapters don't throw an exception if a 4xx or 5xx response is returned. Then, if you want to throw an exception for this kind of response, just register the status code subscriber:

use Ivory\HttpAdapter\Event\Subscriber\StatusCodeSubscriber;

$statusCodeSubscriber = new StatusCodeSubscriber();

Internally, the status code subscriber uses the Ivory\HttpAdapter\Event\StatusCode\StatusCode in order to detect if a request is valid. By default, if you don't provide it, it will be created automatically. If you want to use your own, you can pass it via constructor or getter/setter:

use Ivory\HttpAdapter\Event\StatusCode\StatusCode;
use Ivory\HttpAdapter\Event\Subscriber\StatusCodeSubscriber;

$statusCodeSubscriber = new StatusCodeSubscriber(new StatusCode());

$statusCode = $statusCodeSubscriber->getStatusCode();
$statusCodeSubscriber->setStatusCode($statusCode);

Stopwatch

The stopwatch subscriber is defined by the Ivory\HttpAdapter\Event\Subscriber\StopwatchSubscriber and allows you to time the http adapter sending through the Symfony2 Stopwatch component. To use it:

use Ivory\HttpAdapter\Event\Subscriber\StopwatchSubscriber;
use Symfony\Component\Stopwatch\Stopwatch;

$stopwatch = new Stopwatch();
$stopwatchSubscriber = new StopwatchSubscriber($stopwatch);

You can also change the stopwatch at runtime:

$stopwatch = $stopwatchSubscriber->getStopwatch();
$stopwatchSubscriber->setStopwatch($stopwatch);

Event Subscriber Priorities

All event subscribers can work together (thanks to the event priorities). Here, the summary:

Event Subscriber Pre Send Event Post Send Event Exception Event
Stopwatch 10000 -10000 -10000
Basic Auth 300 - -
Cookie 300 300 -
Status Code - 200 -
History 100 100 -
Logger 100 100 100
Redirect - 0 -
Retry - - 0
Cache -100 -100 -100