2015-08-17 17:00:26 -07:00
< ? php
namespace GuzzleHttp ;
2015-08-27 12:03:05 -07:00
use GuzzleHttp\Cookie\CookieJar ;
use GuzzleHttp\Promise ;
use GuzzleHttp\Psr7 ;
use Psr\Http\Message\UriInterface ;
use Psr\Http\Message\RequestInterface ;
use Psr\Http\Message\ResponseInterface ;
2015-08-17 17:00:26 -07:00
/**
2015-08-27 12:03:05 -07:00
* @ method ResponseInterface get ( $uri , array $options = [])
* @ method ResponseInterface head ( $uri , array $options = [])
* @ method ResponseInterface put ( $uri , array $options = [])
* @ method ResponseInterface post ( $uri , array $options = [])
* @ method ResponseInterface patch ( $uri , array $options = [])
* @ method ResponseInterface delete ( $uri , array $options = [])
* @ method Promise\PromiseInterface getAsync ( $uri , array $options = [])
* @ method Promise\PromiseInterface headAsync ( $uri , array $options = [])
* @ method Promise\PromiseInterface putAsync ( $uri , array $options = [])
* @ method Promise\PromiseInterface postAsync ( $uri , array $options = [])
* @ method Promise\PromiseInterface patchAsync ( $uri , array $options = [])
* @ method Promise\PromiseInterface deleteAsync ( $uri , array $options = [])
2015-08-17 17:00:26 -07:00
*/
class Client implements ClientInterface
{
/** @var array Default request options */
2015-08-27 12:03:05 -07:00
private $config ;
2015-08-17 17:00:26 -07:00
/**
* Clients accept an array of constructor parameters .
*
2015-08-27 12:03:05 -07:00
* Here ' s an example of creating a client using a base_uri and an array of
* default request options to apply to each request :
2015-08-17 17:00:26 -07:00
*
* $client = new Client ([
2015-08-27 12:03:05 -07:00
* 'base_uri' => 'http://www.foo.com/1.0/' ,
* 'timeout' => 0 ,
* 'allow_redirects' => false ,
* 'proxy' => '192.168.16.1:10'
2015-08-17 17:00:26 -07:00
* ]);
*
2015-08-27 12:03:05 -07:00
* Client configuration settings include the following options :
*
* - handler : ( callable ) Function that transfers HTTP requests over the
* wire . The function is called with a Psr7\Http\Message\RequestInterface
* and array of transfer options , and must return a
* GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
* Psr7\Http\Message\ResponseInterface on success . " handler " is a
* constructor only option that cannot be overridden in per / request
* options . If no handler is provided , a default handler will be created
* that enables all of the request options below by attaching all of the
* default middleware to the handler .
* - base_uri : ( string | UriInterface ) Base URI of the client that is merged
* into relative URIs . Can be a string or instance of UriInterface .
* - **: any request option
2015-08-17 17:00:26 -07:00
*
2015-08-27 12:03:05 -07:00
* @ param array $config Client configuration settings .
*
* @ see \GuzzleHttp\RequestOptions for a list of available request options .
2015-08-17 17:00:26 -07:00
*/
2015-08-27 12:03:05 -07:00
public function __construct ( array $config = [])
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
if ( ! isset ( $config [ 'handler' ])) {
$config [ 'handler' ] = HandlerStack :: create ();
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
// Convert the base_uri to a UriInterface
if ( isset ( $config [ 'base_uri' ])) {
$config [ 'base_uri' ] = Psr7\uri_for ( $config [ 'base_uri' ]);
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
$this -> configureDefaults ( $config );
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
public function __call ( $method , $args )
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
if ( count ( $args ) < 1 ) {
throw new \InvalidArgumentException ( 'Magic request methods require a URI and optional options array' );
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
$uri = $args [ 0 ];
$opts = isset ( $args [ 1 ]) ? $args [ 1 ] : [];
2015-08-17 17:00:26 -07:00
2015-08-27 12:03:05 -07:00
return substr ( $method , - 5 ) === 'Async'
? $this -> requestAsync ( substr ( $method , 0 , - 5 ), $uri , $opts )
: $this -> request ( $method , $uri , $opts );
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
public function sendAsync ( RequestInterface $request , array $options = [])
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
// Merge the base URI into the request URI if needed.
$options = $this -> prepareDefaults ( $options );
2015-08-17 17:00:26 -07:00
2015-08-27 12:03:05 -07:00
return $this -> transfer (
2016-07-18 09:07:48 -07:00
$request -> withUri ( $this -> buildUri ( $request -> getUri (), $options ), $request -> hasHeader ( 'Host' )),
2015-08-27 12:03:05 -07:00
$options
);
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
public function send ( RequestInterface $request , array $options = [])
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
$options [ RequestOptions :: SYNCHRONOUS ] = true ;
return $this -> sendAsync ( $request , $options ) -> wait ();
2015-08-17 17:00:26 -07:00
}
2016-07-18 09:07:48 -07:00
public function requestAsync ( $method , $uri = '' , array $options = [])
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
$options = $this -> prepareDefaults ( $options );
// Remove request modifying parameter because it can be done up-front.
$headers = isset ( $options [ 'headers' ]) ? $options [ 'headers' ] : [];
$body = isset ( $options [ 'body' ]) ? $options [ 'body' ] : null ;
$version = isset ( $options [ 'version' ]) ? $options [ 'version' ] : '1.1' ;
// Merge the URI into the base URI.
$uri = $this -> buildUri ( $uri , $options );
if ( is_array ( $body )) {
$this -> invalidBody ();
}
$request = new Psr7\Request ( $method , $uri , $headers , $body , $version );
// Remove the option so that they are not doubly-applied.
unset ( $options [ 'headers' ], $options [ 'body' ], $options [ 'version' ]);
2015-08-17 17:00:26 -07:00
2015-08-27 12:03:05 -07:00
return $this -> transfer ( $request , $options );
2015-08-17 17:00:26 -07:00
}
2016-07-18 09:07:48 -07:00
public function request ( $method , $uri = '' , array $options = [])
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
$options [ RequestOptions :: SYNCHRONOUS ] = true ;
return $this -> requestAsync ( $method , $uri , $options ) -> wait ();
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
public function getConfig ( $option = null )
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
return $option === null
? $this -> config
: ( isset ( $this -> config [ $option ]) ? $this -> config [ $option ] : null );
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
private function buildUri ( $uri , array $config )
2015-08-17 17:00:26 -07:00
{
2016-07-18 09:07:48 -07:00
// for BC we accept null which would otherwise fail in uri_for
$uri = Psr7\uri_for ( $uri === null ? '' : $uri );
if ( isset ( $config [ 'base_uri' ])) {
$uri = Psr7\Uri :: resolve ( Psr7\uri_for ( $config [ 'base_uri' ]), $uri );
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
2016-07-18 09:07:48 -07:00
return $uri -> getScheme () === '' ? $uri -> withScheme ( 'http' ) : $uri ;
2015-08-17 17:00:26 -07:00
}
/**
2015-08-27 12:03:05 -07:00
* Configures the default options for a client .
*
* @ param array $config
2015-08-17 17:00:26 -07:00
*/
2015-08-27 12:03:05 -07:00
private function configureDefaults ( array $config )
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
$defaults = [
'allow_redirects' => RedirectMiddleware :: $defaultSettings ,
'http_errors' => true ,
2015-08-17 17:00:26 -07:00
'decode_content' => true ,
2015-08-27 12:03:05 -07:00
'verify' => true ,
'cookies' => false
2015-08-17 17:00:26 -07:00
];
2016-07-18 09:07:48 -07:00
// Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
// We can only trust the HTTP_PROXY environment variable in a CLI
// process due to the fact that PHP has no reliable mechanism to
// get environment variables that start with "HTTP_".
if ( php_sapi_name () == 'cli' && getenv ( 'HTTP_PROXY' )) {
$defaults [ 'proxy' ][ 'http' ] = getenv ( 'HTTP_PROXY' );
2015-08-17 17:00:26 -07:00
}
if ( $proxy = getenv ( 'HTTPS_PROXY' )) {
2015-08-27 12:03:05 -07:00
$defaults [ 'proxy' ][ 'https' ] = $proxy ;
2015-08-17 17:00:26 -07:00
}
2015-10-08 11:40:12 -07:00
if ( $noProxy = getenv ( 'NO_PROXY' )) {
$cleanedNoProxy = str_replace ( ' ' , '' , $noProxy );
$defaults [ 'proxy' ][ 'no' ] = explode ( ',' , $cleanedNoProxy );
}
2016-07-18 09:07:48 -07:00
2015-08-27 12:03:05 -07:00
$this -> config = $config + $defaults ;
if ( ! empty ( $config [ 'cookies' ]) && $config [ 'cookies' ] === true ) {
$this -> config [ 'cookies' ] = new CookieJar ();
}
// Add the default user-agent header.
if ( ! isset ( $this -> config [ 'headers' ])) {
$this -> config [ 'headers' ] = [ 'User-Agent' => default_user_agent ()];
} else {
// Add the User-Agent header if one was not already set.
foreach ( array_keys ( $this -> config [ 'headers' ]) as $name ) {
if ( strtolower ( $name ) === 'user-agent' ) {
return ;
}
}
$this -> config [ 'headers' ][ 'User-Agent' ] = default_user_agent ();
}
2015-08-17 17:00:26 -07:00
}
/**
2015-08-27 12:03:05 -07:00
* Merges default options into the array .
*
* @ param array $options Options to modify by reference
2015-08-17 17:00:26 -07:00
*
2015-08-27 12:03:05 -07:00
* @ return array
2015-08-17 17:00:26 -07:00
*/
2015-08-27 12:03:05 -07:00
private function prepareDefaults ( $options )
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
$defaults = $this -> config ;
if ( ! empty ( $defaults [ 'headers' ])) {
// Default headers are only added if they are not present.
$defaults [ '_conditional' ] = $defaults [ 'headers' ];
unset ( $defaults [ 'headers' ]);
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
// Special handling for headers is required as they are added as
// conditional headers and as headers passed to a request ctor.
if ( array_key_exists ( 'headers' , $options )) {
// Allows default headers to be unset.
if ( $options [ 'headers' ] === null ) {
$defaults [ '_conditional' ] = null ;
unset ( $options [ 'headers' ]);
} elseif ( ! is_array ( $options [ 'headers' ])) {
throw new \InvalidArgumentException ( 'headers must be an array' );
}
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
// Shallow merge defaults underneath options.
$result = $options + $defaults ;
// Remove null values.
foreach ( $result as $k => $v ) {
if ( $v === null ) {
unset ( $result [ $k ]);
}
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
return $result ;
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
/**
* Transfers the given request and applies request options .
*
* The URI of the request is not modified and the request options are used
* as - is without merging in default options .
*
* @ param RequestInterface $request
* @ param array $options
*
* @ return Promise\PromiseInterface
*/
private function transfer ( RequestInterface $request , array $options )
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
// save_to -> sink
if ( isset ( $options [ 'save_to' ])) {
$options [ 'sink' ] = $options [ 'save_to' ];
unset ( $options [ 'save_to' ]);
2015-08-17 17:00:26 -07:00
}
2016-07-18 09:07:48 -07:00
// exceptions -> http_errors
2015-08-27 12:03:05 -07:00
if ( isset ( $options [ 'exceptions' ])) {
$options [ 'http_errors' ] = $options [ 'exceptions' ];
unset ( $options [ 'exceptions' ]);
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
$request = $this -> applyOptions ( $request , $options );
$handler = $options [ 'handler' ];
try {
return Promise\promise_for ( $handler ( $request , $options ));
} catch ( \Exception $e ) {
return Promise\rejection_for ( $e );
2015-08-17 17:00:26 -07:00
}
}
/**
2015-08-27 12:03:05 -07:00
* Applies the array of request options to a request .
2015-08-17 17:00:26 -07:00
*
2015-08-27 12:03:05 -07:00
* @ param RequestInterface $request
* @ param array $options
2015-08-17 17:00:26 -07:00
*
2015-08-27 12:03:05 -07:00
* @ return RequestInterface
2015-08-17 17:00:26 -07:00
*/
2015-08-27 12:03:05 -07:00
private function applyOptions ( RequestInterface $request , array & $options )
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
$modify = [];
if ( isset ( $options [ 'form_params' ])) {
if ( isset ( $options [ 'multipart' ])) {
throw new \InvalidArgumentException ( 'You cannot use '
. 'form_params and multipart at the same time. Use the '
. 'form_params option if you want to send application/'
. 'x-www-form-urlencoded requests, and the multipart '
. 'option to send multipart/form-data requests.' );
2015-08-17 17:00:26 -07:00
}
2016-07-18 09:07:48 -07:00
$options [ 'body' ] = http_build_query ( $options [ 'form_params' ], '' , '&' );
2015-08-27 12:03:05 -07:00
unset ( $options [ 'form_params' ]);
$options [ '_conditional' ][ 'Content-Type' ] = 'application/x-www-form-urlencoded' ;
}
if ( isset ( $options [ 'multipart' ])) {
2016-07-18 09:07:48 -07:00
$options [ 'body' ] = new Psr7\MultipartStream ( $options [ 'multipart' ]);
2015-08-27 12:03:05 -07:00
unset ( $options [ 'multipart' ]);
2016-07-18 09:07:48 -07:00
}
if ( isset ( $options [ 'json' ])) {
$options [ 'body' ] = \GuzzleHttp\json_encode ( $options [ 'json' ]);
unset ( $options [ 'json' ]);
$options [ '_conditional' ][ 'Content-Type' ] = 'application/json' ;
2015-08-27 12:03:05 -07:00
}
if ( ! empty ( $options [ 'decode_content' ])
&& $options [ 'decode_content' ] !== true
) {
$modify [ 'set_headers' ][ 'Accept-Encoding' ] = $options [ 'decode_content' ];
}
if ( isset ( $options [ 'headers' ])) {
if ( isset ( $modify [ 'set_headers' ])) {
$modify [ 'set_headers' ] = $options [ 'headers' ] + $modify [ 'set_headers' ];
} else {
$modify [ 'set_headers' ] = $options [ 'headers' ];
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
unset ( $options [ 'headers' ]);
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
if ( isset ( $options [ 'body' ])) {
if ( is_array ( $options [ 'body' ])) {
$this -> invalidBody ();
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
$modify [ 'body' ] = Psr7\stream_for ( $options [ 'body' ]);
unset ( $options [ 'body' ]);
2015-08-17 17:00:26 -07:00
}
2016-07-18 09:07:48 -07:00
if ( ! empty ( $options [ 'auth' ]) && is_array ( $options [ 'auth' ])) {
2015-08-27 12:03:05 -07:00
$value = $options [ 'auth' ];
2016-07-18 09:07:48 -07:00
$type = isset ( $value [ 2 ]) ? strtolower ( $value [ 2 ]) : 'basic' ;
switch ( $type ) {
2015-08-27 12:03:05 -07:00
case 'basic' :
$modify [ 'set_headers' ][ 'Authorization' ] = 'Basic '
. base64_encode ( " $value[0] : $value[1] " );
break ;
case 'digest' :
// @todo: Do not rely on curl
$options [ 'curl' ][ CURLOPT_HTTPAUTH ] = CURLAUTH_DIGEST ;
$options [ 'curl' ][ CURLOPT_USERPWD ] = " $value[0] : $value[1] " ;
break ;
}
}
if ( isset ( $options [ 'query' ])) {
$value = $options [ 'query' ];
if ( is_array ( $value )) {
$value = http_build_query ( $value , null , '&' , PHP_QUERY_RFC3986 );
}
if ( ! is_string ( $value )) {
2015-10-08 11:40:12 -07:00
throw new \InvalidArgumentException ( 'query must be a string or array' );
2015-08-27 12:03:05 -07:00
}
$modify [ 'query' ] = $value ;
unset ( $options [ 'query' ]);
}
2016-07-18 09:07:48 -07:00
// Ensure that sink is not an invalid value.
if ( isset ( $options [ 'sink' ])) {
// TODO: Add more sink validation?
if ( is_bool ( $options [ 'sink' ])) {
throw new \InvalidArgumentException ( 'sink must not be a boolean' );
}
2015-08-27 12:03:05 -07:00
}
$request = Psr7\modify_request ( $request , $modify );
2015-10-08 11:40:12 -07:00
if ( $request -> getBody () instanceof Psr7\MultipartStream ) {
// Use a multipart/form-data POST if a Content-Type is not set.
$options [ '_conditional' ][ 'Content-Type' ] = 'multipart/form-data; boundary='
. $request -> getBody () -> getBoundary ();
}
2015-08-27 12:03:05 -07:00
// Merge in conditional headers if they are not present.
if ( isset ( $options [ '_conditional' ])) {
// Build up the changes so it's in a single clone of the message.
$modify = [];
foreach ( $options [ '_conditional' ] as $k => $v ) {
if ( ! $request -> hasHeader ( $k )) {
$modify [ 'set_headers' ][ $k ] = $v ;
}
}
$request = Psr7\modify_request ( $request , $modify );
// Don't pass this internal value along to middleware/handlers.
unset ( $options [ '_conditional' ]);
}
return $request ;
2015-08-17 17:00:26 -07:00
}
2015-08-27 12:03:05 -07:00
private function invalidBody ()
2015-08-17 17:00:26 -07:00
{
2015-08-27 12:03:05 -07:00
throw new \InvalidArgumentException ( 'Passing in the "body" request '
. 'option as an array to send a POST request has been deprecated. '
. 'Please use the "form_params" request option to send a '
. 'application/x-www-form-urlencoded request, or a the "multipart" '
. 'request option to send a multipart/form-data request.' );
2015-08-17 17:00:26 -07:00
}
}