code:
<?php

namespace Http\Client\Common\Plugin;

use Http\Client\Common\Exception\CircularRedirectionException;
use Http\Client\Common\Exception\MultipleRedirectionException;
use Http\Client\Common\Plugin;
use Http\Client\Exception\HttpException;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Follow redirections.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
class RedirectPlugin implements Plugin
{
    /**
     * Rule on how to redirect, change method for the new request.
     *
     * @var array
     */
    protected $redirectCodes = [
        300 => [
            'switch' => [
                'unless' => ['GET', 'HEAD'],
                'to' => 'GET',
            ],
            'multiple' => true,
            'permanent' => false,
        ],
        301 => [
            'switch' => [
                'unless' => ['GET', 'HEAD'],
                'to' => 'GET',
            ],
            'multiple' => false,
            'permanent' => true,
        ],
        302 => [
            'switch' => [
                'unless' => ['GET', 'HEAD'],
                'to' => 'GET',
            ],
            'multiple' => false,
            'permanent' => false,
        ],
        303 => [
            'switch' => [
                'unless' => ['GET', 'HEAD'],
                'to' => 'GET',
            ],
            'multiple' => false,
            'permanent' => false,
        ],
        307 => [
            'switch' => false,
            'multiple' => false,
            'permanent' => false,
        ],
        308 => [
            'switch' => false,
            'multiple' => false,
            'permanent' => true,
        ],
    ];

    /**
     * Determine how header should be preserved from old request.
     *
     * @var bool|array
     *
     * true     will keep all previous headers (default value)
     * false    will ditch all previous headers
     * string[] will keep only headers with the specified names
     */
    protected $preserveHeader;

    /**
     * Store all previous redirect from 301 / 308 status code.
     *
     * @var array
     */
    protected $redirectStorage = [];

    /**
     * Whether the location header must be directly used for a multiple redirection status code (300).
     *
     * @var bool
     */
    protected $useDefaultForMultiple;

    /**
     * @var array
     */
    protected $circularDetection = [];

    /**
     * @param array $config {
     *
     *     @var bool|string[] $preserve_header True keeps all headers, false remove all of them, an array is interpreted as a list of header names to keep
     *     @var bool $use_default_for_multiple Whether the location header must be directly used for a multiple redirection status code (300).
     * }
     */
    public function __construct(array $config = [])
    {
        $resolver = new OptionsResolver();
        $resolver->setDefaults([
            'preserve_header' => true,
            'use_default_for_multiple' => true,
        ]);
        $resolver->setAllowedTypes('preserve_header', ['bool', 'array']);
        $resolver->setAllowedTypes('use_default_for_multiple', 'bool');
        $resolver->setNormalizer('preserve_header', function (OptionsResolver $resolver, $value) {
            if (is_bool($value) && false === $value) {
                return [];
            }

            return $value;
        });
        $options = $resolver->resolve($config);

        $this->preserveHeader = $options['preserve_header'];
        $this->useDefaultForMultiple = $options['use_default_for_multiple'];
    }

    /**
     * {@inheritdoc}
     */
    public function handleRequest(RequestInterface $request, callable $next, callable $first)
    {
        // Check in storage
        if (array_key_exists((string) $request->getUri(), $this->redirectStorage)) {
            $uri = $this->redirectStorage[(string) $request->getUri()]['uri'];
            $statusCode = $this->redirectStorage[(string) $request->getUri()]['status'];
            $redirectRequest = $this->buildRedirectRequest($request, $uri, $statusCode);

            return $first($redirectRequest);
        }

        return $next($request)->then(function (ResponseInterface $response) use ($request, $first) {
            $statusCode = $response->getStatusCode();

            if (!array_key_exists($statusCode, $this->redirectCodes)) {
                return $response;
            }

            $uri = $this->createUri($response, $request);
            $redirectRequest = $this->buildRedirectRequest($request, $uri, $statusCode);
            $chainIdentifier = spl_object_hash((object) $first);

            if (!array_key_exists($chainIdentifier, $this->circularDetection)) {
                $this->circularDetection[$chainIdentifier] = [];
            }

            $this->circularDetection[$chainIdentifier][] = (string) $request->getUri();

            if (in_array((string) $redirectRequest->getUri(), $this->circularDetection[$chainIdentifier])) {
                throw new CircularRedirectionException('Circular redirection detected', $request, $response);
            }

            if ($this->redirectCodes[$statusCode]['permanent']) {
                $this->redirectStorage[(string) $request->getUri()] = [
                    'uri' => $uri,
                    'status' => $statusCode,
                ];
            }

            // Call redirect request in synchrone
            $redirectPromise = $first($redirectRequest);

            return $redirectPromise->wait();
        });
    }

    /**
     * Builds the redirect request.
     *
     * @param RequestInterface $request    Original request
     * @param UriInterface     $uri        New uri
     * @param int              $statusCode Status code from the redirect response
     *
     * @return MessageInterface|RequestInterface
     */
    protected function buildRedirectRequest(RequestInterface $request, UriInterface $uri, $statusCode)
    {
        $request = $request->withUri($uri);

        if (false !== $this->redirectCodes[$statusCode]['switch'] && !in_array($request->getMethod(), $this->redirectCodes[$statusCode]['switch']['unless'])) {
            $request = $request->withMethod($this->redirectCodes[$statusCode]['switch']['to']);
        }

        if (is_array($this->preserveHeader)) {
            $headers = array_keys($request->getHeaders());

            foreach ($headers as $name) {
                if (!in_array($name, $this->preserveHeader)) {
                    $request = $request->withoutHeader($name);
                }
            }
        }

        return $request;
    }

    /**
     * Creates a new Uri from the old request and the location header.
     *
     * @param ResponseInterface $response The redirect response
     * @param RequestInterface  $request  The original request
     *
     * @throws HttpException                If location header is not usable (missing or incorrect)
     * @throws MultipleRedirectionException If a 300 status code is received and default location cannot be resolved (doesn't use the location header or not present)
     *
     * @return UriInterface
     */
    private function createUri(ResponseInterface $response, RequestInterface $request)
    {
        if ($this->redirectCodes[$response->getStatusCode()]['multiple'] && (!$this->useDefaultForMultiple || !$response->hasHeader('Location'))) {
            throw new MultipleRedirectionException('Cannot choose a redirection', $request, $response);
        }

        if (!$response->hasHeader('Location')) {
            throw new HttpException('Redirect status code, but no location header present in the response', $request, $response);
        }

        $location = $response->getHeaderLine('Location');
        $parsedLocation = parse_url($location);

        if (false === $parsedLocation) {
            throw new HttpException(sprintf('Location %s could not be parsed', $location), $request, $response);
        }

        $uri = $request->getUri();

        if (array_key_exists('scheme', $parsedLocation)) {
            $uri = $uri->withScheme($parsedLocation['scheme']);
        }

        if (array_key_exists('host', $parsedLocation)) {
            $uri = $uri->withHost($parsedLocation['host']);
        }

        if (array_key_exists('port', $parsedLocation)) {
            $uri = $uri->withPort($parsedLocation['port']);
        }

        if (array_key_exists('path', $parsedLocation)) {
            $uri = $uri->withPath($parsedLocation['path']);
        }

        if (array_key_exists('query', $parsedLocation)) {
            $uri = $uri->withQuery($parsedLocation['query']);
        } else {
            $uri = $uri->withQuery('');
        }

        if (array_key_exists('fragment', $parsedLocation)) {
            $uri = $uri->withFragment($parsedLocation['fragment']);
        } else {
            $uri = $uri->withFragment('');
        }

        return $uri;
    }
}


code:
<?php

namespace Http\Client\Common\Plugin;

use Http\Client\Common\Plugin;
use Http\Message\RequestMatcher;
use Psr\Http\Message\RequestInterface;

/**
 * Apply a delegated plugin based on a request match.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class RequestMatcherPlugin implements Plugin
{
    /**
     * @var RequestMatcher
     */
    private $requestMatcher;

    /**
     * @var Plugin
     */
    private $delegatedPlugin;

    /**
     * @param RequestMatcher $requestMatcher
     * @param Plugin         $delegatedPlugin
     */
    public function __construct(RequestMatcher $requestMatcher, Plugin $delegatedPlugin)
    {
        $this->requestMatcher = $requestMatcher;
        $this->delegatedPlugin = $delegatedPlugin;
    }

    /**
     * {@inheritdoc}
     */
    public function handleRequest(RequestInterface $request, callable $next, callable $first)
    {
        if ($this->requestMatcher->matches($request)) {
            return $this->delegatedPlugin->handleRequest($request, $next, $first);
        }

        return $next($request);
    }
}

repo_name: spookie/gnu

license: mit


code:
<?php

namespace Http\Client\Common\Plugin;

use Http\Client\Common\Plugin;
use Http\Client\Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Retry the request if an exception is thrown.
 *
 * By default will retry only one time.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
final class RetryPlugin implements Plugin
{
    /**
     * Number of retry before sending an exception.
     *
     * @var int
     */
    private $retry;

    /**
     * @var callable
     */
    private $exceptionDelay;

    /**
     * @var callable
     */
    private $exceptionDecider;

    /**
     * Store the retry counter for each request.
     *
     * @var array
     */
    private $retryStorage = [];

    /**
     * @param array $config {
     *
     *     @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up.
     *     @var callable $exception_decider A callback that gets a request and an exception to decide after a failure whether the request should be retried.
     *     @var callable $exception_delay A callback that gets a request, an exception and the number of retries and returns how many microseconds we should wait before trying again.
     * }
     */
    public function __construct(array $config = [])
    {
        if (array_key_exists('decider', $config)) {
            if (array_key_exists('exception_decider', $config)) {
                throw new \InvalidArgumentException('Do not set both the old "decider" and new "exception_decider" options');
            }
            trigger_error('The "decider" option has been deprecated in favour of "exception_decider"', E_USER_DEPRECATED);
            $config['exception_decider'] = $config['decider'];
            unset($config['decider']);
        }
        if (array_key_exists('delay', $config)) {
            if (array_key_exists('exception_delay', $config)) {
                throw new \InvalidArgumentException('Do not set both the old "delay" and new "exception_delay" options');
            }
            trigger_error('The "delay" option has been deprecated in favour of "exception_delay"', E_USER_DEPRECATED);
            $config['exception_delay'] = $config['delay'];
            unset($config['delay']);
        }

        $resolver = new OptionsResolver();
        $resolver->setDefaults([
            'retries' => 1,
            'exception_decider' => function (RequestInterface $request, Exception $e) {
                return true;
            },
            'exception_delay' => __CLASS__.'::defaultDelay',
        ]);
        $resolver->setAllowedTypes('retries', 'int');
        $resolver->setAllowedTypes('exception_decider', 'callable');
        $resolver->setAllowedTypes('exception_delay', 'callable');
        $options = $resolver->resolve($config);

        $this->retry = $options['retries'];
        $this->exceptionDecider = $options['exception_decider'];
        $this->exceptionDelay = $options['exception_delay'];
    }

