1
This commit is contained in:
+60
@@ -0,0 +1,60 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 1.0.0 - 2016-11-24
|
||||
|
||||
* Add badges to README.md
|
||||
* Switch README from .rst to .md format
|
||||
* Update dependencies
|
||||
* Add command to handler call to provide support for GuzzleServices
|
||||
|
||||
## 0.9.0 - 2016-01-30
|
||||
|
||||
* Updated to use Guzzle 6 and PSR-7.
|
||||
* Event system has been replaced with a middleware system
|
||||
* Middleware at the command layer work the same as middleware from the
|
||||
HTTP layer, but work with `Command` and `Result` objects instead of
|
||||
`Request` and `Response` objects
|
||||
* The command middleware is in a separate `HandlerStack` instance than the
|
||||
HTTP middleware.
|
||||
* `Result` objects are the result of executing a `Command` and are used to hold
|
||||
the parsed response data.
|
||||
* Asynchronous code now uses the `guzzlehttp/promises` package instead of
|
||||
`guzzlehttp/ringphp`, which means that asynchronous results are implemented
|
||||
as Promises/A+ compliant `Promise` objects, instead of futures.
|
||||
* The existing `Subscriber`s were removed.
|
||||
* The `ServiceClientInterface` and `ServiceClient` class now provide the basic
|
||||
foundation of a web service client.
|
||||
|
||||
## 0.8.0 - 2015-02-02
|
||||
|
||||
* Removed `setConfig` from `ServiceClientInterface`.
|
||||
* Added `initTransaction` to `ServiceClientInterface`.
|
||||
|
||||
## 0.7.1 - 2015-01-14
|
||||
|
||||
* Fixed and issue where intercepting commands encapsulated by a
|
||||
CommandToRequestIterator could lead to deep recursion. These commands are
|
||||
now skipped and the iterator moves to the next element using a `goto`
|
||||
statement.
|
||||
|
||||
## 0.7.0 - 2014-10-12
|
||||
|
||||
* Updated to use Guzzle 5, and added support for asynchronous results.
|
||||
* Renamed `prepare` event to `prepared`.
|
||||
* Added `init` event.
|
||||
|
||||
## 0.6.0 - 2014-08-08
|
||||
|
||||
* Added a Debug subscriber that can be used to trace through the lifecycle of
|
||||
a command and how it is modified in each event.
|
||||
|
||||
## 0.5.0 - 2014-08-01
|
||||
|
||||
* Rewrote event system so that all exceptions encountered during the transfer
|
||||
of a command are emitted to the "error" event.
|
||||
* No longer wrapping exceptions thrown during the execution of a command.
|
||||
* Added the ability to get a CommandTransaction from events and updating
|
||||
classes to use a CommandTransaction rather than many constructor arguments.
|
||||
* Fixed an issue with sending many commands in parallel
|
||||
* Added `batch()` to ServiceClientInterface for sending commands in batches
|
||||
* Added subscriber to easily mock commands results
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
test:
|
||||
vendor/bin/phpunit $(TEST)
|
||||
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
# Guzzle Commands
|
||||
|
||||
This library uses Guzzle (``guzzlehttp/guzzle``, version 7.x) and provides the
|
||||
foundations to create fully-featured web service clients by abstracting Guzzle
|
||||
HTTP **requests** and **responses** into higher-level **commands** and
|
||||
**results**. A **middleware** system, analogous to — but separate from — the one
|
||||
in the HTTP layer may be used to customize client behavior when preparing
|
||||
commands into requests and processing responses into results.
|
||||
|
||||
### Commands
|
||||
|
||||
Key-value pair objects representing an operation of a web service. Commands have a name and a set of parameters.
|
||||
|
||||
### Results
|
||||
|
||||
Key-value pair objects representing the processed result of executing an operation of a web service.
|
||||
|
||||
## Installing
|
||||
|
||||
This project can be installed using Composer:
|
||||
|
||||
``composer require guzzlehttp/command``
|
||||
|
||||
For **Guzzle 5**, use ``composer require guzzlehttp/command:0.8.*``. The source
|
||||
code for the Guzzle 5 version is available on the
|
||||
`0.8 branch <https://github.com/guzzle/command/tree/0.8>`_.
|
||||
|
||||
**Note:** If Composer is not installed [globally](https://getcomposer.org/doc/00-intro.md#globally) then you may need to run the preceding Composer commands using ``php composer.phar`` (where ``composer.phar`` is the path to your copy of Composer), instead of just ``composer``.
|
||||
|
||||
## Service Clients
|
||||
|
||||
Service Clients are web service clients that implement the
|
||||
``GuzzleHttp\Command\ServiceClientInterface`` and use an underlying Guzzle HTTP
|
||||
client (``GuzzleHttp\Client``) to communicate with the service. Service clients
|
||||
create and execute **commands** (``GuzzleHttp\Command\CommandInterface``),
|
||||
which encapsulate operations within the web service, including the operation
|
||||
name and parameters. This library provides a generic implementation of a service
|
||||
client: the ``GuzzleHttp\Command\ServiceClient`` class.
|
||||
|
||||
## Instantiating a Service Client
|
||||
|
||||
@TODO Add documentation
|
||||
|
||||
* ``ServiceClient``'s constructor
|
||||
* Transformer functions (``$commandToRequestTransformer`` and ``$responseToResultTransformer``)
|
||||
* The ``HandlerStack``
|
||||
|
||||
## Executing Commands
|
||||
|
||||
Service clients create command objects using the ``getCommand()`` method.
|
||||
|
||||
```php
|
||||
$commandName = 'foo';
|
||||
$arguments = ['baz' => 'bar'];
|
||||
$command = $client->getCommand($commandName, $arguments);
|
||||
|
||||
```
|
||||
|
||||
After creating a command, you may execute the command using the ``execute()`` method of the client.
|
||||
|
||||
```php
|
||||
$result = $client->execute($command);
|
||||
```
|
||||
|
||||
The result of executing a command will be a ``GuzzleHttp\Command\ResultInterface``
|
||||
object. Result objects are ``ArrayAccess``-ible and contain the data parsed from
|
||||
HTTP response.
|
||||
|
||||
Service clients have magic methods that act as shortcuts to executing commands
|
||||
by name without having to create the ``Command`` object in a separate step
|
||||
before executing it.
|
||||
|
||||
```php
|
||||
$result = $client->foo(['baz' => 'bar']);
|
||||
```
|
||||
|
||||
## Asynchronous Commands
|
||||
|
||||
@TODO Add documentation
|
||||
|
||||
* ``-Async`` suffix for client methods
|
||||
* Promises
|
||||
|
||||
```php
|
||||
// Create and execute an asynchronous command.
|
||||
$command = $command = $client->getCommand('foo', ['baz' => 'bar']);
|
||||
$promise = $client->executeAsync($command);
|
||||
|
||||
// Use asynchronous commands with magic methods.
|
||||
$promise = $client->fooAsync(['baz' => 'bar']);
|
||||
```
|
||||
|
||||
@TODO Add documentation
|
||||
|
||||
* ``wait()``-ing on promises.
|
||||
|
||||
```php
|
||||
$result = $promise->wait();
|
||||
|
||||
echo $result['fizz']; //> 'buzz'
|
||||
```
|
||||
|
||||
## Concurrent Requests
|
||||
|
||||
@TODO Add documentation
|
||||
|
||||
* ``executeAll()``
|
||||
* ``executeAllAsync()``.
|
||||
* Options (``fulfilled``, ``rejected``, ``concurrency``)
|
||||
|
||||
## Middleware: Extending the Client
|
||||
|
||||
Middleware can be added to the service client or underlying HTTP client to
|
||||
implement additional behavior and customize the ``Command``-to-``Result`` and
|
||||
``Request``-to-``Response`` lifecycles, respectively.
|
||||
|
||||
## Todo
|
||||
|
||||
* Middleware system and command vs request layers
|
||||
* The ``HandlerStack``
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "guzzlehttp/command",
|
||||
"description": "Provides the foundation for building command-based web service clients",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"guzzlehttp/guzzle": "^7.3",
|
||||
"guzzlehttp/promises": "^1.3",
|
||||
"guzzlehttp/psr7": "^1.7 || ^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.19"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Command\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
backupGlobals="true"
|
||||
colors="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
/**
|
||||
* Default command implementation.
|
||||
*/
|
||||
class Command implements CommandInterface
|
||||
{
|
||||
use HasDataTrait;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var HandlerStack */
|
||||
private $handlerStack;
|
||||
|
||||
/**
|
||||
* @param string $name Name of the command
|
||||
* @param array $args Arguments to pass to the command
|
||||
* @param HandlerStack $handlerStack Stack of middleware for the command
|
||||
*/
|
||||
public function __construct(
|
||||
$name,
|
||||
array $args = [],
|
||||
HandlerStack $handlerStack = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->data = $args;
|
||||
$this->handlerStack = $handlerStack;
|
||||
}
|
||||
|
||||
public function getHandlerStack()
|
||||
{
|
||||
return $this->handlerStack;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function hasParam($name)
|
||||
{
|
||||
return array_key_exists($name, $this->data);
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->handlerStack) {
|
||||
$this->handlerStack = clone $this->handlerStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
/**
|
||||
* A command object encapsulates the input parameters used to control the
|
||||
* creation of a HTTP request and processing of a HTTP response.
|
||||
*
|
||||
* Using the getParams() method will return the input parameters of the command
|
||||
* as an associative array.
|
||||
*/
|
||||
interface CommandInterface extends \ArrayAccess, \IteratorAggregate, \Countable, ToArrayInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves the handler stack specific to this command's execution.
|
||||
*
|
||||
* This can be used to add middleware that is specific to the command instance.
|
||||
*
|
||||
* @return HandlerStack
|
||||
*/
|
||||
public function getHandlerStack();
|
||||
|
||||
/**
|
||||
* Get the name of the command.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Check if the command has a parameter by name.
|
||||
*
|
||||
* @param string $name Name of the parameter to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasParam($name);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Exception;
|
||||
|
||||
/**
|
||||
* Exception encountered when a 4xx level response is received for a request
|
||||
*/
|
||||
class CommandClientException extends CommandException {}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Exception;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Exception encountered while executing a command.
|
||||
*/
|
||||
class CommandException extends \RuntimeException implements GuzzleException
|
||||
{
|
||||
/** @var CommandInterface */
|
||||
private $command;
|
||||
|
||||
/** @var RequestInterface */
|
||||
private $request;
|
||||
|
||||
/** @var ResponseInterface */
|
||||
private $response;
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param \Exception $prev
|
||||
* @return CommandException
|
||||
*/
|
||||
public static function fromPrevious(CommandInterface $command, \Exception $prev)
|
||||
{
|
||||
// If the exception is already a command exception, return it.
|
||||
if ($prev instanceof self && $command === $prev->getCommand()) {
|
||||
return $prev;
|
||||
}
|
||||
|
||||
// If the exception is a RequestException, get the Request and Response.
|
||||
$request = $response = null;
|
||||
if ($prev instanceof RequestException) {
|
||||
$request = $prev->getRequest();
|
||||
$response = $prev->getResponse();
|
||||
}
|
||||
|
||||
// Throw a more specific exception for 4XX or 5XX responses.
|
||||
$class = self::class;
|
||||
$statusCode = $response ? $response->getStatusCode() : 0;
|
||||
if ($statusCode >= 400 && $statusCode < 500) {
|
||||
$class = CommandClientException::class;
|
||||
} elseif ($statusCode >= 500 && $statusCode < 600) {
|
||||
$class = CommandServerException::class;
|
||||
}
|
||||
|
||||
// Prepare the message.
|
||||
$message = 'There was an error executing the ' . $command->getName()
|
||||
. ' command: ' . $prev->getMessage();
|
||||
|
||||
// Create the exception.
|
||||
return new $class($message, $command, $prev, $request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message Exception message
|
||||
* @param CommandInterface $command
|
||||
* @param \Exception $previous Previous exception (if any)
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function __construct(
|
||||
$message,
|
||||
CommandInterface $command,
|
||||
\Exception $previous = null,
|
||||
RequestInterface $request = null,
|
||||
ResponseInterface $response = null
|
||||
) {
|
||||
$this->command = $command;
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command that failed.
|
||||
*
|
||||
* @return CommandInterface
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request that caused the exception
|
||||
*
|
||||
* @return RequestInterface|null
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated response
|
||||
*
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Exception;
|
||||
|
||||
/**
|
||||
* Exception encountered when a 5xx level response is received for a request
|
||||
*/
|
||||
class CommandServerException extends CommandException {}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
/**
|
||||
* Basic collection behavior for Command and Result objects.
|
||||
*
|
||||
* The methods in the class are primarily for implementing the ArrayAccess,
|
||||
* Countable, and IteratorAggregate interfaces.
|
||||
*/
|
||||
trait HasDataTrait
|
||||
{
|
||||
/** @var array Data stored in the collection. */
|
||||
protected $data;
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return print_r($this, true);
|
||||
}
|
||||
|
||||
public function __debugInfo()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return array_key_exists($offset, $this->data);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return isset($this->data[$offset]) ? $this->data[$offset] : null;
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->data);
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->data);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
/**
|
||||
* Default command implementation.
|
||||
*/
|
||||
class Result implements ResultInterface
|
||||
{
|
||||
use HasDataTrait;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
/**
|
||||
* An array-like object that represents the result of executing a command.
|
||||
*/
|
||||
interface ResultInterface extends \ArrayAccess, \IteratorAggregate, \Countable, ToArrayInterface
|
||||
{
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
use GuzzleHttp\ClientInterface as HttpClient;
|
||||
use GuzzleHttp\Command\Exception\CommandException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Promise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* The Guzzle ServiceClient serves as the foundation for creating web service
|
||||
* clients that interact with RPC-style APIs.
|
||||
*/
|
||||
class ServiceClient implements ServiceClientInterface
|
||||
{
|
||||
/** @var HttpClient HTTP client used to send requests */
|
||||
private $httpClient;
|
||||
|
||||
/** @var HandlerStack */
|
||||
private $handlerStack;
|
||||
|
||||
/** @var callable */
|
||||
private $commandToRequestTransformer;
|
||||
|
||||
/** @var callable */
|
||||
private $responseToResultTransformer;
|
||||
|
||||
/**
|
||||
* Instantiates a Guzzle ServiceClient for making requests to a web service.
|
||||
*
|
||||
* @param HttpClient $httpClient A fully-configured Guzzle HTTP client that
|
||||
* will be used to perform the underlying HTTP requests.
|
||||
* @param callable $commandToRequestTransformer A callable that transforms
|
||||
* a Command into a Request. The function should accept a
|
||||
* `GuzzleHttp\Command\CommandInterface` object and return a
|
||||
* `Psr\Http\Message\RequestInterface` object.
|
||||
* @param callable $responseToResultTransformer A callable that transforms a
|
||||
* Response into a Result. The function should accept a
|
||||
* `Psr\Http\Message\ResponseInterface` object (and optionally a
|
||||
* `Psr\Http\Message\RequestInterface` object) and return a
|
||||
* `GuzzleHttp\Command\ResultInterface` object.
|
||||
* @param HandlerStack $commandHandlerStack A Guzzle HandlerStack, which can
|
||||
* be used to add command-level middleware to the service client.
|
||||
*/
|
||||
public function __construct(
|
||||
HttpClient $httpClient,
|
||||
callable $commandToRequestTransformer,
|
||||
callable $responseToResultTransformer,
|
||||
HandlerStack $commandHandlerStack = null
|
||||
) {
|
||||
$this->httpClient = $httpClient;
|
||||
$this->commandToRequestTransformer = $commandToRequestTransformer;
|
||||
$this->responseToResultTransformer = $responseToResultTransformer;
|
||||
$this->handlerStack = $commandHandlerStack ?: new HandlerStack();
|
||||
$this->handlerStack->setHandler($this->createCommandHandler());
|
||||
}
|
||||
|
||||
public function getHttpClient()
|
||||
{
|
||||
return $this->httpClient;
|
||||
}
|
||||
|
||||
public function getHandlerStack()
|
||||
{
|
||||
return $this->handlerStack;
|
||||
}
|
||||
|
||||
public function getCommand($name, array $params = [])
|
||||
{
|
||||
return new Command($name, $params, clone $this->handlerStack);
|
||||
}
|
||||
|
||||
public function execute(CommandInterface $command)
|
||||
{
|
||||
return $this->executeAsync($command)->wait();
|
||||
}
|
||||
|
||||
public function executeAsync(CommandInterface $command)
|
||||
{
|
||||
$stack = $command->getHandlerStack() ?: $this->handlerStack;
|
||||
$handler = $stack->resolve();
|
||||
|
||||
return $handler($command);
|
||||
}
|
||||
|
||||
public function executeAll($commands, array $options = [])
|
||||
{
|
||||
// Modify provided callbacks to track results.
|
||||
$results = [];
|
||||
$options['fulfilled'] = function ($v, $k) use (&$results, $options) {
|
||||
if (isset($options['fulfilled'])) {
|
||||
$options['fulfilled']($v, $k);
|
||||
}
|
||||
$results[$k] = $v;
|
||||
};
|
||||
$options['rejected'] = function ($v, $k) use (&$results, $options) {
|
||||
if (isset($options['rejected'])) {
|
||||
$options['rejected']($v, $k);
|
||||
}
|
||||
$results[$k] = $v;
|
||||
};
|
||||
|
||||
// Execute multiple commands synchronously, then sort and return the results.
|
||||
return $this->executeAllAsync($commands, $options)
|
||||
->then(function () use (&$results) {
|
||||
ksort($results);
|
||||
return $results;
|
||||
})
|
||||
->wait();
|
||||
}
|
||||
|
||||
public function executeAllAsync($commands, array $options = [])
|
||||
{
|
||||
// Apply default concurrency.
|
||||
if (!isset($options['concurrency'])) {
|
||||
$options['concurrency'] = 25;
|
||||
}
|
||||
|
||||
// Convert the iterator of commands to a generator of promises.
|
||||
$commands = Promise\iter_for($commands);
|
||||
$promises = function () use ($commands) {
|
||||
foreach ($commands as $key => $command) {
|
||||
if (!$command instanceof CommandInterface) {
|
||||
throw new \InvalidArgumentException('The iterator must '
|
||||
. 'yield instances of ' . CommandInterface::class);
|
||||
}
|
||||
yield $key => $this->executeAsync($command);
|
||||
}
|
||||
};
|
||||
|
||||
// Execute the commands using a pool.
|
||||
return (new Promise\EachPromise($promises(), $options))->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and executes a command for an operation by name.
|
||||
*
|
||||
* @param string $name Name of the command to execute.
|
||||
* @param array $args Arguments to pass to the getCommand method.
|
||||
*
|
||||
* @return ResultInterface|PromiseInterface
|
||||
* @see \GuzzleHttp\Command\ServiceClientInterface::getCommand
|
||||
*/
|
||||
public function __call($name, array $args)
|
||||
{
|
||||
$args = isset($args[0]) ? $args[0] : [];
|
||||
if (substr($name, -5) === 'Async') {
|
||||
$command = $this->getCommand(substr($name, 0, -5), $args);
|
||||
return $this->executeAsync($command);
|
||||
} else {
|
||||
return $this->execute($this->getCommand($name, $args));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the main handler for commands that uses the HTTP client.
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
private function createCommandHandler()
|
||||
{
|
||||
return function (CommandInterface $command) {
|
||||
return Promise\coroutine(function () use ($command) {
|
||||
// Prepare the HTTP options.
|
||||
$opts = $command['@http'] ?: [];
|
||||
unset($command['@http']);
|
||||
|
||||
try {
|
||||
// Prepare the request from the command and send it.
|
||||
$request = $this->transformCommandToRequest($command);
|
||||
$promise = $this->httpClient->sendAsync($request, $opts);
|
||||
|
||||
// Create a result from the response.
|
||||
$response = (yield $promise);
|
||||
yield $this->transformResponseToResult($response, $request, $command);
|
||||
} catch (\Exception $e) {
|
||||
throw CommandException::fromPrevious($command, $e);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a Command object into a Request object.
|
||||
*
|
||||
* @param CommandInterface $command
|
||||
* @return RequestInterface
|
||||
*/
|
||||
private function transformCommandToRequest(CommandInterface $command)
|
||||
{
|
||||
$transform = $this->commandToRequestTransformer;
|
||||
|
||||
return $transform($command);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a Response object, also using data from the Request object,
|
||||
* into a Result object.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @param CommandInterface $command
|
||||
* @return ResultInterface
|
||||
*/
|
||||
private function transformResponseToResult(
|
||||
ResponseInterface $response,
|
||||
RequestInterface $request,
|
||||
CommandInterface $command
|
||||
) {
|
||||
$transform = $this->responseToResultTransformer;
|
||||
|
||||
return $transform($response, $request, $command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Command\Exception\CommandException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Web service client interface.
|
||||
*/
|
||||
interface ServiceClientInterface
|
||||
{
|
||||
/**
|
||||
* Create a command for an operation name.
|
||||
*
|
||||
* Special keys may be set on the command to control how it behaves.
|
||||
* Implementations SHOULD be able to utilize the following keys or throw
|
||||
* an exception if unable.
|
||||
*
|
||||
* @param string $name Name of the operation to use in the command
|
||||
* @param array $args Arguments to pass to the command
|
||||
*
|
||||
* @return CommandInterface
|
||||
* @throws \InvalidArgumentException if no command can be found by name
|
||||
*/
|
||||
public function getCommand($name, array $args = []);
|
||||
|
||||
/**
|
||||
* Execute a single command.
|
||||
*
|
||||
* @param CommandInterface $command Command to execute
|
||||
*
|
||||
* @return ResultInterface The result of the executed command
|
||||
* @throws CommandException
|
||||
*/
|
||||
public function execute(CommandInterface $command);
|
||||
|
||||
/**
|
||||
* Execute a single command asynchronously
|
||||
*
|
||||
* @param CommandInterface $command Command to execute
|
||||
*
|
||||
* @return PromiseInterface A Promise that resolves to a Result.
|
||||
*/
|
||||
public function executeAsync(CommandInterface $command);
|
||||
|
||||
/**
|
||||
* Executes multiple commands concurrently using a fixed pool size.
|
||||
*
|
||||
* @param array|\Iterator $commands Array or iterator that contains
|
||||
* CommandInterface objects to execute with the client.
|
||||
* @param array $options Associative array of options to apply.
|
||||
* - concurrency: (int) Max number of commands to execute concurrently.
|
||||
* - fulfilled: (callable) Function to invoke when a command completes.
|
||||
* - rejected: (callable) Function to invoke when a command fails.
|
||||
*
|
||||
* @return array
|
||||
* @see GuzzleHttp\Command\ServiceClientInterface::createPool for options.
|
||||
*/
|
||||
public function executeAll($commands, array $options = []);
|
||||
|
||||
/**
|
||||
* Executes multiple commands concurrently and asynchronously using a
|
||||
* fixed pool size.
|
||||
*
|
||||
* @param array|\Iterator $commands Array or iterator that contains
|
||||
* CommandInterface objects to execute with the client.
|
||||
* @param array $options Associative array of options to apply.
|
||||
* - concurrency: (int) Max number of commands to execute concurrently.
|
||||
* - fulfilled: (callable) Function to invoke when a command completes.
|
||||
* - rejected: (callable) Function to invoke when a command fails.
|
||||
*
|
||||
* @return PromiseInterface
|
||||
* @see GuzzleHttp\Command\ServiceClientInterface::createPool for options.
|
||||
*/
|
||||
public function executeAllAsync($commands, array $options = []);
|
||||
|
||||
/**
|
||||
* Get the HTTP client used to send requests for the web service client
|
||||
*
|
||||
* @return ClientInterface
|
||||
*/
|
||||
public function getHttpClient();
|
||||
|
||||
/**
|
||||
* Get the HandlerStack which can be used to add middleware to the client.
|
||||
*
|
||||
* @return HandlerStack
|
||||
*/
|
||||
public function getHandlerStack();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Command;
|
||||
|
||||
/**
|
||||
* An object that can be represented as an array
|
||||
*/
|
||||
interface ToArrayInterface
|
||||
{
|
||||
/**
|
||||
* Get the array representation of an object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray();
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
# Change Log
|
||||
|
||||
## [1.2.0](https://github.com/guzzle/guzzle-services/tree/1.2.0) (2020-11-13)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.2.0...HEAD)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Fix weird "equal equal not" operator [\#154](https://github.com/guzzle/guzzle-services/issues/154)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Support Guzzle 7 [\#176](https://github.com/guzzle/guzzle-services/pull/176) ([ptlevi](https://github.com/ptlevi))
|
||||
|
||||
## [1.1.3](https://github.com/guzzle/guzzle-services/tree/1.1.3) (2017-10-06)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.2...HEAD)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Parameter type configuration causes issue when filters change input type [\#147](https://github.com/guzzle/guzzle-services/issues/147)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Use wire name when visiting array [\#152](https://github.com/guzzle/guzzle-services/pull/152) ([my2ter](https://github.com/my2ter))
|
||||
|
||||
- Adding descriptive error message on parameter failure [\#144](https://github.com/guzzle/guzzle-services/pull/144) ([igorsantos07](https://github.com/igorsantos07))
|
||||
|
||||
## [1.1.2](https://github.com/guzzle/guzzle-services/tree/1.1.2) (2017-05-19)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.1...1.1.2)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Default values ignored in 1.1 [\#146](https://github.com/guzzle/guzzle-services/issues/146)
|
||||
|
||||
- Operations extends is broken in 1.1.1 [\#145](https://github.com/guzzle/guzzle-services/issues/145)
|
||||
|
||||
## [1.1.1](https://github.com/guzzle/guzzle-services/tree/1.1.1) (2017-05-15)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.0...1.1.1)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Filters are applied twice [\#134](https://github.com/guzzle/guzzle-services/issues/134)
|
||||
|
||||
- Is it possible to NOT urlencode a specific uri parameter value? [\#97](https://github.com/guzzle/guzzle-services/issues/97)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix minor typos in documentation. [\#139](https://github.com/guzzle/guzzle-services/pull/139) ([forevermatt](https://github.com/forevermatt))
|
||||
|
||||
- Do not mutate command at validation [\#135](https://github.com/guzzle/guzzle-services/pull/135) ([danizord](https://github.com/danizord))
|
||||
|
||||
- Added tests for JSON array of arrays and array of objects [\#131](https://github.com/guzzle/guzzle-services/pull/131) ([selfcatering](https://github.com/selfcatering))
|
||||
|
||||
- Allow filters on response model [\#138](https://github.com/guzzle/guzzle-services/pull/138) ([danizord](https://github.com/danizord))
|
||||
|
||||
- Exposing properties to a parent class [\#136](https://github.com/guzzle/guzzle-services/pull/136) ([Napas](https://github.com/Napas))
|
||||
|
||||
## [1.1.0](https://github.com/guzzle/guzzle-services/tree/1.1.0) (2017-01-31)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.0.1...1.1.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Grab a list of objects when they are not located at top level of a json response \(HATEOAS\) [\#90](https://github.com/guzzle/guzzle-services/issues/90)
|
||||
|
||||
- Regression of Issue \#51 - XmlLocation response not handling multiple tags of the same name correctly [\#82](https://github.com/guzzle/guzzle-services/issues/82)
|
||||
|
||||
- PUT requests with parameters with location of "postField" result in Exception [\#78](https://github.com/guzzle/guzzle-services/issues/78)
|
||||
|
||||
- Allow to provide Post Body as an Array [\#77](https://github.com/guzzle/guzzle-services/issues/77)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bring more flexibility to query params serialization [\#132](https://github.com/guzzle/guzzle-services/pull/132) ([bakura10](https://github.com/bakura10))
|
||||
|
||||
- Allow to fix validation for parameters with a format [\#130](https://github.com/guzzle/guzzle-services/pull/130) ([bakura10](https://github.com/bakura10))
|
||||
|
||||
## [1.0.1](https://github.com/guzzle/guzzle-services/tree/1.0.1) (2017-01-13)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.0.0...1.0.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Set a name when pushing ValidatedDescriptionHandler to stack [\#127](https://github.com/guzzle/guzzle-services/issues/127)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- combine method in Uri [\#101](https://github.com/guzzle/guzzle-services/issues/101)
|
||||
|
||||
- Undefined Variable [\#88](https://github.com/guzzle/guzzle-services/issues/88)
|
||||
|
||||
- Regression in array parameter serialization [\#128](https://github.com/guzzle/guzzle-services/issues/128)
|
||||
|
||||
- Unable to POST multiple multipart parameters [\#123](https://github.com/guzzle/guzzle-services/issues/123)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Tag pre 1.0.0 release [\#121](https://github.com/guzzle/guzzle-services/issues/121)
|
||||
|
||||
- Adjust inline documentation of Parameter [\#120](https://github.com/guzzle/guzzle-services/issues/120)
|
||||
|
||||
- postField location not recognized after upgrading to 1.0 [\#119](https://github.com/guzzle/guzzle-services/issues/119)
|
||||
|
||||
- Create a new release for the guzzle6 branch [\#118](https://github.com/guzzle/guzzle-services/issues/118)
|
||||
|
||||
- Compatibility problem with PHP7.0 ? [\#116](https://github.com/guzzle/guzzle-services/issues/116)
|
||||
|
||||
- What is the correct type of Parameter static option [\#113](https://github.com/guzzle/guzzle-services/issues/113)
|
||||
|
||||
- Improve the construction of baseUri in Description [\#112](https://github.com/guzzle/guzzle-services/issues/112)
|
||||
|
||||
- Please create version tag for current master branch [\#110](https://github.com/guzzle/guzzle-services/issues/110)
|
||||
|
||||
- Problems with postField params [\#98](https://github.com/guzzle/guzzle-services/issues/98)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix serialization of query params [\#129](https://github.com/guzzle/guzzle-services/pull/129) ([bakura10](https://github.com/bakura10))
|
||||
|
||||
## [1.0.0](https://github.com/guzzle/guzzle-services/tree/1.0.0) (2016-11-24)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.6.0...1.0.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- AbstractClient' not found [\#117](https://github.com/guzzle/guzzle-services/issues/117)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Make Guzzle Services compatible with Guzzle6 [\#109](https://github.com/guzzle/guzzle-services/pull/109) ([Konafets](https://github.com/Konafets))
|
||||
|
||||
## [0.6.0](https://github.com/guzzle/guzzle-services/tree/0.6.0) (2016-10-21)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.5.0...0.6.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Broken composer install [\#111](https://github.com/guzzle/guzzle-services/issues/111)
|
||||
|
||||
- The visit\(\) method is expected to return a RequestInterface but it doesn't in JsonLocation [\#106](https://github.com/guzzle/guzzle-services/issues/106)
|
||||
|
||||
- Allow parameters in baseUrl [\#102](https://github.com/guzzle/guzzle-services/issues/102)
|
||||
|
||||
- Have default params at client construction, gone away? [\#100](https://github.com/guzzle/guzzle-services/issues/100)
|
||||
|
||||
- Runtime Exception Error is always empty [\#99](https://github.com/guzzle/guzzle-services/issues/99)
|
||||
|
||||
- PHP Fatal error: Unsupported operand types in guzzlehttp/guzzle-services/src/GuzzleClient.php on line 72 [\#95](https://github.com/guzzle/guzzle-services/issues/95)
|
||||
|
||||
- Date of next version [\#94](https://github.com/guzzle/guzzle-services/issues/94)
|
||||
|
||||
- Map null reponse values to defined reponse model properties [\#91](https://github.com/guzzle/guzzle-services/issues/91)
|
||||
|
||||
- Map a json-array into a Model [\#80](https://github.com/guzzle/guzzle-services/issues/80)
|
||||
|
||||
- If property specified in json model but empty, notice raised [\#75](https://github.com/guzzle/guzzle-services/issues/75)
|
||||
|
||||
- Allow primitive response types for operations [\#73](https://github.com/guzzle/guzzle-services/issues/73)
|
||||
|
||||
- Allow shortened definition of properties in models [\#71](https://github.com/guzzle/guzzle-services/issues/71)
|
||||
|
||||
- Where's the ServiceDescriptionLoader/AbstractConfigLoader? [\#68](https://github.com/guzzle/guzzle-services/issues/68)
|
||||
|
||||
- errorResposnes from operation is never used [\#66](https://github.com/guzzle/guzzle-services/issues/66)
|
||||
|
||||
- Updating the description [\#65](https://github.com/guzzle/guzzle-services/issues/65)
|
||||
|
||||
- Parameter type validation is too strict [\#7](https://github.com/guzzle/guzzle-services/issues/7)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- fix code example [\#115](https://github.com/guzzle/guzzle-services/pull/115) ([snoek09](https://github.com/snoek09))
|
||||
|
||||
- Bug Fix for GuzzleClient constructor [\#96](https://github.com/guzzle/guzzle-services/pull/96) ([peterfox](https://github.com/peterfox))
|
||||
|
||||
- add plugin section to readme [\#93](https://github.com/guzzle/guzzle-services/pull/93) ([gimler](https://github.com/gimler))
|
||||
|
||||
- Allow mapping null response values to defined response model properties [\#92](https://github.com/guzzle/guzzle-services/pull/92) ([shaun785](https://github.com/shaun785))
|
||||
|
||||
- Updated exception message for better debugging [\#85](https://github.com/guzzle/guzzle-services/pull/85) ([stovak](https://github.com/stovak))
|
||||
|
||||
- Gracefully handle null return from $this-\>getConfig\('defaults'\) [\#84](https://github.com/guzzle/guzzle-services/pull/84) ([fuhry](https://github.com/fuhry))
|
||||
|
||||
- Fixing issue \#82 to address regression for handling elements with the sa... [\#83](https://github.com/guzzle/guzzle-services/pull/83) ([sprak3000](https://github.com/sprak3000))
|
||||
|
||||
- Fix for specified property but no value in json \(notice for undefined in... [\#76](https://github.com/guzzle/guzzle-services/pull/76) ([rfink](https://github.com/rfink))
|
||||
|
||||
- Add ErrorHandler subscriber [\#67](https://github.com/guzzle/guzzle-services/pull/67) ([bakura10](https://github.com/bakura10))
|
||||
|
||||
- Fix combine base url and command uri [\#108](https://github.com/guzzle/guzzle-services/pull/108) ([vlastv](https://github.com/vlastv))
|
||||
|
||||
- Fixing JsonLocation::visit\(\) not returning a request \#106 [\#107](https://github.com/guzzle/guzzle-services/pull/107) ([Pinolo](https://github.com/Pinolo))
|
||||
|
||||
- Fix call to undefined method "GuzzleHttp\Psr7\Uri::combine" [\#105](https://github.com/guzzle/guzzle-services/pull/105) ([horrorin](https://github.com/horrorin))
|
||||
|
||||
- fix description for get request example [\#87](https://github.com/guzzle/guzzle-services/pull/87) ([snoek09](https://github.com/snoek09))
|
||||
|
||||
- Allow raw values \(non array/object\) for root model definitions [\#74](https://github.com/guzzle/guzzle-services/pull/74) ([rfink](https://github.com/rfink))
|
||||
|
||||
- Allow shortened definition of properties by assigning them directly to a type [\#72](https://github.com/guzzle/guzzle-services/pull/72) ([rfink](https://github.com/rfink))
|
||||
|
||||
## [0.5.0](https://github.com/guzzle/guzzle-services/tree/0.5.0) (2014-12-23)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.4.0...0.5.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Does it supports custom class instantiate to define an operation using a service description [\#62](https://github.com/guzzle/guzzle-services/issues/62)
|
||||
|
||||
- Tag version 0.4.0 [\#61](https://github.com/guzzle/guzzle-services/issues/61)
|
||||
|
||||
- XmlLocation not adding attributes to non-leaf child nodes [\#52](https://github.com/guzzle/guzzle-services/issues/52)
|
||||
|
||||
- XmlLocation response not handling multiple tags of the same name correctly [\#51](https://github.com/guzzle/guzzle-services/issues/51)
|
||||
|
||||
- Validation Bug [\#47](https://github.com/guzzle/guzzle-services/issues/47)
|
||||
|
||||
- CommandException doesn't contain response data [\#44](https://github.com/guzzle/guzzle-services/issues/44)
|
||||
|
||||
- \[Fix included\] XmlLocation requires text value to have attributes [\#37](https://github.com/guzzle/guzzle-services/issues/37)
|
||||
|
||||
- Question: Mocking a Response does not throw exception [\#35](https://github.com/guzzle/guzzle-services/issues/35)
|
||||
|
||||
- allow default 'location' on Model [\#26](https://github.com/guzzle/guzzle-services/issues/26)
|
||||
|
||||
- create mock subscriber requests from descriptions [\#25](https://github.com/guzzle/guzzle-services/issues/25)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Documentation: Add 'boolean-string' as a supported "format" value [\#63](https://github.com/guzzle/guzzle-services/pull/63) ([jwcobb](https://github.com/jwcobb))
|
||||
|
||||
## [0.4.0](https://github.com/guzzle/guzzle-services/tree/0.4.0) (2014-11-03)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.3.0...0.4.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Exceptions Thrown From Subscribers Are Ignored? [\#58](https://github.com/guzzle/guzzle-services/issues/58)
|
||||
|
||||
- Totally Broken With Guzzle 5 [\#57](https://github.com/guzzle/guzzle-services/issues/57)
|
||||
|
||||
- GuzzleHTTP/Command Dependency fail [\#50](https://github.com/guzzle/guzzle-services/issues/50)
|
||||
|
||||
- Request parameter PathLocation [\#46](https://github.com/guzzle/guzzle-services/issues/46)
|
||||
|
||||
- Requesting a new version tag [\#45](https://github.com/guzzle/guzzle-services/issues/45)
|
||||
|
||||
- CommandException expects second parameter to be CommandTransaction instance [\#43](https://github.com/guzzle/guzzle-services/issues/43)
|
||||
|
||||
- Cannot add Autorization header to my requests [\#39](https://github.com/guzzle/guzzle-services/issues/39)
|
||||
|
||||
- Resouce Itterators [\#36](https://github.com/guzzle/guzzle-services/issues/36)
|
||||
|
||||
- Question [\#33](https://github.com/guzzle/guzzle-services/issues/33)
|
||||
|
||||
- query location array can be comma separated [\#31](https://github.com/guzzle/guzzle-services/issues/31)
|
||||
|
||||
- Automatically returns array from command? [\#30](https://github.com/guzzle/guzzle-services/issues/30)
|
||||
|
||||
- Arrays nested under objects in JSON response broken? [\#27](https://github.com/guzzle/guzzle-services/issues/27)
|
||||
|
||||
- Question? [\#23](https://github.com/guzzle/guzzle-services/issues/23)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bump the version in the readme [\#60](https://github.com/guzzle/guzzle-services/pull/60) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Bump the next version to 0.4 [\#56](https://github.com/guzzle/guzzle-services/pull/56) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Fixed the guzzlehttp/command version constraint [\#55](https://github.com/guzzle/guzzle-services/pull/55) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Work with latest Guzzle 5 and Command updates [\#54](https://github.com/guzzle/guzzle-services/pull/54) ([mtdowling](https://github.com/mtdowling))
|
||||
|
||||
- Addressing Issue \#51 & Issue \#52 [\#53](https://github.com/guzzle/guzzle-services/pull/53) ([sprak3000](https://github.com/sprak3000))
|
||||
|
||||
- added description interface to extend it [\#49](https://github.com/guzzle/guzzle-services/pull/49) ([danieledangeli](https://github.com/danieledangeli))
|
||||
|
||||
- Update readme to improve documentation \(\#46\) [\#48](https://github.com/guzzle/guzzle-services/pull/48) ([bonndan](https://github.com/bonndan))
|
||||
|
||||
- Fixed the readme version constraint [\#42](https://github.com/guzzle/guzzle-services/pull/42) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Update .travis.yml [\#41](https://github.com/guzzle/guzzle-services/pull/41) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Added a branch alias [\#40](https://github.com/guzzle/guzzle-services/pull/40) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Fixes Response\XmlLocation requires text value [\#38](https://github.com/guzzle/guzzle-services/pull/38) ([magnetik](https://github.com/magnetik))
|
||||
|
||||
- Removing unnecessary \(\) from docblock [\#32](https://github.com/guzzle/guzzle-services/pull/32) ([jamiehannaford](https://github.com/jamiehannaford))
|
||||
|
||||
- Fix JSON response location so that both is supported: arrays nested unde... [\#28](https://github.com/guzzle/guzzle-services/pull/28) ([ukautz](https://github.com/ukautz))
|
||||
|
||||
- Throw Any Exceptions On Process [\#59](https://github.com/guzzle/guzzle-services/pull/59) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Allow extension to work recursively over models [\#34](https://github.com/guzzle/guzzle-services/pull/34) ([jamiehannaford](https://github.com/jamiehannaford))
|
||||
|
||||
- A custom class can be configured for command instances. [\#29](https://github.com/guzzle/guzzle-services/pull/29) ([robinvdvleuten](https://github.com/robinvdvleuten))
|
||||
|
||||
- \[WIP\] doing some experimentation [\#24](https://github.com/guzzle/guzzle-services/pull/24) ([cordoval](https://github.com/cordoval))
|
||||
|
||||
## [0.3.0](https://github.com/guzzle/guzzle-services/tree/0.3.0) (2014-06-01)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.2.0...0.3.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Testing Guzzle Services doesn't work [\#19](https://github.com/guzzle/guzzle-services/issues/19)
|
||||
|
||||
- Description factory [\#18](https://github.com/guzzle/guzzle-services/issues/18)
|
||||
|
||||
- support to load service description from file [\#15](https://github.com/guzzle/guzzle-services/issues/15)
|
||||
|
||||
- Update dependency on guzzlehttp/command [\#11](https://github.com/guzzle/guzzle-services/issues/11)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Add license file [\#22](https://github.com/guzzle/guzzle-services/pull/22) ([siwinski](https://github.com/siwinski))
|
||||
|
||||
- Fix 'Invalid argument supplied for foreach\(\)' [\#21](https://github.com/guzzle/guzzle-services/pull/21) ([Olden](https://github.com/Olden))
|
||||
|
||||
- Fixed string zero \('0'\) values not being filtered in XML. [\#20](https://github.com/guzzle/guzzle-services/pull/20) ([dragonwize](https://github.com/dragonwize))
|
||||
|
||||
- baseUrl can be a string or an uri template [\#16](https://github.com/guzzle/guzzle-services/pull/16) ([robinvdvleuten](https://github.com/robinvdvleuten))
|
||||
|
||||
## [0.2.0](https://github.com/guzzle/guzzle-services/tree/0.2.0) (2014-03-30)
|
||||
|
||||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.1.0...0.2.0)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- please remove wiki [\#13](https://github.com/guzzle/guzzle-services/issues/13)
|
||||
|
||||
- Parameter validation fails for union types [\#12](https://github.com/guzzle/guzzle-services/issues/12)
|
||||
|
||||
- question on integration with Guzzle4 [\#8](https://github.com/guzzle/guzzle-services/issues/8)
|
||||
|
||||
- typehints for operations property [\#6](https://github.com/guzzle/guzzle-services/issues/6)
|
||||
|
||||
- improve exception message [\#5](https://github.com/guzzle/guzzle-services/issues/5)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Update composer.json [\#14](https://github.com/guzzle/guzzle-services/pull/14) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Update composer.json [\#9](https://github.com/guzzle/guzzle-services/pull/9) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- some fixes [\#4](https://github.com/guzzle/guzzle-services/pull/4) ([cordoval](https://github.com/cordoval))
|
||||
|
||||
- Fix the CommandException path used in ValidateInput [\#2](https://github.com/guzzle/guzzle-services/pull/2) ([mookle](https://github.com/mookle))
|
||||
|
||||
- Minor improvements [\#1](https://github.com/guzzle/guzzle-services/pull/1) ([GrahamCampbell](https://github.com/GrahamCampbell))
|
||||
|
||||
- Use latest guzzlehttp/command to fix dependencies [\#10](https://github.com/guzzle/guzzle-services/pull/10) ([sbward](https://github.com/sbward))
|
||||
|
||||
- some collaboration using Gush :\) [\#3](https://github.com/guzzle/guzzle-services/pull/3) ([cordoval](https://github.com/cordoval))
|
||||
|
||||
## [0.1.0](https://github.com/guzzle/guzzle-services/tree/0.1.0) (2014-03-15)
|
||||
|
||||
|
||||
|
||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
test:
|
||||
vendor/bin/phpunit $(TEST)
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Guzzle Services
|
||||
|
||||
Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures.
|
||||
|
||||
```php
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Command\Guzzle\GuzzleClient;
|
||||
use GuzzleHttp\Command\Guzzle\Description;
|
||||
|
||||
$client = new Client();
|
||||
$description = new Description([
|
||||
'baseUri' => 'http://httpbin.org/',
|
||||
'operations' => [
|
||||
'testing' => [
|
||||
'httpMethod' => 'GET',
|
||||
'uri' => '/get{?foo}',
|
||||
'responseModel' => 'getResponse',
|
||||
'parameters' => [
|
||||
'foo' => [
|
||||
'type' => 'string',
|
||||
'location' => 'uri'
|
||||
],
|
||||
'bar' => [
|
||||
'type' => 'string',
|
||||
'location' => 'query'
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'models' => [
|
||||
'getResponse' => [
|
||||
'type' => 'object',
|
||||
'additionalProperties' => [
|
||||
'location' => 'json'
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$guzzleClient = new GuzzleClient($client, $description);
|
||||
|
||||
$result = $guzzleClient->testing(['foo' => 'bar']);
|
||||
echo $result['args']['foo'];
|
||||
// bar
|
||||
```
|
||||
|
||||
## Installing
|
||||
|
||||
This project can be installed using Composer:
|
||||
|
||||
``composer require guzzlehttp/guzzle-services``
|
||||
|
||||
For **Guzzle 5**, use ``composer require guzzlehttp/guzzle-services:0.6``.
|
||||
|
||||
**Note:** If Composer is not installed [globally](https://getcomposer.org/doc/00-intro.md#globally) then you may need to run the preceding Composer commands using ``php composer.phar`` (where ``composer.phar`` is the path to your copy of Composer), instead of just ``composer``.
|
||||
|
||||
## Plugins
|
||||
|
||||
* Load Service description from file [https://github.com/gimler/guzzle-description-loader]
|
||||
|
||||
## Transition guide from Guzzle 5.0 to 6.0
|
||||
|
||||
### Change regarding PostField and PostFile
|
||||
|
||||
The request locations `postField` and `postFile` were removed in favor of `formParam` and `multipart`. If your description looks like
|
||||
|
||||
```php
|
||||
[
|
||||
'baseUri' => 'http://httpbin.org/',
|
||||
'operations' => [
|
||||
'testing' => [
|
||||
'httpMethod' => 'GET',
|
||||
'uri' => '/get{?foo}',
|
||||
'responseModel' => 'getResponse',
|
||||
'parameters' => [
|
||||
'foo' => [
|
||||
'type' => 'string',
|
||||
'location' => 'postField'
|
||||
],
|
||||
'bar' => [
|
||||
'type' => 'string',
|
||||
'location' => 'postFile'
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
you need to change `postField` to `formParam` and `postFile` to `multipart`.
|
||||
|
||||
More documentation coming soon.
|
||||
|
||||
## Cookbook
|
||||
|
||||
### Changing the way query params are serialized
|
||||
|
||||
By default, query params are serialized using strict RFC3986 rules, using `http_build_query` method. With this, array params are serialized this way:
|
||||
|
||||
```php
|
||||
$client->myMethod(['foo' => ['bar', 'baz']]);
|
||||
|
||||
// Query params will be foo[0]=bar&foo[1]=baz
|
||||
```
|
||||
|
||||
However, a lot of APIs in the wild require the numeric indices to be removed, so that the query params end up being `foo[]=bar&foo[]=baz`. You
|
||||
can easily change the behaviour by creating your own serializer and overriding the "query" request location:
|
||||
|
||||
```php
|
||||
use GuzzleHttp\Command\Guzzle\GuzzleClient;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation;
|
||||
use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer;
|
||||
use GuzzleHttp\Command\Guzzle\Serializer;
|
||||
|
||||
$queryLocation = new QueryLocation('query', new Rfc3986Serializer(true));
|
||||
$serializer = new Serializer($description, ['query' => $queryLocation]);
|
||||
$guzzleClient = new GuzzleClient($client, $description, $serializer);
|
||||
```
|
||||
|
||||
You can also create your own serializer if you have specific needs.
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "guzzlehttp/guzzle-services",
|
||||
"description": "Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures.",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
},
|
||||
{
|
||||
"name": "Stefano Kowalke",
|
||||
"email": "blueduck@mail.org",
|
||||
"homepage": "https://github.com/konafets"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"guzzlehttp/guzzle": "^7.3",
|
||||
"guzzlehttp/command": "^1.2",
|
||||
"guzzlehttp/psr7": "^1.7 || ^2.0",
|
||||
"guzzlehttp/uri-template": "^0.2 || ^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.19 || ^9.5.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Command\\Guzzle\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Tests\\Command\\Guzzle\\": "tests/"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"gimler/guzzle-description-loader": "^0.0.4"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
backupGlobals="true"
|
||||
colors="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
/**
|
||||
* Represents a Guzzle service description
|
||||
*/
|
||||
class Description implements DescriptionInterface
|
||||
{
|
||||
/** @var array Array of {@see OperationInterface} objects */
|
||||
private $operations = [];
|
||||
|
||||
/** @var array Array of API models */
|
||||
private $models = [];
|
||||
|
||||
/** @var string Name of the API */
|
||||
private $name;
|
||||
|
||||
/** @var string API version */
|
||||
private $apiVersion;
|
||||
|
||||
/** @var string Summary of the API */
|
||||
private $description;
|
||||
|
||||
/** @var array Any extra API data */
|
||||
private $extraData = [];
|
||||
|
||||
/** @var Uri baseUri/basePath */
|
||||
private $baseUri;
|
||||
|
||||
/** @var SchemaFormatter */
|
||||
private $formatter;
|
||||
|
||||
/**
|
||||
* @param array $config Service description data
|
||||
* @param array $options Custom options to apply to the description
|
||||
* - formatter: Can provide a custom SchemaFormatter class
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $config, array $options = [])
|
||||
{
|
||||
// Keep a list of default keys used in service descriptions that is
|
||||
// later used to determine extra data keys.
|
||||
static $defaultKeys = ['name', 'models', 'apiVersion', 'description'];
|
||||
|
||||
// Pull in the default configuration values
|
||||
foreach ($defaultKeys as $key) {
|
||||
if (isset($config[$key])) {
|
||||
$this->{$key} = $config[$key];
|
||||
}
|
||||
}
|
||||
|
||||
// Set the baseUri
|
||||
// Account for the old style of using baseUrl
|
||||
if (isset($config['baseUrl'])) {
|
||||
$config['baseUri'] = $config['baseUrl'];
|
||||
}
|
||||
$this->baseUri = isset($config['baseUri']) ? new Uri($config['baseUri']) : new Uri();
|
||||
|
||||
// Ensure that the models and operations properties are always arrays
|
||||
$this->models = (array) $this->models;
|
||||
$this->operations = (array) $this->operations;
|
||||
|
||||
// We want to add operations differently than adding the other properties
|
||||
$defaultKeys[] = 'operations';
|
||||
|
||||
// Create operations for each operation
|
||||
if (isset($config['operations'])) {
|
||||
foreach ($config['operations'] as $name => $operation) {
|
||||
if (!is_array($operation)) {
|
||||
throw new \InvalidArgumentException('Operations must be arrays');
|
||||
}
|
||||
$this->operations[$name] = $operation;
|
||||
}
|
||||
}
|
||||
|
||||
// Get all of the additional properties of the service description and
|
||||
// store them in a data array
|
||||
foreach (array_diff(array_keys($config), $defaultKeys) as $key) {
|
||||
$this->extraData[$key] = $config[$key];
|
||||
}
|
||||
|
||||
// Configure the schema formatter
|
||||
if (isset($options['formatter'])) {
|
||||
$this->formatter = $options['formatter'];
|
||||
} else {
|
||||
static $defaultFormatter;
|
||||
if (!$defaultFormatter) {
|
||||
$defaultFormatter = new SchemaFormatter();
|
||||
}
|
||||
$this->formatter = $defaultFormatter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the basePath/baseUri of the description
|
||||
*
|
||||
* @return Uri
|
||||
*/
|
||||
public function getBaseUri()
|
||||
{
|
||||
return $this->baseUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API operations of the service
|
||||
*
|
||||
* @return Operation[] Returns an array of {@see Operation} objects
|
||||
*/
|
||||
public function getOperations()
|
||||
{
|
||||
return $this->operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the service has an operation by name
|
||||
*
|
||||
* @param string $name Name of the operation to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOperation($name)
|
||||
{
|
||||
return isset($this->operations[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an API operation by name
|
||||
*
|
||||
* @param string $name Name of the command
|
||||
*
|
||||
* @return Operation
|
||||
* @throws \InvalidArgumentException if the operation is not found
|
||||
*/
|
||||
public function getOperation($name)
|
||||
{
|
||||
if (!$this->hasOperation($name)) {
|
||||
throw new \InvalidArgumentException("No operation found named $name");
|
||||
}
|
||||
|
||||
// Lazily create operations as they are retrieved
|
||||
if (!($this->operations[$name] instanceof Operation)) {
|
||||
$this->operations[$name]['name'] = $name;
|
||||
$this->operations[$name] = new Operation($this->operations[$name], $this);
|
||||
}
|
||||
|
||||
return $this->operations[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a shared definition structure.
|
||||
*
|
||||
* @param string $id ID/name of the model to retrieve
|
||||
*
|
||||
* @return Parameter
|
||||
* @throws \InvalidArgumentException if the model is not found
|
||||
*/
|
||||
public function getModel($id)
|
||||
{
|
||||
if (!$this->hasModel($id)) {
|
||||
throw new \InvalidArgumentException("No model found named $id");
|
||||
}
|
||||
|
||||
// Lazily create models as they are retrieved
|
||||
if (!($this->models[$id] instanceof Parameter)) {
|
||||
$this->models[$id] = new Parameter(
|
||||
$this->models[$id],
|
||||
['description' => $this]
|
||||
);
|
||||
}
|
||||
|
||||
return $this->models[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all models of the service description.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getModels()
|
||||
{
|
||||
$models = [];
|
||||
foreach ($this->models as $name => $model) {
|
||||
$models[$name] = $this->getModel($name);
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the service description has a model by name.
|
||||
*
|
||||
* @param string $id Name/ID of the model to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasModel($id)
|
||||
{
|
||||
return isset($this->models[$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API version of the service
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getApiVersion()
|
||||
{
|
||||
return $this->apiVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the API
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of the purpose of the API
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a parameter using named formats.
|
||||
*
|
||||
* @param string $format Format to convert it to
|
||||
* @param mixed $input Input string
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function format($format, $input)
|
||||
{
|
||||
return $this->formatter->format($format, $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get arbitrary data from the service description that is not part of the
|
||||
* Guzzle service description specification.
|
||||
*
|
||||
* @param string $key Data key to retrieve or null to retrieve all extra
|
||||
*
|
||||
* @return null|mixed
|
||||
*/
|
||||
public function getData($key = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->extraData;
|
||||
} elseif (isset($this->extraData[$key])) {
|
||||
return $this->extraData[$key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
interface DescriptionInterface
|
||||
{
|
||||
/**
|
||||
* Get the basePath/baseUri of the description
|
||||
*
|
||||
* @return Uri
|
||||
*/
|
||||
public function getBaseUri();
|
||||
|
||||
/**
|
||||
* Get the API operations of the service
|
||||
*
|
||||
* @return Operation[] Returns an array of {@see Operation} objects
|
||||
*/
|
||||
public function getOperations();
|
||||
|
||||
/**
|
||||
* Check if the service has an operation by name
|
||||
*
|
||||
* @param string $name Name of the operation to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOperation($name);
|
||||
|
||||
/**
|
||||
* Get an API operation by name
|
||||
*
|
||||
* @param string $name Name of the command
|
||||
*
|
||||
* @return Operation
|
||||
* @throws \InvalidArgumentException if the operation is not found
|
||||
*/
|
||||
public function getOperation($name);
|
||||
|
||||
/**
|
||||
* Get a shared definition structure.
|
||||
*
|
||||
* @param string $id ID/name of the model to retrieve
|
||||
*
|
||||
* @return Parameter
|
||||
* @throws \InvalidArgumentException if the model is not found
|
||||
*/
|
||||
public function getModel($id);
|
||||
|
||||
/**
|
||||
* Get all models of the service description.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getModels();
|
||||
|
||||
/**
|
||||
* Check if the service description has a model by name.
|
||||
*
|
||||
* @param string $id Name/ID of the model to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasModel($id);
|
||||
|
||||
/**
|
||||
* Get the API version of the service
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getApiVersion();
|
||||
|
||||
/**
|
||||
* Get the name of the API
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Get a summary of the purpose of the API
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Format a parameter using named formats.
|
||||
*
|
||||
* @param string $format Format to convert it to
|
||||
* @param mixed $input Input string
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function format($format, $input);
|
||||
|
||||
/**
|
||||
* Get arbitrary data from the service description that is not part of the
|
||||
* Guzzle service description specification.
|
||||
*
|
||||
* @param string $key Data key to retrieve or null to retrieve all extra
|
||||
*
|
||||
* @return null|mixed
|
||||
*/
|
||||
public function getData($key = null);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\ResponseLocation\BodyLocation;
|
||||
use GuzzleHttp\Command\Guzzle\ResponseLocation\HeaderLocation;
|
||||
use GuzzleHttp\Command\Guzzle\ResponseLocation\JsonLocation;
|
||||
use GuzzleHttp\Command\Guzzle\ResponseLocation\ReasonPhraseLocation;
|
||||
use GuzzleHttp\Command\Guzzle\ResponseLocation\ResponseLocationInterface;
|
||||
use GuzzleHttp\Command\Guzzle\ResponseLocation\StatusCodeLocation;
|
||||
use GuzzleHttp\Command\Guzzle\ResponseLocation\XmlLocation;
|
||||
use GuzzleHttp\Command\Result;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Handler used to create response models based on an HTTP response and
|
||||
* a service description.
|
||||
*
|
||||
* Response location visitors are registered with this Handler to handle
|
||||
* locations (e.g., 'xml', 'json', 'header'). All of the locations of a response
|
||||
* model that will be visited first have their ``before`` method triggered.
|
||||
* After the before method is called on every visitor that will be walked, each
|
||||
* visitor is triggered using the ``visit()`` method. After all of the visitors
|
||||
* are visited, the ``after()`` method is called on each visitor. This is the
|
||||
* place in which you should handle things like additionalProperties with
|
||||
* custom locations (i.e., this is how it is handled in the JSON visitor).
|
||||
*/
|
||||
class Deserializer
|
||||
{
|
||||
/** @var ResponseLocationInterface[] $responseLocations */
|
||||
private $responseLocations;
|
||||
|
||||
/** @var DescriptionInterface $description */
|
||||
private $description;
|
||||
|
||||
/** @var boolean $process */
|
||||
private $process;
|
||||
|
||||
/**
|
||||
* @param DescriptionInterface $description
|
||||
* @param bool $process
|
||||
* @param ResponseLocationInterface[] $responseLocations Extra response locations
|
||||
*/
|
||||
public function __construct(
|
||||
DescriptionInterface $description,
|
||||
$process,
|
||||
array $responseLocations = []
|
||||
) {
|
||||
static $defaultResponseLocations;
|
||||
if (!$defaultResponseLocations) {
|
||||
$defaultResponseLocations = [
|
||||
'body' => new BodyLocation(),
|
||||
'header' => new HeaderLocation(),
|
||||
'reasonPhrase' => new ReasonPhraseLocation(),
|
||||
'statusCode' => new StatusCodeLocation(),
|
||||
'xml' => new XmlLocation(),
|
||||
'json' => new JsonLocation(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->responseLocations = $responseLocations + $defaultResponseLocations;
|
||||
$this->description = $description;
|
||||
$this->process = $process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize the response into the specified result representation
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface|null $request
|
||||
* @param CommandInterface $command
|
||||
* @return Result|ResultInterface|void|ResponseInterface
|
||||
*/
|
||||
public function __invoke(ResponseInterface $response, RequestInterface $request, CommandInterface $command)
|
||||
{
|
||||
// If the user don't want to process the result, just return the plain response here
|
||||
if ($this->process === false) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$name = $command->getName();
|
||||
$operation = $this->description->getOperation($name);
|
||||
|
||||
$this->handleErrorResponses($response, $request, $command, $operation);
|
||||
|
||||
// Add a default Model as the result if no matching schema was found
|
||||
if (!($modelName = $operation->getResponseModel())) {
|
||||
// Not sure if this should be empty or contains the response.
|
||||
// Decided to do it how it was in the old version for now.
|
||||
return new Result();
|
||||
}
|
||||
|
||||
$model = $operation->getServiceDescription()->getModel($modelName);
|
||||
if (!$model) {
|
||||
throw new \RuntimeException("Unknown model: {$modelName}");
|
||||
}
|
||||
|
||||
return $this->visit($model, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles visit() and after() methods of the Response locations
|
||||
*
|
||||
* @param Parameter $model
|
||||
* @param ResponseInterface $response
|
||||
* @return Result|ResultInterface|void
|
||||
*/
|
||||
protected function visit(Parameter $model, ResponseInterface $response)
|
||||
{
|
||||
$result = new Result();
|
||||
$context = ['visitors' => []];
|
||||
|
||||
if ($model->getType() === 'object') {
|
||||
$result = $this->visitOuterObject($model, $result, $response, $context);
|
||||
} elseif ($model->getType() === 'array') {
|
||||
$result = $this->visitOuterArray($model, $result, $response, $context);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid response model: ' . $model->getType());
|
||||
}
|
||||
|
||||
// Call the after() method of each found visitor
|
||||
/** @var ResponseLocationInterface $visitor */
|
||||
foreach ($context['visitors'] as $visitor) {
|
||||
$result = $visitor->after($result, $response, $model);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the before() method of Response locations
|
||||
*
|
||||
* @param string $location
|
||||
* @param Parameter $model
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param array $context
|
||||
* @return ResultInterface
|
||||
*/
|
||||
private function triggerBeforeVisitor(
|
||||
$location,
|
||||
Parameter $model,
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
array &$context
|
||||
) {
|
||||
if (!isset($this->responseLocations[$location])) {
|
||||
throw new \RuntimeException("Unknown location: $location");
|
||||
}
|
||||
|
||||
$context['visitors'][$location] = $this->responseLocations[$location];
|
||||
|
||||
$result = $this->responseLocations[$location]->before(
|
||||
$result,
|
||||
$response,
|
||||
$model
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the outer object
|
||||
*
|
||||
* @param Parameter $model
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param array $context
|
||||
* @return ResultInterface
|
||||
*/
|
||||
private function visitOuterObject(
|
||||
Parameter $model,
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
array &$context
|
||||
) {
|
||||
$parentLocation = $model->getLocation();
|
||||
|
||||
// If top-level additionalProperties is a schema, then visit it
|
||||
$additional = $model->getAdditionalProperties();
|
||||
if ($additional instanceof Parameter) {
|
||||
// Use the model location if none set on additionalProperties.
|
||||
$location = $additional->getLocation() ?: $parentLocation;
|
||||
$result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context);
|
||||
}
|
||||
|
||||
// Use 'location' from all individual defined properties, but fall back
|
||||
// to the model location if no per-property location is set. Collect
|
||||
// the properties that need to be visited into an array.
|
||||
$visitProperties = [];
|
||||
foreach ($model->getProperties() as $schema) {
|
||||
$location = $schema->getLocation() ?: $parentLocation;
|
||||
if ($location) {
|
||||
$visitProperties[] = [$location, $schema];
|
||||
// Trigger the before method on each unique visitor location
|
||||
if (!isset($context['visitors'][$location])) {
|
||||
$result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actually visit each response element
|
||||
foreach ($visitProperties as $property) {
|
||||
$result = $this->responseLocations[$property[0]]->visit($result, $response, $property[1]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the outer array
|
||||
*
|
||||
* @param Parameter $model
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param array $context
|
||||
* @return ResultInterface|void
|
||||
*/
|
||||
private function visitOuterArray(
|
||||
Parameter $model,
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
array &$context
|
||||
) {
|
||||
// Use 'location' defined on the top of the model
|
||||
if (!($location = $model->getLocation())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger the before method on each unique visitor location
|
||||
if (!isset($context['visitors'][$location])) {
|
||||
$result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context);
|
||||
}
|
||||
|
||||
// Visit each item in the response
|
||||
$result = $this->responseLocations[$location]->visit($result, $response, $model);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the "errorResponses" from commands, and trigger appropriate exceptions
|
||||
*
|
||||
* In order for the exception to be properly triggered, all your exceptions must be instance
|
||||
* of "GuzzleHttp\Command\Exception\CommandException". If that's not the case, your exceptions will be wrapped
|
||||
* around a CommandException
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @param CommandInterface $command
|
||||
* @param Operation $operation
|
||||
*/
|
||||
protected function handleErrorResponses(
|
||||
ResponseInterface $response,
|
||||
RequestInterface $request,
|
||||
CommandInterface $command,
|
||||
Operation $operation
|
||||
) {
|
||||
$errors = $operation->getErrorResponses();
|
||||
|
||||
// We iterate through each errors in service description. If the descriptor contains both a phrase and
|
||||
// status code, there must be an exact match of both. Otherwise, a match of status code is enough
|
||||
$bestException = null;
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$code = (int) $error['code'];
|
||||
|
||||
if ($response->getStatusCode() !== $code) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($error['phrase']) && ! ($error['phrase'] === $response->getReasonPhrase())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bestException = $error['class'];
|
||||
|
||||
// If there is an exact match of phrase + code, then we cannot find a more specialized exception in
|
||||
// the array, so we can break early instead of iterating the remaining ones
|
||||
if (isset($error['phrase'])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $bestException) {
|
||||
throw new $bestException($response->getReasonPhrase(), $command, null, $request, $response);
|
||||
}
|
||||
|
||||
// If we reach here, no exception could be match from descriptor, and Guzzle exception will propagate if
|
||||
// option "http_errors" is set to true, which is the default setting.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Handler\ValidatedDescriptionHandler;
|
||||
use GuzzleHttp\Command\ServiceClient;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
/**
|
||||
* Default Guzzle web service client implementation.
|
||||
*/
|
||||
class GuzzleClient extends ServiceClient
|
||||
{
|
||||
/** @var array $config */
|
||||
private $config;
|
||||
|
||||
/** @var DescriptionInterface Guzzle service description */
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* The client constructor accepts an associative array of configuration
|
||||
* options:
|
||||
*
|
||||
* - defaults: Associative array of default command parameters to add to
|
||||
* each command created by the client.
|
||||
* - validate: Specify if command input is validated (defaults to true).
|
||||
* Changing this setting after the client has been created will have no
|
||||
* effect.
|
||||
* - process: Specify if HTTP responses are parsed (defaults to true).
|
||||
* Changing this setting after the client has been created will have no
|
||||
* effect.
|
||||
* - response_locations: Associative array of location types mapping to
|
||||
* ResponseLocationInterface objects.
|
||||
*
|
||||
* @param ClientInterface $client HTTP client to use.
|
||||
* @param DescriptionInterface $description Guzzle service description
|
||||
* @param callable $commandToRequestTransformer
|
||||
* @param callable $responseToResultTransformer
|
||||
* @param HandlerStack $commandHandlerStack
|
||||
* @param array $config Configuration options
|
||||
*/
|
||||
public function __construct(
|
||||
ClientInterface $client,
|
||||
DescriptionInterface $description,
|
||||
callable $commandToRequestTransformer = null,
|
||||
callable $responseToResultTransformer = null,
|
||||
HandlerStack $commandHandlerStack = null,
|
||||
array $config = []
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->description = $description;
|
||||
$serializer = $this->getSerializer($commandToRequestTransformer);
|
||||
$deserializer = $this->getDeserializer($responseToResultTransformer);
|
||||
|
||||
parent::__construct($client, $serializer, $deserializer, $commandHandlerStack);
|
||||
$this->processConfig($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command if valid; otherwise an Exception
|
||||
* @param string $name
|
||||
* @param array $args
|
||||
* @return CommandInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getCommand($name, array $args = [])
|
||||
{
|
||||
if (!$this->description->hasOperation($name)) {
|
||||
$name = ucfirst($name);
|
||||
if (!$this->description->hasOperation($name)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"No operation found named {$name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in default command options
|
||||
$args += $this->getConfig('defaults');
|
||||
|
||||
return parent::getCommand($name, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description
|
||||
*
|
||||
* @return DescriptionInterface
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passed Serializer when set, a new instance otherwise
|
||||
*
|
||||
* @param callable|null $commandToRequestTransformer
|
||||
* @return \GuzzleHttp\Command\Guzzle\Serializer
|
||||
*/
|
||||
private function getSerializer($commandToRequestTransformer)
|
||||
{
|
||||
return $commandToRequestTransformer !== null
|
||||
? $commandToRequestTransformer
|
||||
: new Serializer($this->description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passed Deserializer when set, a new instance otherwise
|
||||
*
|
||||
* @param callable|null $responseToResultTransformer
|
||||
* @return \GuzzleHttp\Command\Guzzle\Deserializer
|
||||
*/
|
||||
private function getDeserializer($responseToResultTransformer)
|
||||
{
|
||||
$process = (! isset($this->config['process']) || $this->config['process'] === true);
|
||||
|
||||
return $responseToResultTransformer !== null
|
||||
? $responseToResultTransformer
|
||||
: new Deserializer($this->description, $process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config of the client
|
||||
*
|
||||
* @param array|string $option
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig($option = null)
|
||||
{
|
||||
return $option === null
|
||||
? $this->config
|
||||
: (isset($this->config[$option]) ? $this->config[$option] : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $option
|
||||
* @param $value
|
||||
*/
|
||||
public function setConfig($option, $value)
|
||||
{
|
||||
$this->config[$option] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the client based on the configuration settings of the client.
|
||||
*
|
||||
* @param array $config Constructor config as an array
|
||||
*/
|
||||
protected function processConfig(array $config)
|
||||
{
|
||||
// set defaults as an array if not provided
|
||||
if (!isset($config['defaults'])) {
|
||||
$config['defaults'] = [];
|
||||
}
|
||||
|
||||
// Add the handlers based on the configuration option
|
||||
$stack = $this->getHandlerStack();
|
||||
|
||||
if (!isset($config['validate']) || $config['validate'] === true) {
|
||||
$stack->push(new ValidatedDescriptionHandler($this->description), 'validate_description');
|
||||
}
|
||||
|
||||
if (!isset($config['process']) || $config['process'] === true) {
|
||||
// TODO: This belongs to the Deserializer and should be handled there.
|
||||
// Question: What is the result when the Deserializer is bypassed?
|
||||
// Possible answer: The raw response.
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php namespace GuzzleHttp\Command\Guzzle\Handler;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Exception\CommandException;
|
||||
use GuzzleHttp\Command\Guzzle\DescriptionInterface;
|
||||
use GuzzleHttp\Command\Guzzle\SchemaValidator;
|
||||
|
||||
/**
|
||||
* Handler used to validate command input against a service description.
|
||||
*
|
||||
* @author Stefano Kowalke <info@arroba-it.de>
|
||||
*/
|
||||
class ValidatedDescriptionHandler
|
||||
{
|
||||
/** @var SchemaValidator $validator */
|
||||
private $validator;
|
||||
|
||||
/** @var DescriptionInterface $description */
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* ValidatedDescriptionHandler constructor.
|
||||
*
|
||||
* @param DescriptionInterface $description
|
||||
* @param SchemaValidator|null $schemaValidator
|
||||
*/
|
||||
public function __construct(DescriptionInterface $description, SchemaValidator $schemaValidator = null)
|
||||
{
|
||||
$this->description = $description;
|
||||
$this->validator = $schemaValidator ?: new SchemaValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $handler
|
||||
* @return \Closure
|
||||
*/
|
||||
public function __invoke(callable $handler)
|
||||
{
|
||||
return function (CommandInterface $command) use ($handler) {
|
||||
$errors = [];
|
||||
$operation = $this->description->getOperation($command->getName());
|
||||
|
||||
foreach ($operation->getParams() as $name => $schema) {
|
||||
$value = $command[$name];
|
||||
|
||||
if ($value) {
|
||||
$value = $schema->filter($value);
|
||||
}
|
||||
|
||||
if (! $this->validator->validate($schema, $value)) {
|
||||
$errors = array_merge($errors, $this->validator->getErrors());
|
||||
} elseif ($value !== $command[$name]) {
|
||||
// Update the config value if it changed and no validation errors were encountered.
|
||||
// This happen when the user extending an operation
|
||||
// See https://github.com/guzzle/guzzle-services/issues/145
|
||||
$command[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($params = $operation->getAdditionalParameters()) {
|
||||
foreach ($command->toArray() as $name => $value) {
|
||||
// It's only additional if it isn't defined in the schema
|
||||
if (! $operation->hasParam($name)) {
|
||||
// Always set the name so that error messages are useful
|
||||
$params->setName($name);
|
||||
if (! $this->validator->validate($params, $value)) {
|
||||
$errors = array_merge($errors, $this->validator->getErrors());
|
||||
} elseif ($value !== $command[$name]) {
|
||||
$command[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
throw new CommandException('Validation errors: ' . implode("\n", $errors), $command);
|
||||
}
|
||||
|
||||
return $handler($command);
|
||||
};
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\Command\ToArrayInterface;
|
||||
|
||||
/**
|
||||
* Guzzle operation
|
||||
*/
|
||||
class Operation implements ToArrayInterface
|
||||
{
|
||||
/** @var array Parameters */
|
||||
private $parameters = [];
|
||||
|
||||
/** @var Parameter Additional parameters schema */
|
||||
private $additionalParameters;
|
||||
|
||||
/** @var DescriptionInterface */
|
||||
private $description;
|
||||
|
||||
/** @var array Config data */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Builds an Operation object using an array of configuration data.
|
||||
*
|
||||
* - name: (string) Name of the command
|
||||
* - httpMethod: (string) HTTP method of the operation
|
||||
* - uri: (string) URI template that can create a relative or absolute URL
|
||||
* - parameters: (array) Associative array of parameters for the command.
|
||||
* Each value must be an array that is used to create {@see Parameter}
|
||||
* objects.
|
||||
* - summary: (string) This is a short summary of what the operation does
|
||||
* - notes: (string) A longer description of the operation.
|
||||
* - documentationUrl: (string) Reference URL providing more information
|
||||
* about the operation.
|
||||
* - responseModel: (string) The model name used for processing response.
|
||||
* - deprecated: (bool) Set to true if this is a deprecated command
|
||||
* - errorResponses: (array) Errors that could occur when executing the
|
||||
* command. Array of hashes, each with a 'code' (the HTTP response code),
|
||||
* 'phrase' (response reason phrase or description of the error), and
|
||||
* 'class' (a custom exception class that would be thrown if the error is
|
||||
* encountered).
|
||||
* - data: (array) Any extra data that might be used to help build or
|
||||
* serialize the operation
|
||||
* - additionalParameters: (null|array) Parameter schema to use when an
|
||||
* option is passed to the operation that is not in the schema
|
||||
*
|
||||
* @param array $config Array of configuration data
|
||||
* @param DescriptionInterface $description Service description used to resolve models if $ref tags are found
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $config = [], DescriptionInterface $description = null)
|
||||
{
|
||||
static $defaults = [
|
||||
'name' => '',
|
||||
'httpMethod' => '',
|
||||
'uri' => '',
|
||||
'responseModel' => null,
|
||||
'notes' => '',
|
||||
'summary' => '',
|
||||
'documentationUrl' => null,
|
||||
'deprecated' => false,
|
||||
'data' => [],
|
||||
'parameters' => [],
|
||||
'additionalParameters' => null,
|
||||
'errorResponses' => []
|
||||
];
|
||||
|
||||
$this->description = $description === null ? new Description([]) : $description;
|
||||
|
||||
if (isset($config['extends'])) {
|
||||
$config = $this->resolveExtends($config['extends'], $config);
|
||||
}
|
||||
|
||||
$this->config = $config + $defaults;
|
||||
|
||||
// Account for the old style of using responseClass
|
||||
if (isset($config['responseClass'])) {
|
||||
$this->config['responseModel'] = $config['responseClass'];
|
||||
}
|
||||
|
||||
$this->resolveParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service description that the operation belongs to
|
||||
*
|
||||
* @return Description
|
||||
*/
|
||||
public function getServiceDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the params of the operation
|
||||
*
|
||||
* @return Parameter[]
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additionalParameters of the operation
|
||||
*
|
||||
* @return Parameter|null
|
||||
*/
|
||||
public function getAdditionalParameters()
|
||||
{
|
||||
return $this->additionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the operation has a specific parameter by name
|
||||
*
|
||||
* @param string $name Name of the param
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasParam($name)
|
||||
{
|
||||
return isset($this->parameters[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single parameter of the operation
|
||||
*
|
||||
* @param string $name Parameter to retrieve by name
|
||||
*
|
||||
* @return Parameter|null
|
||||
*/
|
||||
public function getParam($name)
|
||||
{
|
||||
return isset($this->parameters[$name])
|
||||
? $this->parameters[$name]
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP method of the operation
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHttpMethod()
|
||||
{
|
||||
return $this->config['httpMethod'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the operation
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->config['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a short summary of what the operation does
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
return $this->config['summary'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a longer text field to explain the behavior of the operation
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNotes()
|
||||
{
|
||||
return $this->config['notes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the documentation URL of the operation
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDocumentationUrl()
|
||||
{
|
||||
return $this->config['documentationUrl'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the model used for processing the response.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getResponseModel()
|
||||
{
|
||||
return $this->config['responseModel'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not the operation is deprecated
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getDeprecated()
|
||||
{
|
||||
return $this->config['deprecated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URI that will be merged into the generated request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUri()
|
||||
{
|
||||
return $this->config['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the errors that could be encountered when executing the operation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrorResponses()
|
||||
{
|
||||
return $this->config['errorResponses'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra data from the operation
|
||||
*
|
||||
* @param string $name Name of the data point to retrieve or null to
|
||||
* retrieve all of the extra data.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getData($name = null)
|
||||
{
|
||||
if ($name === null) {
|
||||
return $this->config['data'];
|
||||
} elseif (isset($this->config['data'][$name])) {
|
||||
return $this->config['data'][$name];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
private function resolveExtends($name, array $config)
|
||||
{
|
||||
if (!$this->description->hasOperation($name)) {
|
||||
throw new \InvalidArgumentException('No operation named ' . $name);
|
||||
}
|
||||
|
||||
// Merge parameters together one level deep
|
||||
$base = $this->description->getOperation($name)->toArray();
|
||||
$result = $config + $base;
|
||||
|
||||
if (isset($base['parameters']) && isset($config['parameters'])) {
|
||||
$result['parameters'] = $config['parameters'] + $base['parameters'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the description and extract the parameter config
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function resolveParameters()
|
||||
{
|
||||
// Parameters need special handling when adding
|
||||
foreach ($this->config['parameters'] as $name => $param) {
|
||||
if (!is_array($param)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Parameters must be arrays, {$this->config['name']}.$name is ".gettype($param)
|
||||
);
|
||||
}
|
||||
$param['name'] = $name;
|
||||
$this->parameters[$name] = new Parameter(
|
||||
$param,
|
||||
['description' => $this->description]
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->config['additionalParameters']) {
|
||||
if (is_array($this->config['additionalParameters'])) {
|
||||
$this->additionalParameters = new Parameter(
|
||||
$this->config['additionalParameters'],
|
||||
['description' => $this->description]
|
||||
);
|
||||
} else {
|
||||
$this->additionalParameters = $this->config['additionalParameters'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+655
@@ -0,0 +1,655 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\Command\ToArrayInterface;
|
||||
|
||||
/**
|
||||
* API parameter object used with service descriptions
|
||||
*/
|
||||
class Parameter implements ToArrayInterface
|
||||
{
|
||||
private $originalData;
|
||||
|
||||
/** @var string $name */
|
||||
private $name;
|
||||
|
||||
/** @var string $description */
|
||||
private $description;
|
||||
|
||||
/** @var string|array $type */
|
||||
private $type;
|
||||
|
||||
/** @var bool $required*/
|
||||
private $required;
|
||||
|
||||
/** @var array|null $enum */
|
||||
private $enum;
|
||||
|
||||
/** @var string $pattern */
|
||||
private $pattern;
|
||||
|
||||
/** @var int $minimum*/
|
||||
private $minimum;
|
||||
|
||||
/** @var int $maximum */
|
||||
private $maximum;
|
||||
|
||||
/** @var int $minLength */
|
||||
private $minLength;
|
||||
|
||||
/** @var int $maxLength */
|
||||
private $maxLength;
|
||||
|
||||
/** @var int $minItems */
|
||||
private $minItems;
|
||||
|
||||
/** @var int $maxItems */
|
||||
private $maxItems;
|
||||
|
||||
/** @var mixed $default */
|
||||
private $default;
|
||||
|
||||
/** @var bool $static */
|
||||
private $static;
|
||||
|
||||
/** @var array $filters */
|
||||
private $filters;
|
||||
|
||||
/** @var string $location */
|
||||
private $location;
|
||||
|
||||
/** @var string $sentAs */
|
||||
private $sentAs;
|
||||
|
||||
/** @var array $data */
|
||||
private $data;
|
||||
|
||||
/** @var array $properties */
|
||||
private $properties = [];
|
||||
|
||||
/** @var array|bool|Parameter $additionalProperties */
|
||||
private $additionalProperties;
|
||||
|
||||
/** @var array|Parameter $items */
|
||||
private $items;
|
||||
|
||||
/** @var string $format */
|
||||
private $format;
|
||||
|
||||
private $propertiesCache = null;
|
||||
|
||||
/** @var Description */
|
||||
private $serviceDescription;
|
||||
|
||||
/**
|
||||
* Create a new Parameter using an associative array of data.
|
||||
*
|
||||
* The array can contain the following information:
|
||||
*
|
||||
* - name: (string) Unique name of the parameter
|
||||
*
|
||||
* - type: (string|array) Type of variable (string, number, integer,
|
||||
* boolean, object, array, numeric, null, any). Types are used for
|
||||
* validation and determining the structure of a parameter. You can use a
|
||||
* union type by providing an array of simple types. If one of the union
|
||||
* types matches the provided value, then the value is valid.
|
||||
*
|
||||
* - required: (bool) Whether or not the parameter is required
|
||||
*
|
||||
* - default: (mixed) Default value to use if no value is supplied
|
||||
*
|
||||
* - static: (bool) Set to true to specify that the parameter value cannot
|
||||
* be changed from the default.
|
||||
*
|
||||
* - description: (string) Documentation of the parameter
|
||||
*
|
||||
* - location: (string) The location of a request used to apply a parameter.
|
||||
* Custom locations can be registered with a command, but the defaults
|
||||
* are uri, query, header, body, json, xml, formParam, multipart.
|
||||
*
|
||||
* - sentAs: (string) Specifies how the data being modeled is sent over the
|
||||
* wire. For example, you may wish to include certain headers in a
|
||||
* response model that have a normalized casing of FooBar, but the actual
|
||||
* header is x-foo-bar. In this case, sentAs would be set to x-foo-bar.
|
||||
*
|
||||
* - filters: (array) Array of static method names to run a parameter
|
||||
* value through. Each value in the array must be a string containing the
|
||||
* full class path to a static method or an array of complex filter
|
||||
* information. You can specify static methods of classes using the full
|
||||
* namespace class name followed by '::' (e.g. Foo\Bar::baz). Some
|
||||
* filters require arguments in order to properly filter a value. For
|
||||
* complex filters, use a hash containing a 'method' key pointing to a
|
||||
* static method, and an 'args' key containing an array of positional
|
||||
* arguments to pass to the method. Arguments can contain keywords that
|
||||
* are replaced when filtering a value: '@value' is replaced with the
|
||||
* value being validated, '@api' is replaced with the Parameter object.
|
||||
*
|
||||
* - properties: When the type is an object, you can specify nested parameters
|
||||
*
|
||||
* - additionalProperties: (array) This attribute defines a schema for all
|
||||
* properties that are not explicitly defined in an object type
|
||||
* definition. If specified, the value MUST be a schema or a boolean. If
|
||||
* false is provided, no additional properties are allowed beyond the
|
||||
* properties defined in the schema. The default value is an empty schema
|
||||
* which allows any value for additional properties.
|
||||
*
|
||||
* - items: This attribute defines the allowed items in an instance array,
|
||||
* and MUST be a schema or an array of schemas. The default value is an
|
||||
* empty schema which allows any value for items in the instance array.
|
||||
* When this attribute value is a schema and the instance value is an
|
||||
* array, then all the items in the array MUST be valid according to the
|
||||
* schema.
|
||||
*
|
||||
* - pattern: When the type is a string, you can specify the regex pattern
|
||||
* that a value must match
|
||||
*
|
||||
* - enum: When the type is a string, you can specify a list of acceptable
|
||||
* values.
|
||||
*
|
||||
* - minItems: (int) Minimum number of items allowed in an array
|
||||
*
|
||||
* - maxItems: (int) Maximum number of items allowed in an array
|
||||
*
|
||||
* - minLength: (int) Minimum length of a string
|
||||
*
|
||||
* - maxLength: (int) Maximum length of a string
|
||||
*
|
||||
* - minimum: (int) Minimum value of an integer
|
||||
*
|
||||
* - maximum: (int) Maximum value of an integer
|
||||
*
|
||||
* - data: (array) Any additional custom data to use when serializing,
|
||||
* validating, etc
|
||||
*
|
||||
* - format: (string) Format used to coax a value into the correct format
|
||||
* when serializing or unserializing. You may specify either an array of
|
||||
* filters OR a format, but not both. Supported values: date-time, date,
|
||||
* time, timestamp, date-time-http, and boolean-string.
|
||||
*
|
||||
* - $ref: (string) String referencing a service description model. The
|
||||
* parameter is replaced by the schema contained in the model.
|
||||
*
|
||||
* @param array $data Array of data as seen in service descriptions
|
||||
* @param array $options Options used when creating the parameter. You can
|
||||
* specify a Guzzle service description in the 'description' key.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $data = [], array $options = [])
|
||||
{
|
||||
$this->originalData = $data;
|
||||
|
||||
if (isset($options['description'])) {
|
||||
$this->serviceDescription = $options['description'];
|
||||
if (!($this->serviceDescription instanceof DescriptionInterface)) {
|
||||
throw new \InvalidArgumentException('description must be a Description');
|
||||
}
|
||||
if (isset($data['$ref'])) {
|
||||
if ($model = $this->serviceDescription->getModel($data['$ref'])) {
|
||||
$name = isset($data['name']) ? $data['name'] : null;
|
||||
$data = $model->toArray() + $data;
|
||||
if ($name) {
|
||||
$data['name'] = $name;
|
||||
}
|
||||
}
|
||||
} elseif (isset($data['extends'])) {
|
||||
// If this parameter extends from another parameter then start
|
||||
// with the actual data union in the parent's data (e.g. actual
|
||||
// supersedes parent)
|
||||
if ($extends = $this->serviceDescription->getModel($data['extends'])) {
|
||||
$data += $extends->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pull configuration data into the parameter
|
||||
foreach ($data as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
||||
$this->required = (bool) $this->required;
|
||||
$this->data = (array) $this->data;
|
||||
|
||||
if ($this->filters) {
|
||||
$this->setFilters((array) $this->filters);
|
||||
}
|
||||
|
||||
if ($this->type == 'object' && $this->additionalProperties === null) {
|
||||
$this->additionalProperties = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->originalData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default or static value of the command based on a value
|
||||
*
|
||||
* @param string $value Value that is currently set
|
||||
*
|
||||
* @return mixed Returns the value, a static value if one is present, or a default value
|
||||
*/
|
||||
public function getValue($value)
|
||||
{
|
||||
if ($this->static || ($this->default !== null && $value === null)) {
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a value through the filters OR format attribute associated with the
|
||||
* parameter.
|
||||
*
|
||||
* @param mixed $value Value to filter
|
||||
*
|
||||
* @return mixed Returns the filtered value
|
||||
* @throws \RuntimeException when trying to format when no service
|
||||
* description is available.
|
||||
*/
|
||||
public function filter($value)
|
||||
{
|
||||
// Formats are applied exclusively and supersed filters
|
||||
if ($this->format) {
|
||||
if (!$this->serviceDescription) {
|
||||
throw new \RuntimeException('No service description was set so '
|
||||
. 'the value cannot be formatted.');
|
||||
}
|
||||
return $this->serviceDescription->format($this->format, $value);
|
||||
}
|
||||
|
||||
// Convert Boolean values
|
||||
if ($this->type == 'boolean' && !is_bool($value)) {
|
||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
// Apply filters to the value
|
||||
if ($this->filters) {
|
||||
foreach ($this->filters as $filter) {
|
||||
if (is_array($filter)) {
|
||||
// Convert complex filters that hold value place holders
|
||||
foreach ($filter['args'] as &$data) {
|
||||
if ($data == '@value') {
|
||||
$data = $value;
|
||||
} elseif ($data == '@api') {
|
||||
$data = $this;
|
||||
}
|
||||
}
|
||||
$value = call_user_func_array(
|
||||
$filter['method'],
|
||||
$filter['args']
|
||||
);
|
||||
} else {
|
||||
$value = call_user_func($filter, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the parameter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the parameter
|
||||
*
|
||||
* @param string $name Name to set
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key of the parameter, where sentAs will supersede name if it is
|
||||
* set.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getWireName()
|
||||
{
|
||||
return $this->sentAs ?: $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type(s) of the parameter
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the parameter is required
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired()
|
||||
{
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default value of the parameter
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDefault()
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the parameter
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum acceptable value for an integer
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMinimum()
|
||||
{
|
||||
return $this->minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum acceptable value for an integer
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaximum()
|
||||
{
|
||||
return $this->maximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum allowed length of a string value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMinLength()
|
||||
{
|
||||
return $this->minLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum allowed length of a string value
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaxLength()
|
||||
{
|
||||
return $this->maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum allowed number of items in an array value
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaxItems()
|
||||
{
|
||||
return $this->maxItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum allowed number of items in an array value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMinItems()
|
||||
{
|
||||
return $this->minItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the parameter
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLocation()
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sentAs attribute of the parameter that used with locations to
|
||||
* sentAs an attribute when it is being applied to a location.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSentAs()
|
||||
{
|
||||
return $this->sentAs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a known property from the parameter by name or a data property
|
||||
* by name. When no specific name value is passed, all data properties
|
||||
* will be returned.
|
||||
*
|
||||
* @param string|null $name Specify a particular property name to retrieve
|
||||
*
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
public function getData($name = null)
|
||||
{
|
||||
if (!$name) {
|
||||
return $this->data;
|
||||
} elseif (isset($this->data[$name])) {
|
||||
return $this->data[$name];
|
||||
} elseif (isset($this->{$name})) {
|
||||
return $this->{$name};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not the default value can be changed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isStatic()
|
||||
{
|
||||
return $this->static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of filters used by the parameter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the properties of the parameter
|
||||
*
|
||||
* @return Parameter[]
|
||||
*/
|
||||
public function getProperties()
|
||||
{
|
||||
if (!$this->propertiesCache) {
|
||||
$this->propertiesCache = [];
|
||||
foreach (array_keys($this->properties) as $name) {
|
||||
$this->propertiesCache[$name] = $this->getProperty($name);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->propertiesCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific property from the parameter
|
||||
*
|
||||
* @param string $name Name of the property to retrieve
|
||||
*
|
||||
* @return null|Parameter
|
||||
*/
|
||||
public function getProperty($name)
|
||||
{
|
||||
if (!isset($this->properties[$name])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!($this->properties[$name] instanceof self)) {
|
||||
$this->properties[$name]['name'] = $name;
|
||||
$this->properties[$name] = new static(
|
||||
$this->properties[$name],
|
||||
['description' => $this->serviceDescription]
|
||||
);
|
||||
}
|
||||
|
||||
return $this->properties[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additionalProperties value of the parameter
|
||||
*
|
||||
* @return bool|Parameter|null
|
||||
*/
|
||||
public function getAdditionalProperties()
|
||||
{
|
||||
if (is_array($this->additionalProperties)) {
|
||||
$this->additionalProperties = new static(
|
||||
$this->additionalProperties,
|
||||
['description' => $this->serviceDescription]
|
||||
);
|
||||
}
|
||||
|
||||
return $this->additionalProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item data of the parameter
|
||||
*
|
||||
* @return Parameter
|
||||
*/
|
||||
public function getItems()
|
||||
{
|
||||
if (is_array($this->items)) {
|
||||
$this->items = new static(
|
||||
$this->items,
|
||||
['description' => $this->serviceDescription]
|
||||
);
|
||||
}
|
||||
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enum of strings that are valid for the parameter
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getEnum()
|
||||
{
|
||||
return $this->enum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regex pattern that must match a value when the value is a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPattern()
|
||||
{
|
||||
return $this->pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format attribute of the schema
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormat()
|
||||
{
|
||||
return $this->format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the array of filters used by the parameter
|
||||
*
|
||||
* @param array $filters Array of functions to use as filters
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setFilters(array $filters)
|
||||
{
|
||||
$this->filters = [];
|
||||
foreach ($filters as $filter) {
|
||||
$this->addFilter($filter);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to the parameter
|
||||
*
|
||||
* @param string|array $filter Method to filter the value through
|
||||
*
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function addFilter($filter)
|
||||
{
|
||||
if (is_array($filter)) {
|
||||
if (!isset($filter['method'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
'A [method] value must be specified for each complex filter'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->filters) {
|
||||
$this->filters = [$filter];
|
||||
} else {
|
||||
$this->filters[] = $filter;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a parameter has a specific variable and if it set.
|
||||
*
|
||||
* @param string $var
|
||||
* @return bool
|
||||
*/
|
||||
public function has($var)
|
||||
{
|
||||
if (!is_string($var)) {
|
||||
throw new \InvalidArgumentException('Expected a string. Got: ' . (is_object($var) ? get_class($var) : gettype($var)));
|
||||
}
|
||||
return isset($this->{$var}) && !empty($this->{$var});
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\QuerySerializer;
|
||||
|
||||
interface QuerySerializerInterface
|
||||
{
|
||||
/**
|
||||
* Aggregate query params and transform them into a string
|
||||
*
|
||||
* @param array $queryParams
|
||||
* @return string
|
||||
*/
|
||||
public function aggregate(array $queryParams);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Command\Guzzle\QuerySerializer;
|
||||
|
||||
class Rfc3986Serializer implements QuerySerializerInterface
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $removeNumericIndices;
|
||||
|
||||
/**
|
||||
* @param bool $removeNumericIndices
|
||||
*/
|
||||
public function __construct($removeNumericIndices = false)
|
||||
{
|
||||
$this->removeNumericIndices = $removeNumericIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function aggregate(array $queryParams)
|
||||
{
|
||||
$queryString = http_build_query($queryParams, null, '&', PHP_QUERY_RFC3986);
|
||||
|
||||
if ($this->removeNumericIndices) {
|
||||
$queryString = preg_replace('/%5B[0-9]+%5D/simU', '%5B%5D', $queryString);
|
||||
}
|
||||
|
||||
return $queryString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
abstract class AbstractLocation implements RequestLocationInterface
|
||||
{
|
||||
/** @var string */
|
||||
protected $locationName;
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param $locationName
|
||||
*/
|
||||
public function __construct($locationName)
|
||||
{
|
||||
$this->locationName = $locationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Operation $operation
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare (filter and set desired name for request item) the value for
|
||||
* request.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
protected function prepareValue($value, Parameter $param)
|
||||
{
|
||||
return is_array($value)
|
||||
? $this->resolveRecursively($value, $param)
|
||||
: $param->filter($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively prepare and filter nested values.
|
||||
*
|
||||
* @param array $value Value to map
|
||||
* @param Parameter $param Parameter related to the current key.
|
||||
*
|
||||
* @return array Returns the mapped array
|
||||
*/
|
||||
protected function resolveRecursively(array $value, Parameter $param)
|
||||
{
|
||||
foreach ($value as $name => &$v) {
|
||||
switch ($param->getType()) {
|
||||
case 'object':
|
||||
if ($subParam = $param->getProperty($name)) {
|
||||
$key = $subParam->getWireName();
|
||||
$value[$key] = $this->prepareValue($v, $subParam);
|
||||
if ($name != $key) {
|
||||
unset($value[$name]);
|
||||
}
|
||||
} elseif ($param->getAdditionalProperties() instanceof Parameter) {
|
||||
$v = $this->prepareValue($v, $param->getAdditionalProperties());
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if ($items = $param->getItems()) {
|
||||
$v = $this->prepareValue($v, $items);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $param->filter($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Adds a body to a request
|
||||
*/
|
||||
class BodyLocation extends AbstractLocation
|
||||
{
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'body')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return MessageInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
$oldValue = $request->getBody()->getContents();
|
||||
|
||||
$value = $command[$param->getName()];
|
||||
$value = $param->getName() . '=' . $param->filter($value);
|
||||
|
||||
if ($oldValue !== '') {
|
||||
$value = $oldValue . '&' . $value;
|
||||
}
|
||||
|
||||
return $request->withBody(Psr7\Utils::streamFor($value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Add form_params to a request
|
||||
*/
|
||||
class FormParamLocation extends AbstractLocation
|
||||
{
|
||||
/** @var string $contentType */
|
||||
protected $contentType = 'application/x-www-form-urlencoded; charset=utf-8';
|
||||
|
||||
/** @var array $formParamsData */
|
||||
protected $formParamsData = [];
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'formParam')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
$this->formParamsData['form_params'][$param->getWireName()] = $this->prepareValue(
|
||||
$command[$param->getName()],
|
||||
$param
|
||||
);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Operation $operation
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
) {
|
||||
$data = $this->formParamsData;
|
||||
$this->formParamsData = [];
|
||||
$modify = [];
|
||||
|
||||
// Add additional parameters to the form_params array
|
||||
$additional = $operation->getAdditionalParameters();
|
||||
if ($additional && $additional->getLocation() == $this->locationName) {
|
||||
foreach ($command->toArray() as $key => $value) {
|
||||
if (!$operation->hasParam($key)) {
|
||||
$data['form_params'][$key] = $this->prepareValue($value, $additional);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$body = http_build_query($data['form_params'], '', '&');
|
||||
$modify['body'] = Psr7\Utils::streamFor($body);
|
||||
$modify['set_headers']['Content-Type'] = $this->contentType;
|
||||
|
||||
return Psr7\Utils::modifyRequest($request, $modify);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Request header location
|
||||
*/
|
||||
class HeaderLocation extends AbstractLocation
|
||||
{
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'header')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return MessageInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
$value = $command[$param->getName()];
|
||||
|
||||
return $request->withHeader($param->getWireName(), $param->filter($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Operation $operation
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
) {
|
||||
/** @var Parameter $additional */
|
||||
$additional = $operation->getAdditionalParameters();
|
||||
if ($additional && ($additional->getLocation() === $this->locationName)) {
|
||||
foreach ($command->toArray() as $key => $value) {
|
||||
if (!$operation->hasParam($key)) {
|
||||
$request = $request->withHeader($key, $additional->filter($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Creates a JSON document
|
||||
*/
|
||||
class JsonLocation extends AbstractLocation
|
||||
{
|
||||
/** @var string Whether or not to add a Content-Type header when JSON is found */
|
||||
private $jsonContentType;
|
||||
|
||||
/** @var array */
|
||||
private $jsonData;
|
||||
|
||||
/**
|
||||
* @param string $locationName Name of the location
|
||||
* @param string $contentType Content-Type header to add to the request if
|
||||
* JSON is added to the body. Pass an empty string to omit.
|
||||
*/
|
||||
public function __construct($locationName = 'json', $contentType = 'application/json')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
$this->jsonContentType = $contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
$this->jsonData[$param->getWireName()] = $this->prepareValue(
|
||||
$command[$param->getName()],
|
||||
$param
|
||||
);
|
||||
|
||||
return $request->withBody(Psr7\Utils::streamFor(Utils::jsonEncode($this->jsonData)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Operation $operation
|
||||
*
|
||||
* @return MessageInterface
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
) {
|
||||
$data = $this->jsonData;
|
||||
$this->jsonData = [];
|
||||
|
||||
// Add additional parameters to the JSON document
|
||||
$additional = $operation->getAdditionalParameters();
|
||||
if ($additional && ($additional->getLocation() === $this->locationName)) {
|
||||
foreach ($command->toArray() as $key => $value) {
|
||||
if (!$operation->hasParam($key)) {
|
||||
$data[$key] = $this->prepareValue($value, $additional);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't overwrite the Content-Type if one is set
|
||||
if ($this->jsonContentType && !$request->hasHeader('Content-Type')) {
|
||||
$request = $request->withHeader('Content-Type', $this->jsonContentType);
|
||||
}
|
||||
|
||||
return $request->withBody(Psr7\Utils::streamFor(Utils::jsonEncode($data)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Adds POST files to a request
|
||||
*/
|
||||
class MultiPartLocation extends AbstractLocation
|
||||
{
|
||||
/** @var string $contentType */
|
||||
protected $contentType = 'multipart/form-data; boundary=';
|
||||
|
||||
/** @var array $formParamsData */
|
||||
protected $multipartData = [];
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'multipart')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
$this->multipartData[] = [
|
||||
'name' => $param->getWireName(),
|
||||
'contents' => $this->prepareValue($command[$param->getName()], $param)
|
||||
];
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Operation $operation
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
) {
|
||||
$data = $this->multipartData;
|
||||
$this->multipartData = [];
|
||||
$modify = [];
|
||||
|
||||
$body = new Psr7\MultipartStream($data);
|
||||
$modify['body'] = Psr7\Utils::streamFor($body);
|
||||
$request = Psr7\Utils::modifyRequest($request, $modify);
|
||||
if ($request->getBody() instanceof Psr7\MultipartStream) {
|
||||
// Use a multipart/form-data POST if a Content-Type is not set.
|
||||
$request->withHeader('Content-Type', $this->contentType . $request->getBody()->getBoundary());
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\Guzzle\QuerySerializer\QuerySerializerInterface;
|
||||
use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Adds query string values to requests
|
||||
*/
|
||||
class QueryLocation extends AbstractLocation
|
||||
{
|
||||
/**
|
||||
* @var QuerySerializerInterface
|
||||
*/
|
||||
private $querySerializer;
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
* @param QuerySerializerInterface|null $querySerializer
|
||||
*/
|
||||
public function __construct($locationName = 'query', QuerySerializerInterface $querySerializer = null)
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
|
||||
$this->querySerializer = $querySerializer ?: new Rfc3986Serializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
$uri = $request->getUri();
|
||||
$query = Psr7\Query::parse($uri->getQuery());
|
||||
|
||||
$query[$param->getWireName()] = $this->prepareValue(
|
||||
$command[$param->getName()],
|
||||
$param
|
||||
);
|
||||
|
||||
$uri = $uri->withQuery($this->querySerializer->aggregate($query));
|
||||
|
||||
return $request->withUri($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Operation $operation
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
) {
|
||||
$additional = $operation->getAdditionalParameters();
|
||||
if ($additional && $additional->getLocation() == $this->locationName) {
|
||||
foreach ($command->toArray() as $key => $value) {
|
||||
if (!$operation->hasParam($key)) {
|
||||
$uri = $request->getUri();
|
||||
$query = Psr7\Query::parse($uri->getQuery());
|
||||
|
||||
$query[$key] = $this->prepareValue(
|
||||
$value,
|
||||
$additional
|
||||
);
|
||||
|
||||
$uri = $uri->withQuery($this->querySerializer->aggregate($query));
|
||||
$request = $request->withUri($uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Handles locations specified in a service description
|
||||
*/
|
||||
interface RequestLocationInterface
|
||||
{
|
||||
/**
|
||||
* Visits a location for each top-level parameter
|
||||
*
|
||||
* @param CommandInterface $command Command being prepared
|
||||
* @param RequestInterface $request Request being modified
|
||||
* @param Parameter $param Parameter being visited
|
||||
*
|
||||
* @return RequestInterface Modified request
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
);
|
||||
|
||||
/**
|
||||
* Called when all of the parameters of a command have been visited.
|
||||
*
|
||||
* @param CommandInterface $command Command being prepared
|
||||
* @param RequestInterface $request Request being modified
|
||||
* @param Operation $operation Operation being serialized
|
||||
*
|
||||
* @return RequestInterface Modified request
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\Operation;
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Creates an XML document
|
||||
*/
|
||||
class XmlLocation extends AbstractLocation
|
||||
{
|
||||
/** @var \XMLWriter XML writer resource */
|
||||
private $writer;
|
||||
|
||||
/** @var string Content-Type header added when XML is found */
|
||||
private $contentType;
|
||||
|
||||
/** @var Parameter[] Buffered elements to write */
|
||||
private $buffered = [];
|
||||
|
||||
/**
|
||||
* @param string $locationName Name of the location
|
||||
* @param string $contentType Set to a non-empty string to add a
|
||||
* Content-Type header to a request if any XML content is added to the
|
||||
* body. Pass an empty string to disable the addition of the header.
|
||||
*/
|
||||
public function __construct($locationName = 'xml', $contentType = 'application/xml')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
$this->contentType = $contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function visit(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Parameter $param
|
||||
) {
|
||||
// Buffer and order the parameters to visit based on if they are
|
||||
// top-level attributes or child nodes.
|
||||
// @link https://github.com/guzzle/guzzle/pull/494
|
||||
if ($param->getData('xmlAttribute')) {
|
||||
array_unshift($this->buffered, $param);
|
||||
} else {
|
||||
$this->buffered[] = $param;
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request
|
||||
* @param Operation $operation
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function after(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request,
|
||||
Operation $operation
|
||||
) {
|
||||
foreach ($this->buffered as $param) {
|
||||
$this->visitWithValue(
|
||||
$command[$param->getName()],
|
||||
$param,
|
||||
$operation
|
||||
);
|
||||
}
|
||||
|
||||
$this->buffered = [];
|
||||
|
||||
$additional = $operation->getAdditionalParameters();
|
||||
if ($additional && $additional->getLocation() == $this->locationName) {
|
||||
foreach ($command->toArray() as $key => $value) {
|
||||
if (!$operation->hasParam($key)) {
|
||||
$additional->setName($key);
|
||||
$this->visitWithValue($value, $additional, $operation);
|
||||
}
|
||||
}
|
||||
$additional->setName(null);
|
||||
}
|
||||
|
||||
// If data was found that needs to be serialized, then do so
|
||||
$xml = '';
|
||||
if ($this->writer) {
|
||||
$xml = $this->finishDocument($this->writer);
|
||||
} elseif ($operation->getData('xmlAllowEmpty')) {
|
||||
// Check if XML should always be sent for the command
|
||||
$writer = $this->createRootElement($operation);
|
||||
$xml = $this->finishDocument($writer);
|
||||
}
|
||||
|
||||
if ($xml !== '') {
|
||||
$request = $request->withBody(Psr7\Utils::streamFor($xml));
|
||||
// Don't overwrite the Content-Type if one is set
|
||||
if ($this->contentType && !$request->hasHeader('Content-Type')) {
|
||||
$request = $request->withHeader('Content-Type', $this->contentType);
|
||||
}
|
||||
}
|
||||
|
||||
$this->writer = null;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the root XML element to use with a request
|
||||
*
|
||||
* @param Operation $operation Operation object
|
||||
*
|
||||
* @return \XMLWriter
|
||||
*/
|
||||
protected function createRootElement(Operation $operation)
|
||||
{
|
||||
static $defaultRoot = ['name' => 'Request'];
|
||||
// If no root element was specified, then just wrap the XML in 'Request'
|
||||
$root = $operation->getData('xmlRoot') ?: $defaultRoot;
|
||||
// Allow the XML declaration to be customized with xmlEncoding
|
||||
$encoding = $operation->getData('xmlEncoding');
|
||||
$writer = $this->startDocument($encoding);
|
||||
$writer->startElement($root['name']);
|
||||
|
||||
// Create the wrapping element with no namespaces if no namespaces were present
|
||||
if (!empty($root['namespaces'])) {
|
||||
// Create the wrapping element with an array of one or more namespaces
|
||||
foreach ((array) $root['namespaces'] as $prefix => $uri) {
|
||||
$nsLabel = 'xmlns';
|
||||
if (!is_numeric($prefix)) {
|
||||
$nsLabel .= ':'.$prefix;
|
||||
}
|
||||
$writer->writeAttribute($nsLabel, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
return $writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively build the XML body
|
||||
*
|
||||
* @param \XMLWriter $writer XML to modify
|
||||
* @param Parameter $param API Parameter
|
||||
* @param mixed $value Value to add
|
||||
*/
|
||||
protected function addXml(\XMLWriter $writer, Parameter $param, $value)
|
||||
{
|
||||
$value = $param->filter($value);
|
||||
$type = $param->getType();
|
||||
$name = $param->getWireName();
|
||||
$prefix = null;
|
||||
$namespace = $param->getData('xmlNamespace');
|
||||
if (false !== strpos($name, ':')) {
|
||||
list($prefix, $name) = explode(':', $name, 2);
|
||||
}
|
||||
|
||||
if ($type == 'object' || $type == 'array') {
|
||||
if (!$param->getData('xmlFlattened')) {
|
||||
if ($namespace) {
|
||||
$writer->startElementNS(null, $name, $namespace);
|
||||
} else {
|
||||
$writer->startElement($name);
|
||||
}
|
||||
}
|
||||
if ($param->getType() == 'array') {
|
||||
$this->addXmlArray($writer, $param, $value);
|
||||
} elseif ($param->getType() == 'object') {
|
||||
$this->addXmlObject($writer, $param, $value);
|
||||
}
|
||||
if (!$param->getData('xmlFlattened')) {
|
||||
$writer->endElement();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ($param->getData('xmlAttribute')) {
|
||||
$this->writeAttribute($writer, $prefix, $name, $namespace, $value);
|
||||
} else {
|
||||
$this->writeElement($writer, $prefix, $name, $namespace, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an attribute with namespace if used
|
||||
*
|
||||
* @param \XMLWriter $writer XMLWriter instance
|
||||
* @param string $prefix Namespace prefix if any
|
||||
* @param string $name Attribute name
|
||||
* @param string $namespace The uri of the namespace
|
||||
* @param string $value The attribute content
|
||||
*/
|
||||
protected function writeAttribute($writer, $prefix, $name, $namespace, $value)
|
||||
{
|
||||
if ($namespace) {
|
||||
$writer->writeAttributeNS($prefix, $name, $namespace, $value);
|
||||
} else {
|
||||
$writer->writeAttribute($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an element with namespace if used
|
||||
*
|
||||
* @param \XMLWriter $writer XML writer resource
|
||||
* @param string $prefix Namespace prefix if any
|
||||
* @param string $name Element name
|
||||
* @param string $namespace The uri of the namespace
|
||||
* @param string $value The element content
|
||||
*/
|
||||
protected function writeElement(\XMLWriter $writer, $prefix, $name, $namespace, $value)
|
||||
{
|
||||
if ($namespace) {
|
||||
$writer->startElementNS($prefix, $name, $namespace);
|
||||
} else {
|
||||
$writer->startElement($name);
|
||||
}
|
||||
if (strpbrk($value, '<>&')) {
|
||||
$writer->writeCData($value);
|
||||
} else {
|
||||
$writer->writeRaw($value);
|
||||
}
|
||||
$writer->endElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new xml writer and start a document
|
||||
*
|
||||
* @param string $encoding document encoding
|
||||
*
|
||||
* @return \XMLWriter the writer resource
|
||||
* @throws \RuntimeException if the document cannot be started
|
||||
*/
|
||||
protected function startDocument($encoding)
|
||||
{
|
||||
$this->writer = new \XMLWriter();
|
||||
if (!$this->writer->openMemory()) {
|
||||
throw new \RuntimeException('Unable to open XML document in memory');
|
||||
}
|
||||
if (!$this->writer->startDocument('1.0', $encoding)) {
|
||||
throw new \RuntimeException('Unable to start XML document');
|
||||
}
|
||||
|
||||
return $this->writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* End the document and return the output
|
||||
*
|
||||
* @param \XMLWriter $writer
|
||||
*
|
||||
* @return string the writer resource
|
||||
*/
|
||||
protected function finishDocument($writer)
|
||||
{
|
||||
$writer->endDocument();
|
||||
|
||||
return $writer->outputMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array to the XML
|
||||
*
|
||||
* @param \XMLWriter $writer
|
||||
* @param Parameter $param
|
||||
* @param $value
|
||||
*/
|
||||
protected function addXmlArray(\XMLWriter $writer, Parameter $param, &$value)
|
||||
{
|
||||
if ($items = $param->getItems()) {
|
||||
foreach ($value as $v) {
|
||||
$this->addXml($writer, $items, $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the XML
|
||||
*
|
||||
* @param \XMLWriter $writer
|
||||
* @param Parameter $param
|
||||
* @param $value
|
||||
*/
|
||||
protected function addXmlObject(\XMLWriter $writer, Parameter $param, &$value)
|
||||
{
|
||||
$noAttributes = [];
|
||||
|
||||
// add values which have attributes
|
||||
foreach ($value as $name => $v) {
|
||||
if ($property = $param->getProperty($name)) {
|
||||
if ($property->getData('xmlAttribute')) {
|
||||
$this->addXml($writer, $property, $v);
|
||||
} else {
|
||||
$noAttributes[] = ['value' => $v, 'property' => $property];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now add values with no attributes
|
||||
foreach ($noAttributes as $element) {
|
||||
$this->addXml($writer, $element['property'], $element['value']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @param Parameter $param
|
||||
* @param Operation $operation
|
||||
*/
|
||||
private function visitWithValue(
|
||||
$value,
|
||||
Parameter $param,
|
||||
Operation $operation
|
||||
) {
|
||||
if (!$this->writer) {
|
||||
$this->createRootElement($operation);
|
||||
}
|
||||
|
||||
$this->addXml($this->writer, $param, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractLocation
|
||||
*
|
||||
* @package GuzzleHttp\Command\Guzzle\ResponseLocation
|
||||
*/
|
||||
abstract class AbstractLocation implements ResponseLocationInterface
|
||||
{
|
||||
/** @var string $locationName */
|
||||
protected $locationName;
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param $locationName
|
||||
*/
|
||||
public function __construct($locationName)
|
||||
{
|
||||
$this->locationName = $locationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $model
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function before(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $model
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function after(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $param
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Extracts the body of a response into a result field
|
||||
*/
|
||||
class BodyLocation extends AbstractLocation
|
||||
{
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'body')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $param
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
) {
|
||||
$result[$param->getName()] = $param->filter($response->getBody());
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Extracts headers from the response into a result fields
|
||||
*/
|
||||
class HeaderLocation extends AbstractLocation
|
||||
{
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'header')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $param
|
||||
*
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
) {
|
||||
// Retrieving a single header by name
|
||||
$name = $param->getName();
|
||||
if ($header = $response->getHeader($param->getWireName())) {
|
||||
if (is_array($header)) {
|
||||
$header = array_shift($header);
|
||||
}
|
||||
$result[$name] = $param->filter($header);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\Result;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Extracts elements from a JSON document.
|
||||
*/
|
||||
class JsonLocation extends AbstractLocation
|
||||
{
|
||||
/** @var array The JSON document being visited */
|
||||
private $json = [];
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'json')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \GuzzleHttp\Command\ResultInterface $result
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* @param \GuzzleHttp\Command\Guzzle\Parameter $model
|
||||
*
|
||||
* @return \GuzzleHttp\Command\ResultInterface
|
||||
*/
|
||||
public function before(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
) {
|
||||
$body = (string) $response->getBody();
|
||||
$body = $body ?: "{}";
|
||||
$this->json = \GuzzleHttp\json_decode($body, true);
|
||||
// relocate named arrays, so that they have the same structure as
|
||||
// arrays nested in objects and visit can work on them in the same way
|
||||
if ($model->getType() === 'array' && ($name = $model->getName())) {
|
||||
$this->json = [$name => $this->json];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $model
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function after(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
) {
|
||||
// Handle additional, undefined properties
|
||||
$additional = $model->getAdditionalProperties();
|
||||
if (!($additional instanceof Parameter)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Use the model location as the default if one is not set on additional
|
||||
$addLocation = $additional->getLocation() ?: $model->getLocation();
|
||||
if ($addLocation == $this->locationName) {
|
||||
foreach ($this->json as $prop => $val) {
|
||||
if (!isset($result[$prop])) {
|
||||
// Only recurse if there is a type specified
|
||||
$result[$prop] = $additional->getType()
|
||||
? $this->recurse($additional, $val)
|
||||
: $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->json = [];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $param
|
||||
* @return Result|ResultInterface
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
) {
|
||||
$name = $param->getName();
|
||||
$key = $param->getWireName();
|
||||
|
||||
// Check if the result should be treated as a list
|
||||
if ($param->getType() == 'array') {
|
||||
// Treat as javascript array
|
||||
if ($name) {
|
||||
// name provided, store it under a key in the array
|
||||
$subArray = isset($this->json[$key]) ? $this->json[$key] : null;
|
||||
$result[$name] = $this->recurse($param, $subArray);
|
||||
} else {
|
||||
// top-level `array` or an empty name
|
||||
$result = new Result(array_merge(
|
||||
$result->toArray(),
|
||||
$this->recurse($param, $this->json)
|
||||
));
|
||||
}
|
||||
} elseif (isset($this->json[$key])) {
|
||||
$result[$name] = $this->recurse($param, $this->json[$key]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively process a parameter while applying filters
|
||||
*
|
||||
* @param Parameter $param API parameter being validated
|
||||
* @param mixed $value Value to process.
|
||||
* @return mixed|null
|
||||
*/
|
||||
private function recurse(Parameter $param, $value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return $param->filter($value);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$type = $param->getType();
|
||||
|
||||
if ($type == 'array') {
|
||||
$items = $param->getItems();
|
||||
foreach ($value as $val) {
|
||||
$result[] = $this->recurse($items, $val);
|
||||
}
|
||||
} elseif ($type == 'object' && !isset($value[0])) {
|
||||
// On the above line, we ensure that the array is associative and
|
||||
// not numerically indexed
|
||||
if ($properties = $param->getProperties()) {
|
||||
foreach ($properties as $property) {
|
||||
$key = $property->getWireName();
|
||||
if (array_key_exists($key, $value)) {
|
||||
$result[$property->getName()] = $this->recurse(
|
||||
$property,
|
||||
$value[$key]
|
||||
);
|
||||
// Remove from the value so that AP can later be handled
|
||||
unset($value[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only check additional properties if everything wasn't already
|
||||
// handled
|
||||
if ($value) {
|
||||
$additional = $param->getAdditionalProperties();
|
||||
if ($additional === null || $additional === true) {
|
||||
// Merge the JSON under the resulting array
|
||||
$result += $value;
|
||||
} elseif ($additional instanceof Parameter) {
|
||||
// Process all child elements according to the given schema
|
||||
foreach ($value as $prop => $val) {
|
||||
$result[$prop] = $this->recurse($additional, $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $param->filter($result);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Extracts the reason phrase of a response into a result field
|
||||
*/
|
||||
class ReasonPhraseLocation extends AbstractLocation
|
||||
{
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'reasonPhrase')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $param
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
) {
|
||||
$result[$param->getName()] = $param->filter(
|
||||
$response->getReasonPhrase()
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Location visitor used to parse values out of a response into an associative
|
||||
* array
|
||||
*/
|
||||
interface ResponseLocationInterface
|
||||
{
|
||||
/**
|
||||
* Called before visiting all parameters. This can be used for seeding the
|
||||
* result of a command with default data (e.g. populating with JSON data in
|
||||
* the response then adding to the parsed data).
|
||||
*
|
||||
* @param ResultInterface $result Result being created
|
||||
* @param ResponseInterface $response Response being visited
|
||||
* @param Parameter $model Response model
|
||||
*
|
||||
* @return ResultInterface Modified result
|
||||
*/
|
||||
public function before(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
);
|
||||
|
||||
/**
|
||||
* Called after visiting all parameters
|
||||
*
|
||||
* @param ResultInterface $result Result being created
|
||||
* @param ResponseInterface $response Response being visited
|
||||
* @param Parameter $model Response model
|
||||
*
|
||||
* @return ResultInterface Modified result
|
||||
*/
|
||||
public function after(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
);
|
||||
|
||||
/**
|
||||
* Called once for each parameter being visited that matches the location
|
||||
* type.
|
||||
*
|
||||
* @param ResultInterface $result Result being created
|
||||
* @param ResponseInterface $response Response being visited
|
||||
* @param Parameter $param Parameter being visited
|
||||
*
|
||||
* @return ResultInterface Modified result
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
);
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Extracts the status code of a response into a result field
|
||||
*/
|
||||
class StatusCodeLocation extends AbstractLocation
|
||||
{
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'statusCode')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $param
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
) {
|
||||
$result[$param->getName()] = $param->filter($response->getStatusCode());
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
||||
|
||||
use GuzzleHttp\Command\Guzzle\Parameter;
|
||||
use GuzzleHttp\Command\Result;
|
||||
use GuzzleHttp\Command\ResultInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Extracts elements from an XML document
|
||||
*/
|
||||
class XmlLocation extends AbstractLocation
|
||||
{
|
||||
/** @var \SimpleXMLElement XML document being visited */
|
||||
private $xml;
|
||||
|
||||
/**
|
||||
* Set the name of the location
|
||||
*
|
||||
* @param string $locationName
|
||||
*/
|
||||
public function __construct($locationName = 'xml')
|
||||
{
|
||||
parent::__construct($locationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $model
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function before(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
) {
|
||||
$this->xml = simplexml_load_string((string) $response->getBody());
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $model
|
||||
* @return Result|ResultInterface
|
||||
*/
|
||||
public function after(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $model
|
||||
) {
|
||||
// Handle additional, undefined properties
|
||||
$additional = $model->getAdditionalProperties();
|
||||
if ($additional instanceof Parameter &&
|
||||
$additional->getLocation() == $this->locationName
|
||||
) {
|
||||
$result = new Result(array_merge(
|
||||
$result->toArray(),
|
||||
self::xmlToArray($this->xml)
|
||||
));
|
||||
}
|
||||
|
||||
$this->xml = null;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResultInterface $result
|
||||
* @param ResponseInterface $response
|
||||
* @param Parameter $param
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function visit(
|
||||
ResultInterface $result,
|
||||
ResponseInterface $response,
|
||||
Parameter $param
|
||||
) {
|
||||
$sentAs = $param->getWireName();
|
||||
$ns = null;
|
||||
if (strstr($sentAs, ':')) {
|
||||
list($ns, $sentAs) = explode(':', $sentAs);
|
||||
}
|
||||
|
||||
// Process the primary property
|
||||
if (count($this->xml->children($ns, true)->{$sentAs})) {
|
||||
$result[$param->getName()] = $this->recursiveProcess(
|
||||
$param,
|
||||
$this->xml->children($ns, true)->{$sentAs}
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively process a parameter while applying filters
|
||||
*
|
||||
* @param Parameter $param API parameter being processed
|
||||
* @param \SimpleXMLElement $node Node being processed
|
||||
* @return array
|
||||
*/
|
||||
private function recursiveProcess(
|
||||
Parameter $param,
|
||||
\SimpleXMLElement $node
|
||||
) {
|
||||
$result = [];
|
||||
$type = $param->getType();
|
||||
|
||||
if ($type == 'object') {
|
||||
$result = $this->processObject($param, $node);
|
||||
} elseif ($type == 'array') {
|
||||
$result = $this->processArray($param, $node);
|
||||
} else {
|
||||
// We are probably handling a flat data node (i.e. string or
|
||||
// integer), so let's check if it's childless, which indicates a
|
||||
// node containing plain text.
|
||||
if ($node->children()->count() == 0) {
|
||||
// Retrieve text from node
|
||||
$result = (string) $node;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out the value
|
||||
if (isset($result)) {
|
||||
$result = $param->filter($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Parameter $param
|
||||
* @param \SimpleXMLElement $node
|
||||
* @return array
|
||||
*/
|
||||
private function processArray(Parameter $param, \SimpleXMLElement $node)
|
||||
{
|
||||
// Cast to an array if the value was a string, but should be an array
|
||||
$items = $param->getItems();
|
||||
$sentAs = $items->getWireName();
|
||||
$result = [];
|
||||
$ns = null;
|
||||
|
||||
if (strstr($sentAs, ':')) {
|
||||
// Get namespace from the wire name
|
||||
list($ns, $sentAs) = explode(':', $sentAs);
|
||||
} else {
|
||||
// Get namespace from data
|
||||
$ns = $items->getData('xmlNs');
|
||||
}
|
||||
|
||||
if ($sentAs === null) {
|
||||
// A general collection of nodes
|
||||
foreach ($node as $child) {
|
||||
$result[] = $this->recursiveProcess($items, $child);
|
||||
}
|
||||
} else {
|
||||
// A collection of named, repeating nodes
|
||||
// (i.e. <collection><foo></foo><foo></foo></collection>)
|
||||
$children = $node->children($ns, true)->{$sentAs};
|
||||
foreach ($children as $child) {
|
||||
$result[] = $this->recursiveProcess($items, $child);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an object
|
||||
*
|
||||
* @param Parameter $param API parameter being parsed
|
||||
* @param \SimpleXMLElement $node Value to process
|
||||
* @return array
|
||||
*/
|
||||
private function processObject(Parameter $param, \SimpleXMLElement $node)
|
||||
{
|
||||
$result = $knownProps = $knownAttributes = [];
|
||||
|
||||
// Handle known properties
|
||||
if ($properties = $param->getProperties()) {
|
||||
foreach ($properties as $property) {
|
||||
$name = $property->getName();
|
||||
$sentAs = $property->getWireName();
|
||||
$knownProps[$sentAs] = 1;
|
||||
if (strpos($sentAs, ':')) {
|
||||
list($ns, $sentAs) = explode(':', $sentAs);
|
||||
} else {
|
||||
$ns = $property->getData('xmlNs');
|
||||
}
|
||||
|
||||
if ($property->getData('xmlAttribute')) {
|
||||
// Handle XML attributes
|
||||
$result[$name] = (string) $node->attributes($ns, true)->{$sentAs};
|
||||
$knownAttributes[$sentAs] = 1;
|
||||
} elseif (count($node->children($ns, true)->{$sentAs})) {
|
||||
// Found a child node matching wire name
|
||||
$childNode = $node->children($ns, true)->{$sentAs};
|
||||
$result[$name] = $this->recursiveProcess(
|
||||
$property,
|
||||
$childNode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle additional, undefined properties
|
||||
$additional = $param->getAdditionalProperties();
|
||||
if ($additional instanceof Parameter) {
|
||||
// Process all child elements according to the given schema
|
||||
foreach ($node->children($additional->getData('xmlNs'), true) as $childNode) {
|
||||
$sentAs = $childNode->getName();
|
||||
if (!isset($knownProps[$sentAs])) {
|
||||
$result[$sentAs] = $this->recursiveProcess(
|
||||
$additional,
|
||||
$childNode
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ($additional === null || $additional === true) {
|
||||
// Blindly transform the XML into an array preserving as much data
|
||||
// as possible. Remove processed, aliased properties.
|
||||
$array = array_diff_key(self::xmlToArray($node), $knownProps);
|
||||
// Remove @attributes that were explicitly plucked from the
|
||||
// attributes list.
|
||||
if (isset($array['@attributes']) && $knownAttributes) {
|
||||
$array['@attributes'] = array_diff_key($array['@attributes'], $knownProps);
|
||||
if (!$array['@attributes']) {
|
||||
unset($array['@attributes']);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge it together with the original result
|
||||
$result = array_merge($array, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an XML document to an array.
|
||||
*
|
||||
* @param \SimpleXMLElement $xml
|
||||
* @param int $nesting
|
||||
* @param null $ns
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function xmlToArray(
|
||||
\SimpleXMLElement $xml,
|
||||
$ns = null,
|
||||
$nesting = 0
|
||||
) {
|
||||
$result = [];
|
||||
$children = $xml->children($ns, true);
|
||||
|
||||
foreach ($children as $name => $child) {
|
||||
$attributes = (array) $child->attributes($ns, true);
|
||||
if (!isset($result[$name])) {
|
||||
$childArray = self::xmlToArray($child, $ns, $nesting + 1);
|
||||
$result[$name] = $attributes
|
||||
? array_merge($attributes, $childArray)
|
||||
: $childArray;
|
||||
continue;
|
||||
}
|
||||
// A child element with this name exists so we're assuming
|
||||
// that the node contains a list of elements
|
||||
if (!is_array($result[$name])) {
|
||||
$result[$name] = [$result[$name]];
|
||||
} elseif (!isset($result[$name][0])) {
|
||||
// Convert the first child into the first element of a numerically indexed array
|
||||
$firstResult = $result[$name];
|
||||
$result[$name] = [];
|
||||
$result[$name][] = $firstResult;
|
||||
}
|
||||
$childArray = self::xmlToArray($child, $ns, $nesting + 1);
|
||||
if ($attributes) {
|
||||
$result[$name][] = array_merge($attributes, $childArray);
|
||||
} else {
|
||||
$result[$name][] = $childArray;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract text from node
|
||||
$text = trim((string) $xml);
|
||||
if ($text === '') {
|
||||
$text = null;
|
||||
}
|
||||
|
||||
// Process attributes
|
||||
$attributes = (array) $xml->attributes($ns, true);
|
||||
if ($attributes) {
|
||||
if ($text !== null) {
|
||||
$result['value'] = $text;
|
||||
}
|
||||
$result = array_merge($attributes, $result);
|
||||
} elseif ($text !== null) {
|
||||
$result = $text;
|
||||
}
|
||||
|
||||
// Make sure we're always returning an array
|
||||
if ($nesting == 0 && !is_array($result)) {
|
||||
$result = [$result];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
/**
|
||||
* JSON Schema formatter class
|
||||
*/
|
||||
class SchemaFormatter
|
||||
{
|
||||
/**
|
||||
* Format a value by a registered format name
|
||||
*
|
||||
* @param string $format Registered format used to format the value
|
||||
* @param mixed $value Value being formatted
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function format($format, $value)
|
||||
{
|
||||
switch ($format) {
|
||||
case 'date-time':
|
||||
return $this->formatDateTime($value);
|
||||
case 'date-time-http':
|
||||
return $this->formatDateTimeHttp($value);
|
||||
case 'date':
|
||||
return $this->formatDate($value);
|
||||
case 'time':
|
||||
return $this->formatTime($value);
|
||||
case 'timestamp':
|
||||
return $this->formatTimestamp($value);
|
||||
case 'boolean-string':
|
||||
return $this->formatBooleanAsString($value);
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual DateTime formatting
|
||||
*
|
||||
* @param int|string|\DateTime $dateTime Date time value
|
||||
* @param string $format Format of the result
|
||||
*
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function dateFormatter($dateTime, $format)
|
||||
{
|
||||
if (is_numeric($dateTime)) {
|
||||
return gmdate($format, (int) $dateTime);
|
||||
}
|
||||
|
||||
if (is_string($dateTime)) {
|
||||
$dateTime = new \DateTime($dateTime);
|
||||
}
|
||||
|
||||
if ($dateTime instanceof \DateTimeInterface) {
|
||||
static $utc;
|
||||
if (!$utc) {
|
||||
$utc = new \DateTimeZone('UTC');
|
||||
}
|
||||
return $dateTime->setTimezone($utc)->format($format);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Date/Time values must be either '
|
||||
. 'be a string, integer, or DateTime object');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ISO 8601 (YYYY-MM-DDThh:mm:ssZ) formatted date time value in
|
||||
* UTC time.
|
||||
*
|
||||
* @param string|integer|\DateTime $value Date time value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatDateTime($value)
|
||||
{
|
||||
return $this->dateFormatter($value, 'Y-m-d\TH:i:s\Z');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTTP date (RFC 1123 / RFC 822) formatted UTC date-time string
|
||||
*
|
||||
* @param string|integer|\DateTime $value Date time value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatDateTimeHttp($value)
|
||||
{
|
||||
return $this->dateFormatter($value, 'D, d M Y H:i:s \G\M\T');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a YYYY-MM-DD formatted string
|
||||
*
|
||||
* @param string|integer|\DateTime $value Date time value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatDate($value)
|
||||
{
|
||||
return $this->dateFormatter($value, 'Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a hh:mm:ss formatted string
|
||||
*
|
||||
* @param string|integer|\DateTime $value Date time value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatTime($value)
|
||||
{
|
||||
return $this->dateFormatter($value, 'H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a boolean value as a string
|
||||
*
|
||||
* @param string|integer|bool $value Value to convert to a boolean
|
||||
* 'true' / 'false' value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatBooleanAsString($value)
|
||||
{
|
||||
return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a UNIX timestamp in the UTC timezone
|
||||
*
|
||||
* @param string|integer|\DateTime $value Time value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function formatTimestamp($value)
|
||||
{
|
||||
return (int) $this->dateFormatter($value, 'U');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\Command\ToArrayInterface;
|
||||
|
||||
/**
|
||||
* Default parameter validator
|
||||
*/
|
||||
class SchemaValidator
|
||||
{
|
||||
/**
|
||||
* Whether or not integers are converted to strings when an integer is
|
||||
* received for a string input
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $castIntegerToStringType;
|
||||
|
||||
/** @var array Errors encountered while validating */
|
||||
protected $errors;
|
||||
|
||||
/**
|
||||
* @param bool $castIntegerToStringType Set to true to convert integers
|
||||
* into strings when a required type is a string and the input value is
|
||||
* an integer. Defaults to true.
|
||||
*/
|
||||
public function __construct($castIntegerToStringType = true)
|
||||
{
|
||||
$this->castIntegerToStringType = $castIntegerToStringType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Parameter $param
|
||||
* @param $value
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(Parameter $param, &$value)
|
||||
{
|
||||
$this->errors = [];
|
||||
$this->recursiveProcess($param, $value);
|
||||
|
||||
if (empty($this->errors)) {
|
||||
return true;
|
||||
} else {
|
||||
sort($this->errors);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the errors encountered while validating
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* From the allowable types, determine the type that the variable matches
|
||||
*
|
||||
* @param string|array $type Parameter type
|
||||
* @param mixed $value Value to determine the type
|
||||
*
|
||||
* @return string|false Returns the matching type on
|
||||
*/
|
||||
protected function determineType($type, $value)
|
||||
{
|
||||
foreach ((array) $type as $t) {
|
||||
if ($t == 'string'
|
||||
&& (is_string($value) || (is_object($value) && method_exists($value, '__toString')))
|
||||
) {
|
||||
return 'string';
|
||||
} elseif ($t == 'object' && (is_array($value) || is_object($value))) {
|
||||
return 'object';
|
||||
} elseif ($t == 'array' && is_array($value)) {
|
||||
return 'array';
|
||||
} elseif ($t == 'integer' && is_integer($value)) {
|
||||
return 'integer';
|
||||
} elseif ($t == 'boolean' && is_bool($value)) {
|
||||
return 'boolean';
|
||||
} elseif ($t == 'number' && is_numeric($value)) {
|
||||
return 'number';
|
||||
} elseif ($t == 'numeric' && is_numeric($value)) {
|
||||
return 'numeric';
|
||||
} elseif ($t == 'null' && !$value) {
|
||||
return 'null';
|
||||
} elseif ($t == 'any') {
|
||||
return 'any';
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively validate a parameter
|
||||
*
|
||||
* @param Parameter $param API parameter being validated
|
||||
* @param mixed $value Value to validate and validate. The value may
|
||||
* change during this validate.
|
||||
* @param string $path Current validation path (used for error reporting)
|
||||
* @param int $depth Current depth in the validation validate
|
||||
*
|
||||
* @return bool Returns true if valid, or false if invalid
|
||||
*/
|
||||
protected function recursiveProcess(
|
||||
Parameter $param,
|
||||
&$value,
|
||||
$path = '',
|
||||
$depth = 0
|
||||
) {
|
||||
// Update the value by adding default or static values
|
||||
$value = $param->getValue($value);
|
||||
|
||||
$required = $param->isRequired();
|
||||
// if the value is null and the parameter is not required or is static,
|
||||
// then skip any further recursion
|
||||
if ((null === $value && !$required) || $param->isStatic()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$type = $param->getType();
|
||||
// Attempt to limit the number of times is_array is called by tracking
|
||||
// if the value is an array
|
||||
$valueIsArray = is_array($value);
|
||||
// If a name is set then update the path so that validation messages
|
||||
// are more helpful
|
||||
if ($name = $param->getName()) {
|
||||
$path .= "[{$name}]";
|
||||
}
|
||||
|
||||
if ($type == 'object') {
|
||||
// Determine whether or not this "value" has properties and should
|
||||
// be traversed
|
||||
$traverse = $temporaryValue = false;
|
||||
|
||||
// Convert the value to an array
|
||||
if (!$valueIsArray && $value instanceof ToArrayInterface) {
|
||||
$value = $value->toArray();
|
||||
}
|
||||
|
||||
if ($valueIsArray) {
|
||||
// Ensure that the array is associative and not numerically
|
||||
// indexed
|
||||
if (isset($value[0])) {
|
||||
$this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array.";
|
||||
return false;
|
||||
}
|
||||
$traverse = true;
|
||||
} elseif ($value === null) {
|
||||
// Attempt to let the contents be built up by default values if
|
||||
// possible
|
||||
$value = [];
|
||||
$temporaryValue = $valueIsArray = $traverse = true;
|
||||
}
|
||||
|
||||
if ($traverse) {
|
||||
if ($properties = $param->getProperties()) {
|
||||
// if properties were found, validate each property
|
||||
foreach ($properties as $property) {
|
||||
$name = $property->getName();
|
||||
if (isset($value[$name])) {
|
||||
$this->recursiveProcess($property, $value[$name], $path, $depth + 1);
|
||||
} else {
|
||||
$current = null;
|
||||
$this->recursiveProcess($property, $current, $path, $depth + 1);
|
||||
// Only set the value if it was populated
|
||||
if (null !== $current) {
|
||||
$value[$name] = $current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$additional = $param->getAdditionalProperties();
|
||||
if ($additional !== true) {
|
||||
// If additional properties were found, then validate each
|
||||
// against the additionalProperties attr.
|
||||
$keys = array_keys($value);
|
||||
// Determine the keys that were specified that were not
|
||||
// listed in the properties of the schema
|
||||
$diff = array_diff($keys, array_keys($properties));
|
||||
if (!empty($diff)) {
|
||||
// Determine which keys are not in the properties
|
||||
if ($additional instanceof Parameter) {
|
||||
foreach ($diff as $key) {
|
||||
$this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth);
|
||||
}
|
||||
} else {
|
||||
// if additionalProperties is set to false and there
|
||||
// are additionalProperties in the values, then fail
|
||||
foreach ($diff as $prop) {
|
||||
$this->errors[] = sprintf('%s[%s] is not an allowed property', $path, $prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A temporary value will be used to traverse elements that
|
||||
// have no corresponding input value. This allows nested
|
||||
// required parameters with default values to bubble up into the
|
||||
// input. Here we check if we used a temp value and nothing
|
||||
// bubbled up, then we need to remote the value.
|
||||
if ($temporaryValue && empty($value)) {
|
||||
$value = null;
|
||||
$valueIsArray = false;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($type == 'array' && $valueIsArray && $param->getItems()) {
|
||||
foreach ($value as $i => &$item) {
|
||||
// Validate each item in an array against the items attribute of the schema
|
||||
$this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If the value is required and the type is not null, then there is an
|
||||
// error if the value is not set
|
||||
if ($required && $value === null && $type != 'null') {
|
||||
$message = "{$path} is " . ($param->getType()
|
||||
? ('a required ' . implode(' or ', (array) $param->getType()))
|
||||
: 'required');
|
||||
if ($param->has('description')) {
|
||||
$message .= ': ' . $param->getDescription();
|
||||
}
|
||||
$this->errors[] = $message;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate that the type is correct. If the type is string but an
|
||||
// integer was passed, the class can be instructed to cast the integer
|
||||
// to a string to pass validation. This is the default behavior.
|
||||
if ($type && (!$type = $this->determineType($type, $value))) {
|
||||
if ($this->castIntegerToStringType
|
||||
&& $param->getType() == 'string'
|
||||
&& is_integer($value)
|
||||
) {
|
||||
$value = (string) $value;
|
||||
} else {
|
||||
$this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType());
|
||||
}
|
||||
}
|
||||
|
||||
// Perform type specific validation for strings, arrays, and integers
|
||||
if ($type == 'string') {
|
||||
// Strings can have enums which are a list of predefined values
|
||||
if (($enum = $param->getEnum()) && !in_array($value, $enum)) {
|
||||
$this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) {
|
||||
return '"' . addslashes($s) . '"';
|
||||
}, $enum));
|
||||
}
|
||||
// Strings can have a regex pattern that the value must match
|
||||
if (($pattern = $param->getPattern()) && !preg_match($pattern, $value)) {
|
||||
$this->errors[] = "{$path} must match the following regular expression: {$pattern}";
|
||||
}
|
||||
|
||||
$strLen = null;
|
||||
if ($min = $param->getMinLength()) {
|
||||
$strLen = strlen($value);
|
||||
if ($strLen < $min) {
|
||||
$this->errors[] = "{$path} length must be greater than or equal to {$min}";
|
||||
}
|
||||
}
|
||||
if ($max = $param->getMaxLength()) {
|
||||
if (($strLen ?: strlen($value)) > $max) {
|
||||
$this->errors[] = "{$path} length must be less than or equal to {$max}";
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($type == 'array') {
|
||||
$size = null;
|
||||
if ($min = $param->getMinItems()) {
|
||||
$size = count($value);
|
||||
if ($size < $min) {
|
||||
$this->errors[] = "{$path} must contain {$min} or more elements";
|
||||
}
|
||||
}
|
||||
if ($max = $param->getMaxItems()) {
|
||||
if (($size ?: count($value)) > $max) {
|
||||
$this->errors[] = "{$path} must contain {$max} or fewer elements";
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') {
|
||||
if (($min = $param->getMinimum()) && $value < $min) {
|
||||
$this->errors[] = "{$path} must be greater than or equal to {$min}";
|
||||
}
|
||||
if (($max = $param->getMaximum()) && $value > $max) {
|
||||
$this->errors[] = "{$path} must be less than or equal to {$max}";
|
||||
}
|
||||
}
|
||||
|
||||
return empty($this->errors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Command\Guzzle;
|
||||
|
||||
use GuzzleHttp\Command\CommandInterface;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\BodyLocation;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\FormParamLocation;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\HeaderLocation;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\JsonLocation;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\MultiPartLocation;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\RequestLocationInterface;
|
||||
use GuzzleHttp\Command\Guzzle\RequestLocation\XmlLocation;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
use GuzzleHttp\Psr7\UriResolver;
|
||||
use GuzzleHttp\UriTemplate\UriTemplate;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Serializes requests for a given command.
|
||||
*/
|
||||
class Serializer
|
||||
{
|
||||
/** @var RequestLocationInterface[] */
|
||||
private $locations;
|
||||
|
||||
/** @var DescriptionInterface */
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @param DescriptionInterface $description
|
||||
* @param RequestLocationInterface[] $requestLocations Extra request locations
|
||||
*/
|
||||
public function __construct(
|
||||
DescriptionInterface $description,
|
||||
array $requestLocations = []
|
||||
) {
|
||||
static $defaultRequestLocations;
|
||||
if (!$defaultRequestLocations) {
|
||||
$defaultRequestLocations = [
|
||||
'body' => new BodyLocation(),
|
||||
'query' => new QueryLocation(),
|
||||
'header' => new HeaderLocation(),
|
||||
'json' => new JsonLocation(),
|
||||
'xml' => new XmlLocation(),
|
||||
'formParam' => new FormParamLocation(),
|
||||
'multipart' => new MultiPartLocation(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->locations = $requestLocations + $defaultRequestLocations;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CommandInterface $command
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function __invoke(CommandInterface $command)
|
||||
{
|
||||
$request = $this->createRequest($command);
|
||||
return $this->prepareRequest($command, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a request for sending using location visitors
|
||||
*
|
||||
* @param CommandInterface $command
|
||||
* @param RequestInterface $request Request being created
|
||||
* @return RequestInterface
|
||||
* @throws \RuntimeException If a location cannot be handled
|
||||
*/
|
||||
protected function prepareRequest(
|
||||
CommandInterface $command,
|
||||
RequestInterface $request
|
||||
) {
|
||||
$visitedLocations = [];
|
||||
$operation = $this->description->getOperation($command->getName());
|
||||
|
||||
// Visit each actual parameter
|
||||
foreach ($operation->getParams() as $name => $param) {
|
||||
/* @var Parameter $param */
|
||||
$location = $param->getLocation();
|
||||
// Skip parameters that have not been set or are URI location
|
||||
if ($location == 'uri' || !$command->hasParam($name)) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($this->locations[$location])) {
|
||||
throw new \RuntimeException("No location registered for $name");
|
||||
}
|
||||
$visitedLocations[$location] = true;
|
||||
$request = $this->locations[$location]->visit($command, $request, $param);
|
||||
}
|
||||
|
||||
// Ensure that the after() method is invoked for additionalParameters
|
||||
/** @var Parameter $additional */
|
||||
if ($additional = $operation->getAdditionalParameters()) {
|
||||
$visitedLocations[$additional->getLocation()] = true;
|
||||
}
|
||||
|
||||
// Call the after() method for each visited location
|
||||
foreach (array_keys($visitedLocations) as $location) {
|
||||
$request = $this->locations[$location]->after($command, $request, $operation);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a request for the command and operation
|
||||
*
|
||||
* @param CommandInterface $command
|
||||
*
|
||||
* @return RequestInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function createRequest(CommandInterface $command)
|
||||
{
|
||||
$operation = $this->description->getOperation($command->getName());
|
||||
|
||||
// If command does not specify a template, assume the client's base URL.
|
||||
if (null === $operation->getUri()) {
|
||||
return new Request(
|
||||
$operation->getHttpMethod() ?: 'GET',
|
||||
$this->description->getBaseUri()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->createCommandWithUri($operation, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a request for an operation with a uri merged onto a base URI
|
||||
*
|
||||
* @param \GuzzleHttp\Command\Guzzle\Operation $operation
|
||||
* @param \GuzzleHttp\Command\CommandInterface $command
|
||||
*
|
||||
* @return \GuzzleHttp\Psr7\Request
|
||||
*/
|
||||
private function createCommandWithUri(
|
||||
Operation $operation,
|
||||
CommandInterface $command
|
||||
) {
|
||||
// Get the path values and use the client config settings
|
||||
$variables = [];
|
||||
foreach ($operation->getParams() as $name => $arg) {
|
||||
/* @var Parameter $arg */
|
||||
if ($arg->getLocation() == 'uri') {
|
||||
if (isset($command[$name])) {
|
||||
$variables[$name] = $arg->filter($command[$name]);
|
||||
if (!is_array($variables[$name])) {
|
||||
$variables[$name] = (string) $variables[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expand the URI template.
|
||||
$uri = new Uri(UriTemplate::expand($operation->getUri(), $variables));
|
||||
|
||||
return new Request(
|
||||
$operation->getHttpMethod() ?: 'GET',
|
||||
UriResolver::resolve($this->description->getBaseUri(), $uri)
|
||||
);
|
||||
}
|
||||
}
|
||||
+1462
File diff suppressed because it is too large
Load Diff
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||

|
||||
|
||||
# Guzzle, PHP HTTP client
|
||||
|
||||
[](https://github.com/guzzle/guzzle/releases)
|
||||
[](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
|
||||
[](https://packagist.org/packages/guzzlehttp/guzzle)
|
||||
|
||||
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
|
||||
trivial to integrate with web services.
|
||||
|
||||
- Simple interface for building query strings, POST requests, streaming large
|
||||
uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
|
||||
etc...
|
||||
- Can send both synchronous and asynchronous requests using the same interface.
|
||||
- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
|
||||
to utilize other PSR-7 compatible libraries with Guzzle.
|
||||
- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients.
|
||||
- Abstracts away the underlying HTTP transport, allowing you to write
|
||||
environment and transport agnostic code; i.e., no hard dependency on cURL,
|
||||
PHP streams, sockets, or non-blocking event loops.
|
||||
- Middleware system allows you to augment and compose client behavior.
|
||||
|
||||
```php
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
|
||||
|
||||
echo $response->getStatusCode(); // 200
|
||||
echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8'
|
||||
echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}'
|
||||
|
||||
// Send an asynchronous request.
|
||||
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
|
||||
$promise = $client->sendAsync($request)->then(function ($response) {
|
||||
echo 'I completed! ' . $response->getBody();
|
||||
});
|
||||
|
||||
$promise->wait();
|
||||
```
|
||||
|
||||
## Help and docs
|
||||
|
||||
We use GitHub issues only to discuss bugs and new features. For support please refer to:
|
||||
|
||||
- [Documentation](http://guzzlephp.org/)
|
||||
- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
|
||||
- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](http://slack.httplug.io/)
|
||||
- [Gitter](https://gitter.im/guzzle/guzzle)
|
||||
|
||||
|
||||
## Installing Guzzle
|
||||
|
||||
The recommended way to install Guzzle is through
|
||||
[Composer](https://getcomposer.org/).
|
||||
|
||||
```bash
|
||||
composer require guzzlehttp/guzzle
|
||||
```
|
||||
|
||||
|
||||
## Version Guidance
|
||||
|
||||
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
|
||||
|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
|
||||
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
|
||||
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
|
||||
| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
|
||||
| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
|
||||
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >= 7.2 |
|
||||
|
||||
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
|
||||
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
|
||||
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
|
||||
[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5
|
||||
[guzzle-7-repo]: https://github.com/guzzle/guzzle
|
||||
[guzzle-3-docs]: http://guzzle3.readthedocs.org
|
||||
[guzzle-5-docs]: http://docs.guzzlephp.org/en/5.3/
|
||||
[guzzle-6-docs]: http://docs.guzzlephp.org/en/6.5/
|
||||
[guzzle-7-docs]: http://docs.guzzlephp.org/en/latest/
|
||||
+1253
File diff suppressed because it is too large
Load Diff
+73
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"type": "library",
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": [
|
||||
"framework",
|
||||
"http",
|
||||
"rest",
|
||||
"web service",
|
||||
"curl",
|
||||
"client",
|
||||
"HTTP client",
|
||||
"PSR-7",
|
||||
"PSR-18"
|
||||
],
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://sagikazarmark.hu"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.4",
|
||||
"guzzlehttp/psr7": "^1.7 || ^2.0",
|
||||
"psr/http-client": "^1.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"bamarni/composer-bin-plugin": "^1.4.1",
|
||||
"php-http/client-integration-tests": "^3.0",
|
||||
"phpunit/phpunit": "^8.5.5 || ^9.3.5",
|
||||
"psr/log": "^1.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Required for CURL handler support",
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "7.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
|
||||
final class BodySummarizer implements BodySummarizerInterface
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $truncateAt;
|
||||
|
||||
public function __construct(int $truncateAt = null)
|
||||
{
|
||||
$this->truncateAt = $truncateAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summarized message body.
|
||||
*/
|
||||
public function summarize(MessageInterface $message): ?string
|
||||
{
|
||||
return $this->truncateAt === null
|
||||
? \GuzzleHttp\Psr7\Message::bodySummary($message)
|
||||
: \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
|
||||
interface BodySummarizerInterface
|
||||
{
|
||||
/**
|
||||
* Returns a summarized message body.
|
||||
*/
|
||||
public function summarize(MessageInterface $message): ?string;
|
||||
}
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\InvalidArgumentException;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
|
||||
{
|
||||
use ClientTrait;
|
||||
|
||||
/**
|
||||
* @var array Default request options
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Clients accept an array of constructor parameters.
|
||||
*
|
||||
* Here's an example of creating a client using a base_uri and an array of
|
||||
* default request options to apply to each request:
|
||||
*
|
||||
* $client = new Client([
|
||||
* 'base_uri' => 'http://www.foo.com/1.0/',
|
||||
* 'timeout' => 0,
|
||||
* 'allow_redirects' => false,
|
||||
* 'proxy' => '192.168.16.1:10'
|
||||
* ]);
|
||||
*
|
||||
* 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.
|
||||
* 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
|
||||
*
|
||||
* @param array $config Client configuration settings.
|
||||
*
|
||||
* @see \GuzzleHttp\RequestOptions for a list of available request options.
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
if (!isset($config['handler'])) {
|
||||
$config['handler'] = HandlerStack::create();
|
||||
} elseif (!\is_callable($config['handler'])) {
|
||||
throw new InvalidArgumentException('handler must be a callable');
|
||||
}
|
||||
|
||||
// Convert the base_uri to a UriInterface
|
||||
if (isset($config['base_uri'])) {
|
||||
$config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']);
|
||||
}
|
||||
|
||||
$this->configureDefaults($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
*
|
||||
* @return PromiseInterface|ResponseInterface
|
||||
*
|
||||
* @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (\count($args) < 1) {
|
||||
throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
|
||||
}
|
||||
|
||||
$uri = $args[0];
|
||||
$opts = $args[1] ?? [];
|
||||
|
||||
return \substr($method, -5) === 'Async'
|
||||
? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
|
||||
: $this->request($method, $uri, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously send an HTTP request.
|
||||
*
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer. See \GuzzleHttp\RequestOptions.
|
||||
*/
|
||||
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
|
||||
{
|
||||
// Merge the base URI into the request URI if needed.
|
||||
$options = $this->prepareDefaults($options);
|
||||
|
||||
return $this->transfer(
|
||||
$request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
|
||||
$options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an HTTP request.
|
||||
*
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer. See \GuzzleHttp\RequestOptions.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function send(RequestInterface $request, array $options = []): ResponseInterface
|
||||
{
|
||||
$options[RequestOptions::SYNCHRONOUS] = true;
|
||||
return $this->sendAsync($request, $options)->wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* The HttpClient PSR (PSR-18) specify this method.
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sendRequest(RequestInterface $request): ResponseInterface
|
||||
{
|
||||
$options[RequestOptions::SYNCHRONOUS] = true;
|
||||
$options[RequestOptions::ALLOW_REDIRECTS] = false;
|
||||
$options[RequestOptions::HTTP_ERRORS] = false;
|
||||
|
||||
return $this->sendAsync($request, $options)->wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
|
||||
*/
|
||||
public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
|
||||
{
|
||||
$options = $this->prepareDefaults($options);
|
||||
// Remove request modifying parameter because it can be done up-front.
|
||||
$headers = $options['headers'] ?? [];
|
||||
$body = $options['body'] ?? null;
|
||||
$version = $options['version'] ?? '1.1';
|
||||
// Merge the URI into the base URI.
|
||||
$uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
|
||||
if (\is_array($body)) {
|
||||
throw $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']);
|
||||
|
||||
return $this->transfer($request, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function request(string $method, $uri = '', array $options = []): ResponseInterface
|
||||
{
|
||||
$options[RequestOptions::SYNCHRONOUS] = true;
|
||||
return $this->requestAsync($method, $uri, $options)->wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a client configuration option.
|
||||
*
|
||||
* These options include default request options of the client, a "handler"
|
||||
* (if utilized by the concrete client), and a "base_uri" if utilized by
|
||||
* the concrete client.
|
||||
*
|
||||
* @param string|null $option The config option to retrieve.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
|
||||
*/
|
||||
public function getConfig(?string $option = null)
|
||||
{
|
||||
return $option === null
|
||||
? $this->config
|
||||
: ($this->config[$option] ?? null);
|
||||
}
|
||||
|
||||
private function buildUri(UriInterface $uri, array $config): UriInterface
|
||||
{
|
||||
if (isset($config['base_uri'])) {
|
||||
$uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
|
||||
}
|
||||
|
||||
if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
|
||||
$idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
|
||||
$uri = Utils::idnUriConvert($uri, $idnOptions);
|
||||
}
|
||||
|
||||
return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default options for a client.
|
||||
*/
|
||||
private function configureDefaults(array $config): void
|
||||
{
|
||||
$defaults = [
|
||||
'allow_redirects' => RedirectMiddleware::$defaultSettings,
|
||||
'http_errors' => true,
|
||||
'decode_content' => true,
|
||||
'verify' => true,
|
||||
'cookies' => false,
|
||||
'idn_conversion' => false,
|
||||
];
|
||||
|
||||
// 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 === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) {
|
||||
$defaults['proxy']['http'] = $proxy;
|
||||
}
|
||||
|
||||
if ($proxy = Utils::getenv('HTTPS_PROXY')) {
|
||||
$defaults['proxy']['https'] = $proxy;
|
||||
}
|
||||
|
||||
if ($noProxy = Utils::getenv('NO_PROXY')) {
|
||||
$cleanedNoProxy = \str_replace(' ', '', $noProxy);
|
||||
$defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
|
||||
}
|
||||
|
||||
$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' => Utils::defaultUserAgent()];
|
||||
} 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'] = Utils::defaultUserAgent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges default options into the array.
|
||||
*
|
||||
* @param array $options Options to modify by reference
|
||||
*/
|
||||
private function prepareDefaults(array $options): array
|
||||
{
|
||||
$defaults = $this->config;
|
||||
|
||||
if (!empty($defaults['headers'])) {
|
||||
// Default headers are only added if they are not present.
|
||||
$defaults['_conditional'] = $defaults['headers'];
|
||||
unset($defaults['headers']);
|
||||
}
|
||||
|
||||
// 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'] = [];
|
||||
unset($options['headers']);
|
||||
} elseif (!\is_array($options['headers'])) {
|
||||
throw new InvalidArgumentException('headers must be an array');
|
||||
}
|
||||
}
|
||||
|
||||
// Shallow merge defaults underneath options.
|
||||
$result = $options + $defaults;
|
||||
|
||||
// Remove null values.
|
||||
foreach ($result as $k => $v) {
|
||||
if ($v === null) {
|
||||
unset($result[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 array $options See \GuzzleHttp\RequestOptions.
|
||||
*/
|
||||
private function transfer(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
$request = $this->applyOptions($request, $options);
|
||||
/** @var HandlerStack $handler */
|
||||
$handler = $options['handler'];
|
||||
|
||||
try {
|
||||
return P\Create::promiseFor($handler($request, $options));
|
||||
} catch (\Exception $e) {
|
||||
return P\Create::rejectionFor($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the array of request options to a request.
|
||||
*/
|
||||
private function applyOptions(RequestInterface $request, array &$options): RequestInterface
|
||||
{
|
||||
$modify = [
|
||||
'set_headers' => [],
|
||||
];
|
||||
|
||||
if (isset($options['headers'])) {
|
||||
$modify['set_headers'] = $options['headers'];
|
||||
unset($options['headers']);
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
$options['body'] = \http_build_query($options['form_params'], '', '&');
|
||||
unset($options['form_params']);
|
||||
// Ensure that we don't have the header in different case and set the new value.
|
||||
$options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
|
||||
$options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
if (isset($options['multipart'])) {
|
||||
$options['body'] = new Psr7\MultipartStream($options['multipart']);
|
||||
unset($options['multipart']);
|
||||
}
|
||||
|
||||
if (isset($options['json'])) {
|
||||
$options['body'] = Utils::jsonEncode($options['json']);
|
||||
unset($options['json']);
|
||||
// Ensure that we don't have the header in different case and set the new value.
|
||||
$options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
|
||||
$options['_conditional']['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
if (!empty($options['decode_content'])
|
||||
&& $options['decode_content'] !== true
|
||||
) {
|
||||
// Ensure that we don't have the header in different case and set the new value.
|
||||
$options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
|
||||
$modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
|
||||
}
|
||||
|
||||
if (isset($options['body'])) {
|
||||
if (\is_array($options['body'])) {
|
||||
throw $this->invalidBody();
|
||||
}
|
||||
$modify['body'] = Psr7\Utils::streamFor($options['body']);
|
||||
unset($options['body']);
|
||||
}
|
||||
|
||||
if (!empty($options['auth']) && \is_array($options['auth'])) {
|
||||
$value = $options['auth'];
|
||||
$type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
|
||||
switch ($type) {
|
||||
case 'basic':
|
||||
// Ensure that we don't have the header in different case and set the new value.
|
||||
$modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
|
||||
$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;
|
||||
case 'ntlm':
|
||||
$options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
|
||||
$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, '', '&', \PHP_QUERY_RFC3986);
|
||||
}
|
||||
if (!\is_string($value)) {
|
||||
throw new InvalidArgumentException('query must be a string or array');
|
||||
}
|
||||
$modify['query'] = $value;
|
||||
unset($options['query']);
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
$request = Psr7\Utils::modifyRequest($request, $modify);
|
||||
if ($request->getBody() instanceof Psr7\MultipartStream) {
|
||||
// Use a multipart/form-data POST if a Content-Type is not set.
|
||||
// Ensure that we don't have the header in different case and set the new value.
|
||||
$options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
|
||||
$options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
|
||||
. $request->getBody()->getBoundary();
|
||||
}
|
||||
|
||||
// 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\Utils::modifyRequest($request, $modify);
|
||||
// Don't pass this internal value along to middleware/handlers.
|
||||
unset($options['_conditional']);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an InvalidArgumentException with pre-set message.
|
||||
*/
|
||||
private function invalidBody(): InvalidArgumentException
|
||||
{
|
||||
return new InvalidArgumentException('Passing in the "body" request '
|
||||
. 'option as an array to send a request is not supported. '
|
||||
. 'Please use the "form_params" request option to send a '
|
||||
. 'application/x-www-form-urlencoded request, or the "multipart" '
|
||||
. 'request option to send a multipart/form-data request.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Client interface for sending HTTP requests.
|
||||
*/
|
||||
interface ClientInterface
|
||||
{
|
||||
/**
|
||||
* The Guzzle major version.
|
||||
*/
|
||||
const MAJOR_VERSION = 7;
|
||||
|
||||
/**
|
||||
* Send an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function send(RequestInterface $request, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* Asynchronously send an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send
|
||||
* @param array $options Request options to apply to the given
|
||||
* request and to the transfer.
|
||||
*/
|
||||
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function request(string $method, $uri, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;
|
||||
|
||||
/**
|
||||
* Get a client configuration option.
|
||||
*
|
||||
* These options include default request options of the client, a "handler"
|
||||
* (if utilized by the concrete client), and a "base_uri" if utilized by
|
||||
* the concrete client.
|
||||
*
|
||||
* @param string|null $option The config option to retrieve.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0.
|
||||
*/
|
||||
public function getConfig(?string $option = null);
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Client interface for sending HTTP requests.
|
||||
*/
|
||||
trait ClientTrait
|
||||
{
|
||||
/**
|
||||
* Create and send an HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
abstract public function request(string $method, $uri, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an HTTP GET request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function get($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('GET', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP HEAD request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function head($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('HEAD', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP PUT request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function put($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('PUT', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP POST request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function post($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('POST', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP PATCH request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function patch($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('PATCH', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an HTTP DELETE request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function delete($uri, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('DELETE', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP GET request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function getAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('GET', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP HEAD request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function headAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('HEAD', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP PUT request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function putAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('PUT', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP POST request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function postAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('POST', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP PATCH request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function patchAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('PATCH', $uri, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send an asynchronous HTTP DELETE request.
|
||||
*
|
||||
* Use an absolute path to override the base path of the client, or a
|
||||
* relative path to append to the base path of the client. The URL can
|
||||
* contain the query string as well. Use an array to provide a URL
|
||||
* template and additional variables to use in the URL template expansion.
|
||||
*
|
||||
* @param string|UriInterface $uri URI object or string.
|
||||
* @param array $options Request options to apply.
|
||||
*/
|
||||
public function deleteAsync($uri, array $options = []): PromiseInterface
|
||||
{
|
||||
return $this->requestAsync('DELETE', $uri, $options);
|
||||
}
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Cookie jar that stores cookies as an array
|
||||
*/
|
||||
class CookieJar implements CookieJarInterface
|
||||
{
|
||||
/**
|
||||
* @var SetCookie[] Loaded cookie data
|
||||
*/
|
||||
private $cookies = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $strictMode;
|
||||
|
||||
/**
|
||||
* @param bool $strictMode Set to true to throw exceptions when invalid
|
||||
* cookies are added to the cookie jar.
|
||||
* @param array $cookieArray Array of SetCookie objects or a hash of
|
||||
* arrays that can be used with the SetCookie
|
||||
* constructor
|
||||
*/
|
||||
public function __construct(bool $strictMode = false, array $cookieArray = [])
|
||||
{
|
||||
$this->strictMode = $strictMode;
|
||||
|
||||
foreach ($cookieArray as $cookie) {
|
||||
if (!($cookie instanceof SetCookie)) {
|
||||
$cookie = new SetCookie($cookie);
|
||||
}
|
||||
$this->setCookie($cookie);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Cookie jar from an associative array and domain.
|
||||
*
|
||||
* @param array $cookies Cookies to create the jar from
|
||||
* @param string $domain Domain to set the cookies to
|
||||
*/
|
||||
public static function fromArray(array $cookies, string $domain): self
|
||||
{
|
||||
$cookieJar = new self();
|
||||
foreach ($cookies as $name => $value) {
|
||||
$cookieJar->setCookie(new SetCookie([
|
||||
'Domain' => $domain,
|
||||
'Name' => $name,
|
||||
'Value' => $value,
|
||||
'Discard' => true
|
||||
]));
|
||||
}
|
||||
|
||||
return $cookieJar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate if this cookie should be persisted to storage
|
||||
* that survives between requests.
|
||||
*
|
||||
* @param SetCookie $cookie Being evaluated.
|
||||
* @param bool $allowSessionCookies If we should persist session cookies
|
||||
*/
|
||||
public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool
|
||||
{
|
||||
if ($cookie->getExpires() || $allowSessionCookies) {
|
||||
if (!$cookie->getDiscard()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the cookie based on the name
|
||||
*
|
||||
* @param string $name cookie name to search for
|
||||
*
|
||||
* @return SetCookie|null cookie that was found or null if not found
|
||||
*/
|
||||
public function getCookieByName(string $name): ?SetCookie
|
||||
{
|
||||
foreach ($this->cookies as $cookie) {
|
||||
if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) {
|
||||
return $cookie;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return \array_map(static function (SetCookie $cookie): array {
|
||||
return $cookie->toArray();
|
||||
}, $this->getIterator()->getArrayCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void
|
||||
{
|
||||
if (!$domain) {
|
||||
$this->cookies = [];
|
||||
return;
|
||||
} elseif (!$path) {
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($domain): bool {
|
||||
return !$cookie->matchesDomain($domain);
|
||||
}
|
||||
);
|
||||
} elseif (!$name) {
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($path, $domain): bool {
|
||||
return !($cookie->matchesPath($path) &&
|
||||
$cookie->matchesDomain($domain));
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie) use ($path, $domain, $name) {
|
||||
return !($cookie->getName() == $name &&
|
||||
$cookie->matchesPath($path) &&
|
||||
$cookie->matchesDomain($domain));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function clearSessionCookies(): void
|
||||
{
|
||||
$this->cookies = \array_filter(
|
||||
$this->cookies,
|
||||
static function (SetCookie $cookie): bool {
|
||||
return !$cookie->getDiscard() && $cookie->getExpires();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setCookie(SetCookie $cookie): bool
|
||||
{
|
||||
// If the name string is empty (but not 0), ignore the set-cookie
|
||||
// string entirely.
|
||||
$name = $cookie->getName();
|
||||
if (!$name && $name !== '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow cookies with set and valid domain, name, value
|
||||
$result = $cookie->validate();
|
||||
if ($result !== true) {
|
||||
if ($this->strictMode) {
|
||||
throw new \RuntimeException('Invalid cookie: ' . $result);
|
||||
}
|
||||
$this->removeCookieIfEmpty($cookie);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve conflicts with previously set cookies
|
||||
foreach ($this->cookies as $i => $c) {
|
||||
|
||||
// Two cookies are identical, when their path, and domain are
|
||||
// identical.
|
||||
if ($c->getPath() != $cookie->getPath() ||
|
||||
$c->getDomain() != $cookie->getDomain() ||
|
||||
$c->getName() != $cookie->getName()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The previously set cookie is a discard cookie and this one is
|
||||
// not so allow the new cookie to be set
|
||||
if (!$cookie->getDiscard() && $c->getDiscard()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the new cookie's expiration is further into the future, then
|
||||
// replace the old cookie
|
||||
if ($cookie->getExpires() > $c->getExpires()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the value has changed, we better change it
|
||||
if ($cookie->getValue() !== $c->getValue()) {
|
||||
unset($this->cookies[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The cookie exists, so no need to continue
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->cookies[] = $cookie;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->cookies);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \ArrayIterator<int, SetCookie>
|
||||
*/
|
||||
public function getIterator(): \ArrayIterator
|
||||
{
|
||||
return new \ArrayIterator(\array_values($this->cookies));
|
||||
}
|
||||
|
||||
public function extractCookies(RequestInterface $request, ResponseInterface $response): void
|
||||
{
|
||||
if ($cookieHeader = $response->getHeader('Set-Cookie')) {
|
||||
foreach ($cookieHeader as $cookie) {
|
||||
$sc = SetCookie::fromString($cookie);
|
||||
if (!$sc->getDomain()) {
|
||||
$sc->setDomain($request->getUri()->getHost());
|
||||
}
|
||||
if (0 !== \strpos($sc->getPath(), '/')) {
|
||||
$sc->setPath($this->getCookiePathFromRequest($request));
|
||||
}
|
||||
$this->setCookie($sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes cookie path following RFC 6265 section 5.1.4
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc6265#section-5.1.4
|
||||
*/
|
||||
private function getCookiePathFromRequest(RequestInterface $request): string
|
||||
{
|
||||
$uriPath = $request->getUri()->getPath();
|
||||
if ('' === $uriPath) {
|
||||
return '/';
|
||||
}
|
||||
if (0 !== \strpos($uriPath, '/')) {
|
||||
return '/';
|
||||
}
|
||||
if ('/' === $uriPath) {
|
||||
return '/';
|
||||
}
|
||||
$lastSlashPos = \strrpos($uriPath, '/');
|
||||
if (0 === $lastSlashPos || false === $lastSlashPos) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
return \substr($uriPath, 0, $lastSlashPos);
|
||||
}
|
||||
|
||||
public function withCookieHeader(RequestInterface $request): RequestInterface
|
||||
{
|
||||
$values = [];
|
||||
$uri = $request->getUri();
|
||||
$scheme = $uri->getScheme();
|
||||
$host = $uri->getHost();
|
||||
$path = $uri->getPath() ?: '/';
|
||||
|
||||
foreach ($this->cookies as $cookie) {
|
||||
if ($cookie->matchesPath($path) &&
|
||||
$cookie->matchesDomain($host) &&
|
||||
!$cookie->isExpired() &&
|
||||
(!$cookie->getSecure() || $scheme === 'https')
|
||||
) {
|
||||
$values[] = $cookie->getName() . '='
|
||||
. $cookie->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return $values
|
||||
? $request->withHeader('Cookie', \implode('; ', $values))
|
||||
: $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cookie already exists and the server asks to set it again with a
|
||||
* null value, the cookie must be deleted.
|
||||
*/
|
||||
private function removeCookieIfEmpty(SetCookie $cookie): void
|
||||
{
|
||||
$cookieValue = $cookie->getValue();
|
||||
if ($cookieValue === null || $cookieValue === '') {
|
||||
$this->clear(
|
||||
$cookie->getDomain(),
|
||||
$cookie->getPath(),
|
||||
$cookie->getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Stores HTTP cookies.
|
||||
*
|
||||
* It extracts cookies from HTTP requests, and returns them in HTTP responses.
|
||||
* CookieJarInterface instances automatically expire contained cookies when
|
||||
* necessary. Subclasses are also responsible for storing and retrieving
|
||||
* cookies from a file, database, etc.
|
||||
*
|
||||
* @link https://docs.python.org/2/library/cookielib.html Inspiration
|
||||
* @extends \IteratorAggregate<SetCookie>
|
||||
*/
|
||||
interface CookieJarInterface extends \Countable, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Create a request with added cookie headers.
|
||||
*
|
||||
* If no matching cookies are found in the cookie jar, then no Cookie
|
||||
* header is added to the request and the same request is returned.
|
||||
*
|
||||
* @param RequestInterface $request Request object to modify.
|
||||
*
|
||||
* @return RequestInterface returns the modified request.
|
||||
*/
|
||||
public function withCookieHeader(RequestInterface $request): RequestInterface;
|
||||
|
||||
/**
|
||||
* Extract cookies from an HTTP response and store them in the CookieJar.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface $response Response that was received
|
||||
*/
|
||||
public function extractCookies(RequestInterface $request, ResponseInterface $response): void;
|
||||
|
||||
/**
|
||||
* Sets a cookie in the cookie jar.
|
||||
*
|
||||
* @param SetCookie $cookie Cookie to set.
|
||||
*
|
||||
* @return bool Returns true on success or false on failure
|
||||
*/
|
||||
public function setCookie(SetCookie $cookie): bool;
|
||||
|
||||
/**
|
||||
* Remove cookies currently held in the cookie jar.
|
||||
*
|
||||
* Invoking this method without arguments will empty the whole cookie jar.
|
||||
* If given a $domain argument only cookies belonging to that domain will
|
||||
* be removed. If given a $domain and $path argument, cookies belonging to
|
||||
* the specified path within that domain are removed. If given all three
|
||||
* arguments, then the cookie with the specified name, path and domain is
|
||||
* removed.
|
||||
*
|
||||
* @param string|null $domain Clears cookies matching a domain
|
||||
* @param string|null $path Clears cookies matching a domain and path
|
||||
* @param string|null $name Clears cookies matching a domain, path, and name
|
||||
*/
|
||||
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void;
|
||||
|
||||
/**
|
||||
* Discard all sessions cookies.
|
||||
*
|
||||
* Removes cookies that don't have an expire field or a have a discard
|
||||
* field set to true. To be called when the user agent shuts down according
|
||||
* to RFC 2965.
|
||||
*/
|
||||
public function clearSessionCookies(): void;
|
||||
|
||||
/**
|
||||
* Converts the cookie jar to an array.
|
||||
*/
|
||||
public function toArray(): array;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
use GuzzleHttp\Utils;
|
||||
|
||||
/**
|
||||
* Persists non-session cookies using a JSON formatted file
|
||||
*/
|
||||
class FileCookieJar extends CookieJar
|
||||
{
|
||||
/**
|
||||
* @var string filename
|
||||
*/
|
||||
private $filename;
|
||||
|
||||
/**
|
||||
* @var bool Control whether to persist session cookies or not.
|
||||
*/
|
||||
private $storeSessionCookies;
|
||||
|
||||
/**
|
||||
* Create a new FileCookieJar object
|
||||
*
|
||||
* @param string $cookieFile File to store the cookie data
|
||||
* @param bool $storeSessionCookies Set to true to store session cookies
|
||||
* in the cookie jar.
|
||||
*
|
||||
* @throws \RuntimeException if the file cannot be found or created
|
||||
*/
|
||||
public function __construct(string $cookieFile, bool $storeSessionCookies = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->filename = $cookieFile;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
|
||||
if (\file_exists($cookieFile)) {
|
||||
$this->load($cookieFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the file when shutting down
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->save($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the cookies to a file.
|
||||
*
|
||||
* @param string $filename File to save
|
||||
*
|
||||
* @throws \RuntimeException if the file cannot be found or created
|
||||
*/
|
||||
public function save(string $filename): void
|
||||
{
|
||||
$json = [];
|
||||
/** @var SetCookie $cookie */
|
||||
foreach ($this as $cookie) {
|
||||
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
|
||||
$json[] = $cookie->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
$jsonStr = Utils::jsonEncode($json);
|
||||
if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) {
|
||||
throw new \RuntimeException("Unable to save file {$filename}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cookies from a JSON formatted file.
|
||||
*
|
||||
* Old cookies are kept unless overwritten by newly loaded ones.
|
||||
*
|
||||
* @param string $filename Cookie file to load.
|
||||
*
|
||||
* @throws \RuntimeException if the file cannot be loaded.
|
||||
*/
|
||||
public function load(string $filename): void
|
||||
{
|
||||
$json = \file_get_contents($filename);
|
||||
if (false === $json) {
|
||||
throw new \RuntimeException("Unable to load file {$filename}");
|
||||
}
|
||||
if ($json === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = Utils::jsonDecode($json, true);
|
||||
if (\is_array($data)) {
|
||||
foreach ($data as $cookie) {
|
||||
$this->setCookie(new SetCookie($cookie));
|
||||
}
|
||||
} elseif (\is_scalar($data) && !empty($data)) {
|
||||
throw new \RuntimeException("Invalid cookie file: {$filename}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
/**
|
||||
* Persists cookies in the client session
|
||||
*/
|
||||
class SessionCookieJar extends CookieJar
|
||||
{
|
||||
/**
|
||||
* @var string session key
|
||||
*/
|
||||
private $sessionKey;
|
||||
|
||||
/**
|
||||
* @var bool Control whether to persist session cookies or not.
|
||||
*/
|
||||
private $storeSessionCookies;
|
||||
|
||||
/**
|
||||
* Create a new SessionCookieJar object
|
||||
*
|
||||
* @param string $sessionKey Session key name to store the cookie
|
||||
* data in session
|
||||
* @param bool $storeSessionCookies Set to true to store session cookies
|
||||
* in the cookie jar.
|
||||
*/
|
||||
public function __construct(string $sessionKey, bool $storeSessionCookies = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->sessionKey = $sessionKey;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
$this->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves cookies to session when shutting down
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save cookies to the client session
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$json = [];
|
||||
/** @var SetCookie $cookie */
|
||||
foreach ($this as $cookie) {
|
||||
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
|
||||
$json[] = $cookie->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION[$this->sessionKey] = \json_encode($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the contents of the client session into the data array
|
||||
*/
|
||||
protected function load(): void
|
||||
{
|
||||
if (!isset($_SESSION[$this->sessionKey])) {
|
||||
return;
|
||||
}
|
||||
$data = \json_decode($_SESSION[$this->sessionKey], true);
|
||||
if (\is_array($data)) {
|
||||
foreach ($data as $cookie) {
|
||||
$this->setCookie(new SetCookie($cookie));
|
||||
}
|
||||
} elseif (\strlen($data)) {
|
||||
throw new \RuntimeException("Invalid cookie data");
|
||||
}
|
||||
}
|
||||
}
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Cookie;
|
||||
|
||||
/**
|
||||
* Set-Cookie object
|
||||
*/
|
||||
class SetCookie
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $defaults = [
|
||||
'Name' => null,
|
||||
'Value' => null,
|
||||
'Domain' => null,
|
||||
'Path' => '/',
|
||||
'Max-Age' => null,
|
||||
'Expires' => null,
|
||||
'Secure' => false,
|
||||
'Discard' => false,
|
||||
'HttpOnly' => false
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Cookie data
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* Create a new SetCookie object from a string.
|
||||
*
|
||||
* @param string $cookie Set-Cookie header string
|
||||
*/
|
||||
public static function fromString(string $cookie): self
|
||||
{
|
||||
// Create the default return array
|
||||
$data = self::$defaults;
|
||||
// Explode the cookie string using a series of semicolons
|
||||
$pieces = \array_filter(\array_map('trim', \explode(';', $cookie)));
|
||||
// The name of the cookie (first kvp) must exist and include an equal sign.
|
||||
if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) {
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
// Add the cookie pieces into the parsed data array
|
||||
foreach ($pieces as $part) {
|
||||
$cookieParts = \explode('=', $part, 2);
|
||||
$key = \trim($cookieParts[0]);
|
||||
$value = isset($cookieParts[1])
|
||||
? \trim($cookieParts[1], " \n\r\t\0\x0B")
|
||||
: true;
|
||||
|
||||
// Only check for non-cookies when cookies have been found
|
||||
if (!isset($data['Name'])) {
|
||||
$data['Name'] = $key;
|
||||
$data['Value'] = $value;
|
||||
} else {
|
||||
foreach (\array_keys(self::$defaults) as $search) {
|
||||
if (!\strcasecmp($search, $key)) {
|
||||
$data[$search] = $value;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return new self($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data Array of cookie data provided by a Cookie parser
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
/** @var array|null $replaced will be null in case of replace error */
|
||||
$replaced = \array_replace(self::$defaults, $data);
|
||||
if ($replaced === null) {
|
||||
throw new \InvalidArgumentException('Unable to replace the default values for the Cookie.');
|
||||
}
|
||||
|
||||
$this->data = $replaced;
|
||||
// Extract the Expires value and turn it into a UNIX timestamp if needed
|
||||
if (!$this->getExpires() && $this->getMaxAge()) {
|
||||
// Calculate the Expires date
|
||||
$this->setExpires(\time() + $this->getMaxAge());
|
||||
} elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) {
|
||||
$this->setExpires($expires);
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
|
||||
foreach ($this->data as $k => $v) {
|
||||
if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
|
||||
if ($k === 'Expires') {
|
||||
$str .= 'Expires=' . \gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
|
||||
} else {
|
||||
$str .= ($v === true ? $k : "{$k}={$v}") . '; ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \rtrim($str, '; ');
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->data['Name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie name.
|
||||
*
|
||||
* @param string $name Cookie name
|
||||
*/
|
||||
public function setName($name): void
|
||||
{
|
||||
$this->data['Name'] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie value.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->data['Value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie value.
|
||||
*
|
||||
* @param string $value Cookie value
|
||||
*/
|
||||
public function setValue($value): void
|
||||
{
|
||||
$this->data['Value'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->data['Domain'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the domain of the cookie.
|
||||
*
|
||||
* @param string $domain
|
||||
*/
|
||||
public function setDomain($domain): void
|
||||
{
|
||||
$this->data['Domain'] = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->data['Path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path of the cookie.
|
||||
*
|
||||
* @param string $path Path of the cookie
|
||||
*/
|
||||
public function setPath($path): void
|
||||
{
|
||||
$this->data['Path'] = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum lifetime of the cookie in seconds.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaxAge()
|
||||
{
|
||||
return $this->data['Max-Age'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max-age of the cookie.
|
||||
*
|
||||
* @param int $maxAge Max age of the cookie in seconds
|
||||
*/
|
||||
public function setMaxAge($maxAge): void
|
||||
{
|
||||
$this->data['Max-Age'] = $maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* The UNIX timestamp when the cookie Expires.
|
||||
*
|
||||
* @return string|int|null
|
||||
*/
|
||||
public function getExpires()
|
||||
{
|
||||
return $this->data['Expires'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the unix timestamp for which the cookie will expire.
|
||||
*
|
||||
* @param int|string $timestamp Unix timestamp or any English textual datetime description.
|
||||
*/
|
||||
public function setExpires($timestamp): void
|
||||
{
|
||||
$this->data['Expires'] = \is_numeric($timestamp)
|
||||
? (int) $timestamp
|
||||
: \strtotime($timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is a secure cookie.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getSecure()
|
||||
{
|
||||
return $this->data['Secure'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the cookie is secure.
|
||||
*
|
||||
* @param bool $secure Set to true or false if secure
|
||||
*/
|
||||
public function setSecure($secure): void
|
||||
{
|
||||
$this->data['Secure'] = $secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is a session cookie.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getDiscard()
|
||||
{
|
||||
return $this->data['Discard'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this is a session cookie.
|
||||
*
|
||||
* @param bool $discard Set to true or false if this is a session cookie
|
||||
*/
|
||||
public function setDiscard($discard): void
|
||||
{
|
||||
$this->data['Discard'] = $discard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not this is an HTTP only cookie.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getHttpOnly()
|
||||
{
|
||||
return $this->data['HttpOnly'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this is an HTTP only cookie.
|
||||
*
|
||||
* @param bool $httpOnly Set to true or false if this is HTTP only
|
||||
*/
|
||||
public function setHttpOnly($httpOnly): void
|
||||
{
|
||||
$this->data['HttpOnly'] = $httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie matches a path value.
|
||||
*
|
||||
* A request-path path-matches a given cookie-path if at least one of
|
||||
* the following conditions holds:
|
||||
*
|
||||
* - The cookie-path and the request-path are identical.
|
||||
* - The cookie-path is a prefix of the request-path, and the last
|
||||
* character of the cookie-path is %x2F ("/").
|
||||
* - The cookie-path is a prefix of the request-path, and the first
|
||||
* character of the request-path that is not included in the cookie-
|
||||
* path is a %x2F ("/") character.
|
||||
*
|
||||
* @param string $requestPath Path to check against
|
||||
*/
|
||||
public function matchesPath(string $requestPath): bool
|
||||
{
|
||||
$cookiePath = $this->getPath();
|
||||
|
||||
// Match on exact matches or when path is the default empty "/"
|
||||
if ($cookiePath === '/' || $cookiePath == $requestPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure that the cookie-path is a prefix of the request path.
|
||||
if (0 !== \strpos($requestPath, $cookiePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match if the last character of the cookie-path is "/"
|
||||
if (\substr($cookiePath, -1, 1) === '/') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Match if the first character not included in cookie path is "/"
|
||||
return \substr($requestPath, \strlen($cookiePath), 1) === '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie matches a domain value.
|
||||
*
|
||||
* @param string $domain Domain to check against
|
||||
*/
|
||||
public function matchesDomain(string $domain): bool
|
||||
{
|
||||
$cookieDomain = $this->getDomain();
|
||||
if (null === $cookieDomain) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the leading '.' as per spec in RFC 6265.
|
||||
// https://tools.ietf.org/html/rfc6265#section-5.2.3
|
||||
$cookieDomain = \ltrim($cookieDomain, '.');
|
||||
|
||||
// Domain not set or exact match.
|
||||
if (!$cookieDomain || !\strcasecmp($domain, $cookieDomain)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Matching the subdomain according to RFC 6265.
|
||||
// https://tools.ietf.org/html/rfc6265#section-5.1.3
|
||||
if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) \preg_match('/\.' . \preg_quote($cookieDomain, '/') . '$/', $domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie is expired.
|
||||
*/
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->getExpires() !== null && \time() > $this->getExpires();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cookie is valid according to RFC 6265.
|
||||
*
|
||||
* @return bool|string Returns true if valid or an error message if invalid
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
$name = $this->getName();
|
||||
if ($name === '') {
|
||||
return 'The cookie name must not be empty';
|
||||
}
|
||||
|
||||
// Check if any of the invalid characters are present in the cookie name
|
||||
if (\preg_match(
|
||||
'/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
|
||||
$name
|
||||
)) {
|
||||
return 'Cookie name must not contain invalid characters: ASCII '
|
||||
. 'Control characters (0-31;127), space, tab and the '
|
||||
. 'following characters: ()<>@,;:\"/?={}';
|
||||
}
|
||||
|
||||
// Value must not be null. 0 and empty string are valid. Empty strings
|
||||
// are technically against RFC 6265, but known to happen in the wild.
|
||||
$value = $this->getValue();
|
||||
if ($value === null) {
|
||||
return 'The cookie value must not be empty';
|
||||
}
|
||||
|
||||
// Domains must not be empty, but can be 0. "0" is not a valid internet
|
||||
// domain, but may be used as server name in a private network.
|
||||
$domain = $this->getDomain();
|
||||
if ($domain === null || $domain === '') {
|
||||
return 'The cookie domain must not be empty';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Exception when an HTTP error occurs (4xx or 5xx error)
|
||||
*/
|
||||
class BadResponseException extends RequestException
|
||||
{
|
||||
public function __construct(
|
||||
string $message,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
\Throwable $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
parent::__construct($message, $request, $response, $previous, $handlerContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current exception and the ones that extend it will always have a response.
|
||||
*/
|
||||
public function hasResponse(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function narrows the return type from the parent class and does not allow it to be nullable.
|
||||
*/
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
/** @var ResponseInterface */
|
||||
return parent::getResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
/**
|
||||
* Exception when a client error is encountered (4xx codes)
|
||||
*/
|
||||
class ClientException extends BadResponseException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Exception thrown when a connection cannot be established.
|
||||
*
|
||||
* Note that no response is present for a ConnectException
|
||||
*/
|
||||
class ConnectException extends TransferException implements NetworkExceptionInterface
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $handlerContext;
|
||||
|
||||
public function __construct(
|
||||
string $message,
|
||||
RequestInterface $request,
|
||||
\Throwable $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
parent::__construct($message, 0, $previous);
|
||||
$this->request = $request;
|
||||
$this->handlerContext = $handlerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request that caused the exception
|
||||
*/
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contextual information about the error from the underlying handler.
|
||||
*
|
||||
* The contents of this array will vary depending on which handler you are
|
||||
* using. It may also be just an empty array. Relying on this data will
|
||||
* couple you to a specific handler, but can give more debug information
|
||||
* when needed.
|
||||
*/
|
||||
public function getHandlerContext(): array
|
||||
{
|
||||
return $this->handlerContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
|
||||
interface GuzzleException extends ClientExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
final class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
use GuzzleHttp\BodySummarizer;
|
||||
use GuzzleHttp\BodySummarizerInterface;
|
||||
use Psr\Http\Client\RequestExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* HTTP Request exception
|
||||
*/
|
||||
class RequestException extends TransferException implements RequestExceptionInterface
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var ResponseInterface|null
|
||||
*/
|
||||
private $response;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $handlerContext;
|
||||
|
||||
public function __construct(
|
||||
string $message,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response = null,
|
||||
\Throwable $previous = null,
|
||||
array $handlerContext = []
|
||||
) {
|
||||
// Set the code of the exception if the response is set and not future.
|
||||
$code = $response ? $response->getStatusCode() : 0;
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->handlerContext = $handlerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap non-RequestExceptions with a RequestException
|
||||
*/
|
||||
public static function wrapException(RequestInterface $request, \Throwable $e): RequestException
|
||||
{
|
||||
return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new exception with a normalized error message
|
||||
*
|
||||
* @param RequestInterface $request Request sent
|
||||
* @param ResponseInterface $response Response received
|
||||
* @param \Throwable|null $previous Previous exception
|
||||
* @param array $handlerContext Optional handler context
|
||||
* @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer
|
||||
*/
|
||||
public static function create(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response = null,
|
||||
\Throwable $previous = null,
|
||||
array $handlerContext = [],
|
||||
BodySummarizerInterface $bodySummarizer = null
|
||||
): self {
|
||||
if (!$response) {
|
||||
return new self(
|
||||
'Error completing request',
|
||||
$request,
|
||||
null,
|
||||
$previous,
|
||||
$handlerContext
|
||||
);
|
||||
}
|
||||
|
||||
$level = (int) \floor($response->getStatusCode() / 100);
|
||||
if ($level === 4) {
|
||||
$label = 'Client error';
|
||||
$className = ClientException::class;
|
||||
} elseif ($level === 5) {
|
||||
$label = 'Server error';
|
||||
$className = ServerException::class;
|
||||
} else {
|
||||
$label = 'Unsuccessful request';
|
||||
$className = __CLASS__;
|
||||
}
|
||||
|
||||
$uri = $request->getUri();
|
||||
$uri = static::obfuscateUri($uri);
|
||||
|
||||
// Client Error: `GET /` resulted in a `404 Not Found` response:
|
||||
// <html> ... (truncated)
|
||||
$message = \sprintf(
|
||||
'%s: `%s %s` resulted in a `%s %s` response',
|
||||
$label,
|
||||
$request->getMethod(),
|
||||
$uri,
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
);
|
||||
|
||||
$summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response);
|
||||
|
||||
if ($summary !== null) {
|
||||
$message .= ":\n{$summary}\n";
|
||||
}
|
||||
|
||||
return new $className($message, $request, $response, $previous, $handlerContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obfuscates URI if there is a username and a password present
|
||||
*/
|
||||
private static function obfuscateUri(UriInterface $uri): UriInterface
|
||||
{
|
||||
$userInfo = $uri->getUserInfo();
|
||||
|
||||
if (false !== ($pos = \strpos($userInfo, ':'))) {
|
||||
return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request that caused the exception
|
||||
*/
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated response
|
||||
*/
|
||||
public function getResponse(): ?ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a response was received
|
||||
*/
|
||||
public function hasResponse(): bool
|
||||
{
|
||||
return $this->response !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contextual information about the error from the underlying handler.
|
||||
*
|
||||
* The contents of this array will vary depending on which handler you are
|
||||
* using. It may also be just an empty array. Relying on this data will
|
||||
* couple you to a specific handler, but can give more debug information
|
||||
* when needed.
|
||||
*/
|
||||
public function getHandlerContext(): array
|
||||
{
|
||||
return $this->handlerContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
/**
|
||||
* Exception when a server error is encountered (5xx codes)
|
||||
*/
|
||||
class ServerException extends BadResponseException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TooManyRedirectsException extends RequestException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TransferException extends \RuntimeException implements GuzzleException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\FulfilledPromise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7\LazyOpenStream;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Creates curl resources from a request
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class CurlFactory implements CurlFactoryInterface
|
||||
{
|
||||
public const CURL_VERSION_STR = 'curl_version';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public const LOW_CURL_VERSION_NUMBER = '7.21.2';
|
||||
|
||||
/**
|
||||
* @var resource[]|\CurlHandle[]
|
||||
*/
|
||||
private $handles = [];
|
||||
|
||||
/**
|
||||
* @var int Total number of idle handles to keep in cache
|
||||
*/
|
||||
private $maxHandles;
|
||||
|
||||
/**
|
||||
* @param int $maxHandles Maximum number of idle handles.
|
||||
*/
|
||||
public function __construct(int $maxHandles)
|
||||
{
|
||||
$this->maxHandles = $maxHandles;
|
||||
}
|
||||
|
||||
public function create(RequestInterface $request, array $options): EasyHandle
|
||||
{
|
||||
if (isset($options['curl']['body_as_string'])) {
|
||||
$options['_body_as_string'] = $options['curl']['body_as_string'];
|
||||
unset($options['curl']['body_as_string']);
|
||||
}
|
||||
|
||||
$easy = new EasyHandle;
|
||||
$easy->request = $request;
|
||||
$easy->options = $options;
|
||||
$conf = $this->getDefaultConf($easy);
|
||||
$this->applyMethod($easy, $conf);
|
||||
$this->applyHandlerOptions($easy, $conf);
|
||||
$this->applyHeaders($easy, $conf);
|
||||
unset($conf['_headers']);
|
||||
|
||||
// Add handler options from the request configuration options
|
||||
if (isset($options['curl'])) {
|
||||
$conf = \array_replace($conf, $options['curl']);
|
||||
}
|
||||
|
||||
$conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
|
||||
$easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init();
|
||||
curl_setopt_array($easy->handle, $conf);
|
||||
|
||||
return $easy;
|
||||
}
|
||||
|
||||
public function release(EasyHandle $easy): void
|
||||
{
|
||||
$resource = $easy->handle;
|
||||
unset($easy->handle);
|
||||
|
||||
if (\count($this->handles) >= $this->maxHandles) {
|
||||
\curl_close($resource);
|
||||
} else {
|
||||
// Remove all callback functions as they can hold onto references
|
||||
// and are not cleaned up by curl_reset. Using curl_setopt_array
|
||||
// does not work for some reason, so removing each one
|
||||
// individually.
|
||||
\curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null);
|
||||
\curl_setopt($resource, \CURLOPT_READFUNCTION, null);
|
||||
\curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null);
|
||||
\curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null);
|
||||
\curl_reset($resource);
|
||||
$this->handles[] = $resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes a cURL transaction, either returning a response promise or a
|
||||
* rejected promise.
|
||||
*
|
||||
* @param callable(RequestInterface, array): PromiseInterface $handler
|
||||
* @param CurlFactoryInterface $factory Dictates how the handle is released
|
||||
*/
|
||||
public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface
|
||||
{
|
||||
if (isset($easy->options['on_stats'])) {
|
||||
self::invokeStats($easy);
|
||||
}
|
||||
|
||||
if (!$easy->response || $easy->errno) {
|
||||
return self::finishError($handler, $easy, $factory);
|
||||
}
|
||||
|
||||
// Return the response if it is present and there is no error.
|
||||
$factory->release($easy);
|
||||
|
||||
// Rewind the body of the response if possible.
|
||||
$body = $easy->response->getBody();
|
||||
if ($body->isSeekable()) {
|
||||
$body->rewind();
|
||||
}
|
||||
|
||||
return new FulfilledPromise($easy->response);
|
||||
}
|
||||
|
||||
private static function invokeStats(EasyHandle $easy): void
|
||||
{
|
||||
$curlStats = \curl_getinfo($easy->handle);
|
||||
$curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME);
|
||||
$stats = new TransferStats(
|
||||
$easy->request,
|
||||
$easy->response,
|
||||
$curlStats['total_time'],
|
||||
$easy->errno,
|
||||
$curlStats
|
||||
);
|
||||
($easy->options['on_stats'])($stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(RequestInterface, array): PromiseInterface $handler
|
||||
*/
|
||||
private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface
|
||||
{
|
||||
// Get error information and release the handle to the factory.
|
||||
$ctx = [
|
||||
'errno' => $easy->errno,
|
||||
'error' => \curl_error($easy->handle),
|
||||
'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME),
|
||||
] + \curl_getinfo($easy->handle);
|
||||
$ctx[self::CURL_VERSION_STR] = \curl_version()['version'];
|
||||
$factory->release($easy);
|
||||
|
||||
// Retry when nothing is present or when curl failed to rewind.
|
||||
if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) {
|
||||
return self::retryFailedRewind($handler, $easy, $ctx);
|
||||
}
|
||||
|
||||
return self::createRejection($easy, $ctx);
|
||||
}
|
||||
|
||||
private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface
|
||||
{
|
||||
static $connectionErrors = [
|
||||
\CURLE_OPERATION_TIMEOUTED => true,
|
||||
\CURLE_COULDNT_RESOLVE_HOST => true,
|
||||
\CURLE_COULDNT_CONNECT => true,
|
||||
\CURLE_SSL_CONNECT_ERROR => true,
|
||||
\CURLE_GOT_NOTHING => true,
|
||||
];
|
||||
|
||||
if ($easy->createResponseException) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException(
|
||||
'An error was encountered while creating the response',
|
||||
$easy->request,
|
||||
$easy->response,
|
||||
$easy->createResponseException,
|
||||
$ctx
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// If an exception was encountered during the onHeaders event, then
|
||||
// return a rejected promise that wraps that exception.
|
||||
if ($easy->onHeadersException) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException(
|
||||
'An error was encountered during the on_headers event',
|
||||
$easy->request,
|
||||
$easy->response,
|
||||
$easy->onHeadersException,
|
||||
$ctx
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$message = \sprintf(
|
||||
'cURL error %s: %s (%s)',
|
||||
$ctx['errno'],
|
||||
$ctx['error'],
|
||||
'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
|
||||
);
|
||||
$uriString = (string) $easy->request->getUri();
|
||||
if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) {
|
||||
$message .= \sprintf(' for %s', $uriString);
|
||||
}
|
||||
|
||||
// Create a connection exception if it was a specific error code.
|
||||
$error = isset($connectionErrors[$easy->errno])
|
||||
? new ConnectException($message, $easy->request, null, $ctx)
|
||||
: new RequestException($message, $easy->request, $easy->response, null, $ctx);
|
||||
|
||||
return P\Create::rejectionFor($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
private function getDefaultConf(EasyHandle $easy): array
|
||||
{
|
||||
$conf = [
|
||||
'_headers' => $easy->request->getHeaders(),
|
||||
\CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
|
||||
\CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
|
||||
\CURLOPT_RETURNTRANSFER => false,
|
||||
\CURLOPT_HEADER => false,
|
||||
\CURLOPT_CONNECTTIMEOUT => 150,
|
||||
];
|
||||
|
||||
if (\defined('CURLOPT_PROTOCOLS')) {
|
||||
$conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS;
|
||||
}
|
||||
|
||||
$version = $easy->request->getProtocolVersion();
|
||||
if ($version == 1.1) {
|
||||
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
|
||||
} elseif ($version == 2.0) {
|
||||
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
|
||||
} else {
|
||||
$conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
|
||||
}
|
||||
|
||||
return $conf;
|
||||
}
|
||||
|
||||
private function applyMethod(EasyHandle $easy, array &$conf): void
|
||||
{
|
||||
$body = $easy->request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size === null || $size > 0) {
|
||||
$this->applyBody($easy->request, $easy->options, $conf);
|
||||
return;
|
||||
}
|
||||
|
||||
$method = $easy->request->getMethod();
|
||||
if ($method === 'PUT' || $method === 'POST') {
|
||||
// See https://tools.ietf.org/html/rfc7230#section-3.3.2
|
||||
if (!$easy->request->hasHeader('Content-Length')) {
|
||||
$conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
|
||||
}
|
||||
} elseif ($method === 'HEAD') {
|
||||
$conf[\CURLOPT_NOBODY] = true;
|
||||
unset(
|
||||
$conf[\CURLOPT_WRITEFUNCTION],
|
||||
$conf[\CURLOPT_READFUNCTION],
|
||||
$conf[\CURLOPT_FILE],
|
||||
$conf[\CURLOPT_INFILE]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyBody(RequestInterface $request, array $options, array &$conf): void
|
||||
{
|
||||
$size = $request->hasHeader('Content-Length')
|
||||
? (int) $request->getHeaderLine('Content-Length')
|
||||
: null;
|
||||
|
||||
// Send the body as a string if the size is less than 1MB OR if the
|
||||
// [curl][body_as_string] request value is set.
|
||||
if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) {
|
||||
$conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody();
|
||||
// Don't duplicate the Content-Length header
|
||||
$this->removeHeader('Content-Length', $conf);
|
||||
$this->removeHeader('Transfer-Encoding', $conf);
|
||||
} else {
|
||||
$conf[\CURLOPT_UPLOAD] = true;
|
||||
if ($size !== null) {
|
||||
$conf[\CURLOPT_INFILESIZE] = $size;
|
||||
$this->removeHeader('Content-Length', $conf);
|
||||
}
|
||||
$body = $request->getBody();
|
||||
if ($body->isSeekable()) {
|
||||
$body->rewind();
|
||||
}
|
||||
$conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) {
|
||||
return $body->read($length);
|
||||
};
|
||||
}
|
||||
|
||||
// If the Expect header is not present, prevent curl from adding it
|
||||
if (!$request->hasHeader('Expect')) {
|
||||
$conf[\CURLOPT_HTTPHEADER][] = 'Expect:';
|
||||
}
|
||||
|
||||
// cURL sometimes adds a content-type by default. Prevent this.
|
||||
if (!$request->hasHeader('Content-Type')) {
|
||||
$conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:';
|
||||
}
|
||||
}
|
||||
|
||||
private function applyHeaders(EasyHandle $easy, array &$conf): void
|
||||
{
|
||||
foreach ($conf['_headers'] as $name => $values) {
|
||||
foreach ($values as $value) {
|
||||
$value = (string) $value;
|
||||
if ($value === '') {
|
||||
// cURL requires a special format for empty headers.
|
||||
// See https://github.com/guzzle/guzzle/issues/1882 for more details.
|
||||
$conf[\CURLOPT_HTTPHEADER][] = "$name;";
|
||||
} else {
|
||||
$conf[\CURLOPT_HTTPHEADER][] = "$name: $value";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the Accept header if one was not set
|
||||
if (!$easy->request->hasHeader('Accept')) {
|
||||
$conf[\CURLOPT_HTTPHEADER][] = 'Accept:';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a header from the options array.
|
||||
*
|
||||
* @param string $name Case-insensitive header to remove
|
||||
* @param array $options Array of options to modify
|
||||
*/
|
||||
private function removeHeader(string $name, array &$options): void
|
||||
{
|
||||
foreach (\array_keys($options['_headers']) as $key) {
|
||||
if (!\strcasecmp($key, $name)) {
|
||||
unset($options['_headers'][$key]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function applyHandlerOptions(EasyHandle $easy, array &$conf): void
|
||||
{
|
||||
$options = $easy->options;
|
||||
if (isset($options['verify'])) {
|
||||
if ($options['verify'] === false) {
|
||||
unset($conf[\CURLOPT_CAINFO]);
|
||||
$conf[\CURLOPT_SSL_VERIFYHOST] = 0;
|
||||
$conf[\CURLOPT_SSL_VERIFYPEER] = false;
|
||||
} else {
|
||||
$conf[\CURLOPT_SSL_VERIFYHOST] = 2;
|
||||
$conf[\CURLOPT_SSL_VERIFYPEER] = true;
|
||||
if (\is_string($options['verify'])) {
|
||||
// Throw an error if the file/folder/link path is not valid or doesn't exist.
|
||||
if (!\file_exists($options['verify'])) {
|
||||
throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}");
|
||||
}
|
||||
// If it's a directory or a link to a directory use CURLOPT_CAPATH.
|
||||
// If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
|
||||
if (
|
||||
\is_dir($options['verify']) ||
|
||||
(
|
||||
\is_link($options['verify']) === true &&
|
||||
($verifyLink = \readlink($options['verify'])) !== false &&
|
||||
\is_dir($verifyLink)
|
||||
)
|
||||
) {
|
||||
$conf[\CURLOPT_CAPATH] = $options['verify'];
|
||||
} else {
|
||||
$conf[\CURLOPT_CAINFO] = $options['verify'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) {
|
||||
$accept = $easy->request->getHeaderLine('Accept-Encoding');
|
||||
if ($accept) {
|
||||
$conf[\CURLOPT_ENCODING] = $accept;
|
||||
} else {
|
||||
$conf[\CURLOPT_ENCODING] = '';
|
||||
// Don't let curl send the header over the wire
|
||||
$conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($options['sink'])) {
|
||||
// Use a default temp stream if no sink was set.
|
||||
$options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+');
|
||||
}
|
||||
$sink = $options['sink'];
|
||||
if (!\is_string($sink)) {
|
||||
$sink = \GuzzleHttp\Psr7\Utils::streamFor($sink);
|
||||
} elseif (!\is_dir(\dirname($sink))) {
|
||||
// Ensure that the directory exists before failing in curl.
|
||||
throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink));
|
||||
} else {
|
||||
$sink = new LazyOpenStream($sink, 'w+');
|
||||
}
|
||||
$easy->sink = $sink;
|
||||
$conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int {
|
||||
return $sink->write($write);
|
||||
};
|
||||
|
||||
$timeoutRequiresNoSignal = false;
|
||||
if (isset($options['timeout'])) {
|
||||
$timeoutRequiresNoSignal |= $options['timeout'] < 1;
|
||||
$conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
|
||||
}
|
||||
|
||||
// CURL default value is CURL_IPRESOLVE_WHATEVER
|
||||
if (isset($options['force_ip_resolve'])) {
|
||||
if ('v4' === $options['force_ip_resolve']) {
|
||||
$conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4;
|
||||
} elseif ('v6' === $options['force_ip_resolve']) {
|
||||
$conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['connect_timeout'])) {
|
||||
$timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
|
||||
$conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
|
||||
}
|
||||
|
||||
if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') {
|
||||
$conf[\CURLOPT_NOSIGNAL] = true;
|
||||
}
|
||||
|
||||
if (isset($options['proxy'])) {
|
||||
if (!\is_array($options['proxy'])) {
|
||||
$conf[\CURLOPT_PROXY] = $options['proxy'];
|
||||
} else {
|
||||
$scheme = $easy->request->getUri()->getScheme();
|
||||
if (isset($options['proxy'][$scheme])) {
|
||||
$host = $easy->request->getUri()->getHost();
|
||||
if (!isset($options['proxy']['no']) || !Utils::isHostInNoProxy($host, $options['proxy']['no'])) {
|
||||
$conf[\CURLOPT_PROXY] = $options['proxy'][$scheme];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['cert'])) {
|
||||
$cert = $options['cert'];
|
||||
if (\is_array($cert)) {
|
||||
$conf[\CURLOPT_SSLCERTPASSWD] = $cert[1];
|
||||
$cert = $cert[0];
|
||||
}
|
||||
if (!\file_exists($cert)) {
|
||||
throw new \InvalidArgumentException("SSL certificate not found: {$cert}");
|
||||
}
|
||||
# OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files.
|
||||
# see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html
|
||||
$ext = pathinfo($cert, \PATHINFO_EXTENSION);
|
||||
if (preg_match('#^(der|p12)$#i', $ext)) {
|
||||
$conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext);
|
||||
}
|
||||
$conf[\CURLOPT_SSLCERT] = $cert;
|
||||
}
|
||||
|
||||
if (isset($options['ssl_key'])) {
|
||||
if (\is_array($options['ssl_key'])) {
|
||||
if (\count($options['ssl_key']) === 2) {
|
||||
[$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key'];
|
||||
} else {
|
||||
[$sslKey] = $options['ssl_key'];
|
||||
}
|
||||
}
|
||||
|
||||
$sslKey = $sslKey ?? $options['ssl_key'];
|
||||
|
||||
if (!\file_exists($sslKey)) {
|
||||
throw new \InvalidArgumentException("SSL private key not found: {$sslKey}");
|
||||
}
|
||||
$conf[\CURLOPT_SSLKEY] = $sslKey;
|
||||
}
|
||||
|
||||
if (isset($options['progress'])) {
|
||||
$progress = $options['progress'];
|
||||
if (!\is_callable($progress)) {
|
||||
throw new \InvalidArgumentException('progress client option must be callable');
|
||||
}
|
||||
$conf[\CURLOPT_NOPROGRESS] = false;
|
||||
$conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) {
|
||||
$progress($downloadSize, $downloaded, $uploadSize, $uploaded);
|
||||
};
|
||||
}
|
||||
|
||||
if (!empty($options['debug'])) {
|
||||
$conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']);
|
||||
$conf[\CURLOPT_VERBOSE] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function ensures that a response was set on a transaction. If one
|
||||
* was not set, then the request is retried if possible. This error
|
||||
* typically means you are sending a payload, curl encountered a
|
||||
* "Connection died, retrying a fresh connect" error, tried to rewind the
|
||||
* stream, and then encountered a "necessary data rewind wasn't possible"
|
||||
* error, causing the request to be sent through curl_multi_info_read()
|
||||
* without an error status.
|
||||
*
|
||||
* @param callable(RequestInterface, array): PromiseInterface $handler
|
||||
*/
|
||||
private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface
|
||||
{
|
||||
try {
|
||||
// Only rewind if the body has been read from.
|
||||
$body = $easy->request->getBody();
|
||||
if ($body->tell() > 0) {
|
||||
$body->rewind();
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
$ctx['error'] = 'The connection unexpectedly failed without '
|
||||
. 'providing an error. The request would have been retried, '
|
||||
. 'but attempting to rewind the request body failed. '
|
||||
. 'Exception: ' . $e;
|
||||
return self::createRejection($easy, $ctx);
|
||||
}
|
||||
|
||||
// Retry no more than 3 times before giving up.
|
||||
if (!isset($easy->options['_curl_retries'])) {
|
||||
$easy->options['_curl_retries'] = 1;
|
||||
} elseif ($easy->options['_curl_retries'] == 2) {
|
||||
$ctx['error'] = 'The cURL request was retried 3 times '
|
||||
. 'and did not succeed. The most likely reason for the failure '
|
||||
. 'is that cURL was unable to rewind the body of the request '
|
||||
. 'and subsequent retries resulted in the same error. Turn on '
|
||||
. 'the debug option to see what went wrong. See '
|
||||
. 'https://bugs.php.net/bug.php?id=47204 for more information.';
|
||||
return self::createRejection($easy, $ctx);
|
||||
} else {
|
||||
$easy->options['_curl_retries']++;
|
||||
}
|
||||
|
||||
return $handler($easy->request, $easy->options);
|
||||
}
|
||||
|
||||
private function createHeaderFn(EasyHandle $easy): callable
|
||||
{
|
||||
if (isset($easy->options['on_headers'])) {
|
||||
$onHeaders = $easy->options['on_headers'];
|
||||
|
||||
if (!\is_callable($onHeaders)) {
|
||||
throw new \InvalidArgumentException('on_headers must be callable');
|
||||
}
|
||||
} else {
|
||||
$onHeaders = null;
|
||||
}
|
||||
|
||||
return static function ($ch, $h) use (
|
||||
$onHeaders,
|
||||
$easy,
|
||||
&$startingResponse
|
||||
) {
|
||||
$value = \trim($h);
|
||||
if ($value === '') {
|
||||
$startingResponse = true;
|
||||
try {
|
||||
$easy->createResponse();
|
||||
} catch (\Exception $e) {
|
||||
$easy->createResponseException = $e;
|
||||
return -1;
|
||||
}
|
||||
if ($onHeaders !== null) {
|
||||
try {
|
||||
$onHeaders($easy->response);
|
||||
} catch (\Exception $e) {
|
||||
// Associate the exception with the handle and trigger
|
||||
// a curl header write error by returning 0.
|
||||
$easy->onHeadersException = $e;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} elseif ($startingResponse) {
|
||||
$startingResponse = false;
|
||||
$easy->headers = [$value];
|
||||
} else {
|
||||
$easy->headers[] = $value;
|
||||
}
|
||||
return \strlen($h);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
interface CurlFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Creates a cURL handle resource.
|
||||
*
|
||||
* @param RequestInterface $request Request
|
||||
* @param array $options Transfer options
|
||||
*
|
||||
* @throws \RuntimeException when an option cannot be applied
|
||||
*/
|
||||
public function create(RequestInterface $request, array $options): EasyHandle;
|
||||
|
||||
/**
|
||||
* Release an easy handle, allowing it to be reused or closed.
|
||||
*
|
||||
* This function must call unset on the easy handle's "handle" property.
|
||||
*/
|
||||
public function release(EasyHandle $easy): void;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* HTTP handler that uses cURL easy handles as a transport layer.
|
||||
*
|
||||
* When using the CurlHandler, custom curl options can be specified as an
|
||||
* associative array of curl option constants mapping to values in the
|
||||
* **curl** key of the "client" key of the request.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class CurlHandler
|
||||
{
|
||||
/**
|
||||
* @var CurlFactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* Accepts an associative array of options:
|
||||
*
|
||||
* - factory: Optional curl factory used to create cURL handles.
|
||||
*
|
||||
* @param array $options Array of options to use with the handler
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->factory = $options['handle_factory']
|
||||
?? new CurlFactory(3);
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
if (isset($options['delay'])) {
|
||||
\usleep($options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$easy = $this->factory->create($request, $options);
|
||||
\curl_exec($easy->handle);
|
||||
$easy->errno = \curl_errno($easy->handle);
|
||||
|
||||
return CurlFactory::finish($this, $easy, $this->factory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\Promise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Returns an asynchronous response using curl_multi_* functions.
|
||||
*
|
||||
* When using the CurlMultiHandler, custom curl options can be specified as an
|
||||
* associative array of curl option constants mapping to values in the
|
||||
* **curl** key of the provided request options.
|
||||
*
|
||||
* @property resource|\CurlMultiHandle $_mh Internal use only. Lazy loaded multi-handle.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class CurlMultiHandler
|
||||
{
|
||||
/**
|
||||
* @var CurlFactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $selectTimeout;
|
||||
|
||||
/**
|
||||
* @var resource|\CurlMultiHandle|null the currently executing resource in `curl_multi_exec`.
|
||||
*/
|
||||
private $active;
|
||||
|
||||
/**
|
||||
* @var array Request entry handles, indexed by handle id in `addRequest`.
|
||||
*
|
||||
* @see CurlMultiHandler::addRequest
|
||||
*/
|
||||
private $handles = [];
|
||||
|
||||
/**
|
||||
* @var array<int, float> An array of delay times, indexed by handle id in `addRequest`.
|
||||
*
|
||||
* @see CurlMultiHandler::addRequest
|
||||
*/
|
||||
private $delays = [];
|
||||
|
||||
/**
|
||||
* @var array<mixed> An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
|
||||
*/
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* This handler accepts the following options:
|
||||
*
|
||||
* - handle_factory: An optional factory used to create curl handles
|
||||
* - select_timeout: Optional timeout (in seconds) to block before timing
|
||||
* out while selecting curl handles. Defaults to 1 second.
|
||||
* - options: An associative array of CURLMOPT_* options and
|
||||
* corresponding values for curl_multi_setopt()
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->factory = $options['handle_factory'] ?? new CurlFactory(50);
|
||||
|
||||
if (isset($options['select_timeout'])) {
|
||||
$this->selectTimeout = $options['select_timeout'];
|
||||
} elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
|
||||
@trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED);
|
||||
$this->selectTimeout = (int) $selectTimeout;
|
||||
} else {
|
||||
$this->selectTimeout = 1;
|
||||
}
|
||||
|
||||
$this->options = $options['options'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return resource|\CurlMultiHandle
|
||||
*
|
||||
* @throws \BadMethodCallException when another field as `_mh` will be gotten
|
||||
* @throws \RuntimeException when curl can not initialize a multi handle
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name !== '_mh') {
|
||||
throw new \BadMethodCallException("Can not get other property as '_mh'.");
|
||||
}
|
||||
|
||||
$multiHandle = \curl_multi_init();
|
||||
|
||||
if (false === $multiHandle) {
|
||||
throw new \RuntimeException('Can not initialize curl multi handle.');
|
||||
}
|
||||
|
||||
$this->_mh = $multiHandle;
|
||||
|
||||
foreach ($this->options as $option => $value) {
|
||||
// A warning is raised in case of a wrong option.
|
||||
curl_multi_setopt($this->_mh, $option, $value);
|
||||
}
|
||||
|
||||
return $this->_mh;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->_mh)) {
|
||||
\curl_multi_close($this->_mh);
|
||||
unset($this->_mh);
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
$easy = $this->factory->create($request, $options);
|
||||
$id = (int) $easy->handle;
|
||||
|
||||
$promise = new Promise(
|
||||
[$this, 'execute'],
|
||||
function () use ($id) {
|
||||
return $this->cancel($id);
|
||||
}
|
||||
);
|
||||
|
||||
$this->addRequest(['easy' => $easy, 'deferred' => $promise]);
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticks the curl event loop.
|
||||
*/
|
||||
public function tick(): void
|
||||
{
|
||||
// Add any delayed handles if needed.
|
||||
if ($this->delays) {
|
||||
$currentTime = Utils::currentTime();
|
||||
foreach ($this->delays as $id => $delay) {
|
||||
if ($currentTime >= $delay) {
|
||||
unset($this->delays[$id]);
|
||||
\curl_multi_add_handle(
|
||||
$this->_mh,
|
||||
$this->handles[$id]['easy']->handle
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step through the task queue which may add additional requests.
|
||||
P\Utils::queue()->run();
|
||||
|
||||
if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) {
|
||||
// Perform a usleep if a select returns -1.
|
||||
// See: https://bugs.php.net/bug.php?id=61141
|
||||
\usleep(250);
|
||||
}
|
||||
|
||||
while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM);
|
||||
|
||||
$this->processMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs until all outstanding connections have completed.
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
$queue = P\Utils::queue();
|
||||
|
||||
while ($this->handles || !$queue->isEmpty()) {
|
||||
// If there are no transfers, then sleep for the next delay
|
||||
if (!$this->active && $this->delays) {
|
||||
\usleep($this->timeToNext());
|
||||
}
|
||||
$this->tick();
|
||||
}
|
||||
}
|
||||
|
||||
private function addRequest(array $entry): void
|
||||
{
|
||||
$easy = $entry['easy'];
|
||||
$id = (int) $easy->handle;
|
||||
$this->handles[$id] = $entry;
|
||||
if (empty($easy->options['delay'])) {
|
||||
\curl_multi_add_handle($this->_mh, $easy->handle);
|
||||
} else {
|
||||
$this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a handle from sending and removes references to it.
|
||||
*
|
||||
* @param int $id Handle ID to cancel and remove.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
private function cancel($id): bool
|
||||
{
|
||||
// Cannot cancel if it has been processed.
|
||||
if (!isset($this->handles[$id])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$handle = $this->handles[$id]['easy']->handle;
|
||||
unset($this->delays[$id], $this->handles[$id]);
|
||||
\curl_multi_remove_handle($this->_mh, $handle);
|
||||
\curl_close($handle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function processMessages(): void
|
||||
{
|
||||
while ($done = \curl_multi_info_read($this->_mh)) {
|
||||
$id = (int) $done['handle'];
|
||||
\curl_multi_remove_handle($this->_mh, $done['handle']);
|
||||
|
||||
if (!isset($this->handles[$id])) {
|
||||
// Probably was cancelled.
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = $this->handles[$id];
|
||||
unset($this->handles[$id], $this->delays[$id]);
|
||||
$entry['easy']->errno = $done['result'];
|
||||
$entry['deferred']->resolve(
|
||||
CurlFactory::finish($this, $entry['easy'], $this->factory)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function timeToNext(): int
|
||||
{
|
||||
$currentTime = Utils::currentTime();
|
||||
$nextTime = \PHP_INT_MAX;
|
||||
foreach ($this->delays as $time) {
|
||||
if ($time < $nextTime) {
|
||||
$nextTime = $time;
|
||||
}
|
||||
}
|
||||
|
||||
return ((int) \max(0, $nextTime - $currentTime)) * 1000000;
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Represents a cURL easy handle and the data it populates.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EasyHandle
|
||||
{
|
||||
/**
|
||||
* @var resource|\CurlHandle cURL resource
|
||||
*/
|
||||
public $handle;
|
||||
|
||||
/**
|
||||
* @var StreamInterface Where data is being written
|
||||
*/
|
||||
public $sink;
|
||||
|
||||
/**
|
||||
* @var array Received HTTP headers so far
|
||||
*/
|
||||
public $headers = [];
|
||||
|
||||
/**
|
||||
* @var ResponseInterface|null Received response (if any)
|
||||
*/
|
||||
public $response;
|
||||
|
||||
/**
|
||||
* @var RequestInterface Request being sent
|
||||
*/
|
||||
public $request;
|
||||
|
||||
/**
|
||||
* @var array Request options
|
||||
*/
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* @var int cURL error number (if any)
|
||||
*/
|
||||
public $errno = 0;
|
||||
|
||||
/**
|
||||
* @var \Throwable|null Exception during on_headers (if any)
|
||||
*/
|
||||
public $onHeadersException;
|
||||
|
||||
/**
|
||||
* @var \Exception|null Exception during createResponse (if any)
|
||||
*/
|
||||
public $createResponseException;
|
||||
|
||||
/**
|
||||
* Attach a response to the easy handle based on the received headers.
|
||||
*
|
||||
* @throws \RuntimeException if no headers have been received or the first
|
||||
* header line is invalid.
|
||||
*/
|
||||
public function createResponse(): void
|
||||
{
|
||||
[$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($this->headers);
|
||||
|
||||
$normalizedKeys = Utils::normalizeHeaderKeys($headers);
|
||||
|
||||
if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) {
|
||||
$headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
|
||||
unset($headers[$normalizedKeys['content-encoding']]);
|
||||
if (isset($normalizedKeys['content-length'])) {
|
||||
$headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
|
||||
|
||||
$bodyLength = (int) $this->sink->getSize();
|
||||
if ($bodyLength) {
|
||||
$headers[$normalizedKeys['content-length']] = $bodyLength;
|
||||
} else {
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach a response to the easy handle with the parsed headers.
|
||||
$this->response = new Response(
|
||||
$status,
|
||||
$headers,
|
||||
$this->sink,
|
||||
$ver,
|
||||
$reason
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
$msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name;
|
||||
throw new \BadMethodCallException($msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Utils;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HeaderProcessor
|
||||
{
|
||||
/**
|
||||
* Returns the HTTP version, status code, reason phrase, and headers.
|
||||
*
|
||||
* @param string[] $headers
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*
|
||||
* @return array{0:string, 1:int, 2:?string, 3:array}
|
||||
*/
|
||||
public static function parseHeaders(array $headers): array
|
||||
{
|
||||
if ($headers === []) {
|
||||
throw new \RuntimeException('Expected a non-empty array of header data');
|
||||
}
|
||||
|
||||
$parts = \explode(' ', \array_shift($headers), 3);
|
||||
$version = \explode('/', $parts[0])[1] ?? null;
|
||||
|
||||
if ($version === null) {
|
||||
throw new \RuntimeException('HTTP version missing from header data');
|
||||
}
|
||||
|
||||
$status = $parts[1] ?? null;
|
||||
|
||||
if ($status === null) {
|
||||
throw new \RuntimeException('HTTP status code missing from header data');
|
||||
}
|
||||
|
||||
return [$version, (int) $status, $parts[2] ?? null, Utils::headersFromLines($headers)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* Handler that returns responses or throw exceptions from a queue.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class MockHandler implements \Countable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $queue = [];
|
||||
|
||||
/**
|
||||
* @var RequestInterface|null
|
||||
*/
|
||||
private $lastRequest;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lastOptions = [];
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $onFulfilled;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $onRejected;
|
||||
|
||||
/**
|
||||
* Creates a new MockHandler that uses the default handler stack list of
|
||||
* middlewares.
|
||||
*
|
||||
* @param array|null $queue Array of responses, callables, or exceptions.
|
||||
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
|
||||
* @param callable|null $onRejected Callback to invoke when the return value is rejected.
|
||||
*/
|
||||
public static function createWithMiddleware(array $queue = null, callable $onFulfilled = null, callable $onRejected = null): HandlerStack
|
||||
{
|
||||
return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
|
||||
}
|
||||
|
||||
/**
|
||||
* The passed in value must be an array of
|
||||
* {@see \Psr\Http\Message\ResponseInterface} objects, Exceptions,
|
||||
* callables, or Promises.
|
||||
*
|
||||
* @param array<int, mixed>|null $queue The parameters to be passed to the append function, as an indexed array.
|
||||
* @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
|
||||
* @param callable|null $onRejected Callback to invoke when the return value is rejected.
|
||||
*/
|
||||
public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null)
|
||||
{
|
||||
$this->onFulfilled = $onFulfilled;
|
||||
$this->onRejected = $onRejected;
|
||||
|
||||
if ($queue) {
|
||||
// array_values included for BC
|
||||
$this->append(...array_values($queue));
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
if (!$this->queue) {
|
||||
throw new \OutOfBoundsException('Mock queue is empty');
|
||||
}
|
||||
|
||||
if (isset($options['delay']) && \is_numeric($options['delay'])) {
|
||||
\usleep((int) $options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$this->lastRequest = $request;
|
||||
$this->lastOptions = $options;
|
||||
$response = \array_shift($this->queue);
|
||||
|
||||
if (isset($options['on_headers'])) {
|
||||
if (!\is_callable($options['on_headers'])) {
|
||||
throw new \InvalidArgumentException('on_headers must be callable');
|
||||
}
|
||||
try {
|
||||
$options['on_headers']($response);
|
||||
} catch (\Exception $e) {
|
||||
$msg = 'An error was encountered during the on_headers event';
|
||||
$response = new RequestException($msg, $request, $response, $e);
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_callable($response)) {
|
||||
$response = $response($request, $options);
|
||||
}
|
||||
|
||||
$response = $response instanceof \Throwable
|
||||
? P\Create::rejectionFor($response)
|
||||
: P\Create::promiseFor($response);
|
||||
|
||||
return $response->then(
|
||||
function (?ResponseInterface $value) use ($request, $options) {
|
||||
$this->invokeStats($request, $options, $value);
|
||||
if ($this->onFulfilled) {
|
||||
($this->onFulfilled)($value);
|
||||
}
|
||||
|
||||
if ($value !== null && isset($options['sink'])) {
|
||||
$contents = (string) $value->getBody();
|
||||
$sink = $options['sink'];
|
||||
|
||||
if (\is_resource($sink)) {
|
||||
\fwrite($sink, $contents);
|
||||
} elseif (\is_string($sink)) {
|
||||
\file_put_contents($sink, $contents);
|
||||
} elseif ($sink instanceof StreamInterface) {
|
||||
$sink->write($contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function ($reason) use ($request, $options) {
|
||||
$this->invokeStats($request, $options, null, $reason);
|
||||
if ($this->onRejected) {
|
||||
($this->onRejected)($reason);
|
||||
}
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more variadic requests, exceptions, callables, or promises
|
||||
* to the queue.
|
||||
*
|
||||
* @param mixed ...$values
|
||||
*/
|
||||
public function append(...$values): void
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
if ($value instanceof ResponseInterface
|
||||
|| $value instanceof \Throwable
|
||||
|| $value instanceof PromiseInterface
|
||||
|| \is_callable($value)
|
||||
) {
|
||||
$this->queue[] = $value;
|
||||
} else {
|
||||
throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . Utils::describeType($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last received request.
|
||||
*/
|
||||
public function getLastRequest(): ?RequestInterface
|
||||
{
|
||||
return $this->lastRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last received request options.
|
||||
*/
|
||||
public function getLastOptions(): array
|
||||
{
|
||||
return $this->lastOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of remaining items in the queue.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->queue);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $reason Promise or reason.
|
||||
*/
|
||||
private function invokeStats(
|
||||
RequestInterface $request,
|
||||
array $options,
|
||||
ResponseInterface $response = null,
|
||||
$reason = null
|
||||
): void {
|
||||
if (isset($options['on_stats'])) {
|
||||
$transferTime = $options['transfer_time'] ?? 0;
|
||||
$stats = new TransferStats($request, $response, $transferTime, $reason);
|
||||
($options['on_stats'])($stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Provides basic proxies for handlers.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class Proxy
|
||||
{
|
||||
/**
|
||||
* Sends synchronous requests to a specific handler while sending all other
|
||||
* requests to another handler.
|
||||
*
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for normal responses
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $sync Handler used for synchronous responses.
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
|
||||
*/
|
||||
public static function wrapSync(callable $default, callable $sync): callable
|
||||
{
|
||||
return static function (RequestInterface $request, array $options) use ($default, $sync): PromiseInterface {
|
||||
return empty($options[RequestOptions::SYNCHRONOUS]) ? $default($request, $options) : $sync($request, $options);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends streaming requests to a streaming compatible handler while sending
|
||||
* all other requests to a default handler.
|
||||
*
|
||||
* This, for example, could be useful for taking advantage of the
|
||||
* performance benefits of curl while still supporting true streaming
|
||||
* through the StreamHandler.
|
||||
*
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for non-streaming responses
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
|
||||
*/
|
||||
public static function wrapStreaming(callable $default, callable $streaming): callable
|
||||
{
|
||||
return static function (RequestInterface $request, array $options) use ($default, $streaming): PromiseInterface {
|
||||
return empty($options['stream']) ? $default($request, $options) : $streaming($request, $options);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Handler;
|
||||
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\FulfilledPromise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use GuzzleHttp\Utils;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* HTTP handler that uses PHP's HTTP stream wrapper.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class StreamHandler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lastHeaders = [];
|
||||
|
||||
/**
|
||||
* Sends an HTTP request.
|
||||
*
|
||||
* @param RequestInterface $request Request to send.
|
||||
* @param array $options Request transfer options.
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
// Sleep if there is a delay specified.
|
||||
if (isset($options['delay'])) {
|
||||
\usleep($options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
|
||||
|
||||
try {
|
||||
// Does not support the expect header.
|
||||
$request = $request->withoutHeader('Expect');
|
||||
|
||||
// Append a content-length header if body size is zero to match
|
||||
// cURL's behavior.
|
||||
if (0 === $request->getBody()->getSize()) {
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
}
|
||||
|
||||
return $this->createResponse(
|
||||
$request,
|
||||
$options,
|
||||
$this->createStream($request, $options),
|
||||
$startTime
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
// Determine if the error was a networking error.
|
||||
$message = $e->getMessage();
|
||||
// This list can probably get more comprehensive.
|
||||
if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed
|
||||
|| false !== \strpos($message, 'Connection refused')
|
||||
|| false !== \strpos($message, "couldn't connect to host") // error on HHVM
|
||||
|| false !== \strpos($message, "connection attempt failed")
|
||||
) {
|
||||
$e = new ConnectException($e->getMessage(), $request, $e);
|
||||
} else {
|
||||
$e = RequestException::wrapException($request, $e);
|
||||
}
|
||||
$this->invokeStats($options, $request, $startTime, null, $e);
|
||||
|
||||
return P\Create::rejectionFor($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function invokeStats(
|
||||
array $options,
|
||||
RequestInterface $request,
|
||||
?float $startTime,
|
||||
ResponseInterface $response = null,
|
||||
\Throwable $error = null
|
||||
): void {
|
||||
if (isset($options['on_stats'])) {
|
||||
$stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []);
|
||||
($options['on_stats'])($stats);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
*/
|
||||
private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface
|
||||
{
|
||||
$hdrs = $this->lastHeaders;
|
||||
$this->lastHeaders = [];
|
||||
|
||||
try {
|
||||
[$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs);
|
||||
} catch (\Exception $e) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException('An error was encountered while creating the response', $request, null, $e)
|
||||
);
|
||||
}
|
||||
|
||||
[$stream, $headers] = $this->checkDecode($options, $headers, $stream);
|
||||
$stream = Psr7\Utils::streamFor($stream);
|
||||
$sink = $stream;
|
||||
|
||||
if (\strcasecmp('HEAD', $request->getMethod())) {
|
||||
$sink = $this->createSink($stream, $options);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
|
||||
} catch (\Exception $e) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException('An error was encountered while creating the response', $request, null, $e)
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($options['on_headers'])) {
|
||||
try {
|
||||
$options['on_headers']($response);
|
||||
} catch (\Exception $e) {
|
||||
return P\Create::rejectionFor(
|
||||
new RequestException('An error was encountered during the on_headers event', $request, $response, $e)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Do not drain when the request is a HEAD request because they have
|
||||
// no body.
|
||||
if ($sink !== $stream) {
|
||||
$this->drain($stream, $sink, $response->getHeaderLine('Content-Length'));
|
||||
}
|
||||
|
||||
$this->invokeStats($options, $request, $startTime, $response, null);
|
||||
|
||||
return new FulfilledPromise($response);
|
||||
}
|
||||
|
||||
private function createSink(StreamInterface $stream, array $options): StreamInterface
|
||||
{
|
||||
if (!empty($options['stream'])) {
|
||||
return $stream;
|
||||
}
|
||||
|
||||
$sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+');
|
||||
|
||||
return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
*/
|
||||
private function checkDecode(array $options, array $headers, $stream): array
|
||||
{
|
||||
// Automatically decode responses when instructed.
|
||||
if (!empty($options['decode_content'])) {
|
||||
$normalizedKeys = Utils::normalizeHeaderKeys($headers);
|
||||
if (isset($normalizedKeys['content-encoding'])) {
|
||||
$encoding = $headers[$normalizedKeys['content-encoding']];
|
||||
if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
|
||||
$stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream));
|
||||
$headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
|
||||
|
||||
// Remove content-encoding header
|
||||
unset($headers[$normalizedKeys['content-encoding']]);
|
||||
|
||||
// Fix content-length header
|
||||
if (isset($normalizedKeys['content-length'])) {
|
||||
$headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
|
||||
$length = (int) $stream->getSize();
|
||||
if ($length === 0) {
|
||||
unset($headers[$normalizedKeys['content-length']]);
|
||||
} else {
|
||||
$headers[$normalizedKeys['content-length']] = [$length];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$stream, $headers];
|
||||
}
|
||||
|
||||
/**
|
||||
* Drains the source stream into the "sink" client option.
|
||||
*
|
||||
* @param string $contentLength Header specifying the amount of
|
||||
* data to read.
|
||||
*
|
||||
* @throws \RuntimeException when the sink option is invalid.
|
||||
*/
|
||||
private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface
|
||||
{
|
||||
// If a content-length header is provided, then stop reading once
|
||||
// that number of bytes has been read. This can prevent infinitely
|
||||
// reading from a stream when dealing with servers that do not honor
|
||||
// Connection: Close headers.
|
||||
Psr7\Utils::copyToStream(
|
||||
$source,
|
||||
$sink,
|
||||
(\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
|
||||
);
|
||||
|
||||
$sink->seek(0);
|
||||
$source->close();
|
||||
|
||||
return $sink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resource and check to ensure it was created successfully
|
||||
*
|
||||
* @param callable $callback Callable that returns stream resource
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @throws \RuntimeException on error
|
||||
*/
|
||||
private function createResource(callable $callback)
|
||||
{
|
||||
$errors = [];
|
||||
\set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool {
|
||||
$errors[] = [
|
||||
'message' => $msg,
|
||||
'file' => $file,
|
||||
'line' => $line
|
||||
];
|
||||
return true;
|
||||
});
|
||||
|
||||
$resource = $callback();
|
||||
\restore_error_handler();
|
||||
|
||||
if (!$resource) {
|
||||
$message = 'Error creating resource: ';
|
||||
foreach ($errors as $err) {
|
||||
foreach ($err as $key => $value) {
|
||||
$message .= "[$key] $value" . \PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(\trim($message));
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
private function createStream(RequestInterface $request, array $options)
|
||||
{
|
||||
static $methods;
|
||||
if (!$methods) {
|
||||
$methods = \array_flip(\get_class_methods(__CLASS__));
|
||||
}
|
||||
|
||||
// HTTP/1.1 streams using the PHP stream wrapper require a
|
||||
// Connection: close header
|
||||
if ($request->getProtocolVersion() == '1.1'
|
||||
&& !$request->hasHeader('Connection')
|
||||
) {
|
||||
$request = $request->withHeader('Connection', 'close');
|
||||
}
|
||||
|
||||
// Ensure SSL is verified by default
|
||||
if (!isset($options['verify'])) {
|
||||
$options['verify'] = true;
|
||||
}
|
||||
|
||||
$params = [];
|
||||
$context = $this->getDefaultContext($request);
|
||||
|
||||
if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) {
|
||||
throw new \InvalidArgumentException('on_headers must be callable');
|
||||
}
|
||||
|
||||
if (!empty($options)) {
|
||||
foreach ($options as $key => $value) {
|
||||
$method = "add_{$key}";
|
||||
if (isset($methods[$method])) {
|
||||
$this->{$method}($request, $context, $value, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['stream_context'])) {
|
||||
if (!\is_array($options['stream_context'])) {
|
||||
throw new \InvalidArgumentException('stream_context must be an array');
|
||||
}
|
||||
$context = \array_replace_recursive($context, $options['stream_context']);
|
||||
}
|
||||
|
||||
// Microsoft NTLM authentication only supported with curl handler
|
||||
if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) {
|
||||
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
|
||||
}
|
||||
|
||||
$uri = $this->resolveHost($request, $options);
|
||||
|
||||
$contextResource = $this->createResource(
|
||||
static function () use ($context, $params) {
|
||||
return \stream_context_create($context, $params);
|
||||
}
|
||||
);
|
||||
|
||||
return $this->createResource(
|
||||
function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) {
|
||||
$resource = @\fopen((string) $uri, 'r', false, $contextResource);
|
||||
$this->lastHeaders = $http_response_header;
|
||||
|
||||
if (false === $resource) {
|
||||
throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context);
|
||||
}
|
||||
|
||||
if (isset($options['read_timeout'])) {
|
||||
$readTimeout = $options['read_timeout'];
|
||||
$sec = (int) $readTimeout;
|
||||
$usec = ($readTimeout - $sec) * 100000;
|
||||
\stream_set_timeout($resource, $sec, $usec);
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function resolveHost(RequestInterface $request, array $options): UriInterface
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
|
||||
if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) {
|
||||
if ('v4' === $options['force_ip_resolve']) {
|
||||
$records = \dns_get_record($uri->getHost(), \DNS_A);
|
||||
if (false === $records || !isset($records[0]['ip'])) {
|
||||
throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
|
||||
}
|
||||
return $uri->withHost($records[0]['ip']);
|
||||
}
|
||||
if ('v6' === $options['force_ip_resolve']) {
|
||||
$records = \dns_get_record($uri->getHost(), \DNS_AAAA);
|
||||
if (false === $records || !isset($records[0]['ipv6'])) {
|
||||
throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
|
||||
}
|
||||
return $uri->withHost('[' . $records[0]['ipv6'] . ']');
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function getDefaultContext(RequestInterface $request): array
|
||||
{
|
||||
$headers = '';
|
||||
foreach ($request->getHeaders() as $name => $value) {
|
||||
foreach ($value as $val) {
|
||||
$headers .= "$name: $val\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
$context = [
|
||||
'http' => [
|
||||
'method' => $request->getMethod(),
|
||||
'header' => $headers,
|
||||
'protocol_version' => $request->getProtocolVersion(),
|
||||
'ignore_errors' => true,
|
||||
'follow_location' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
$body = (string) $request->getBody();
|
||||
|
||||
if (!empty($body)) {
|
||||
$context['http']['content'] = $body;
|
||||
// Prevent the HTTP handler from adding a Content-Type header.
|
||||
if (!$request->hasHeader('Content-Type')) {
|
||||
$context['http']['header'] .= "Content-Type:\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
$context['http']['header'] = \rtrim($context['http']['header']);
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
$uri = null;
|
||||
|
||||
if (!\is_array($value)) {
|
||||
$uri = $value;
|
||||
} else {
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
if (isset($value[$scheme])) {
|
||||
if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) {
|
||||
$uri = $value[$scheme];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parsed = $this->parse_proxy($uri);
|
||||
$options['http']['proxy'] = $parsed['proxy'];
|
||||
|
||||
if ($parsed['auth']) {
|
||||
if (!isset($options['http']['header'])) {
|
||||
$options['http']['header'] = [];
|
||||
}
|
||||
$options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given proxy URL to make it compatible with the format PHP's stream context expects.
|
||||
*/
|
||||
private function parse_proxy(string $url): array
|
||||
{
|
||||
$parsed = \parse_url($url);
|
||||
|
||||
if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
|
||||
if (isset($parsed['host']) && isset($parsed['port'])) {
|
||||
$auth = null;
|
||||
if (isset($parsed['user']) && isset($parsed['pass'])) {
|
||||
$auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}");
|
||||
}
|
||||
|
||||
return [
|
||||
'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}",
|
||||
'auth' => $auth ? "Basic {$auth}" : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Return proxy as-is.
|
||||
return [
|
||||
'proxy' => $url,
|
||||
'auth' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
if ($value > 0) {
|
||||
$options['http']['timeout'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
if ($value === false) {
|
||||
$options['ssl']['verify_peer'] = false;
|
||||
$options['ssl']['verify_peer_name'] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_string($value)) {
|
||||
$options['ssl']['cafile'] = $value;
|
||||
if (!\file_exists($value)) {
|
||||
throw new \RuntimeException("SSL CA bundle not found: $value");
|
||||
}
|
||||
} elseif ($value !== true) {
|
||||
throw new \InvalidArgumentException('Invalid verify request option');
|
||||
}
|
||||
|
||||
$options['ssl']['verify_peer'] = true;
|
||||
$options['ssl']['verify_peer_name'] = true;
|
||||
$options['ssl']['allow_self_signed'] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
if (\is_array($value)) {
|
||||
$options['ssl']['passphrase'] = $value[1];
|
||||
$value = $value[0];
|
||||
}
|
||||
|
||||
if (!\file_exists($value)) {
|
||||
throw new \RuntimeException("SSL certificate not found: {$value}");
|
||||
}
|
||||
|
||||
$options['ssl']['local_cert'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
self::addNotification(
|
||||
$params,
|
||||
static function ($code, $a, $b, $c, $transferred, $total) use ($value) {
|
||||
if ($code == \STREAM_NOTIFY_PROGRESS) {
|
||||
$value($total, $transferred, null, null);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value as passed via Request transfer options.
|
||||
*/
|
||||
private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void
|
||||
{
|
||||
if ($value === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $map = [
|
||||
\STREAM_NOTIFY_CONNECT => 'CONNECT',
|
||||
\STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
|
||||
\STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
|
||||
\STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
|
||||
\STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
|
||||
\STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
|
||||
\STREAM_NOTIFY_PROGRESS => 'PROGRESS',
|
||||
\STREAM_NOTIFY_FAILURE => 'FAILURE',
|
||||
\STREAM_NOTIFY_COMPLETED => 'COMPLETED',
|
||||
\STREAM_NOTIFY_RESOLVE => 'RESOLVE',
|
||||
];
|
||||
static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max'];
|
||||
|
||||
$value = Utils::debugResource($value);
|
||||
$ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
|
||||
self::addNotification(
|
||||
$params,
|
||||
static function (int $code, ...$passed) use ($ident, $value, $map, $args): void {
|
||||
\fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
|
||||
foreach (\array_filter($passed) as $i => $v) {
|
||||
\fwrite($value, $args[$i] . ': "' . $v . '" ');
|
||||
}
|
||||
\fwrite($value, "\n");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static function addNotification(array &$params, callable $notify): void
|
||||
{
|
||||
// Wrap the existing function if needed.
|
||||
if (!isset($params['notification'])) {
|
||||
$params['notification'] = $notify;
|
||||
} else {
|
||||
$params['notification'] = self::callArray([
|
||||
$params['notification'],
|
||||
$notify
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function callArray(array $functions): callable
|
||||
{
|
||||
return static function (...$args) use ($functions) {
|
||||
foreach ($functions as $fn) {
|
||||
$fn(...$args);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Creates a composed Guzzle handler function by stacking middlewares on top of
|
||||
* an HTTP handler function.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class HandlerStack
|
||||
{
|
||||
/**
|
||||
* @var null|callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $handler;
|
||||
|
||||
/**
|
||||
* @var array{(callable(callable(RequestInterface, array): PromiseInterface): callable), (string|null)}[]
|
||||
*/
|
||||
private $stack = [];
|
||||
|
||||
/**
|
||||
* @var null|callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $cached;
|
||||
|
||||
/**
|
||||
* Creates a default handler stack that can be used by clients.
|
||||
*
|
||||
* The returned handler will wrap the provided handler or use the most
|
||||
* appropriate default handler for your system. The returned HandlerStack has
|
||||
* support for cookies, redirects, HTTP error exceptions, and preparing a body
|
||||
* before sending.
|
||||
*
|
||||
* The returned handler stack can be passed to a client in the "handler"
|
||||
* option.
|
||||
*
|
||||
* @param null|callable(RequestInterface, array): PromiseInterface $handler HTTP handler function to use with the stack. If no
|
||||
* handler is provided, the best handler for your
|
||||
* system will be utilized.
|
||||
*/
|
||||
public static function create(?callable $handler = null): self
|
||||
{
|
||||
$stack = new self($handler ?: Utils::chooseHandler());
|
||||
$stack->push(Middleware::httpErrors(), 'http_errors');
|
||||
$stack->push(Middleware::redirect(), 'allow_redirects');
|
||||
$stack->push(Middleware::cookies(), 'cookies');
|
||||
$stack->push(Middleware::prepareBody(), 'prepare_body');
|
||||
|
||||
return $stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|callable(RequestInterface, array): PromiseInterface $handler Underlying HTTP handler.
|
||||
*/
|
||||
public function __construct(callable $handler = null)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the handler stack as a composed handler
|
||||
*
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function __invoke(RequestInterface $request, array $options)
|
||||
{
|
||||
$handler = $this->resolve();
|
||||
|
||||
return $handler($request, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a string representation of the stack.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$depth = 0;
|
||||
$stack = [];
|
||||
|
||||
if ($this->handler !== null) {
|
||||
$stack[] = "0) Handler: " . $this->debugCallable($this->handler);
|
||||
}
|
||||
|
||||
$result = '';
|
||||
foreach (\array_reverse($this->stack) as $tuple) {
|
||||
$depth++;
|
||||
$str = "{$depth}) Name: '{$tuple[1]}', ";
|
||||
$str .= "Function: " . $this->debugCallable($tuple[0]);
|
||||
$result = "> {$str}\n{$result}";
|
||||
$stack[] = $str;
|
||||
}
|
||||
|
||||
foreach (\array_keys($stack) as $k) {
|
||||
$result .= "< {$stack[$k]}\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP handler that actually returns a promise.
|
||||
*
|
||||
* @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and
|
||||
* returns a Promise.
|
||||
*/
|
||||
public function setHandler(callable $handler): void
|
||||
{
|
||||
$this->handler = $handler;
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the builder has a handler.
|
||||
*/
|
||||
public function hasHandler(): bool
|
||||
{
|
||||
return $this->handler !== null ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshift a middleware to the bottom of the stack.
|
||||
*
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $name Name to register for this middleware.
|
||||
*/
|
||||
public function unshift(callable $middleware, ?string $name = null): void
|
||||
{
|
||||
\array_unshift($this->stack, [$middleware, $name]);
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a middleware to the top of the stack.
|
||||
*
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $name Name to register for this middleware.
|
||||
*/
|
||||
public function push(callable $middleware, string $name = ''): void
|
||||
{
|
||||
$this->stack[] = [$middleware, $name];
|
||||
$this->cached = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a middleware before another middleware by name.
|
||||
*
|
||||
* @param string $findName Middleware to find
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $withName Name to register for this middleware.
|
||||
*/
|
||||
public function before(string $findName, callable $middleware, string $withName = ''): void
|
||||
{
|
||||
$this->splice($findName, $withName, $middleware, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a middleware after another middleware by name.
|
||||
*
|
||||
* @param string $findName Middleware to find
|
||||
* @param callable(callable): callable $middleware Middleware function
|
||||
* @param string $withName Name to register for this middleware.
|
||||
*/
|
||||
public function after(string $findName, callable $middleware, string $withName = ''): void
|
||||
{
|
||||
$this->splice($findName, $withName, $middleware, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a middleware by instance or name from the stack.
|
||||
*
|
||||
* @param callable|string $remove Middleware to remove by instance or name.
|
||||
*/
|
||||
public function remove($remove): void
|
||||
{
|
||||
$this->cached = null;
|
||||
$idx = \is_callable($remove) ? 0 : 1;
|
||||
$this->stack = \array_values(\array_filter(
|
||||
$this->stack,
|
||||
static function ($tuple) use ($idx, $remove) {
|
||||
return $tuple[$idx] !== $remove;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the middleware and handler into a single callable function.
|
||||
*
|
||||
* @return callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
public function resolve(): callable
|
||||
{
|
||||
if ($this->cached === null) {
|
||||
if (($prev = $this->handler) === null) {
|
||||
throw new \LogicException('No handler has been specified');
|
||||
}
|
||||
|
||||
foreach (\array_reverse($this->stack) as $fn) {
|
||||
/** @var callable(RequestInterface, array): PromiseInterface $prev */
|
||||
$prev = $fn[0]($prev);
|
||||
}
|
||||
|
||||
$this->cached = $prev;
|
||||
}
|
||||
|
||||
return $this->cached;
|
||||
}
|
||||
|
||||
private function findByName(string $name): int
|
||||
{
|
||||
foreach ($this->stack as $k => $v) {
|
||||
if ($v[1] === $name) {
|
||||
return $k;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Middleware not found: $name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Splices a function into the middleware list at a specific position.
|
||||
*/
|
||||
private function splice(string $findName, string $withName, callable $middleware, bool $before): void
|
||||
{
|
||||
$this->cached = null;
|
||||
$idx = $this->findByName($findName);
|
||||
$tuple = [$middleware, $withName];
|
||||
|
||||
if ($before) {
|
||||
if ($idx === 0) {
|
||||
\array_unshift($this->stack, $tuple);
|
||||
} else {
|
||||
$replacement = [$tuple, $this->stack[$idx]];
|
||||
\array_splice($this->stack, $idx, 1, $replacement);
|
||||
}
|
||||
} elseif ($idx === \count($this->stack) - 1) {
|
||||
$this->stack[] = $tuple;
|
||||
} else {
|
||||
$replacement = [$this->stack[$idx], $tuple];
|
||||
\array_splice($this->stack, $idx, 1, $replacement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a debug string for a given callable.
|
||||
*
|
||||
* @param callable $fn Function to write as a string.
|
||||
*/
|
||||
private function debugCallable($fn): string
|
||||
{
|
||||
if (\is_string($fn)) {
|
||||
return "callable({$fn})";
|
||||
}
|
||||
|
||||
if (\is_array($fn)) {
|
||||
return \is_string($fn[0])
|
||||
? "callable({$fn[0]}::{$fn[1]})"
|
||||
: "callable(['" . \get_class($fn[0]) . "', '{$fn[1]}'])";
|
||||
}
|
||||
|
||||
/** @var object $fn */
|
||||
return 'callable(' . \spl_object_hash($fn) . ')';
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Formats log messages using variable substitutions for requests, responses,
|
||||
* and other transactional data.
|
||||
*
|
||||
* The following variable substitutions are supported:
|
||||
*
|
||||
* - {request}: Full HTTP request message
|
||||
* - {response}: Full HTTP response message
|
||||
* - {ts}: ISO 8601 date in GMT
|
||||
* - {date_iso_8601} ISO 8601 date in GMT
|
||||
* - {date_common_log} Apache common log date using the configured timezone.
|
||||
* - {host}: Host of the request
|
||||
* - {method}: Method of the request
|
||||
* - {uri}: URI of the request
|
||||
* - {version}: Protocol version
|
||||
* - {target}: Request target of the request (path + query + fragment)
|
||||
* - {hostname}: Hostname of the machine that sent the request
|
||||
* - {code}: Status code of the response (if available)
|
||||
* - {phrase}: Reason phrase of the response (if available)
|
||||
* - {error}: Any error messages (if available)
|
||||
* - {req_header_*}: Replace `*` with the lowercased name of a request header to add to the message
|
||||
* - {res_header_*}: Replace `*` with the lowercased name of a response header to add to the message
|
||||
* - {req_headers}: Request headers
|
||||
* - {res_headers}: Response headers
|
||||
* - {req_body}: Request body
|
||||
* - {res_body}: Response body
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class MessageFormatter implements MessageFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Apache Common Log Format.
|
||||
*
|
||||
* @link https://httpd.apache.org/docs/2.4/logs.html#common
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CLF = "{hostname} {req_header_User-Agent} - [{date_common_log}] \"{method} {target} HTTP/{version}\" {code} {res_header_Content-Length}";
|
||||
public const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
|
||||
public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
|
||||
|
||||
/**
|
||||
* @var string Template used to format log messages
|
||||
*/
|
||||
private $template;
|
||||
|
||||
/**
|
||||
* @param string $template Log message template
|
||||
*/
|
||||
public function __construct(?string $template = self::CLF)
|
||||
{
|
||||
$this->template = $template ?: self::CLF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted message string.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface|null $response Response that was received
|
||||
* @param \Throwable|null $error Exception that was received
|
||||
*/
|
||||
public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string
|
||||
{
|
||||
$cache = [];
|
||||
|
||||
/** @var string */
|
||||
return \preg_replace_callback(
|
||||
'/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
|
||||
function (array $matches) use ($request, $response, $error, &$cache) {
|
||||
if (isset($cache[$matches[1]])) {
|
||||
return $cache[$matches[1]];
|
||||
}
|
||||
|
||||
$result = '';
|
||||
switch ($matches[1]) {
|
||||
case 'request':
|
||||
$result = Psr7\Message::toString($request);
|
||||
break;
|
||||
case 'response':
|
||||
$result = $response ? Psr7\Message::toString($response) : '';
|
||||
break;
|
||||
case 'req_headers':
|
||||
$result = \trim($request->getMethod()
|
||||
. ' ' . $request->getRequestTarget())
|
||||
. ' HTTP/' . $request->getProtocolVersion() . "\r\n"
|
||||
. $this->headers($request);
|
||||
break;
|
||||
case 'res_headers':
|
||||
$result = $response ?
|
||||
\sprintf(
|
||||
'HTTP/%s %d %s',
|
||||
$response->getProtocolVersion(),
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
) . "\r\n" . $this->headers($response)
|
||||
: 'NULL';
|
||||
break;
|
||||
case 'req_body':
|
||||
$result = $request->getBody()->__toString();
|
||||
break;
|
||||
case 'res_body':
|
||||
if (!$response instanceof ResponseInterface) {
|
||||
$result = 'NULL';
|
||||
break;
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
|
||||
if (!$body->isSeekable()) {
|
||||
$result = 'RESPONSE_NOT_LOGGEABLE';
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $response->getBody()->__toString();
|
||||
break;
|
||||
case 'ts':
|
||||
case 'date_iso_8601':
|
||||
$result = \gmdate('c');
|
||||
break;
|
||||
case 'date_common_log':
|
||||
$result = \date('d/M/Y:H:i:s O');
|
||||
break;
|
||||
case 'method':
|
||||
$result = $request->getMethod();
|
||||
break;
|
||||
case 'version':
|
||||
$result = $request->getProtocolVersion();
|
||||
break;
|
||||
case 'uri':
|
||||
case 'url':
|
||||
$result = $request->getUri();
|
||||
break;
|
||||
case 'target':
|
||||
$result = $request->getRequestTarget();
|
||||
break;
|
||||
case 'req_version':
|
||||
$result = $request->getProtocolVersion();
|
||||
break;
|
||||
case 'res_version':
|
||||
$result = $response
|
||||
? $response->getProtocolVersion()
|
||||
: 'NULL';
|
||||
break;
|
||||
case 'host':
|
||||
$result = $request->getHeaderLine('Host');
|
||||
break;
|
||||
case 'hostname':
|
||||
$result = \gethostname();
|
||||
break;
|
||||
case 'code':
|
||||
$result = $response ? $response->getStatusCode() : 'NULL';
|
||||
break;
|
||||
case 'phrase':
|
||||
$result = $response ? $response->getReasonPhrase() : 'NULL';
|
||||
break;
|
||||
case 'error':
|
||||
$result = $error ? $error->getMessage() : 'NULL';
|
||||
break;
|
||||
default:
|
||||
// handle prefixed dynamic headers
|
||||
if (\strpos($matches[1], 'req_header_') === 0) {
|
||||
$result = $request->getHeaderLine(\substr($matches[1], 11));
|
||||
} elseif (\strpos($matches[1], 'res_header_') === 0) {
|
||||
$result = $response
|
||||
? $response->getHeaderLine(\substr($matches[1], 11))
|
||||
: 'NULL';
|
||||
}
|
||||
}
|
||||
|
||||
$cache[$matches[1]] = $result;
|
||||
return $result;
|
||||
},
|
||||
$this->template
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers from message as string
|
||||
*/
|
||||
private function headers(MessageInterface $message): string
|
||||
{
|
||||
$result = '';
|
||||
foreach ($message->getHeaders() as $name => $values) {
|
||||
$result .= $name . ': ' . \implode(', ', $values) . "\r\n";
|
||||
}
|
||||
|
||||
return \trim($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
interface MessageFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Returns a formatted message string.
|
||||
*
|
||||
* @param RequestInterface $request Request that was sent
|
||||
* @param ResponseInterface|null $response Response that was received
|
||||
* @param \Throwable|null $error Exception that was received
|
||||
*/
|
||||
public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string;
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Cookie\CookieJarInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Functions used to create and wrap handlers with handler middleware.
|
||||
*/
|
||||
final class Middleware
|
||||
{
|
||||
/**
|
||||
* Middleware that adds cookies to requests.
|
||||
*
|
||||
* The options array must be set to a CookieJarInterface in order to use
|
||||
* cookies. This is typically handled for you by a client.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function cookies(): callable
|
||||
{
|
||||
return static function (callable $handler): callable {
|
||||
return static function ($request, array $options) use ($handler) {
|
||||
if (empty($options['cookies'])) {
|
||||
return $handler($request, $options);
|
||||
} elseif (!($options['cookies'] instanceof CookieJarInterface)) {
|
||||
throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
|
||||
}
|
||||
$cookieJar = $options['cookies'];
|
||||
$request = $cookieJar->withCookieHeader($request);
|
||||
return $handler($request, $options)
|
||||
->then(
|
||||
static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface {
|
||||
$cookieJar->extractCookies($request, $response);
|
||||
return $response;
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that throws exceptions for 4xx or 5xx responses when the
|
||||
* "http_errors" request option is set to true.
|
||||
*
|
||||
* @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages.
|
||||
*
|
||||
* @return callable(callable): callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable
|
||||
{
|
||||
return static function (callable $handler) use ($bodySummarizer): callable {
|
||||
return static function ($request, array $options) use ($handler, $bodySummarizer) {
|
||||
if (empty($options['http_errors'])) {
|
||||
return $handler($request, $options);
|
||||
}
|
||||
return $handler($request, $options)->then(
|
||||
static function (ResponseInterface $response) use ($request, $bodySummarizer) {
|
||||
$code = $response->getStatusCode();
|
||||
if ($code < 400) {
|
||||
return $response;
|
||||
}
|
||||
throw RequestException::create($request, $response, null, [], $bodySummarizer);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that pushes history data to an ArrayAccess container.
|
||||
*
|
||||
* @param array|\ArrayAccess<int, array> $container Container to hold the history (by reference).
|
||||
*
|
||||
* @return callable(callable): callable Returns a function that accepts the next handler.
|
||||
*
|
||||
* @throws \InvalidArgumentException if container is not an array or ArrayAccess.
|
||||
*/
|
||||
public static function history(&$container): callable
|
||||
{
|
||||
if (!\is_array($container) && !$container instanceof \ArrayAccess) {
|
||||
throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
|
||||
}
|
||||
|
||||
return static function (callable $handler) use (&$container): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, &$container) {
|
||||
return $handler($request, $options)->then(
|
||||
static function ($value) use ($request, &$container, $options) {
|
||||
$container[] = [
|
||||
'request' => $request,
|
||||
'response' => $value,
|
||||
'error' => null,
|
||||
'options' => $options
|
||||
];
|
||||
return $value;
|
||||
},
|
||||
static function ($reason) use ($request, &$container, $options) {
|
||||
$container[] = [
|
||||
'request' => $request,
|
||||
'response' => null,
|
||||
'error' => $reason,
|
||||
'options' => $options
|
||||
];
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that invokes a callback before and after sending a request.
|
||||
*
|
||||
* The provided listener cannot modify or alter the response. It simply
|
||||
* "taps" into the chain to be notified before returning the promise. The
|
||||
* before listener accepts a request and options array, and the after
|
||||
* listener accepts a request, options array, and response promise.
|
||||
*
|
||||
* @param callable $before Function to invoke before forwarding the request.
|
||||
* @param callable $after Function invoked after forwarding.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function tap(callable $before = null, callable $after = null): callable
|
||||
{
|
||||
return static function (callable $handler) use ($before, $after): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, $before, $after) {
|
||||
if ($before) {
|
||||
$before($request, $options);
|
||||
}
|
||||
$response = $handler($request, $options);
|
||||
if ($after) {
|
||||
$after($request, $options, $response);
|
||||
}
|
||||
return $response;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that handles request redirects.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function redirect(): callable
|
||||
{
|
||||
return static function (callable $handler): RedirectMiddleware {
|
||||
return new RedirectMiddleware($handler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that retries requests based on the boolean result of
|
||||
* invoking the provided "decider" function.
|
||||
*
|
||||
* If no delay function is provided, a simple implementation of exponential
|
||||
* backoff will be utilized.
|
||||
*
|
||||
* @param callable $decider Function that accepts the number of retries,
|
||||
* a request, [response], and [exception] and
|
||||
* returns true if the request is to be retried.
|
||||
* @param callable $delay Function that accepts the number of retries and
|
||||
* returns the number of milliseconds to delay.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function retry(callable $decider, callable $delay = null): callable
|
||||
{
|
||||
return static function (callable $handler) use ($decider, $delay): RetryMiddleware {
|
||||
return new RetryMiddleware($decider, $handler, $delay);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that logs requests, responses, and errors using a message
|
||||
* formatter.
|
||||
*
|
||||
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
|
||||
*
|
||||
* @param LoggerInterface $logger Logs messages.
|
||||
* @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
|
||||
* @param string $logLevel Level at which to log requests.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable
|
||||
{
|
||||
// To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter
|
||||
if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) {
|
||||
throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class));
|
||||
}
|
||||
|
||||
return static function (callable $handler) use ($logger, $formatter, $logLevel): callable {
|
||||
return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) {
|
||||
return $handler($request, $options)->then(
|
||||
static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface {
|
||||
$message = $formatter->format($request, $response);
|
||||
$logger->log($logLevel, $message);
|
||||
return $response;
|
||||
},
|
||||
static function ($reason) use ($logger, $request, $formatter): PromiseInterface {
|
||||
$response = $reason instanceof RequestException ? $reason->getResponse() : null;
|
||||
$message = $formatter->format($request, $response, P\Create::exceptionFor($reason));
|
||||
$logger->error($message);
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This middleware adds a default content-type if possible, a default
|
||||
* content-length or transfer-encoding header, and the expect header.
|
||||
*/
|
||||
public static function prepareBody(): callable
|
||||
{
|
||||
return static function (callable $handler): PrepareBodyMiddleware {
|
||||
return new PrepareBodyMiddleware($handler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that applies a map function to the request before passing to
|
||||
* the next handler.
|
||||
*
|
||||
* @param callable $fn Function that accepts a RequestInterface and returns
|
||||
* a RequestInterface.
|
||||
*/
|
||||
public static function mapRequest(callable $fn): callable
|
||||
{
|
||||
return static function (callable $handler) use ($fn): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, $fn) {
|
||||
return $handler($fn($request), $options);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that applies a map function to the resolved promise's
|
||||
* response.
|
||||
*
|
||||
* @param callable $fn Function that accepts a ResponseInterface and
|
||||
* returns a ResponseInterface.
|
||||
*/
|
||||
public static function mapResponse(callable $fn): callable
|
||||
{
|
||||
return static function (callable $handler) use ($fn): callable {
|
||||
return static function (RequestInterface $request, array $options) use ($handler, $fn) {
|
||||
return $handler($request, $options)->then($fn);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\EachPromise;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Promise\PromisorInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Sends an iterator of requests concurrently using a capped pool size.
|
||||
*
|
||||
* The pool will read from an iterator until it is cancelled or until the
|
||||
* iterator is consumed. When a request is yielded, the request is sent after
|
||||
* applying the "request_options" request options (if provided in the ctor).
|
||||
*
|
||||
* When a function is yielded by the iterator, the function is provided the
|
||||
* "request_options" array that should be merged on top of any existing
|
||||
* options, and the function MUST then return a wait-able promise.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class Pool implements PromisorInterface
|
||||
{
|
||||
/**
|
||||
* @var EachPromise
|
||||
*/
|
||||
private $each;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client used to send the requests.
|
||||
* @param array|\Iterator $requests Requests or functions that return
|
||||
* requests to send concurrently.
|
||||
* @param array $config Associative array of options
|
||||
* - concurrency: (int) Maximum number of requests to send concurrently
|
||||
* - options: Array of request options to apply to each request.
|
||||
* - fulfilled: (callable) Function to invoke when a request completes.
|
||||
* - rejected: (callable) Function to invoke when a request is rejected.
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $requests, array $config = [])
|
||||
{
|
||||
if (!isset($config['concurrency'])) {
|
||||
$config['concurrency'] = 25;
|
||||
}
|
||||
|
||||
if (isset($config['options'])) {
|
||||
$opts = $config['options'];
|
||||
unset($config['options']);
|
||||
} else {
|
||||
$opts = [];
|
||||
}
|
||||
|
||||
$iterable = P\Create::iterFor($requests);
|
||||
$requests = static function () use ($iterable, $client, $opts) {
|
||||
foreach ($iterable as $key => $rfn) {
|
||||
if ($rfn instanceof RequestInterface) {
|
||||
yield $key => $client->sendAsync($rfn, $opts);
|
||||
} elseif (\is_callable($rfn)) {
|
||||
yield $key => $rfn($opts);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$this->each = new EachPromise($requests(), $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get promise
|
||||
*/
|
||||
public function promise(): PromiseInterface
|
||||
{
|
||||
return $this->each->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends multiple requests concurrently and returns an array of responses
|
||||
* and exceptions that uses the same ordering as the provided requests.
|
||||
*
|
||||
* IMPORTANT: This method keeps every request and response in memory, and
|
||||
* as such, is NOT recommended when sending a large number or an
|
||||
* indeterminate number of requests concurrently.
|
||||
*
|
||||
* @param ClientInterface $client Client used to send the requests
|
||||
* @param array|\Iterator $requests Requests to send concurrently.
|
||||
* @param array $options Passes through the options available in
|
||||
* {@see \GuzzleHttp\Pool::__construct}
|
||||
*
|
||||
* @return array Returns an array containing the response or an exception
|
||||
* in the same order that the requests were sent.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the event format is incorrect.
|
||||
*/
|
||||
public static function batch(ClientInterface $client, $requests, array $options = []): array
|
||||
{
|
||||
$res = [];
|
||||
self::cmpCallback($options, 'fulfilled', $res);
|
||||
self::cmpCallback($options, 'rejected', $res);
|
||||
$pool = new static($client, $requests, $options);
|
||||
$pool->promise()->wait();
|
||||
\ksort($res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute callback(s)
|
||||
*/
|
||||
private static function cmpCallback(array &$options, string $name, array &$results): void
|
||||
{
|
||||
if (!isset($options[$name])) {
|
||||
$options[$name] = static function ($v, $k) use (&$results) {
|
||||
$results[$k] = $v;
|
||||
};
|
||||
} else {
|
||||
$currentFn = $options[$name];
|
||||
$options[$name] = static function ($v, $k) use (&$results, $currentFn) {
|
||||
$currentFn($v, $k);
|
||||
$results[$k] = $v;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Prepares requests that contain a body, adding the Content-Length,
|
||||
* Content-Type, and Expect headers.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class PrepareBodyMiddleware
|
||||
{
|
||||
/**
|
||||
* @var callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
|
||||
*/
|
||||
public function __construct(callable $nextHandler)
|
||||
{
|
||||
$this->nextHandler = $nextHandler;
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
$fn = $this->nextHandler;
|
||||
|
||||
// Don't do anything if the request has no body.
|
||||
if ($request->getBody()->getSize() === 0) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
$modify = [];
|
||||
|
||||
// Add a default content-type if possible.
|
||||
if (!$request->hasHeader('Content-Type')) {
|
||||
if ($uri = $request->getBody()->getMetadata('uri')) {
|
||||
if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) {
|
||||
$modify['set_headers']['Content-Type'] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a default content-length or transfer-encoding header.
|
||||
if (!$request->hasHeader('Content-Length')
|
||||
&& !$request->hasHeader('Transfer-Encoding')
|
||||
) {
|
||||
$size = $request->getBody()->getSize();
|
||||
if ($size !== null) {
|
||||
$modify['set_headers']['Content-Length'] = $size;
|
||||
} else {
|
||||
$modify['set_headers']['Transfer-Encoding'] = 'chunked';
|
||||
}
|
||||
}
|
||||
|
||||
// Add the expect header if needed.
|
||||
$this->addExpectHeader($request, $options, $modify);
|
||||
|
||||
return $fn(Psr7\Utils::modifyRequest($request, $modify), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add expect header
|
||||
*/
|
||||
private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void
|
||||
{
|
||||
// Determine if the Expect header should be used
|
||||
if ($request->hasHeader('Expect')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$expect = $options['expect'] ?? null;
|
||||
|
||||
// Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
|
||||
if ($expect === false || $request->getProtocolVersion() < 1.1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The expect header is unconditionally enabled
|
||||
if ($expect === true) {
|
||||
$modify['set_headers']['Expect'] = '100-Continue';
|
||||
return;
|
||||
}
|
||||
|
||||
// By default, send the expect header when the payload is > 1mb
|
||||
if ($expect === null) {
|
||||
$expect = 1048576;
|
||||
}
|
||||
|
||||
// Always add if the body cannot be rewound, the size cannot be
|
||||
// determined, or the size is greater than the cutoff threshold
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
|
||||
$modify['set_headers']['Expect'] = '100-Continue';
|
||||
}
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Exception\TooManyRedirectsException;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Request redirect middleware.
|
||||
*
|
||||
* Apply this middleware like other middleware using
|
||||
* {@see \GuzzleHttp\Middleware::redirect()}.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class RedirectMiddleware
|
||||
{
|
||||
public const HISTORY_HEADER = 'X-Guzzle-Redirect-History';
|
||||
|
||||
public const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $defaultSettings = [
|
||||
'max' => 5,
|
||||
'protocols' => ['http', 'https'],
|
||||
'strict' => false,
|
||||
'referer' => false,
|
||||
'track_redirects' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
|
||||
*/
|
||||
public function __construct(callable $nextHandler)
|
||||
{
|
||||
$this->nextHandler = $nextHandler;
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
$fn = $this->nextHandler;
|
||||
|
||||
if (empty($options['allow_redirects'])) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
if ($options['allow_redirects'] === true) {
|
||||
$options['allow_redirects'] = self::$defaultSettings;
|
||||
} elseif (!\is_array($options['allow_redirects'])) {
|
||||
throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
|
||||
} else {
|
||||
// Merge the default settings with the provided settings
|
||||
$options['allow_redirects'] += self::$defaultSettings;
|
||||
}
|
||||
|
||||
if (empty($options['allow_redirects']['max'])) {
|
||||
return $fn($request, $options);
|
||||
}
|
||||
|
||||
return $fn($request, $options)
|
||||
->then(function (ResponseInterface $response) use ($request, $options) {
|
||||
return $this->checkRedirect($request, $options, $response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response)
|
||||
{
|
||||
if (\strpos((string) $response->getStatusCode(), '3') !== 0
|
||||
|| !$response->hasHeader('Location')
|
||||
) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->guardMax($request, $response, $options);
|
||||
$nextRequest = $this->modifyRequest($request, $options, $response);
|
||||
|
||||
if (isset($options['allow_redirects']['on_redirect'])) {
|
||||
($options['allow_redirects']['on_redirect'])(
|
||||
$request,
|
||||
$response,
|
||||
$nextRequest->getUri()
|
||||
);
|
||||
}
|
||||
|
||||
$promise = $this($nextRequest, $options);
|
||||
|
||||
// Add headers to be able to track history of redirects.
|
||||
if (!empty($options['allow_redirects']['track_redirects'])) {
|
||||
return $this->withTracking(
|
||||
$promise,
|
||||
(string) $nextRequest->getUri(),
|
||||
$response->getStatusCode()
|
||||
);
|
||||
}
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable tracking on promise.
|
||||
*/
|
||||
private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface
|
||||
{
|
||||
return $promise->then(
|
||||
static function (ResponseInterface $response) use ($uri, $statusCode) {
|
||||
// Note that we are pushing to the front of the list as this
|
||||
// would be an earlier response than what is currently present
|
||||
// in the history header.
|
||||
$historyHeader = $response->getHeader(self::HISTORY_HEADER);
|
||||
$statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
|
||||
\array_unshift($historyHeader, $uri);
|
||||
\array_unshift($statusHeader, (string) $statusCode);
|
||||
|
||||
return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
|
||||
->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for too many redirects
|
||||
*
|
||||
* @throws TooManyRedirectsException Too many redirects.
|
||||
*/
|
||||
private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void
|
||||
{
|
||||
$current = $options['__redirect_count']
|
||||
?? 0;
|
||||
$options['__redirect_count'] = $current + 1;
|
||||
$max = $options['allow_redirects']['max'];
|
||||
|
||||
if ($options['__redirect_count'] > $max) {
|
||||
throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response);
|
||||
}
|
||||
}
|
||||
|
||||
public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface
|
||||
{
|
||||
// Request modifications to apply.
|
||||
$modify = [];
|
||||
$protocols = $options['allow_redirects']['protocols'];
|
||||
|
||||
// Use a GET request if this is an entity enclosing request and we are
|
||||
// not forcing RFC compliance, but rather emulating what all browsers
|
||||
// would do.
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($statusCode == 303 ||
|
||||
($statusCode <= 302 && !$options['allow_redirects']['strict'])
|
||||
) {
|
||||
$safeMethods = ['GET', 'HEAD', 'OPTIONS'];
|
||||
$requestMethod = $request->getMethod();
|
||||
|
||||
$modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET';
|
||||
$modify['body'] = '';
|
||||
}
|
||||
|
||||
$uri = $this->redirectUri($request, $response, $protocols);
|
||||
if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
|
||||
$idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion'];
|
||||
$uri = Utils::idnUriConvert($uri, $idnOptions);
|
||||
}
|
||||
|
||||
$modify['uri'] = $uri;
|
||||
Psr7\Message::rewindBody($request);
|
||||
|
||||
// Add the Referer header if it is told to do so and only
|
||||
// add the header if we are not redirecting from https to http.
|
||||
if ($options['allow_redirects']['referer']
|
||||
&& $modify['uri']->getScheme() === $request->getUri()->getScheme()
|
||||
) {
|
||||
$uri = $request->getUri()->withUserInfo('');
|
||||
$modify['set_headers']['Referer'] = (string) $uri;
|
||||
} else {
|
||||
$modify['remove_headers'][] = 'Referer';
|
||||
}
|
||||
|
||||
// Remove Authorization header if host is different.
|
||||
if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
|
||||
$modify['remove_headers'][] = 'Authorization';
|
||||
}
|
||||
|
||||
return Psr7\Utils::modifyRequest($request, $modify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the appropriate URL on the request based on the location header
|
||||
*/
|
||||
private function redirectUri(RequestInterface $request, ResponseInterface $response, array $protocols): UriInterface
|
||||
{
|
||||
$location = Psr7\UriResolver::resolve(
|
||||
$request->getUri(),
|
||||
new Psr7\Uri($response->getHeaderLine('Location'))
|
||||
);
|
||||
|
||||
// Ensure that the redirect URI is allowed based on the protocols.
|
||||
if (!\in_array($location->getScheme(), $protocols)) {
|
||||
throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response);
|
||||
}
|
||||
|
||||
return $location;
|
||||
}
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
/**
|
||||
* This class contains a list of built-in Guzzle request options.
|
||||
*
|
||||
* More documentation for each option can be found at http://guzzlephp.org/.
|
||||
*
|
||||
* @link http://docs.guzzlephp.org/en/v6/request-options.html
|
||||
*/
|
||||
final class RequestOptions
|
||||
{
|
||||
/**
|
||||
* allow_redirects: (bool|array) Controls redirect behavior. Pass false
|
||||
* to disable redirects, pass true to enable redirects, pass an
|
||||
* associative to provide custom redirect settings. Defaults to "false".
|
||||
* This option only works if your handler has the RedirectMiddleware. When
|
||||
* passing an associative array, you can provide the following key value
|
||||
* pairs:
|
||||
*
|
||||
* - max: (int, default=5) maximum number of allowed redirects.
|
||||
* - strict: (bool, default=false) Set to true to use strict redirects
|
||||
* meaning redirect POST requests with POST requests vs. doing what most
|
||||
* browsers do which is redirect POST requests with GET requests
|
||||
* - referer: (bool, default=false) Set to true to enable the Referer
|
||||
* header.
|
||||
* - protocols: (array, default=['http', 'https']) Allowed redirect
|
||||
* protocols.
|
||||
* - on_redirect: (callable) PHP callable that is invoked when a redirect
|
||||
* is encountered. The callable is invoked with the request, the redirect
|
||||
* response that was received, and the effective URI. Any return value
|
||||
* from the on_redirect function is ignored.
|
||||
*/
|
||||
public const ALLOW_REDIRECTS = 'allow_redirects';
|
||||
|
||||
/**
|
||||
* auth: (array) Pass an array of HTTP authentication parameters to use
|
||||
* with the request. The array must contain the username in index [0],
|
||||
* the password in index [1], and you can optionally provide a built-in
|
||||
* authentication type in index [2]. Pass null to disable authentication
|
||||
* for a request.
|
||||
*/
|
||||
public const AUTH = 'auth';
|
||||
|
||||
/**
|
||||
* body: (resource|string|null|int|float|StreamInterface|callable|\Iterator)
|
||||
* Body to send in the request.
|
||||
*/
|
||||
public const BODY = 'body';
|
||||
|
||||
/**
|
||||
* cert: (string|array) Set to a string to specify the path to a file
|
||||
* containing a PEM formatted SSL client side certificate. If a password
|
||||
* is required, then set cert to an array containing the path to the PEM
|
||||
* file in the first array element followed by the certificate password
|
||||
* in the second array element.
|
||||
*/
|
||||
public const CERT = 'cert';
|
||||
|
||||
/**
|
||||
* cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false)
|
||||
* Specifies whether or not cookies are used in a request or what cookie
|
||||
* jar to use or what cookies to send. This option only works if your
|
||||
* handler has the `cookie` middleware. Valid values are `false` and
|
||||
* an instance of {@see \GuzzleHttp\Cookie\CookieJarInterface}.
|
||||
*/
|
||||
public const COOKIES = 'cookies';
|
||||
|
||||
/**
|
||||
* connect_timeout: (float, default=0) Float describing the number of
|
||||
* seconds to wait while trying to connect to a server. Use 0 to wait
|
||||
* indefinitely (the default behavior).
|
||||
*/
|
||||
public const CONNECT_TIMEOUT = 'connect_timeout';
|
||||
|
||||
/**
|
||||
* debug: (bool|resource) Set to true or set to a PHP stream returned by
|
||||
* fopen() enable debug output with the HTTP handler used to send a
|
||||
* request.
|
||||
*/
|
||||
public const DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* decode_content: (bool, default=true) Specify whether or not
|
||||
* Content-Encoding responses (gzip, deflate, etc.) are automatically
|
||||
* decoded.
|
||||
*/
|
||||
public const DECODE_CONTENT = 'decode_content';
|
||||
|
||||
/**
|
||||
* delay: (int) The amount of time to delay before sending in milliseconds.
|
||||
*/
|
||||
public const DELAY = 'delay';
|
||||
|
||||
/**
|
||||
* expect: (bool|integer) Controls the behavior of the
|
||||
* "Expect: 100-Continue" header.
|
||||
*
|
||||
* Set to `true` to enable the "Expect: 100-Continue" header for all
|
||||
* requests that sends a body. Set to `false` to disable the
|
||||
* "Expect: 100-Continue" header for all requests. Set to a number so that
|
||||
* the size of the payload must be greater than the number in order to send
|
||||
* the Expect header. Setting to a number will send the Expect header for
|
||||
* all requests in which the size of the payload cannot be determined or
|
||||
* where the body is not rewindable.
|
||||
*
|
||||
* By default, Guzzle will add the "Expect: 100-Continue" header when the
|
||||
* size of the body of a request is greater than 1 MB and a request is
|
||||
* using HTTP/1.1.
|
||||
*/
|
||||
public const EXPECT = 'expect';
|
||||
|
||||
/**
|
||||
* form_params: (array) Associative array of form field names to values
|
||||
* where each value is a string or array of strings. Sets the Content-Type
|
||||
* header to application/x-www-form-urlencoded when no Content-Type header
|
||||
* is already present.
|
||||
*/
|
||||
public const FORM_PARAMS = 'form_params';
|
||||
|
||||
/**
|
||||
* headers: (array) Associative array of HTTP headers. Each value MUST be
|
||||
* a string or array of strings.
|
||||
*/
|
||||
public const HEADERS = 'headers';
|
||||
|
||||
/**
|
||||
* http_errors: (bool, default=true) Set to false to disable exceptions
|
||||
* when a non- successful HTTP response is received. By default,
|
||||
* exceptions will be thrown for 4xx and 5xx responses. This option only
|
||||
* works if your handler has the `httpErrors` middleware.
|
||||
*/
|
||||
public const HTTP_ERRORS = 'http_errors';
|
||||
|
||||
/**
|
||||
* idn: (bool|int, default=true) A combination of IDNA_* constants for
|
||||
* idn_to_ascii() PHP's function (see "options" parameter). Set to false to
|
||||
* disable IDN support completely, or to true to use the default
|
||||
* configuration (IDNA_DEFAULT constant).
|
||||
*/
|
||||
public const IDN_CONVERSION = 'idn_conversion';
|
||||
|
||||
/**
|
||||
* json: (mixed) Adds JSON data to a request. The provided value is JSON
|
||||
* encoded and a Content-Type header of application/json will be added to
|
||||
* the request if no Content-Type header is already present.
|
||||
*/
|
||||
public const JSON = 'json';
|
||||
|
||||
/**
|
||||
* multipart: (array) Array of associative arrays, each containing a
|
||||
* required "name" key mapping to the form field, name, a required
|
||||
* "contents" key mapping to a StreamInterface|resource|string, an
|
||||
* optional "headers" associative array of custom headers, and an
|
||||
* optional "filename" key mapping to a string to send as the filename in
|
||||
* the part. If no "filename" key is present, then no "filename" attribute
|
||||
* will be added to the part.
|
||||
*/
|
||||
public const MULTIPART = 'multipart';
|
||||
|
||||
/**
|
||||
* on_headers: (callable) A callable that is invoked when the HTTP headers
|
||||
* of the response have been received but the body has not yet begun to
|
||||
* download.
|
||||
*/
|
||||
public const ON_HEADERS = 'on_headers';
|
||||
|
||||
/**
|
||||
* on_stats: (callable) allows you to get access to transfer statistics of
|
||||
* a request and access the lower level transfer details of the handler
|
||||
* associated with your client. ``on_stats`` is a callable that is invoked
|
||||
* when a handler has finished sending a request. The callback is invoked
|
||||
* with transfer statistics about the request, the response received, or
|
||||
* the error encountered. Included in the data is the total amount of time
|
||||
* taken to send the request.
|
||||
*/
|
||||
public const ON_STATS = 'on_stats';
|
||||
|
||||
/**
|
||||
* progress: (callable) Defines a function to invoke when transfer
|
||||
* progress is made. The function accepts the following positional
|
||||
* arguments: the total number of bytes expected to be downloaded, the
|
||||
* number of bytes downloaded so far, the number of bytes expected to be
|
||||
* uploaded, the number of bytes uploaded so far.
|
||||
*/
|
||||
public const PROGRESS = 'progress';
|
||||
|
||||
/**
|
||||
* proxy: (string|array) Pass a string to specify an HTTP proxy, or an
|
||||
* array to specify different proxies for different protocols (where the
|
||||
* key is the protocol and the value is a proxy string).
|
||||
*/
|
||||
public const PROXY = 'proxy';
|
||||
|
||||
/**
|
||||
* query: (array|string) Associative array of query string values to add
|
||||
* to the request. This option uses PHP's http_build_query() to create
|
||||
* the string representation. Pass a string value if you need more
|
||||
* control than what this method provides
|
||||
*/
|
||||
public const QUERY = 'query';
|
||||
|
||||
/**
|
||||
* sink: (resource|string|StreamInterface) Where the data of the
|
||||
* response is written to. Defaults to a PHP temp stream. Providing a
|
||||
* string will write data to a file by the given name.
|
||||
*/
|
||||
public const SINK = 'sink';
|
||||
|
||||
/**
|
||||
* synchronous: (bool) Set to true to inform HTTP handlers that you intend
|
||||
* on waiting on the response. This can be useful for optimizations. Note
|
||||
* that a promise is still returned if you are using one of the async
|
||||
* client methods.
|
||||
*/
|
||||
public const SYNCHRONOUS = 'synchronous';
|
||||
|
||||
/**
|
||||
* ssl_key: (array|string) Specify the path to a file containing a private
|
||||
* SSL key in PEM format. If a password is required, then set to an array
|
||||
* containing the path to the SSL key in the first array element followed
|
||||
* by the password required for the certificate in the second element.
|
||||
*/
|
||||
public const SSL_KEY = 'ssl_key';
|
||||
|
||||
/**
|
||||
* stream: Set to true to attempt to stream a response rather than
|
||||
* download it all up-front.
|
||||
*/
|
||||
public const STREAM = 'stream';
|
||||
|
||||
/**
|
||||
* verify: (bool|string, default=true) Describes the SSL certificate
|
||||
* verification behavior of a request. Set to true to enable SSL
|
||||
* certificate verification using the system CA bundle when available
|
||||
* (the default). Set to false to disable certificate verification (this
|
||||
* is insecure!). Set to a string to provide the path to a CA bundle on
|
||||
* disk to enable verification using a custom certificate.
|
||||
*/
|
||||
public const VERIFY = 'verify';
|
||||
|
||||
/**
|
||||
* timeout: (float, default=0) Float describing the timeout of the
|
||||
* request in seconds. Use 0 to wait indefinitely (the default behavior).
|
||||
*/
|
||||
public const TIMEOUT = 'timeout';
|
||||
|
||||
/**
|
||||
* read_timeout: (float, default=default_socket_timeout ini setting) Float describing
|
||||
* the body read timeout, for stream requests.
|
||||
*/
|
||||
public const READ_TIMEOUT = 'read_timeout';
|
||||
|
||||
/**
|
||||
* version: (float) Specifies the HTTP protocol version to attempt to use.
|
||||
*/
|
||||
public const VERSION = 'version';
|
||||
|
||||
/**
|
||||
* force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol
|
||||
*/
|
||||
public const FORCE_IP_RESOLVE = 'force_ip_resolve';
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Promise as P;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Middleware that retries requests based on the boolean result of
|
||||
* invoking the provided "decider" function.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class RetryMiddleware
|
||||
{
|
||||
/**
|
||||
* @var callable(RequestInterface, array): PromiseInterface
|
||||
*/
|
||||
private $nextHandler;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $decider;
|
||||
|
||||
/**
|
||||
* @var callable(int)
|
||||
*/
|
||||
private $delay;
|
||||
|
||||
/**
|
||||
* @param callable $decider Function that accepts the number of retries,
|
||||
* a request, [response], and [exception] and
|
||||
* returns true if the request is to be
|
||||
* retried.
|
||||
* @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
|
||||
* @param null|callable(int): int $delay Function that accepts the number of retries
|
||||
* and returns the number of
|
||||
* milliseconds to delay.
|
||||
*/
|
||||
public function __construct(callable $decider, callable $nextHandler, callable $delay = null)
|
||||
{
|
||||
$this->decider = $decider;
|
||||
$this->nextHandler = $nextHandler;
|
||||
$this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default exponential backoff delay function.
|
||||
*
|
||||
* @return int milliseconds.
|
||||
*/
|
||||
public static function exponentialDelay(int $retries): int
|
||||
{
|
||||
return (int) \pow(2, $retries - 1) * 1000;
|
||||
}
|
||||
|
||||
public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
||||
{
|
||||
if (!isset($options['retries'])) {
|
||||
$options['retries'] = 0;
|
||||
}
|
||||
|
||||
$fn = $this->nextHandler;
|
||||
return $fn($request, $options)
|
||||
->then(
|
||||
$this->onFulfilled($request, $options),
|
||||
$this->onRejected($request, $options)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute fulfilled closure
|
||||
*/
|
||||
private function onFulfilled(RequestInterface $request, array $options): callable
|
||||
{
|
||||
return function ($value) use ($request, $options) {
|
||||
if (!($this->decider)(
|
||||
$options['retries'],
|
||||
$request,
|
||||
$value,
|
||||
null
|
||||
)) {
|
||||
return $value;
|
||||
}
|
||||
return $this->doRetry($request, $options, $value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute rejected closure
|
||||
*/
|
||||
private function onRejected(RequestInterface $req, array $options): callable
|
||||
{
|
||||
return function ($reason) use ($req, $options) {
|
||||
if (!($this->decider)(
|
||||
$options['retries'],
|
||||
$req,
|
||||
null,
|
||||
$reason
|
||||
)) {
|
||||
return P\Create::rejectionFor($reason);
|
||||
}
|
||||
return $this->doRetry($req, $options);
|
||||
};
|
||||
}
|
||||
|
||||
private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface
|
||||
{
|
||||
$options['delay'] = ($this->delay)(++$options['retries'], $response);
|
||||
|
||||
return $this($request, $options);
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Represents data at the point after it was transferred either successfully
|
||||
* or after a network error.
|
||||
*/
|
||||
final class TransferStats
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var ResponseInterface|null
|
||||
*/
|
||||
private $response;
|
||||
|
||||
/**
|
||||
* @var float|null
|
||||
*/
|
||||
private $transferTime;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $handlerStats;
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
private $handlerErrorData;
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request Request that was sent.
|
||||
* @param ResponseInterface|null $response Response received (if any)
|
||||
* @param float|null $transferTime Total handler transfer time.
|
||||
* @param mixed $handlerErrorData Handler error data.
|
||||
* @param array $handlerStats Handler specific stats.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestInterface $request,
|
||||
?ResponseInterface $response = null,
|
||||
?float $transferTime = null,
|
||||
$handlerErrorData = null,
|
||||
array $handlerStats = []
|
||||
) {
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->transferTime = $transferTime;
|
||||
$this->handlerErrorData = $handlerErrorData;
|
||||
$this->handlerStats = $handlerStats;
|
||||
}
|
||||
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response that was received (if any).
|
||||
*/
|
||||
public function getResponse(): ?ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a response was received.
|
||||
*/
|
||||
public function hasResponse(): bool
|
||||
{
|
||||
return $this->response !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets handler specific error data.
|
||||
*
|
||||
* This might be an exception, a integer representing an error code, or
|
||||
* anything else. Relying on this value assumes that you know what handler
|
||||
* you are using.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getHandlerErrorData()
|
||||
{
|
||||
return $this->handlerErrorData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective URI the request was sent to.
|
||||
*/
|
||||
public function getEffectiveUri(): UriInterface
|
||||
{
|
||||
return $this->request->getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated time the request was being transferred by the handler.
|
||||
*
|
||||
* @return float|null Time in seconds.
|
||||
*/
|
||||
public function getTransferTime(): ?float
|
||||
{
|
||||
return $this->transferTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all of the handler specific transfer data.
|
||||
*/
|
||||
public function getHandlerStats(): array
|
||||
{
|
||||
return $this->handlerStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific handler statistic from the handler by name.
|
||||
*
|
||||
* @param string $stat Handler specific transfer stat to retrieve.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getHandlerStat(string $stat)
|
||||
{
|
||||
return $this->handlerStats[$stat] ?? null;
|
||||
}
|
||||
}
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
use GuzzleHttp\Exception\InvalidArgumentException;
|
||||
use GuzzleHttp\Handler\CurlHandler;
|
||||
use GuzzleHttp\Handler\CurlMultiHandler;
|
||||
use GuzzleHttp\Handler\Proxy;
|
||||
use GuzzleHttp\Handler\StreamHandler;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class Utils
|
||||
{
|
||||
/**
|
||||
* Debug function used to describe the provided value type and class.
|
||||
*
|
||||
* @param mixed $input
|
||||
*
|
||||
* @return string Returns a string containing the type of the variable and
|
||||
* if a class is provided, the class name.
|
||||
*/
|
||||
public static function describeType($input): string
|
||||
{
|
||||
switch (\gettype($input)) {
|
||||
case 'object':
|
||||
return 'object(' . \get_class($input) . ')';
|
||||
case 'array':
|
||||
return 'array(' . \count($input) . ')';
|
||||
default:
|
||||
\ob_start();
|
||||
\var_dump($input);
|
||||
// normalize float vs double
|
||||
/** @var string $varDumpContent */
|
||||
$varDumpContent = \ob_get_clean();
|
||||
|
||||
return \str_replace('double(', 'float(', \rtrim($varDumpContent));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an array of header lines into an associative array of headers.
|
||||
*
|
||||
* @param iterable $lines Header lines array of strings in the following
|
||||
* format: "Name: Value"
|
||||
*/
|
||||
public static function headersFromLines(iterable $lines): array
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$parts = \explode(':', $line, 2);
|
||||
$headers[\trim($parts[0])][] = isset($parts[1]) ? \trim($parts[1]) : null;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a debug stream based on the provided variable.
|
||||
*
|
||||
* @param mixed $value Optional value
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public static function debugResource($value = null)
|
||||
{
|
||||
if (\is_resource($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (\defined('STDOUT')) {
|
||||
return \STDOUT;
|
||||
}
|
||||
|
||||
return \GuzzleHttp\Psr7\Utils::tryFopen('php://output', 'w');
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses and creates a default handler to use based on the environment.
|
||||
*
|
||||
* The returned handler is not wrapped by any default middlewares.
|
||||
*
|
||||
* @throws \RuntimeException if no viable Handler is available.
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
|
||||
*/
|
||||
public static function chooseHandler(): callable
|
||||
{
|
||||
$handler = null;
|
||||
if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) {
|
||||
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
|
||||
} elseif (\function_exists('curl_exec')) {
|
||||
$handler = new CurlHandler();
|
||||
} elseif (\function_exists('curl_multi_exec')) {
|
||||
$handler = new CurlMultiHandler();
|
||||
}
|
||||
|
||||
if (\ini_get('allow_url_fopen')) {
|
||||
$handler = $handler
|
||||
? Proxy::wrapStreaming($handler, new StreamHandler())
|
||||
: new StreamHandler();
|
||||
} elseif (!$handler) {
|
||||
throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.');
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default User-Agent string to use with Guzzle.
|
||||
*/
|
||||
public static function defaultUserAgent(): string
|
||||
{
|
||||
return sprintf('GuzzleHttp/%d', ClientInterface::MAJOR_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default cacert bundle for the current system.
|
||||
*
|
||||
* First, the openssl.cafile and curl.cainfo php.ini settings are checked.
|
||||
* If those settings are not configured, then the common locations for
|
||||
* bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
|
||||
* and Windows are checked. If any of these file locations are found on
|
||||
* disk, they will be utilized.
|
||||
*
|
||||
* Note: the result of this function is cached for subsequent calls.
|
||||
*
|
||||
* @throws \RuntimeException if no bundle can be found.
|
||||
*
|
||||
* @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+.
|
||||
*/
|
||||
public static function defaultCaBundle(): string
|
||||
{
|
||||
static $cached = null;
|
||||
static $cafiles = [
|
||||
// Red Hat, CentOS, Fedora (provided by the ca-certificates package)
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
// Ubuntu, Debian (provided by the ca-certificates package)
|
||||
'/etc/ssl/certs/ca-certificates.crt',
|
||||
// FreeBSD (provided by the ca_root_nss package)
|
||||
'/usr/local/share/certs/ca-root-nss.crt',
|
||||
// SLES 12 (provided by the ca-certificates package)
|
||||
'/var/lib/ca-certificates/ca-bundle.pem',
|
||||
// OS X provided by homebrew (using the default path)
|
||||
'/usr/local/etc/openssl/cert.pem',
|
||||
// Google app engine
|
||||
'/etc/ca-certificates.crt',
|
||||
// Windows?
|
||||
'C:\\windows\\system32\\curl-ca-bundle.crt',
|
||||
'C:\\windows\\curl-ca-bundle.crt',
|
||||
];
|
||||
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
if ($ca = \ini_get('openssl.cafile')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
if ($ca = \ini_get('curl.cainfo')) {
|
||||
return $cached = $ca;
|
||||
}
|
||||
|
||||
foreach ($cafiles as $filename) {
|
||||
if (\file_exists($filename)) {
|
||||
return $cached = $filename;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException(
|
||||
<<< EOT
|
||||
No system CA bundle could be found in any of the the common system locations.
|
||||
PHP versions earlier than 5.6 are not properly configured to use the system's
|
||||
CA bundle by default. In order to verify peer certificates, you will need to
|
||||
supply the path on disk to a certificate bundle to the 'verify' request
|
||||
option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
|
||||
need a specific certificate bundle, then Mozilla provides a commonly used CA
|
||||
bundle which can be downloaded here (provided by the maintainer of cURL):
|
||||
https://curl.haxx.se/ca/cacert.pem. Once
|
||||
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
|
||||
ini setting to point to the path to the file, allowing you to omit the 'verify'
|
||||
request option. See https://curl.haxx.se/docs/sslcerts.html for more
|
||||
information.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an associative array of lowercase header names to the actual
|
||||
* header casing.
|
||||
*/
|
||||
public static function normalizeHeaderKeys(array $headers): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (\array_keys($headers) as $key) {
|
||||
$result[\strtolower($key)] = $key;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided host matches any of the no proxy areas.
|
||||
*
|
||||
* This method will strip a port from the host if it is present. Each pattern
|
||||
* can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
|
||||
* partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
|
||||
* "baz.foo.com", but ".foo.com" != "foo.com").
|
||||
*
|
||||
* Areas are matched in the following cases:
|
||||
* 1. "*" (without quotes) always matches any hosts.
|
||||
* 2. An exact match.
|
||||
* 3. The area starts with "." and the area is the last part of the host. e.g.
|
||||
* '.mit.edu' will match any host that ends with '.mit.edu'.
|
||||
*
|
||||
* @param string $host Host to check against the patterns.
|
||||
* @param string[] $noProxyArray An array of host patterns.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function isHostInNoProxy(string $host, array $noProxyArray): bool
|
||||
{
|
||||
if (\strlen($host) === 0) {
|
||||
throw new InvalidArgumentException('Empty host provided');
|
||||
}
|
||||
|
||||
// Strip port if present.
|
||||
if (\strpos($host, ':')) {
|
||||
/** @var string[] $hostParts will never be false because of the checks above */
|
||||
$hostParts = \explode(':', $host, 2);
|
||||
$host = $hostParts[0];
|
||||
}
|
||||
|
||||
foreach ($noProxyArray as $area) {
|
||||
// Always match on wildcards.
|
||||
if ($area === '*') {
|
||||
return true;
|
||||
} elseif (empty($area)) {
|
||||
// Don't match on empty values.
|
||||
continue;
|
||||
} elseif ($area === $host) {
|
||||
// Exact matches.
|
||||
return true;
|
||||
}
|
||||
// Special match if the area when prefixed with ".". Remove any
|
||||
// existing leading "." and add a new leading ".".
|
||||
$area = '.' . \ltrim($area, '.');
|
||||
if (\substr($host, -(\strlen($area))) === $area) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for json_decode that throws when an error occurs.
|
||||
*
|
||||
* @param string $json JSON data to parse
|
||||
* @param bool $assoc When true, returned objects will be converted
|
||||
* into associative arrays.
|
||||
* @param int $depth User specified recursion depth.
|
||||
* @param int $options Bitmask of JSON decode options.
|
||||
*
|
||||
* @return object|array|string|int|float|bool|null
|
||||
*
|
||||
* @throws InvalidArgumentException if the JSON cannot be decoded.
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.json-decode.php
|
||||
*/
|
||||
public static function jsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
|
||||
{
|
||||
$data = \json_decode($json, $assoc, $depth, $options);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new InvalidArgumentException('json_decode error: ' . \json_last_error_msg());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for JSON encoding that throws when an error occurs.
|
||||
*
|
||||
* @param mixed $value The value being encoded
|
||||
* @param int $options JSON encode option bitmask
|
||||
* @param int $depth Set the maximum depth. Must be greater than zero.
|
||||
*
|
||||
* @throws InvalidArgumentException if the JSON cannot be encoded.
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.json-encode.php
|
||||
*/
|
||||
public static function jsonEncode($value, int $options = 0, int $depth = 512): string
|
||||
{
|
||||
$json = \json_encode($value, $options, $depth);
|
||||
if (\JSON_ERROR_NONE !== \json_last_error()) {
|
||||
throw new InvalidArgumentException('json_encode error: ' . \json_last_error_msg());
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the hrtime() or microtime() functions
|
||||
* (depending on the PHP version, one of the two is used)
|
||||
*
|
||||
* @return float UNIX timestamp
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function currentTime(): float
|
||||
{
|
||||
return (float) \function_exists('hrtime') ? \hrtime(true) / 1e9 : \microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function idnUriConvert(UriInterface $uri, int $options = 0): UriInterface
|
||||
{
|
||||
if ($uri->getHost()) {
|
||||
$asciiHost = self::idnToAsci($uri->getHost(), $options, $info);
|
||||
if ($asciiHost === false) {
|
||||
$errorBitSet = $info['errors'] ?? 0;
|
||||
|
||||
$errorConstants = array_filter(array_keys(get_defined_constants()), static function ($name) {
|
||||
return substr($name, 0, 11) === 'IDNA_ERROR_';
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
foreach ($errorConstants as $errorConstant) {
|
||||
if ($errorBitSet & constant($errorConstant)) {
|
||||
$errors[] = $errorConstant;
|
||||
}
|
||||
}
|
||||
|
||||
$errorMessage = 'IDN conversion failed';
|
||||
if ($errors) {
|
||||
$errorMessage .= ' (errors: ' . implode(', ', $errors) . ')';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($errorMessage);
|
||||
}
|
||||
if ($uri->getHost() !== $asciiHost) {
|
||||
// Replace URI only if the ASCII version is different
|
||||
$uri = $uri->withHost($asciiHost);
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function getenv(string $name): ?string
|
||||
{
|
||||
if (isset($_SERVER[$name])) {
|
||||
return (string) $_SERVER[$name];
|
||||
}
|
||||
|
||||
if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
private static function idnToAsci(string $domain, int $options, ?array &$info = [])
|
||||
{
|
||||
if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) {
|
||||
return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info);
|
||||
}
|
||||
|
||||
throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old');
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp;
|
||||
|
||||
/**
|
||||
* Debug function used to describe the provided value type and class.
|
||||
*
|
||||
* @param mixed $input Any type of variable to describe the type of. This
|
||||
* parameter misses a typehint because of that.
|
||||
*
|
||||
* @return string Returns a string containing the type of the variable and
|
||||
* if a class is provided, the class name.
|
||||
*
|
||||
* @deprecated describe_type will be removed in guzzlehttp/guzzle:8.0. Use Utils::describeType instead.
|
||||
*/
|
||||
function describe_type($input): string
|
||||
{
|
||||
return Utils::describeType($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an array of header lines into an associative array of headers.
|
||||
*
|
||||
* @param iterable $lines Header lines array of strings in the following
|
||||
* format: "Name: Value"
|
||||
*
|
||||
* @deprecated headers_from_lines will be removed in guzzlehttp/guzzle:8.0. Use Utils::headersFromLines instead.
|
||||
*/
|
||||
function headers_from_lines(iterable $lines): array
|
||||
{
|
||||
return Utils::headersFromLines($lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a debug stream based on the provided variable.
|
||||
*
|
||||
* @param mixed $value Optional value
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @deprecated debug_resource will be removed in guzzlehttp/guzzle:8.0. Use Utils::debugResource instead.
|
||||
*/
|
||||
function debug_resource($value = null)
|
||||
{
|
||||
return Utils::debugResource($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses and creates a default handler to use based on the environment.
|
||||
*
|
||||
* The returned handler is not wrapped by any default middlewares.
|
||||
*
|
||||
* @throws \RuntimeException if no viable Handler is available.
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
|
||||
*
|
||||
* @deprecated choose_handler will be removed in guzzlehttp/guzzle:8.0. Use Utils::chooseHandler instead.
|
||||
*/
|
||||
function choose_handler(): callable
|
||||
{
|
||||
return Utils::chooseHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default User-Agent string to use with Guzzle.
|
||||
*
|
||||
* @deprecated default_user_agent will be removed in guzzlehttp/guzzle:8.0. Use Utils::defaultUserAgent instead.
|
||||
*/
|
||||
function default_user_agent(): string
|
||||
{
|
||||
return Utils::defaultUserAgent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default cacert bundle for the current system.
|
||||
*
|
||||
* First, the openssl.cafile and curl.cainfo php.ini settings are checked.
|
||||
* If those settings are not configured, then the common locations for
|
||||
* bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
|
||||
* and Windows are checked. If any of these file locations are found on
|
||||
* disk, they will be utilized.
|
||||
*
|
||||
* Note: the result of this function is cached for subsequent calls.
|
||||
*
|
||||
* @throws \RuntimeException if no bundle can be found.
|
||||
*
|
||||
* @deprecated default_ca_bundle will be removed in guzzlehttp/guzzle:8.0. This function is not needed in PHP 5.6+.
|
||||
*/
|
||||
function default_ca_bundle(): string
|
||||
{
|
||||
return Utils::defaultCaBundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an associative array of lowercase header names to the actual
|
||||
* header casing.
|
||||
*
|
||||
* @deprecated normalize_header_keys will be removed in guzzlehttp/guzzle:8.0. Use Utils::normalizeHeaderKeys instead.
|
||||
*/
|
||||
function normalize_header_keys(array $headers): array
|
||||
{
|
||||
return Utils::normalizeHeaderKeys($headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided host matches any of the no proxy areas.
|
||||
*
|
||||
* This method will strip a port from the host if it is present. Each pattern
|
||||
* can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
|
||||
* partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
|
||||
* "baz.foo.com", but ".foo.com" != "foo.com").
|
||||
*
|
||||
* Areas are matched in the following cases:
|
||||
* 1. "*" (without quotes) always matches any hosts.
|
||||
* 2. An exact match.
|
||||
* 3. The area starts with "." and the area is the last part of the host. e.g.
|
||||
* '.mit.edu' will match any host that ends with '.mit.edu'.
|
||||
*
|
||||
* @param string $host Host to check against the patterns.
|
||||
* @param string[] $noProxyArray An array of host patterns.
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*
|
||||
* @deprecated is_host_in_noproxy will be removed in guzzlehttp/guzzle:8.0. Use Utils::isHostInNoProxy instead.
|
||||
*/
|
||||
function is_host_in_noproxy(string $host, array $noProxyArray): bool
|
||||
{
|
||||
return Utils::isHostInNoProxy($host, $noProxyArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for json_decode that throws when an error occurs.
|
||||
*
|
||||
* @param string $json JSON data to parse
|
||||
* @param bool $assoc When true, returned objects will be converted
|
||||
* into associative arrays.
|
||||
* @param int $depth User specified recursion depth.
|
||||
* @param int $options Bitmask of JSON decode options.
|
||||
*
|
||||
* @return object|array|string|int|float|bool|null
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.json-decode.php
|
||||
* @deprecated json_decode will be removed in guzzlehttp/guzzle:8.0. Use Utils::jsonDecode instead.
|
||||
*/
|
||||
function json_decode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
|
||||
{
|
||||
return Utils::jsonDecode($json, $assoc, $depth, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for JSON encoding that throws when an error occurs.
|
||||
*
|
||||
* @param mixed $value The value being encoded
|
||||
* @param int $options JSON encode option bitmask
|
||||
* @param int $depth Set the maximum depth. Must be greater than zero.
|
||||
*
|
||||
* @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.json-encode.php
|
||||
* @deprecated json_encode will be removed in guzzlehttp/guzzle:8.0. Use Utils::jsonEncode instead.
|
||||
*/
|
||||
function json_encode($value, int $options = 0, int $depth = 512): string
|
||||
{
|
||||
return Utils::jsonEncode($value, $options, $depth);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
// Don't redefine the functions if included multiple times.
|
||||
if (!\function_exists('GuzzleHttp\describe_type')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"friendsofphp/php-cs-fixer": "2.18.3"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpstan/phpstan": "0.12.81",
|
||||
"phpstan/phpstan-deprecation-rules": "0.12.6"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user