    /**
     * {@inheritdoc}
     */
    public function handleRequest(RequestInterface $request, callable $next, callable $first)
    {
        $chainIdentifier = spl_object_hash((object) $first);

        return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) {
            if (array_key_exists($chainIdentifier, $this->retryStorage)) {
                unset($this->retryStorage[$chainIdentifier]);
            }

            return $response;
        }, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) {
            if (!array_key_exists($chainIdentifier, $this->retryStorage)) {
                $this->retryStorage[$chainIdentifier] = 0;
            }

            if ($this->retryStorage[$chainIdentifier] >= $this->retry) {
                unset($this->retryStorage[$chainIdentifier]);

                throw $exception;
            }

            if (!call_user_func($this->exceptionDecider, $request, $exception)) {
                throw $exception;
            }

            $time = call_user_func($this->exceptionDelay, $request, $exception, $this->retryStorage[$chainIdentifier]);
            usleep($time);

            // Retry in synchrone
            ++$this->retryStorage[$chainIdentifier];
            $promise = $this->handleRequest($request, $next, $first);

            return $promise->wait();
        });
    }

    /**
     * @param RequestInterface $request
     * @param Exception        $e
     * @param int              $retries The number of retries we made before. First time this get called it will be 0.
     *
     * @return int
     */
    public static function defaultDelay(RequestInterface $request, Exception $e, $retries)
    {
        return pow(2, $retries) * 500000;
    }
}


code:
<?php

namespace Http\Client\Common\Plugin;

use Psr\Http\Message\RequestInterface;

/**
 * A plugin that helps you migrate from php-http/client-common 1.x to 2.x. This
 * will also help you to support PHP5 at the same time you support 2.x.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
trait VersionBridgePlugin
{
    abstract protected function doHandleRequest(RequestInterface $request, callable $next, callable $first);

    public function handleRequest(RequestInterface $request, callable $next, callable $first)
    {
        return $this->doHandleRequest($request, $next, $first);
    }
}


code:
<?php

namespace Http\Client\Common;

use Http\Client\Common\Exception\LoopException;
use Http\Client\Exception as HttplugException;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Client\Promise\HttpFulfilledPromise;
use Http\Client\Promise\HttpRejectedPromise;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * The client managing plugins and providing a decorator around HTTP Clients.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
final class PluginClient implements HttpClient, HttpAsyncClient
{
    /**
     * An HTTP async client.
     *
     * @var HttpAsyncClient
     */
    private $client;

    /**
     * The plugin chain.
     *
     * @var Plugin[]
     */
    private $plugins;

    /**
     * A list of options.
     *
     * @var array
     */
    private $options;

    /**
     * @param HttpClient|HttpAsyncClient|ClientInterface $client
     * @param Plugin[]                                   $plugins
     * @param array                                      $options {
     *
     *     @var int      $max_restarts
     *     @var Plugin[] $debug_plugins an array of plugins that are injected between each normal plugin
     * }
     *
     * @throws \RuntimeException if client is not an instance of HttpClient or HttpAsyncClient
     */
    public function __construct($client, array $plugins = [], array $options = [])
    {
        if ($client instanceof HttpAsyncClient) {
            $this->client = $client;
        } elseif ($client instanceof HttpClient || $client instanceof ClientInterface) {
            $this->client = new EmulatedHttpAsyncClient($client);
        } else {
            throw new \RuntimeException('Client must be an instance of Http\\Client\\HttpClient or Http\\Client\\HttpAsyncClient');
        }

        $this->plugins = $plugins;
        $this->options = $this->configure($options);
    }

    /**
     * {@inheritdoc}
     */
    public function sendRequest(RequestInterface $request)
    {
        // If we don't have an http client, use the async call
        if (!($this->client instanceof HttpClient)) {
            return $this->sendAsyncRequest($request)->wait();
        }

        // Else we want to use the synchronous call of the underlying client, and not the async one in the case
        // we have both an async and sync call
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
            try {
                return new HttpFulfilledPromise($this->client->sendRequest($request));
            } catch (HttplugException $exception) {
                return new HttpRejectedPromise($exception);
            }
        });

        return $pluginChain($request)->wait();
    }

    /**
     * {@inheritdoc}
     */
    public function sendAsyncRequest(RequestInterface $request)
    {
        $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) {
            return $this->client->sendAsyncRequest($request);
        });

        return $pluginChain($request);
    }

    /**
     * Configure the plugin client.
     *
     * @param array $options
     *
     * @return array
     */
    private function configure(array $options = [])
    {
        if (isset($options['debug_plugins'])) {
            @trigger_error('The "debug_plugins" option is deprecated since 1.5 and will be removed in 2.0.', E_USER_DEPRECATED);
        }

        $resolver = new OptionsResolver();
        $resolver->setDefaults([
            'max_restarts' => 10,
            'debug_plugins' => [],
        ]);

        $resolver
            ->setAllowedTypes('debug_plugins', 'array')
            ->setAllowedValues('debug_plugins', function (array $plugins) {
                foreach ($plugins as $plugin) {
                    // Make sure each object passed with the `debug_plugins` is an instance of Plugin.
                    if (!$plugin instanceof Plugin) {
                        return false;
                    }
                }

                return true;
            });

        return $resolver->resolve($options);
    }

    /**
     * Create the plugin chain.
     *
     * @param Plugin[] $pluginList     A list of plugins
     * @param callable $clientCallable Callable making the HTTP call
     *
     * @return callable
     */
    private function createPluginChain($pluginList, callable $clientCallable)
    {
        $firstCallable = $lastCallable = $clientCallable;

        /*
         * Inject debug plugins between each plugin.
         */
        $pluginListWithDebug = $this->options['debug_plugins'];
        foreach ($pluginList as $plugin) {
            $pluginListWithDebug[] = $plugin;
            $pluginListWithDebug = array_merge($pluginListWithDebug, $this->options['debug_plugins']);
        }

        while ($plugin = array_pop($pluginListWithDebug)) {
            $lastCallable = function (RequestInterface $request) use ($plugin, $lastCallable, &$firstCallable) {
                return $plugin->handleRequest($request, $lastCallable, $firstCallable);
            };

            $firstCallable = $lastCallable;
        }

        $firstCalls = 0;
        $firstCallable = function (RequestInterface $request) use ($lastCallable, &$firstCalls) {
            if ($firstCalls > $this->options['max_restarts']) {
                throw new LoopException('Too many restarts in plugin client', $request);
            }

            ++$firstCalls;

            return $lastCallable($request);
        };

        return $firstCallable;
    }
}


code:
<?php

namespace Http\Client\Common;

use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Psr\Http\Client\ClientInterface;

/**
 * Factory to create PluginClient instances. Using this factory instead of calling PluginClient constructor will enable
 * the Symfony profiling without any configuration.
 *
 * @author Fabien Bourigault <bourigaultfabien@gmail.com>
 */
final class PluginClientFactory
{
    /**
     * @var callable
     */
    private static $factory;

    /**
     * Set the factory to use.
     * The callable to provide must have the same arguments and return type as PluginClientFactory::createClient.
     * This is used by the HTTPlugBundle to provide a better Symfony integration.
     * Unlike the createClient method, this one is static to allow zero configuration profiling by hooking into early
     * application execution.
     *
     * @internal
     *
     * @param callable $factory
     */
    public static function setFactory(callable $factory)
    {
        static::$factory = $factory;
    }

    /**
     * @param HttpClient|HttpAsyncClient|ClientInterface $client
     * @param Plugin[]                                   $plugins
     * @param array                                      $options {
     *
     *     @var string $client_name to give client a name which may be used when displaying client information  like in
     *         the HTTPlugBundle profiler.
     * }
     *
     * @see PluginClient constructor for PluginClient specific $options.
     *
     * @return PluginClient
     */
    public function createClient($client, array $plugins = [], array $options = [])
    {
        if (static::$factory) {
            $factory = static::$factory;

            return $factory($client, $plugins, $options);
        }

        unset($options['client_name']);

        return new PluginClient($client, $plugins, $options);
    }
}


code:
<?php

namespace Http\Client\Common;

use Psr\Http\Message\RequestInterface;

/**
 * A client that helps you migrate from php-http/httplug 1.x to 2.x. This
 * will also help you to support PHP5 at the same time you support 2.x.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
trait VersionBridgeClient
{
    abstract protected function doSendRequest(RequestInterface $request);

    public function sendRequest(RequestInterface $request)
    {
        return $this->doSendRequest($request);
    }
}


code:
# Change Log

## 1.7.0 - 2019-06-30

### Added

- Dropped support for PHP < 7.1
- Support for `symfony/http-client`

## 1.6.1 - 2019-02-23

### Fixed

- MockClientStrategy also provides the mock client when requesting an async client

## 1.6.0 - 2019-01-23

### Added

- Support for PSR-17 factories
- Support for PSR-18 clients

## 1.5.2 - 2018-12-31

Corrected mistakes in 1.5.1. The different between 1.5.2 and 1.5.0 is that
we removed some PHP 7 code.

https://github.com/php-http/discovery/compare/1.5.0...1.5.2

## 1.5.1 - 2018-12-31

This version added new features by mistake. These are reverted in 1.5.2.

Do not use 1.5.1.

### Fixed

- Removed PHP 7 code

## 1.5.0 - 2018-12-30

### Added

- Support for `nyholm/psr7` version 1.0.
- `ClassDiscovery::safeClassExists` which will help Magento users.
- Support for HTTPlug 2.0
- Support for Buzz 1.0
- Better error message when nothing found by introducing a new exception: `NoCandidateFoundException`.

### Fixed

- Fixed condition evaluation, it should stop after first invalid condition.

## 1.4.0 - 2018-02-06

### Added

- Discovery support for nyholm/psr7

## 1.3.0 - 2017-08-03

### Added

- Discovery support for CakePHP adapter
- Discovery support for Zend adapter
- Discovery support for Artax adapter

## 1.2.1 - 2017-03-02

### Fixed

- Fixed minor issue with `MockClientStrategy`, also added more tests.

## 1.2.0 - 2017-02-12

### Added

- MockClientStrategy class.

## 1.1.1 - 2016-11-27

### Changed

- Made exception messages clearer. `StrategyUnavailableException` is no longer the previous exception to `DiscoveryFailedException`.
- `CommonClassesStrategy` is using `self` instead of `static`. Using `static` makes no sense when `CommonClassesStrategy` is final.

## 1.1.0 - 2016-10-20

### Added

- Discovery support for Slim Framework factories

## 1.0.0 - 2016-07-18

### Added

- Added back `Http\Discovery\NotFoundException` to preserve BC with 0.8 version. You may upgrade from 0.8.x and 0.9.x to 1.0.0 without any BC breaks.
- Added interface `Http\Discovery\Exception` which is implemented by all our exceptions

### Changed

- Puli strategy renamed to Puli Beta strategy to prevent incompatibility with a future Puli stable

### Deprecated

- For BC reasons, the old `Http\Discovery\NotFoundException` (extending the new exception) will be thrown until version 2.0

## 0.9.1 - 2016-06-28

### Changed

- Dropping PHP 5.4 support because we use the ::class constant.

## 0.9.0 - 2016-06-25

### Added

- Discovery strategies to find classes

### Changed

- [Puli](http://puli.io) made optional
- Improved exceptions
- **[BC] `NotFoundException` moved to `Http\Discovery\Exception\NotFoundException`**

## 0.8.0 - 2016-02-11

### Changed

- Puli composer plugin must be installed separately

## 0.7.0 - 2016-01-15

### Added

- Temporary puli.phar (Beta 10) executable

### Changed

- Updated HTTPlug dependencies
- Updated Puli dependencies
- Local configuration to make tests passing

### Removed

- Puli CLI dependency

## 0.6.4 - 2016-01-07

### Fixed

- Puli [not working](https://twitter.com/PuliPHP/status/685132540588507137) with the latest json-schema

## 0.6.3 - 2016-01-04

### Changed

- Adjust Puli dependencies

## 0.6.2 - 2016-01-04

### Changed

- Make Puli CLI a requirement

## 0.6.1 - 2016-01-03

### Changed

- More flexible Puli requirement

## 0.6.0 - 2015-12-30

### Changed

- Use [Puli](http://puli.io) for discovery
- Improved exception messages

## 0.5.0 - 2015-12-25

### Changed

- Updated message factory dependency (php-http/message)

## 0.4.0 - 2015-12-17

### Added

- Array condition evaluation in the Class Discovery

### Removed

- Message factories (moved to php-http/utils)

## 0.3.0 - 2015-11-18

### Added

- HTTP Async Client Discovery
- Stream factories

### Changed

- Discoveries and Factories are final
- Message and Uri factories have the type in their names
- Diactoros Message factory uses Stream factory internally

### Fixed

- Improved docblocks for API documentation generation

## 0.2.0 - 2015-10-31

### Changed

- Renamed AdapterDiscovery to ClientDiscovery

## 0.1.1 - 2015-06-13

### Fixed

- Bad HTTP Adapter class name for Guzzle 5

## 0.1.0 - 2015-06-12

### Added

- Initial release


code:
# HTTPlug Discovery

[![Latest Version](https://img.shields.io/github/release/php-http/discovery.svg?style=flat-square)](https://github.com/php-http/discovery/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/travis/php-http/discovery/master.svg?style=flat-square)](https://travis-ci.org/php-http/discovery)
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery)
[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/discovery.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/discovery)
[![Total Downloads](https://img.shields.io/packagist/dt/php-http/discovery.svg?style=flat-square)](https://packagist.org/packages/php-http/discovery)

**Finds installed HTTPlug implementations and PSR-7 message factories.**

## Install

Via Composer

``` bash
$ composer require php-http/discovery
```

## Documentation

Please see the [official documentation](http://php-http.readthedocs.org/en/latest/discovery.html).

## Testing

``` bash
$ composer test
```

## Contributing

Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html).

## Security

If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org).

## License

The MIT License (MIT). Please see [License File](LICENSE) for more information.


php-http/discovery

description: Finds installed HTTPlug implementations and PSR-7 message factories

license: MIT

keywords: http discovery client adapter message factory psr7

puli/composer-plugin: Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details.

php-http/message: Allow to use Guzzle, Diactoros or Slim Framework factories

Http\Discovery\: src/

spec\Http\Discovery\: spec/

test: vendor/bin/phpspec run

test-ci: vendor/bin/phpspec run -c phpspec.ci.yml

minimum-stability: beta


code:
<?php

namespace Http\Discovery;

use Http\Discovery\Exception\ClassInstantiationFailedException;
use Http\Discovery\Exception\DiscoveryFailedException;
use Http\Discovery\Exception\NoCandidateFoundException;
use Http\Discovery\Exception\StrategyUnavailableException;

/**
 * Registry that based find results on class existence.
 *
 * @author David de Boer <david@ddeboer.nl>
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
abstract class ClassDiscovery
{
    /**
     * A list of strategies to find classes.
     *
     * @var array
     */
    private static $strategies = [
        Strategy\PuliBetaStrategy::class,
        Strategy\CommonClassesStrategy::class,
        Strategy\CommonPsr17ClassesStrategy::class,
    ];

    /**
     * Discovery cache to make the second time we use discovery faster.
     *
     * @var array
     */
    private static $cache = [];

    /**
     * Finds a class.
     *
     * @param string $type
     *
     * @return string|\Closure
     *
     * @throws DiscoveryFailedException
     */
    protected static function findOneByType($type)
    {
        // Look in the cache
        if (null !== ($class = self::getFromCache($type))) {
            return $class;
        }

        $exceptions = [];
        foreach (self::$strategies as $strategy) {
            try {
                $candidates = call_user_func($strategy.'::getCandidates', $type);
            } catch (StrategyUnavailableException $e) {
                $exceptions[] = $e;

                continue;
            }

            foreach ($candidates as $candidate) {
                if (isset($candidate['condition'])) {
                    if (!self::evaluateCondition($candidate['condition'])) {
                        continue;
                    }
                }

                // save the result for later use
                self::storeInCache($type, $candidate);

                return $candidate['class'];
            }

            $exceptions[] = new NoCandidateFoundException($strategy, $candidates);
        }

        throw DiscoveryFailedException::create($exceptions);
    }

    /**
     * Get a value from cache.
     *
     * @param string $type
     *
     * @return string|null
     */
    private static function getFromCache($type)
    {
        if (!isset(self::$cache[$type])) {
            return;
        }

        $candidate = self::$cache[$type];
        if (isset($candidate['condition'])) {
            if (!self::evaluateCondition($candidate['condition'])) {
                return;
            }
        }

        return $candidate['class'];
    }

    /**
     * Store a value in cache.
     *
     * @param string $type
     * @param string $class
     */
    private static function storeInCache($type, $class)
    {
        self::$cache[$type] = $class;
    }

    /**
     * Set new strategies and clear the cache.
     *
     * @param array $strategies string array of fully qualified class name to a DiscoveryStrategy
     */
    public static function setStrategies(array $strategies)
    {
        self::$strategies = $strategies;
        self::clearCache();
    }

    /**
     * Append a strategy at the end of the strategy queue.
     *
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
     */
    public static function appendStrategy($strategy)
    {
        self::$strategies[] = $strategy;
        self::clearCache();
    }

    /**
     * Prepend a strategy at the beginning of the strategy queue.
     *
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
     */
    public static function prependStrategy($strategy)
    {
        array_unshift(self::$strategies, $strategy);
        self::clearCache();
    }

    /**
     * Clear the cache.
     */
    public static function clearCache()
    {
        self::$cache = [];
    }

    /**
     * Evaluates conditions to boolean.
     *
     * @param mixed $condition
     *
     * @return bool
     */
    protected static function evaluateCondition($condition)
    {
        if (is_string($condition)) {
            // Should be extended for functions, extensions???
            return self::safeClassExists($condition);
        }
        if (is_callable($condition)) {
            return (bool) $condition();
        }
        if (is_bool($condition)) {
            return $condition;
        }
        if (is_array($condition)) {
            foreach ($condition as $c) {
                if (false === static::evaluateCondition($c)) {
                    // Immediately stop execution if the condition is false
                    return false;
                }
            }

            return true;
        }

        return false;
    }

    /**
     * Get an instance of the $class.
     *
     * @param string|\Closure $class A FQCN of a class or a closure that instantiate the class.
     *
     * @return object
     *
     * @throws ClassInstantiationFailedException
     */
    protected static function instantiateClass($class)
    {
        try {
            if (is_string($class)) {
                return new $class();
            }

            if (is_callable($class)) {
                return $class();
            }
        } catch (\Exception $e) {
            throw new ClassInstantiationFailedException('Unexpected exception when instantiating class.', 0, $e);
        }

        throw new ClassInstantiationFailedException('Could not instantiate class because parameter is neither a callable nor a string');
    }

    /**
     * We want to do a "safe" version of PHP's "class_exists" because Magento has a bug
     * (or they call it a "feature"). Magento is throwing an exception if you do class_exists()
     * on a class that ends with "Factory" and if that file does not exits.
     *
     * This function will catch all potential exceptions and make sure it returns a boolean.
     *
     * @param string $class
     * @param bool   $autoload
     *
     * @return bool
     */
    public static function safeClassExists($class)
    {
        try {
            return class_exists($class);
        } catch (\Exception $e) {
            return false;
        }
    }
}


code:
<?php

namespace Http\Discovery;

/**
 * An interface implemented by all discovery related exceptions.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
interface Exception
{
}


code:
<?php

namespace Http\Discovery\Exception;

use Http\Discovery\Exception;

/**
 * Thrown when a class fails to instantiate.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class ClassInstantiationFailedException extends \RuntimeException implements Exception
{
}


code:
<?php

namespace Http\Discovery\Exception;

use Http\Discovery\Exception;

/**
 * Thrown when all discovery strategies fails to find a resource.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class DiscoveryFailedException extends \Exception implements Exception
{
    /**
     * @var \Exception[]
     */
    private $exceptions;

    /**
     * @param string       $message
     * @param \Exception[] $exceptions
     */
    public function __construct($message, array $exceptions = [])
    {
        $this->exceptions = $exceptions;

        parent::__construct($message);
    }

    /**
     * @param \Exception[] $exceptions
     */
    public static function create($exceptions)
    {
        $message = 'Could not find resource using any discovery strategy. Find more information at http://docs.php-http.org/en/latest/discovery.html#common-errors';
        foreach ($exceptions as $e) {
            $message .= "\n - ".$e->getMessage();
        }
        $message .= "\n\n";

        return new self($message, $exceptions);
    }

    /**
     * @return \Exception[]
     */
    public function getExceptions()
    {
        return $this->exceptions;
    }
}


code:
<?php

namespace Http\Discovery\Exception;

use Http\Discovery\Exception;

/**
 * When we have used a strategy but no candidates provided by that strategy could be used.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class NoCandidateFoundException extends \Exception implements Exception
{
    /**
     * @param string $strategy
     * @param array  $candidates
     */
    public function __construct($strategy, array $candidates)
    {
        $classes = array_map(
            function ($a) {
                return $a['class'];
            },
            $candidates
        );

        $message = sprintf(
            'No valid candidate found using strategy "%s". We tested the following candidates: %s.',
            $strategy,
            implode(', ', array_map([$this, 'stringify'], $classes))
        );

        parent::__construct($message);
    }

    private function stringify($mixed)
    {
        if (is_string($mixed)) {
            return $mixed;
        }

        if (is_array($mixed) && 2 === count($mixed)) {
            return sprintf('%s::%s', $this->stringify($mixed[0]), $mixed[1]);
        }

        return is_object($mixed) ? get_class($mixed) : gettype($mixed);
    }
}


code:
<?php

namespace Http\Discovery\Exception;

use Http\Discovery\Exception;

/**
 * Thrown when a discovery does not find any matches.
 *
 * @final do NOT extend this class, not final for BC reasons
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
/*final */class NotFoundException extends \RuntimeException implements Exception
{
}


code:
<?php

namespace Http\Discovery\Exception;

/**
 * Thrown when we can't use Puli for discovery.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class PuliUnavailableException extends StrategyUnavailableException
{
}


code:
<?php

namespace Http\Discovery\Exception;

use Http\Discovery\Exception;

/**
 * This exception is thrown when we cannot use a discovery strategy. This is *not* thrown when
 * the discovery fails to find a class.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
class StrategyUnavailableException extends \RuntimeException implements Exception
{
}


code:
<?php

namespace Http\Discovery;

use Http\Client\HttpAsyncClient;
use Http\Discovery\Exception\DiscoveryFailedException;

/**
 * Finds an HTTP Asynchronous Client.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
final class HttpAsyncClientDiscovery extends ClassDiscovery
{
    /**
     * Finds an HTTP Async Client.
     *
     * @return HttpAsyncClient
     *
     * @throws Exception\NotFoundException
     */
    public static function find()
    {
        try {
            $asyncClient = static::findOneByType(HttpAsyncClient::class);
        } catch (DiscoveryFailedException $e) {
            throw new NotFoundException(
                'No HTTPlug async clients found. Make sure to install a package providing "php-http/async-client-implementation". Example: "php-http/guzzle6-adapter".',
                0,
                $e
            );
        }

        return static::instantiateClass($asyncClient);
    }
}


code:
<?php

namespace Http\Discovery;

use Http\Client\HttpClient;
use Http\Discovery\Exception\DiscoveryFailedException;

/**
 * Finds an HTTP Client.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class HttpClientDiscovery extends ClassDiscovery
{
    /**
     * Finds an HTTP Client.
     *
     * @return HttpClient
     *
     * @throws Exception\NotFoundException
     */
    public static function find()
    {
        try {
            $client = static::findOneByType(HttpClient::class);
        } catch (DiscoveryFailedException $e) {
            throw new NotFoundException(
                'No HTTPlug clients found. Make sure to install a package providing "php-http/client-implementation". Example: "php-http/guzzle6-adapter".',
                0,
                $e
            );
        }

        return static::instantiateClass($client);
    }
}


code:
<?php

namespace Http\Discovery;

use Http\Discovery\Exception\DiscoveryFailedException;
use Http\Message\MessageFactory;

/**
 * Finds a Message Factory.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 *
 * @deprecated This will be removed in 2.0. Consider using Psr17FactoryDiscovery.
 */
final class MessageFactoryDiscovery extends ClassDiscovery
{
    /**
     * Finds a Message Factory.
     *
     * @return MessageFactory
     *
     * @throws Exception\NotFoundException
     */
    public static function find()
    {
        try {
            $messageFactory = static::findOneByType(MessageFactory::class);
        } catch (DiscoveryFailedException $e) {
            throw new NotFoundException(
                'No message factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.',
                0,
                $e
            );
        }

        return static::instantiateClass($messageFactory);
    }
}


code:
<?php

namespace Http\Discovery;

/**
 * Thrown when a discovery does not find any matches.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 *
 * @deprecated since since version 1.0, and will be removed in 2.0. Use {@link \Http\Discovery\Exception\NotFoundException} instead.
 */
final class NotFoundException extends \Http\Discovery\Exception\NotFoundException
{
}


code:
<?php

namespace Http\Discovery;

use Http\Discovery\Exception\DiscoveryFailedException;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;

/**
 * Finds PSR-17 factories.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class Psr17FactoryDiscovery extends ClassDiscovery
{
    private static function createException($type, Exception $e)
    {
        return new \Http\Discovery\Exception\NotFoundException(
            'No PSR-17 '.$type.' found. Install a package from this list: https://packagist.org/providers/psr/http-factory-implementation',
            0,
            $e
        );
    }

    /**
     * @return RequestFactoryInterface
     *
     * @throws Exception\NotFoundException
     */
    public static function findRequestFactory()
    {
        try {
            $messageFactory = static::findOneByType(RequestFactoryInterface::class);
        } catch (DiscoveryFailedException $e) {
            throw self::createException('request factory', $e);
        }

        return static::instantiateClass($messageFactory);
    }

    /**
     * @return ResponseFactoryInterface
     *
     * @throws Exception\NotFoundException
     */
    public static function findResponseFactory()
    {
        try {
            $messageFactory = static::findOneByType(ResponseFactoryInterface::class);
        } catch (DiscoveryFailedException $e) {
            throw self::createException('response factory', $e);
        }

        return static::instantiateClass($messageFactory);
    }

    /**
     * @return ServerRequestFactoryInterface
     *
     * @throws Exception\NotFoundException
     */
    public static function findServerRequestFactory()
    {
        try {
            $messageFactory = static::findOneByType(ServerRequestFactoryInterface::class);
        } catch (DiscoveryFailedException $e) {
            throw self::createException('server request factory', $e);
        }

        return static::instantiateClass($messageFactory);
    }

    /**
     * @return StreamFactoryInterface
     *
     * @throws Exception\NotFoundException
     */
    public static function findStreamFactory()
    {
        try {
            $messageFactory = static::findOneByType(StreamFactoryInterface::class);
        } catch (DiscoveryFailedException $e) {
            throw self::createException('stream factory', $e);
        }

        return static::instantiateClass($messageFactory);
    }

    /**
     * @return UploadedFileFactoryInterface
     *
     * @throws Exception\NotFoundException
     */
    public static function findUploadedFileFactory()
    {
        try {
            $messageFactory = static::findOneByType(UploadedFileFactoryInterface::class);
        } catch (DiscoveryFailedException $e) {
            throw self::createException('uploaded file factory', $e);
        }

        return static::instantiateClass($messageFactory);
    }

    /**
     * @return UriFactoryInterface
     *
     * @throws Exception\NotFoundException
     */
    public static function findUrlFactory()
    {
        try {
            $messageFactory = static::findOneByType(UriFactoryInterface::class);
        } catch (DiscoveryFailedException $e) {
            throw self::createException('url factory', $e);
        }

        return static::instantiateClass($messageFactory);
    }
}


code:
<?php

namespace Http\Discovery;

use Http\Discovery\Exception\DiscoveryFailedException;
use Psr\Http\Client\ClientInterface;

/**
 * Finds a PSR-18 HTTP Client.
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class Psr18ClientDiscovery extends ClassDiscovery
{
    /**
     * Finds a PSR-18 HTTP Client.
     *
     * @return ClientInterface
     *
     * @throws Exception\NotFoundException
     */
    public static function find()
    {
        try {
            $client = static::findOneByType(ClientInterface::class);
        } catch (DiscoveryFailedException $e) {
            throw new \Http\Discovery\Exception\NotFoundException(
                'No PSR-18 clients found. Make sure to install a package providing "psr/http-client-implementation". Example: "php-http/guzzle6-adapter".',
                0,
                $e
            );
        }

        return static::instantiateClass($client);
    }
}


code:
<?php

namespace Http\Discovery\Strategy;

use GuzzleHttp\Psr7\Request as GuzzleRequest;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Message\MessageFactory;
use Http\Message\MessageFactory\GuzzleMessageFactory;
use Http\Message\StreamFactory;
use Http\Message\StreamFactory\GuzzleStreamFactory;
use Http\Message\UriFactory;
use Http\Message\UriFactory\GuzzleUriFactory;
use Http\Message\MessageFactory\DiactorosMessageFactory;
use Http\Message\StreamFactory\DiactorosStreamFactory;
use Http\Message\UriFactory\DiactorosUriFactory;
use Psr\Http\Client\ClientInterface as Psr18Client;
use Zend\Diactoros\Request as DiactorosRequest;
use Http\Message\MessageFactory\SlimMessageFactory;
use Http\Message\StreamFactory\SlimStreamFactory;
use Http\Message\UriFactory\SlimUriFactory;
use Slim\Http\Request as SlimRequest;
use Http\Adapter\Guzzle6\Client as Guzzle6;
use Http\Adapter\Guzzle5\Client as Guzzle5;
use Http\Client\Curl\Client as Curl;
use Http\Client\Socket\Client as Socket;
use Http\Adapter\React\Client as React;
use Http\Adapter\Buzz\Client as Buzz;
use Http\Adapter\Cake\Client as Cake;
use Http\Adapter\Zend\Client as Zend;
use Http\Adapter\Artax\Client as Artax;
use Symfony\Component\HttpClient\HttplugClient as SymfonyHttplug;
use Symfony\Component\HttpClient\Psr18Client as SymfonyPsr18;
use Nyholm\Psr7\Factory\HttplugFactory as NyholmHttplugFactory;

/**
 * @internal
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class CommonClassesStrategy implements DiscoveryStrategy
{
    /**
     * @var array
     */
    private static $classes = [
        MessageFactory::class => [
            ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]],
            ['class' => GuzzleMessageFactory::class, 'condition' => [GuzzleRequest::class, GuzzleMessageFactory::class]],
            ['class' => DiactorosMessageFactory::class, 'condition' => [DiactorosRequest::class, DiactorosMessageFactory::class]],
            ['class' => SlimMessageFactory::class, 'condition' => [SlimRequest::class, SlimMessageFactory::class]],
        ],
        StreamFactory::class => [
            ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]],
            ['class' => GuzzleStreamFactory::class, 'condition' => [GuzzleRequest::class, GuzzleStreamFactory::class]],
            ['class' => DiactorosStreamFactory::class, 'condition' => [DiactorosRequest::class, DiactorosStreamFactory::class]],
            ['class' => SlimStreamFactory::class, 'condition' => [SlimRequest::class, SlimStreamFactory::class]],
        ],
        UriFactory::class => [
            ['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]],
            ['class' => GuzzleUriFactory::class, 'condition' => [GuzzleRequest::class, GuzzleUriFactory::class]],
            ['class' => DiactorosUriFactory::class, 'condition' => [DiactorosRequest::class, DiactorosUriFactory::class]],
            ['class' => SlimUriFactory::class, 'condition' => [SlimRequest::class, SlimUriFactory::class]],
        ],
        HttpAsyncClient::class => [
            ['class' => Guzzle6::class, 'condition' => Guzzle6::class],
            ['class' => Curl::class, 'condition' => Curl::class],
            ['class' => React::class, 'condition' => React::class],
        ],
        HttpClient::class => [
            [
                'class' => [self::class, 'symfonyHttplugInstantiate'],
                'condition' => SymfonyHttplug::class,
            ],
            ['class' => Guzzle6::class, 'condition' => Guzzle6::class],
            ['class' => Guzzle5::class, 'condition' => Guzzle5::class],
            ['class' => Curl::class, 'condition' => Curl::class],
            ['class' => Socket::class, 'condition' => Socket::class],
            ['class' => Buzz::class, 'condition' => Buzz::class],
            ['class' => React::class, 'condition' => React::class],
            ['class' => Cake::class, 'condition' => Cake::class],
            ['class' => Zend::class, 'condition' => Zend::class],
            ['class' => Artax::class, 'condition' => Artax::class],
            [
                'class' => [self::class, 'buzzInstantiate'],
                'condition' => [\Buzz\Client\FileGetContents::class, \Buzz\Message\ResponseBuilder::class],
            ],
        ],
        Psr18Client::class => [
            [
                'class' => [self::class, 'symfonyPsr18Instantiate'],
                'condition' => SymfonyPsr18::class,
            ],
            [
                'class' => [self::class, 'buzzInstantiate'],
                'condition' => [\Buzz\Client\FileGetContents::class, \Buzz\Message\ResponseBuilder::class],
            ],
        ],
    ];

    /**
     * {@inheritdoc}
     */
    public static function getCandidates($type)
    {
        if (Psr18Client::class === $type) {
            $candidates = self::$classes[PSR18Client::class];

            // HTTPlug 2.0 clients implements PSR18Client too.
            foreach (self::$classes[HttpClient::class] as $c) {
                if (is_subclass_of($c['class'], Psr18Client::class)) {
                    $candidates[] = $c;
                }
            }

            return $candidates;
        }

        if (isset(self::$classes[$type])) {
            return self::$classes[$type];
        }

        return [];
    }

    public static function buzzInstantiate()
    {
        return new \Buzz\Client\FileGetContents(MessageFactoryDiscovery::find());
    }

    public static function symfonyHttplugInstantiate()
    {
        return new SymfonyHttplug(null, Psr17FactoryDiscovery::findResponseFactory(), Psr17FactoryDiscovery::findStreamFactory());
    }

    public static function symfonyPsr18Instantiate()
    {
        return new SymfonyPsr18(null, Psr17FactoryDiscovery::findResponseFactory(), Psr17FactoryDiscovery::findStreamFactory());
    }
}


code:
<?php

namespace Http\Discovery\Strategy;

use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;

/**
 * @internal
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
final class CommonPsr17ClassesStrategy implements DiscoveryStrategy
{
    /**
     * @var array
     */
    private static $classes = [
        RequestFactoryInterface::class => [
            'Nyholm\Psr7\Factory\Psr17Factory',
            'Zend\Diactoros\RequestFactory',
            'Http\Factory\Diactoros\RequestFactory',
            'Http\Factory\Guzzle\RequestFactory',
            'Http\Factory\Slim\RequestFactory',
        ],
        ResponseFactoryInterface::class => [
            'Nyholm\Psr7\Factory\Psr17Factory',
            'Zend\Diactoros\ResponseFactory',
            'Http\Factory\Diactoros\ResponseFactory',
            'Http\Factory\Guzzle\ResponseFactory',
            'Http\Factory\Slim\ResponseFactory',
        ],
        ServerRequestFactoryInterface::class => [
            'Nyholm\Psr7\Factory\Psr17Factory',
            'Zend\Diactoros\ServerRequestFactory',
            'Http\Factory\Diactoros\ServerRequestFactory',
            'Http\Factory\Guzzle\ServerRequestFactory',
            'Http\Factory\Slim\ServerRequestFactory',
        ],
        StreamFactoryInterface::class => [
            'Nyholm\Psr7\Factory\Psr17Factory',
            'Zend\Diactoros\StreamFactory',
            'Http\Factory\Diactoros\StreamFactory',
            'Http\Factory\Guzzle\StreamFactory',
            'Http\Factory\Slim\StreamFactory',
        ],
        UploadedFileFactoryInterface::class => [
            'Nyholm\Psr7\Factory\Psr17Factory',
            'Zend\Diactoros\UploadedFileFactory',
            'Http\Factory\Diactoros\UploadedFileFactory',
            'Http\Factory\Guzzle\UploadedFileFactory',
            'Http\Factory\Slim\UploadedFileFactory',
        ],
        UriFactoryInterface::class => [
            'Nyholm\Psr7\Factory\Psr17Factory',
            'Zend\Diactoros\UriFactory',
            'Http\Factory\Diactoros\UriFactory',
            'Http\Factory\Guzzle\UriFactory',
            'Http\Factory\Slim\UriFactory',
        ],
    ];

    /**
     * {@inheritdoc}
     */
    public static function getCandidates($type)
    {
        $candidates = [];
        if (isset(self::$classes[$type])) {
            foreach (self::$classes[$type] as $class) {
                $candidates[] = ['class' => $class, 'condition' => [$class]];
            }
        }

        return $candidates;
    }
}


code:
<?php

namespace Http\Discovery\Strategy;

use Http\Discovery\Exception\StrategyUnavailableException;

/**
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
interface DiscoveryStrategy
{
    /**
     * Find a resource of a specific type.
     *
     * @param string $type
     *
     * @return array The return value is always an array with zero or more elements. Each
     *               element is an array with two keys ['class' => string, 'condition' => mixed].
     *
     * @throws StrategyUnavailableException if we cannot use this strategy.
     */
    public static function getCandidates($type);
}


code:
<?php

namespace Http\Discovery\Strategy;

use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Mock\Client as Mock;

/**
 * Find the Mock client.
 *
 * @author Sam Rapaport <me@samrapdev.com>
 */
final class MockClientStrategy implements DiscoveryStrategy
{
    /**
     * {@inheritdoc}
     */
    public static function getCandidates($type)
    {
        switch ($type) {
            case HttpClient::class:
            case HttpAsyncClient::class:
                return [['class' => Mock::class, 'condition' => Mock::class]];
            default:
                return [];
       }
    }
}


code:
<?php

namespace Http\Discovery\Strategy;

use Http\Discovery\ClassDiscovery;
use Http\Discovery\Exception\PuliUnavailableException;
use Puli\Discovery\Api\Discovery;
use Puli\GeneratedPuliFactory;

/**
 * Find candidates using Puli.
 *
 * @internal
 * @final
 *
 * @author David de Boer <david@ddeboer.nl>
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class PuliBetaStrategy implements DiscoveryStrategy
{
    /**
     * @var GeneratedPuliFactory
     */
    protected static $puliFactory;

    /**
     * @var Discovery
     */
    protected static $puliDiscovery;

    /**
     * @return GeneratedPuliFactory
     *
     * @throws PuliUnavailableException
     */
    private static function getPuliFactory()
    {
        if (null === self::$puliFactory) {
            if (!defined('PULI_FACTORY_CLASS')) {
                throw new PuliUnavailableException('Puli Factory is not available');
            }

            $puliFactoryClass = PULI_FACTORY_CLASS;

            if (!ClassDiscovery::safeClassExists($puliFactoryClass)) {
                throw new PuliUnavailableException('Puli Factory class does not exist');
            }

            self::$puliFactory = new $puliFactoryClass();
        }

        return self::$puliFactory;
    }

    /**
     * Returns the Puli discovery layer.
     *
     * @return Discovery
     *
     * @throws PuliUnavailableException
     */
    private static function getPuliDiscovery()
    {
        if (!isset(self::$puliDiscovery)) {
            $factory = self::getPuliFactory();
            $repository = $factory->createRepository();

            self::$puliDiscovery = $factory->createDiscovery($repository);
        }

        return self::$puliDiscovery;
    }

    /**
     * {@inheritdoc}
     */
    public static function getCandidates($type)
    {
        $returnData = [];
        $bindings = self::getPuliDiscovery()->findBindings($type);

        foreach ($bindings as $binding) {
            $condition = true;
            if ($binding->hasParameterValue('depends')) {
                $condition = $binding->getParameterValue('depends');
            }
            $returnData[] = ['class' => $binding->getClassName(), 'condition' => $condition];
        }

        return $returnData;
    }
}


code:
<?php

namespace Http\Discovery;

use Http\Discovery\Exception\DiscoveryFailedException;
use Http\Message\StreamFactory;

/**
 * Finds a Stream Factory.
 *
 * @author Михаил Красильников <m.krasilnikov@yandex.ru>
 *
 * @deprecated This will be removed in 2.0. Consider using Psr17FactoryDiscovery.
 */
final class StreamFactoryDiscovery extends ClassDiscovery
{
    /**
     * Finds a Stream Factory.
     *
     * @return StreamFactory
     *
     * @throws Exception\NotFoundException
     */
    public static function find()
    {
        try {
            $streamFactory = static::findOneByType(StreamFactory::class);
        } catch (DiscoveryFailedException $e) {
            throw new NotFoundException(
                'No stream factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.',
                0,
                $e
            );
        }

        return static::instantiateClass($streamFactory);
    }
}


code:
<?php

namespace Http\Discovery;

use Http\Discovery\Exception\DiscoveryFailedException;
use Http\Message\UriFactory;

/**
 * Finds a URI Factory.
 *
 * @author David de Boer <david@ddeboer.nl>
 *
 * @deprecated This will be removed in 2.0. Consider using Psr17FactoryDiscovery.
 */
final class UriFactoryDiscovery extends ClassDiscovery
{
    /**
     * Finds a URI Factory.
     *
     * @return UriFactory
     *
     * @throws Exception\NotFoundException
     */
    public static function find()
    {
        try {
            $uriFactory = static::findOneByType(UriFactory::class);
        } catch (DiscoveryFailedException $e) {
            throw new NotFoundException(
                'No uri factories found. To use Guzzle, Diactoros or Slim Framework factories install php-http/message and the chosen message implementation.',
                0,
                $e
            );
        }

        return static::instantiateClass($uriFactory);
    }
}


code:
# Change Log

## 1.1.1 - 2016-05-10

### Fixed

- Adapter can again be instantiated without a guzzle client.

## 1.1.0 - 2016-05-09

### Added

- Factory method Client::createWithConfig to create an adapter with custom
  configuration for the underlying guzzle client.

## 1.0.0 - 2016-01-26

## 0.4.1 - 2016-01-13

### Changed

- Updated integration tests

### Removed

- Client common dependency

## 0.4.0 - 2016-01-12

### Changed

- Updated package files
- Updated HTTPlug to RC1

## 0.2.1 - 2015-12-17

### Added

- Puli configuration and bindings

### Changed

- Guzzle setup conforms to HTTPlug requirement now: Minimal functionality in client

## 0.2.0 - 2015-12-15

### Added

- Async client capabalities

### Changed

- HTTPlug instead of HTTP Adapter

## 0.1.0 - 2015-06-12

### Added

- Initial release


code:
# Guzzle 6 HTTP Adapter

[![Latest Version](https://img.shields.io/github/release/php-http/guzzle6-adapter.svg?style=flat-square)](https://github.com/php-http/guzzle6-adapter/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/travis/php-http/guzzle6-adapter.svg?style=flat-square)](https://travis-ci.org/php-http/guzzle6-adapter)
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/guzzle6-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/guzzle6-adapter)
[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/guzzle6-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/guzzle6-adapter)
[![Total Downloads](https://img.shields.io/packagist/dt/php-http/guzzle6-adapter.svg?style=flat-square)](https://packagist.org/packages/php-http/guzzle6-adapter)

**Guzzle 6 HTTP Adapter.**

## Install

Via Composer

``` bash
$ composer require php-http/guzzle6-adapter
```

## Documentation

Please see the [official documentation](http://docs.php-http.org/en/latest/clients/guzzle6-adapter.html).

## Testing

First launch the http server:

```bash
$ ./vendor/bin/http_test_server > /dev/null 2>&1 &
```

Then the test suite:

``` bash
$ composer test
```

## Contributing

Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html).

## Security

If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org).

## Credits

Thanks to [David de Boer](https://github.com/ddeboer) for implementing this adapter.

## License

The MIT License (MIT). Please see [License File](LICENSE) for more information.


description: Guzzle 6 HTTP Adapter

keywords: guzzle http

Http\Adapter\Guzzle6\: src/

Http\Adapter\Guzzle6\Tests\: tests/

test: vendor/bin/phpunit


_class: Puli\Discovery\Binding\ClassBinding

type: Http\Client\HttpClient

type: Http\Client\HttpAsyncClient


code:
<?php

namespace Http\Adapter\Guzzle6;

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Psr\Http\Message\RequestInterface;

/**
 * HTTP Adapter for Guzzle 6.
 *
 * @author David de Boer <david@ddeboer.nl>
 */
class Client implements HttpClient, HttpAsyncClient
{
    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @param ClientInterface|null $client
     */
    public function __construct(ClientInterface $client = null)
    {
        if (!$client) {
            $client = static::buildClient();
        }

        $this->client = $client;
    }

    /**
     * Factory method to create the guzzle 6 adapter with custom configuration for guzzle.
     *
     * @param array $config Configuration to create guzzle with.
     *
     * @return Client
     */
    public static function createWithConfig(array $config)
    {
        return new self(static::buildClient($config));
    }

    /**
     * {@inheritdoc}
     */
    public function sendRequest(RequestInterface $request)
    {
        $promise = $this->sendAsyncRequest($request);

        return $promise->wait();
    }

    /**
     * {@inheritdoc}
     */
    public function sendAsyncRequest(RequestInterface $request)
    {
        $promise = $this->client->sendAsync($request);

        return new Promise($promise, $request);
    }

    /**
     * Build the guzzle client instance.
     *
     * @param array $config Additional configuration
     *
     * @return GuzzleClient
     */
    private static function buildClient(array $config = [])
    {
        $handlerStack = new HandlerStack(\GuzzleHttp\choose_handler());
        $handlerStack->push(Middleware::prepareBody(), 'prepare_body');
        $config = array_merge(['handler' => $handlerStack], $config);

        return new GuzzleClient($config);
    }
}


code:
<?php

namespace Http\Adapter\Guzzle6;

use GuzzleHttp\Exception as GuzzleExceptions;
use GuzzleHttp\Promise\PromiseInterface;
use Http\Client\Exception as HttplugException;
use Http\Promise\Promise as HttpPromise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Wrapper around Guzzle promises.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
class Promise implements HttpPromise
{
    /**
     * @var PromiseInterface
     */
    private $promise;

    /**
     * @var string State of the promise
     */
    private $state;

    /**
     * @var ResponseInterface
     */
    private $response;

    /**
     * @var HttplugException
     */
    private $exception;

    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @param PromiseInterface $promise
     * @param RequestInterface $request
     */
    public function __construct(PromiseInterface $promise, RequestInterface $request)
    {
        $this->request = $request;
        $this->state = self::PENDING;
        $this->promise = $promise->then(function ($response) {
            $this->response = $response;
            $this->state = self::FULFILLED;

            return $response;
        }, function ($reason) use ($request) {
            $this->state = self::REJECTED;

            if ($reason instanceof HttplugException) {
                $this->exception = $reason;
            } elseif ($reason instanceof GuzzleExceptions\GuzzleException) {
                $this->exception = $this->handleException($reason, $request);
            } elseif ($reason instanceof \Exception) {
                $this->exception = new \RuntimeException('Invalid exception returned from Guzzle6', 0, $reason);
            } else {
                $this->exception = new \UnexpectedValueException('Reason returned from Guzzle6 must be an Exception', 0, $reason);
            }

            throw $this->exception;
        });
    }

    /**
     * {@inheritdoc}
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null)
    {
        return new static($this->promise->then($onFulfilled, $onRejected), $this->request);
    }

    /**
     * {@inheritdoc}
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * {@inheritdoc}
     */
    public function wait($unwrap = true)
    {
        $this->promise->wait(false);

        if ($unwrap) {
            if ($this->getState() == self::REJECTED) {
                throw $this->exception;
            }

            return $this->response;
        }
    }

    /**
     * Converts a Guzzle exception into an Httplug exception.
     *
     * @param GuzzleExceptions\GuzzleException $exception
     * @param RequestInterface                 $request
     *
     * @return HttplugException
     */
    private function handleException(GuzzleExceptions\GuzzleException $exception, RequestInterface $request)
    {
        if ($exception instanceof GuzzleExceptions\SeekException) {
            return new HttplugException\RequestException($exception->getMessage(), $request, $exception);
        }

        if ($exception instanceof GuzzleExceptions\ConnectException) {
            return new HttplugException\NetworkException($exception->getMessage(), $exception->getRequest(), $exception);
        }

        if ($exception instanceof GuzzleExceptions\RequestException) {
            // Make sure we have a response for the HttpException
            if ($exception->hasResponse()) {
                return new HttplugException\HttpException(
                    $exception->getMessage(),
                    $exception->getRequest(),
                    $exception->getResponse(),
                    $exception
                );
            }

            return new HttplugException\RequestException($exception->getMessage(), $exception->getRequest(), $exception);
        }

        return new HttplugException\TransferException($exception->getMessage(), 0, $exception);
    }
}


code:
# Change Log

## 1.1.0 - 2016-08-31

- Added HttpFulfilledPromise and HttpRejectedPromise which respect the HttpAsyncClient interface

## 1.0.0 - 2016-01-26

### Removed

- Stability configuration from composer

## 1.0.0-RC1 - 2016-01-12

### Changed

- Updated package files
- Updated promise dependency to RC1

## 1.0.0-beta - 2015-12-17

### Added

- Puli configuration and binding types

### Changed

- Exception concept

## 1.0.0-alpha3 - 2015-12-13

### Changed

- Async client does not throw exceptions

### Removed

- Promise interface moved to its own repository: [php-http/promise](https://github.com/php-http/promise)

## 1.0.0-alpha2 - 2015-11-16

### Added

- Async client and Promise interface

## 1.0.0-alpha - 2015-10-26

### Added

- Better domain exceptions.

### Changed

- Purpose of the library: general HTTP CLient abstraction.

### Removed

- Request options: they should be configured at construction time.
- Multiple request sending: should be done asynchronously using Async Client.
- `getName` method

## 0.1.0 - 2015-06-03

### Added

- Initial release


code:
# HTTPlug

[![Latest Version](https://img.shields.io/github/release/php-http/httplug.svg?style=flat-square)](https://github.com/php-http/httplug/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/travis/php-http/httplug.svg?style=flat-square)](https://travis-ci.org/php-http/httplug)
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/httplug.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/httplug)
[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/httplug.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/httplug)
[![Total Downloads](https://img.shields.io/packagist/dt/php-http/httplug.svg?style=flat-square)](https://packagist.org/packages/php-http/httplug)

[![Slack Status](http://slack.httplug.io/badge.svg)](http://slack.httplug.io)
[![Email](https://img.shields.io/badge/email-team@httplug.io-blue.svg?style=flat-square)](mailto:team@httplug.io)

**HTTPlug, the HTTP client abstraction for PHP.**

## Install

Via Composer

``` bash
$ composer require php-http/httplug
```

## Intro

This is the contract package for HTTP Client.
Use it to create HTTP Clients which are interoperable and compatible with [PSR-7](http://www.php-fig.org/psr/psr-7/).

This library is the official successor of the [ivory http adapter](https://github.com/egeloen/ivory-http-adapter).

## Documentation

Please see the [official documentation](http://docs.php-http.org).

## Testing

``` bash
$ composer test
```

## Contributing

Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html).

## Security

If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org).

## License

The MIT License (MIT). Please see [License File](LICENSE) for more information.


php-http/httplug

description: HTTPlug, the HTTP client abstraction for PHP

keywords: http client

Http\Client\: src/


description: Async HTTP Client

description: HTTP Client


code:
<?php

namespace Http\Client;

/**
 * Every HTTP Client related Exception must implement this interface.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface Exception
{
}


code:
<?php

namespace Http\Client\Exception;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Thrown when a response was received but the request itself failed.
 *
 * In addition to the request, this exception always provides access to the response object.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class HttpException extends RequestException
{
    /**
     * @var ResponseInterface
     */
    protected $response;

    /**
     * @param string            $message
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param \Exception|null   $previous
     */
    public function __construct(
        $message,
        RequestInterface $request,
        ResponseInterface $response,
        \Exception $previous = null
    ) {
        parent::__construct($message, $request, $previous);

        $this->response = $response;
        $this->code = $response->getStatusCode();
    }

    /**
     * Returns the response.
     *
     * @return ResponseInterface
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Factory method to create a new exception with a normalized error message.
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param \Exception|null   $previous
     *
     * @return HttpException
     */
    public static function create(
        RequestInterface $request,
        ResponseInterface $response,
        \Exception $previous = null
    ) {
        $message = sprintf(
            '[url] %s [http method] %s [status code] %s [reason phrase] %s',
            $request->getRequestTarget(),
            $request->getMethod(),
            $response->getStatusCode(),
            $response->getReasonPhrase()
        );

        return new self($message, $request, $response, $previous);
    }
}


code:
<?php

namespace Http\Client\Exception;

/**
 * Thrown when the request cannot be completed because of network issues.
 *
 * There is no response object as this exception is thrown when no response has been received.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class NetworkException extends RequestException
{
}


code:
<?php

namespace Http\Client\Exception;

use Psr\Http\Message\RequestInterface;

/**
 * Exception for when a request failed, providing access to the failed request.
 *
 * This could be due to an invalid request, or one of the extending exceptions
 * for network errors or HTTP error responses.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class RequestException extends TransferException
{
    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @param string           $message
     * @param RequestInterface $request
     * @param \Exception|null  $previous
     */
    public function __construct($message, RequestInterface $request, \Exception $previous = null)
    {
        $this->request = $request;

        parent::__construct($message, 0, $previous);
    }

    /**
     * Returns the request.
     *
     * @return RequestInterface
     */
    public function getRequest()
    {
        return $this->request;
    }
}


code:
<?php

namespace Http\Client\Exception;

use Http\Client\Exception;

/**
 * Base exception for transfer related exceptions.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class TransferException extends \RuntimeException implements Exception
{
}


code:
<?php

namespace Http\Client;

use Http\Promise\Promise;
use Psr\Http\Message\RequestInterface;

/**
 * Sends a PSR-7 Request in an asynchronous way by returning a Promise.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
interface HttpAsyncClient
{
    /**
     * Sends a PSR-7 request in an asynchronous way.
     *
     * Exceptions related to processing the request are available from the returned Promise.
     *
     * @param RequestInterface $request
     *
     * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception.
     *
     * @throws \Exception If processing the request is impossible (eg. bad configuration).
     */
    public function sendAsyncRequest(RequestInterface $request);
}


code:
<?php

namespace Http\Client;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Sends a PSR-7 Request and returns a PSR-7 response.
 *
 * @author GeLo <geloen.eric@gmail.com>
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 * @author David Buchmann <mail@davidbu.ch>
 */
interface HttpClient
{
    /**
     * Sends a PSR-7 request.
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws \Http\Client\Exception If an error happens during processing the request.
     * @throws \Exception             If processing the request is impossible (eg. bad configuration).
     */
    public function sendRequest(RequestInterface $request);
}


code:
<?php

namespace Http\Client\Promise;

use Http\Client\Exception;
use Http\Promise\Promise;
use Psr\Http\Message\ResponseInterface;

final class HttpFulfilledPromise implements Promise
{
    /**
     * @var ResponseInterface
     */
    private $response;

    /**
     * @param ResponseInterface $response
     */
    public function __construct(ResponseInterface $response)
    {
        $this->response = $response;
    }

    /**
     * {@inheritdoc}
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null)
    {
        if (null === $onFulfilled) {
            return $this;
        }

        try {
            return new self($onFulfilled($this->response));
        } catch (Exception $e) {
            return new HttpRejectedPromise($e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getState()
    {
        return Promise::FULFILLED;
    }

    /**
     * {@inheritdoc}
     */
    public function wait($unwrap = true)
    {
        if ($unwrap) {
            return $this->response;
        }
    }
}


code:
<?php

namespace Http\Client\Promise;

use Http\Client\Exception;
use Http\Promise\Promise;

final class HttpRejectedPromise implements Promise
{
    /**
     * @var Exception
     */
    private $exception;

    /**
     * @param Exception $exception
     */
    public function __construct(Exception $exception)
    {
        $this->exception = $exception;
    }

    /**
     * {@inheritdoc}
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null)
    {
        if (null === $onRejected) {
            return $this;
        }

        try {
            return new HttpFulfilledPromise($onRejected($this->exception));
        } catch (Exception $e) {
            return new self($e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getState()
    {
        return Promise::REJECTED;
    }

    /**
     * {@inheritdoc}
     */
    public function wait($unwrap = true)
    {
        if ($unwrap) {
            throw $this->exception;
        }
    }
}


code:
# Change Log

## 1.0.2 - 2015-12-19

### Added

- Request and Response factory binding types to Puli

## 1.0.1 - 2015-12-17

### Added

- Puli configuration and binding types

## 1.0.0 - 2015-12-15

### Added

- Response Factory in order to be reused in Message and Server Message factories
- Request Factory

### Changed

- Message Factory extends Request and Response factories

## 1.0.0-RC1 - 2015-12-14

### Added

- CS check

### Changed

- RuntimeException is thrown when the StreamFactory cannot write to the underlying stream

## 0.3.0 - 2015-11-16

### Removed

- Client Context Factory
- Factory Awares and Templates

## 0.2.0 - 2015-11-16

### Changed

- Reordered the parameters when creating a message to have the protocol last,
as its the least likely to need to be changed.

## 0.1.0 - 2015-06-01

### Added

- Initial release

### Changed

- Helpers are renamed to templates


code:
# PSR-7 Message Factory

[![Latest Version](https://img.shields.io/github/release/php-http/message-factory.svg?style=flat-square)](https://github.com/php-http/message-factory/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message-factory.svg?style=flat-square)](https://packagist.org/packages/php-http/message-factory)

**Factory interfaces for PSR-7 HTTP Message.**

## Install

Via Composer

``` bash
$ composer require php-http/message-factory
```

## Documentation

Please see the [official documentation](http://php-http.readthedocs.org/en/latest/message-factory/).

## Contributing

Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details.

## Security

If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org).

## License

The MIT License (MIT). Please see [License File](LICENSE) for more information.


php-http/message-factory

description: Factory interfaces for PSR-7 HTTP Message

keywords: http factory message stream uri

Http\Message\: src/


description: PSR-7 Message Factory

description: Optional class dependency which can be checked by consumers

description: PSR-7 Stream Factory

description: PSR-7 URI Factory


code:
<?php

namespace Http\Message;

/**
 * Factory for PSR-7 Request and Response.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface MessageFactory extends RequestFactory, ResponseFactory
{
}


code:
<?php

namespace Http\Message;

use Psr\Http\Message\UriInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Factory for PSR-7 Request.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface RequestFactory
{
    /**
     * Creates a new PSR-7 request.
     *
     * @param string                               $method
     * @param string|UriInterface                  $uri
     * @param array                                $headers
     * @param resource|string|StreamInterface|null $body
     * @param string                               $protocolVersion
     *
     * @return RequestInterface
     */
    public function createRequest(
        $method,
        $uri,
        array $headers = [],
        $body = null,
        $protocolVersion = '1.1'
    );
}


code:
<?php

namespace Http\Message;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Factory for PSR-7 Response.
 *
 * This factory contract can be reused in Message and Server Message factories.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface ResponseFactory
{
    /**
     * Creates a new PSR-7 response.
     *
     * @param int                                  $statusCode
     * @param string|null                          $reasonPhrase
     * @param array                                $headers
     * @param resource|string|StreamInterface|null $body
     * @param string                               $protocolVersion
     *
     * @return ResponseInterface
     */
    public function createResponse(
        $statusCode = 200,
        $reasonPhrase = null,
        array $headers = [],
        $body = null,
        $protocolVersion = '1.1'
    );
}


code:
<?php

namespace Http\Message;

use Psr\Http\Message\StreamInterface;

/**
 * Factory for PSR-7 Stream.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface StreamFactory
{
    /**
     * Creates a new PSR-7 stream.
     *
     * @param string|resource|StreamInterface|null $body
     *
     * @return StreamInterface
     *
     * @throws \InvalidArgumentException If the stream body is invalid.
     * @throws \RuntimeException         If creating the stream from $body fails.
     */
    public function createStream($body = null);
}


code:
<?php

namespace Http\Message;

use Psr\Http\Message\UriInterface;

/**
 * Factory for PSR-7 URI.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface UriFactory
{
    /**
     * Creates an PSR-7 URI.
     *
     * @param string|UriInterface $uri
     *
     * @return UriInterface
     *
     * @throws \InvalidArgumentException If the $uri argument can not be converted into a valid URI.
     */
    public function createUri($uri);
}


code:
# Change Log

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

## [1.8.0] - 2019-08-05

### Changed

- Raised minimum PHP version to 7.1

### Fixed

- Fatal error on `CurlCommandFormatter` when body is larger than `escapeshellarg` allowed length.
- Do not read stream in message formatter if stream is not seekable.

## [1.7.2] - 2018-10-30

### Fixed

- FilteredStream uses `@trigger_error` instead of throwing exceptions to not
  break careless users. You still need to fix your stream code to respect
  `isSeekable`. Seeking does not work as expected, and we will add exceptions
  in version 2.

## [1.7.1] - 2018-10-29

### Fixed

- FilteredStream is not actually seekable

## [1.7.0] - 2018-08-15

### Fixed

- Fix CurlCommandFormatter for binary request payloads
- Fix QueryParam authentication to assemble proper URL regardless of PHP `arg_separator.output` directive
- Do not pass `null` parameters to `Clue\StreamFilter\fun`

### Changed

- Dropped tests on HHVM

## [1.6.0] - 2017-07-05

### Added

- CookieUtil::parseDate to create a date from cookie date string

### Fixed

- Fix curl command of CurlFormatter when there is an user-agent header

## [1.5.0] - 2017-02-14

### Added

- Check for empty string in Stream factories
- Cookie::createWithoutValidation Static constructor to create a cookie. Will not perform any attribute validation during instantiation.
- Cookie::isValid Method to check if cookie attributes are valid.

### Fixed

- FilteredStream::getSize returns null because the contents size is unknown.
- Stream factories does not rewinds streams. The previous behavior was not coherent between factories and inputs.

### Deprecated

- FilteredStream::getReadFilter The read filter is internal and should never be used by consuming code.
- FilteredStream::getWriteFilter We did not implement writing to the streams at all. And if we do, the filter is an internal information and should not be used by consuming code.

## [1.4.1] - 2016-12-16

### Fixed

- Cookie::matchPath Cookie with root path (`/`) will not match sub path (e.g. `/cookie`).

## [1.4.0] - 2016-10-20

### Added

- Message, stream and URI factories for [Slim Framework](https://github.com/slimphp/Slim)
- BufferedStream that allow you to decorate a non-seekable stream with a seekable one.
- cUrlFormatter to be able to redo the request with a cURL command

## [1.3.1] - 2016-07-15

### Fixed

- FullHttpMessageFormatter will not read from streams that you cannot rewind (non-seekable)
- FullHttpMessageFormatter will not read from the stream if $maxBodyLength is zero
- FullHttpMessageFormatter rewinds streams after they are read

## [1.3.0] - 2016-07-14

### Added

- FullHttpMessageFormatter to include headers and body in the formatted message

### Fixed

- #41: Response builder broke header value

## [1.2.0] - 2016-03-29

### Added

- The RequestMatcher is built after the Symfony RequestMatcher and separates
   scheme, host and path expressions and provides an option to filter on the
   method
- New RequestConditional authentication method using request matchers
- Add automatic basic auth info detection based on the URL

### Changed

- Improved ResponseBuilder

### Deprecated

- RegexRequestMatcher, use RequestMatcher instead
- Matching authenitcation method, use RequestConditional instead

## [1.1.0] - 2016-02-25

### Added

 - Add a request matcher interface and regex implementation
 - Add a callback request matcher implementation
 - Add a ResponseBuilder, to create PSR7 Response from a string

### Fixed

 - Fix casting string on a FilteredStream not filtering the output

## [1.0.0] - 2016-01-27

## [0.2.0] - 2015-12-29

### Added

- Autoregistration of stream filters using Composer autoload
- Cookie
- [Apigen](http://www.apigen.org/) configuration

## [0.1.2] - 2015-12-26

### Added

- Request and response factory bindings

### Fixed

- Chunk filter namespace in Dechunk stream

## [0.1.1] - 2015-12-25

### Added

- Formatter

## 0.1.0 - 2015-12-24

### Added

- Authentication
- Encoding
- Message decorator
- Message factory (Guzzle, Diactoros)

[Unreleased]: https://github.com/php-http/message/compare/1.7.2...HEAD
[1.7.2]: https://github.com/php-http/message/compare/v1.7.1...1.7.2
[1.7.1]: https://github.com/php-http/message/compare/1.7.0...v1.7.1
[1.7.0]: https://github.com/php-http/message/compare/1.6.0...1.7.0
[1.6.0]: https://github.com/php-http/message/compare/1.5.0...1.6.0
[1.5.0]: https://github.com/php-http/message/compare/v1.4.1...1.5.0
[1.4.1]: https://github.com/php-http/message/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/php-http/message/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/php-http/message/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/php-http/message/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/php-http/message/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/php-http/message/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/php-http/message/compare/0.2.0...v1.0.0
[0.2.0]: https://github.com/php-http/message/compare/v0.1.2...0.2.0
[0.1.2]: https://github.com/php-http/message/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/php-http/message/compare/v0.1.0...v0.1.1


code:
# HTTP Message

[![Latest Version](https://img.shields.io/github/release/php-http/message.svg?style=flat-square)](https://github.com/php-http/message/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/travis/php-http/message.svg?style=flat-square)](https://travis-ci.org/php-http/message)
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/message.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/message)
[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/message.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/message)
[![Total Downloads](https://img.shields.io/packagist/dt/php-http/message.svg?style=flat-square)](https://packagist.org/packages/php-http/message)

**HTTP Message related tools.**

## Install

Via Composer

``` bash
$ composer require php-http/message
```

## Intro

This package contains various PSR-7 tools which might be useful in an HTTP workflow:

- Authentication method implementations
- Various Stream encoding tools
- Message decorators
- Message factory implementations for Guzzle PSR-7 and Diactoros
- Cookie implementation
- Request matchers

## Documentation

Please see the [official documentation](http://docs.php-http.org/en/latest/message.html).

## Testing

``` bash
$ composer test
```

## Contributing

Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html).

## Credits

Thanks to [Cuzzle](https://github.com/namshi/cuzzle) for inpiration for the `CurlCommandFormatter`.

## Security

If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org).

## License

The MIT License (MIT). Please see [License File](LICENSE) for more information.


code:
source:
    - src/

destination: build/api/

templateTheme: bootstrap


php-http/message

description: HTTP Message related tools

keywords: message http psr-7

zendframework/zend-diactoros: Used with Diactoros Factories

guzzlehttp/psr7: Used with Guzzle PSR-7 Factories

slim/slim: Used with Slim Framework PSR-7 implementation

ext-zlib: Used with compressor/decompressor streams

spec\Http\Message\: spec/


type: Http\Message\MessageFactory

depends: Zend\Diactoros\Request

type: Http\Message\ResponseFactory

type: Http\Message\RequestFactory

type: Http\Message\StreamFactory

type: Http\Message\UriFactory

depends: Zend\Diactoros\Uri

depends: Zend\Diactoros\Response

class: Http\Message\UriFactory\SlimUriFactory

depends: Zend\Diactoros\Stream


code:
<?php

namespace Http\Message;

use Psr\Http\Message\RequestInterface;

/**
 * Add authentication information to a PSR-7 Request.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface Authentication
{
    /**
     * Alter the request to add the authentication credentials.
     *
     * To do that, the implementation might use pre-stored credentials or do
     * separate HTTP requests to obtain a valid token.
     *
     * @param RequestInterface $request The request without authentication information
     *
     * @return RequestInterface The request with added authentication information
     */
    public function authenticate(RequestInterface $request);
}


code:
<?php

namespace Http\Message\Authentication;

use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;

/**
 * Authenticate a PSR-7 Request using Basic Auth based on credentials in the URI.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class AutoBasicAuth implements Authentication
{
    /**
     * Whether user info should be removed from the URI.
     *
     * @var bool
     */
    private $shouldRemoveUserInfo;

    /**
     * @param bool|true $shouldRremoveUserInfo
     */
    public function __construct($shouldRremoveUserInfo = true)
    {
        $this->shouldRemoveUserInfo = (bool) $shouldRremoveUserInfo;
    }

    /**
     * {@inheritdoc}
     */
    public function authenticate(RequestInterface $request)
    {
        $uri = $request->getUri();
        $userInfo = $uri->getUserInfo();

        if (!empty($userInfo)) {
            if ($this->shouldRemoveUserInfo) {
                $request = $request->withUri($uri->withUserInfo(''));
            }

            $request = $request->withHeader('Authorization', sprintf('Basic %s', base64_encode($userInfo)));
        }

        return $request;
    }
}


code:
<?php

namespace Http\Message\Authentication;

use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;

/**
 * Authenticate a PSR-7 Request using Basic Auth.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class BasicAuth implements Authentication
{
    /**
     * @var string
     */
    private $username;

    /**
     * @var string
     */
    private $password;

    /**
     * @param string $username
     * @param string $password
     */
    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }

    /**
     * {@inheritdoc}
     */
    public function authenticate(RequestInterface $request)
    {
        $header = sprintf('Basic %s', base64_encode(sprintf('%s:%s', $this->username, $this->password)));

        return $request->withHeader('Authorization', $header);
    }
}


code:
<?php

namespace Http\Message\Authentication;

use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;

/**
 * Authenticate a PSR-7 Request using a token.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class Bearer implements Authentication
{
    /**
     * @var string
     */
    private $token;

    /**
     * @param string $token
     */
    public function __construct($token)
    {
        $this->token = $token;
    }

    /**
     * {@inheritdoc}
     */
    public function authenticate(RequestInterface $request)
    {
        $header = sprintf('Bearer %s', $this->token);

        return $request->withHeader('Authorization', $header);
    }
}


code:
<?php

namespace Http\Message\Authentication;

use Http\Message\Authentication;
use Psr\Http\Message\RequestInterface;

/**
 * Authenticate a PSR-7 Request with a multiple authentication methods.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class Chain implements Authentication
{
    /**
     * @var Authentication[]
     */
    private $authenticationChain = [];

    /**
     * @param Authentication[] $authenticationChain
     */
    public function __construct(array $authenticationChain = [])
    {
        foreach ($authenticationChain as $authentication) {
            if (!$authentication instanceof Authentication) {
                throw new \InvalidArgumentException(
                    'Members of the authentication chain must be of type Http\Message\Authentication'
                );
            }
        }

        $this->authenticationChain = $authenticationChain;
    }

    /**
     * {@inheritdoc}
     */
    public function authenticate(RequestInterface $request)
    {
        foreach ($this->authenticationChain as $authentication) {
            $request = $authentication->authenticate($request);
        }

        return $request;
    }
}