mirror of
https://github.com/guzzle/guzzle.git
synced 2025-02-24 18:13:00 +01:00
Simplifying the Request FSM
FSM is now passed to a client as a callable. FSM now requires an adapter and message factory rather than a function. FSM now is merged with RequestFsm to simplify the API (at least for now) Removed unnecessary circular reference from client/FSM.
This commit is contained in:
parent
286525b387
commit
22389c611c
@ -32,7 +32,7 @@ class Client implements ClientInterface
|
|||||||
/** @var array Default request options */
|
/** @var array Default request options */
|
||||||
private $defaults;
|
private $defaults;
|
||||||
|
|
||||||
/** @var Fsm Request state machine */
|
/** @var callable Request state machine */
|
||||||
private $fsm;
|
private $fsm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +63,10 @@ class Client implements ClientInterface
|
|||||||
* - message_factory: Factory used to create request and response object
|
* - message_factory: Factory used to create request and response object
|
||||||
* - defaults: Default request options to apply to each request
|
* - defaults: Default request options to apply to each request
|
||||||
* - emitter: Event emitter used for request events
|
* - emitter: Event emitter used for request events
|
||||||
* - fsm: (internal use only) The request finite state machine.
|
* - fsm: (internal use only) The request finite state machine. A
|
||||||
|
* function that accepts a transaction and optional final state. The
|
||||||
|
* function is responsible for transitioning a request through its
|
||||||
|
* lifecycle events.
|
||||||
*/
|
*/
|
||||||
public function __construct(array $config = [])
|
public function __construct(array $config = [])
|
||||||
{
|
{
|
||||||
@ -77,12 +80,15 @@ class Client implements ClientInterface
|
|||||||
$this->messageFactory = isset($config['message_factory'])
|
$this->messageFactory = isset($config['message_factory'])
|
||||||
? $config['message_factory']
|
? $config['message_factory']
|
||||||
: new MessageFactory();
|
: new MessageFactory();
|
||||||
$adapter = isset($config['adapter'])
|
|
||||||
? $config['adapter']
|
if (isset($config['fsm'])) {
|
||||||
: self::getDefaultAdapter();
|
$this->fsm = $config['fsm'];
|
||||||
$this->fsm = isset($config['fsm'])
|
} else {
|
||||||
? $config['fsm']
|
$this->fsm = new RequestFsm(
|
||||||
: $this->createDefaultFsm($adapter, $this->messageFactory);
|
isset($config['adapter']) ? $config['adapter'] : self::getDefaultAdapter(),
|
||||||
|
$this->messageFactory
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -227,11 +233,12 @@ class Client implements ClientInterface
|
|||||||
public function send(RequestInterface $request)
|
public function send(RequestInterface $request)
|
||||||
{
|
{
|
||||||
$trans = new Transaction($this, $request);
|
$trans = new Transaction($this, $request);
|
||||||
|
$fn = $this->fsm;
|
||||||
|
|
||||||
// Ensure a future response is returned if one was requested.
|
// Ensure a future response is returned if one was requested.
|
||||||
if ($request->getConfig()->get('future')) {
|
if ($request->getConfig()->get('future')) {
|
||||||
try {
|
try {
|
||||||
$this->fsm->run($trans);
|
$fn($trans);
|
||||||
// Turn the normal response into a future if needed.
|
// Turn the normal response into a future if needed.
|
||||||
return $trans->response instanceof FutureInterface
|
return $trans->response instanceof FutureInterface
|
||||||
? $trans->response
|
? $trans->response
|
||||||
@ -242,7 +249,7 @@ class Client implements ClientInterface
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$this->fsm->run($trans);
|
$fn($trans);
|
||||||
return $trans->response instanceof FutureInterface
|
return $trans->response instanceof FutureInterface
|
||||||
? $trans->response->wait()
|
? $trans->response->wait()
|
||||||
: $trans->response;
|
: $trans->response;
|
||||||
@ -368,23 +375,6 @@ class Client implements ClientInterface
|
|||||||
return $this->defaults['headers'];
|
return $this->defaults['headers'];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createDefaultFsm(
|
|
||||||
callable $adapter,
|
|
||||||
MessageFactoryInterface $mf
|
|
||||||
) {
|
|
||||||
return new RequestFsm(function (Transaction $t) use ($adapter, $mf) {
|
|
||||||
$t->response = FutureResponse::proxy(
|
|
||||||
$adapter(RingBridge::prepareRingRequest($t)),
|
|
||||||
function ($value) use ($t) {
|
|
||||||
RingBridge::completeRingResponse(
|
|
||||||
$t, $value, $this->messageFactory, $this->fsm
|
|
||||||
);
|
|
||||||
return $t->response;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@see GuzzleHttp\Pool} instead.
|
* @deprecated Use {@see GuzzleHttp\Pool} instead.
|
||||||
* @see GuzzleHttp\Pool
|
* @see GuzzleHttp\Pool
|
||||||
|
128
src/Fsm.php
128
src/Fsm.php
@ -1,128 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace GuzzleHttp;
|
|
||||||
|
|
||||||
use GuzzleHttp\Exception\StateException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a basic finite state machine that transitions transaction objects
|
|
||||||
* through state transitions provided in the constructor.
|
|
||||||
*
|
|
||||||
* As states transition, any exceptions thrown in the state are caught and
|
|
||||||
* passed to the corresponding error state if available. If no error state is
|
|
||||||
* available, then the exception is thrown. If a
|
|
||||||
* {@see GuzzleHttp\Exception\StateException} is thrown, then the exception
|
|
||||||
* is thrown immediately without allowing any further transitions.
|
|
||||||
*
|
|
||||||
* States can return true or false/null. When a state returns true, it tells
|
|
||||||
* the FSM to transition to the defined "intercept" state. If no intercept
|
|
||||||
* state is defined then a StateException is thrown. If a state returns
|
|
||||||
* false/null, then the FSM transitions to the "complete" state if one is
|
|
||||||
* defined.
|
|
||||||
*/
|
|
||||||
class Fsm
|
|
||||||
{
|
|
||||||
private $states;
|
|
||||||
private $initialState;
|
|
||||||
private $maxTransitions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The states array is an associative array of associative arrays
|
|
||||||
* describing each state transition. Each key of the outer array is a state
|
|
||||||
* name, and each value is an associative array that can contain the
|
|
||||||
* following key value pairs:
|
|
||||||
*
|
|
||||||
* - transition: A callable that is invoked when entering the state. If
|
|
||||||
* the callable throws an exception then the FSM transitions to the
|
|
||||||
* error state. Otherwise, the FSM transitions to the success state.
|
|
||||||
* - success: The state to transition to when no error is raised. If not
|
|
||||||
* present, then this is a terminal state.
|
|
||||||
* - intercept: The state to transition to if the state is intercepted.
|
|
||||||
* You can intercept states by returning true in a transition function.
|
|
||||||
* - error: The state to transition to when an error is raised. If not
|
|
||||||
* present and an exception occurs, then the exception is thrown.
|
|
||||||
*
|
|
||||||
* @param string $initialState The initial state of the FSM
|
|
||||||
* @param array $states Associative array of state transitions.
|
|
||||||
* @param int $maxTransitions The maximum number of allows transitions
|
|
||||||
* before failing. This is basically a
|
|
||||||
* fail-safe to prevent infinite loops.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
$initialState,
|
|
||||||
array $states,
|
|
||||||
$maxTransitions = 200
|
|
||||||
) {
|
|
||||||
$this->states = $states;
|
|
||||||
$this->initialState = $initialState;
|
|
||||||
$this->maxTransitions = $maxTransitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs the state machine until a terminal state is entered or the
|
|
||||||
* optionally supplied $finalState is entered.
|
|
||||||
*
|
|
||||||
* @param Transaction $trans Transaction being transitioned.
|
|
||||||
* @param string $finalState The state to stop on. If unspecified,
|
|
||||||
* runs until a terminal state is found.
|
|
||||||
*
|
|
||||||
* @throws \Exception if a terminal state throws an exception.
|
|
||||||
*/
|
|
||||||
public function run(Transaction $trans, $finalState = null)
|
|
||||||
{
|
|
||||||
$trans->_transitionCount = 1;
|
|
||||||
|
|
||||||
if (!$trans->state) {
|
|
||||||
$trans->state = $this->initialState;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ($trans->state !== $finalState) {
|
|
||||||
|
|
||||||
if (!isset($this->states[$trans->state])) {
|
|
||||||
throw new StateException("Invalid state: {$trans->state}");
|
|
||||||
} elseif (++$trans->_transitionCount > $this->maxTransitions) {
|
|
||||||
throw new StateException('Too many state transitions were '
|
|
||||||
. ' encountered ({$trans->_transitionCount}). This likely '
|
|
||||||
. 'means that a combination of event listeners are in an '
|
|
||||||
. 'infinite loop.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$state = $this->states[$trans->state];
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Call the transition function if available.
|
|
||||||
if (isset($state['transition'])) {
|
|
||||||
// Handles transitioning to the "intercept" state.
|
|
||||||
if ($state['transition']($trans)) {
|
|
||||||
if (isset($state['intercept'])) {
|
|
||||||
$trans->state = $state['intercept'];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw new StateException('Invalid intercept state '
|
|
||||||
. 'transition from ' . $trans->state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($state['success'])) {
|
|
||||||
// Transition to the success state
|
|
||||||
$trans->state = $state['success'];
|
|
||||||
} else {
|
|
||||||
// Break: this is a terminal state with no transition.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (StateException $e) {
|
|
||||||
// State exceptions are thrown no matter what.
|
|
||||||
throw $e;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$trans->exception = $e;
|
|
||||||
// Terminal error states throw the exception.
|
|
||||||
if (!isset($state['error'])) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
// Transition to the error state.
|
|
||||||
$trans->state = $state['error'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,53 +5,123 @@ use GuzzleHttp\Event\BeforeEvent;
|
|||||||
use GuzzleHttp\Event\ErrorEvent;
|
use GuzzleHttp\Event\ErrorEvent;
|
||||||
use GuzzleHttp\Event\CompleteEvent;
|
use GuzzleHttp\Event\CompleteEvent;
|
||||||
use GuzzleHttp\Event\EndEvent;
|
use GuzzleHttp\Event\EndEvent;
|
||||||
|
use GuzzleHttp\Exception\StateException;
|
||||||
use GuzzleHttp\Exception\RequestException;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
|
use GuzzleHttp\Message\FutureResponse;
|
||||||
|
use GuzzleHttp\Message\MessageFactoryInterface;
|
||||||
use GuzzleHttp\Ring\Future\FutureInterface;
|
use GuzzleHttp\Ring\Future\FutureInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for transitioning requests through lifecycle events.
|
* Responsible for transitioning requests through lifecycle events.
|
||||||
*/
|
*/
|
||||||
class RequestFsm extends Fsm
|
class RequestFsm
|
||||||
{
|
{
|
||||||
private $sendFn;
|
private $adapter;
|
||||||
|
private $mf;
|
||||||
|
private $maxTransitions;
|
||||||
|
|
||||||
public function __construct(callable $sendFn)
|
private $states = [
|
||||||
{
|
// When a mock intercepts the emitted "before" event, then we
|
||||||
$this->sendFn = $sendFn;
|
// transition to the "complete" intercept state.
|
||||||
parent::__construct('before', [
|
'before' => [
|
||||||
// When a mock intercepts the emitted "before" event, then we
|
'success' => 'send',
|
||||||
// transition to the "complete" intercept state.
|
'intercept' => 'complete',
|
||||||
'before' => [
|
'error' => 'error'
|
||||||
'success' => 'send',
|
],
|
||||||
'intercept' => 'complete',
|
// The complete and error events are handled using the "then" of
|
||||||
'error' => 'error',
|
// the Guzzle-Ring request, so we exit the FSM.
|
||||||
'transition' => [$this, 'beforeTransition']
|
'send' => ['error' => 'error'],
|
||||||
],
|
'complete' => [
|
||||||
// The complete and error events are handled using the "then" of
|
'success' => 'end',
|
||||||
// the Guzzle-Ring request, so we exit the FSM.
|
'intercept' => 'before',
|
||||||
'send' => [
|
'error' => 'error'
|
||||||
'error' => 'error',
|
],
|
||||||
'transition' => $this->sendFn
|
'error' => [
|
||||||
],
|
'success' => 'complete',
|
||||||
'complete' => [
|
'intercept' => 'before',
|
||||||
'success' => 'end',
|
'error' => 'end'
|
||||||
'intercept' => 'before',
|
],
|
||||||
'error' => 'error',
|
'end' => []
|
||||||
'transition' => [$this, 'completeTransition']
|
];
|
||||||
],
|
|
||||||
'error' => [
|
public function __construct(
|
||||||
'success' => 'complete',
|
callable $adapter,
|
||||||
'intercept' => 'before',
|
MessageFactoryInterface $messageFactory,
|
||||||
'error' => 'end',
|
$maxTransitions = 200
|
||||||
'transition' => [$this, 'ErrorTransition']
|
) {
|
||||||
],
|
$this->mf = $messageFactory;
|
||||||
'end' => [
|
$this->maxTransitions = $maxTransitions;
|
||||||
'transition' => [$this, 'endTransition']
|
$this->adapter = $adapter;
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function beforeTransition(Transaction $trans)
|
/**
|
||||||
|
* Runs the state machine until a terminal state is entered or the
|
||||||
|
* optionally supplied $finalState is entered.
|
||||||
|
*
|
||||||
|
* @param Transaction $trans Transaction being transitioned.
|
||||||
|
* @param string $finalState The state to stop on. If unspecified,
|
||||||
|
* runs until a terminal state is found.
|
||||||
|
*
|
||||||
|
* @throws \Exception if a terminal state throws an exception.
|
||||||
|
*/
|
||||||
|
public function __invoke(Transaction $trans, $finalState = null)
|
||||||
|
{
|
||||||
|
$trans->_transitionCount = 1;
|
||||||
|
|
||||||
|
if (!$trans->state) {
|
||||||
|
$trans->state = 'before';
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($trans->state !== $finalState) {
|
||||||
|
|
||||||
|
if (!isset($this->states[$trans->state])) {
|
||||||
|
throw new StateException("Invalid state: {$trans->state}");
|
||||||
|
} elseif (++$trans->_transitionCount > $this->maxTransitions) {
|
||||||
|
throw new StateException('Too many state transitions were '
|
||||||
|
. 'encountered ({$trans->_transitionCount}). This likely '
|
||||||
|
. 'means that a combination of event listeners are in an '
|
||||||
|
. 'infinite loop.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = $this->states[$trans->state];
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var callable $fn */
|
||||||
|
$fn = [$this, $trans->state];
|
||||||
|
if ($fn($trans)) {
|
||||||
|
// Handles transitioning to the "intercept" state.
|
||||||
|
if (isset($state['intercept'])) {
|
||||||
|
$trans->state = $state['intercept'];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new StateException('Invalid intercept state '
|
||||||
|
. 'transition from ' . $trans->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($state['success'])) {
|
||||||
|
// Transition to the success state
|
||||||
|
$trans->state = $state['success'];
|
||||||
|
} else {
|
||||||
|
// Break: this is a terminal state with no transition.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (StateException $e) {
|
||||||
|
// State exceptions are thrown no matter what.
|
||||||
|
throw $e;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$trans->exception = $e;
|
||||||
|
// Terminal error states throw the exception.
|
||||||
|
if (!isset($state['error'])) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
// Transition to the error state.
|
||||||
|
$trans->state = $state['error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function before(Transaction $trans)
|
||||||
{
|
{
|
||||||
$trans->request->getEmitter()->emit('before', new BeforeEvent($trans));
|
$trans->request->getEmitter()->emit('before', new BeforeEvent($trans));
|
||||||
|
|
||||||
@ -61,6 +131,18 @@ class RequestFsm extends Fsm
|
|||||||
return (bool) $trans->response;
|
return (bool) $trans->response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function send(Transaction $trans)
|
||||||
|
{
|
||||||
|
$fn = $this->adapter;
|
||||||
|
$trans->response = FutureResponse::proxy(
|
||||||
|
$fn(RingBridge::prepareRingRequest($trans)),
|
||||||
|
function ($value) use ($trans) {
|
||||||
|
RingBridge::completeRingResponse($trans, $value, $this->mf, $this);
|
||||||
|
return $trans->response;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits the error event and ensures that the exception is set and is an
|
* Emits the error event and ensures that the exception is set and is an
|
||||||
* instance of RequestException. If the error event is not intercepted,
|
* instance of RequestException. If the error event is not intercepted,
|
||||||
@ -69,7 +151,7 @@ class RequestFsm extends Fsm
|
|||||||
* to the "before" event. Otherwise, when no retries, and the exception is
|
* to the "before" event. Otherwise, when no retries, and the exception is
|
||||||
* intercepted, transition to the "complete" event.
|
* intercepted, transition to the "complete" event.
|
||||||
*/
|
*/
|
||||||
protected function errorTransition(Transaction $trans)
|
private function error(Transaction $trans)
|
||||||
{
|
{
|
||||||
// Convert non-request exception to a wrapped exception
|
// Convert non-request exception to a wrapped exception
|
||||||
if (!($trans->exception instanceof RequestException)) {
|
if (!($trans->exception instanceof RequestException)) {
|
||||||
@ -96,7 +178,7 @@ class RequestFsm extends Fsm
|
|||||||
* Emits a complete event, and if a request is marked for a retry during
|
* Emits a complete event, and if a request is marked for a retry during
|
||||||
* the complete event, then the "before" state is transitioned to.
|
* the complete event, then the "before" state is transitioned to.
|
||||||
*/
|
*/
|
||||||
protected function completeTransition(Transaction $trans)
|
private function complete(Transaction $trans)
|
||||||
{
|
{
|
||||||
// Futures will have their own end events emitted when dereferenced.
|
// Futures will have their own end events emitted when dereferenced.
|
||||||
if ($trans->response instanceof FutureInterface) {
|
if ($trans->response instanceof FutureInterface) {
|
||||||
@ -113,7 +195,7 @@ class RequestFsm extends Fsm
|
|||||||
/**
|
/**
|
||||||
* Emits the "end" event and throws an exception if one is present.
|
* Emits the "end" event and throws an exception if one is present.
|
||||||
*/
|
*/
|
||||||
protected function endTransition(Transaction $trans)
|
private function end(Transaction $trans)
|
||||||
{
|
{
|
||||||
// Futures will have their own end events emitted when dereferenced,
|
// Futures will have their own end events emitted when dereferenced,
|
||||||
// but still emit, even for futures, when an exception is present.
|
// but still emit, even for futures, when an exception is present.
|
||||||
|
@ -78,13 +78,13 @@ class RingBridge
|
|||||||
* @param Transaction $trans Owns request and response.
|
* @param Transaction $trans Owns request and response.
|
||||||
* @param array $response Ring response array
|
* @param array $response Ring response array
|
||||||
* @param MessageFactoryInterface $messageFactory Creates response objects.
|
* @param MessageFactoryInterface $messageFactory Creates response objects.
|
||||||
* @param Fsm $fsm State machine.
|
* @param callable $fsm Request FSM function.
|
||||||
*/
|
*/
|
||||||
public static function completeRingResponse(
|
public static function completeRingResponse(
|
||||||
Transaction $trans,
|
Transaction $trans,
|
||||||
array $response,
|
array $response,
|
||||||
MessageFactoryInterface $messageFactory,
|
MessageFactoryInterface $messageFactory,
|
||||||
Fsm $fsm
|
callable $fsm
|
||||||
) {
|
) {
|
||||||
$trans->state = 'complete';
|
$trans->state = 'complete';
|
||||||
$trans->transferInfo = isset($response['transfer_info'])
|
$trans->transferInfo = isset($response['transfer_info'])
|
||||||
@ -118,7 +118,7 @@ class RingBridge
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Complete the lifecycle of the request.
|
// Complete the lifecycle of the request.
|
||||||
$fsm->run($trans);
|
$fsm($trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,174 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace GuzzleHttp\Tests;
|
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
|
||||||
use GuzzleHttp\Exception\StateException;
|
|
||||||
use GuzzleHttp\Transaction;
|
|
||||||
use GuzzleHttp\Fsm;
|
|
||||||
|
|
||||||
class FsmTest extends \PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @expectedException \RuntimeException
|
|
||||||
*/
|
|
||||||
public function testValidatesStateNames()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
(new Fsm('foo', []))->run(new Transaction($client, $request));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testTransitionsThroughStates()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
$t = new Transaction($client, $request);
|
|
||||||
$c = [];
|
|
||||||
$fsm = new Fsm('begin', [
|
|
||||||
'begin' => [
|
|
||||||
'success' => 'end',
|
|
||||||
'transition' => function (Transaction $trans) use ($t, &$c) {
|
|
||||||
$this->assertSame($t, $trans);
|
|
||||||
$c[] = 'begin';
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'end' => [
|
|
||||||
'transition' => function (Transaction $trans) use ($t, &$c) {
|
|
||||||
$this->assertSame($t, $trans);
|
|
||||||
$c[] = 'end';
|
|
||||||
}
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$fsm->run($t);
|
|
||||||
$this->assertEquals(['begin', 'end'], $c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testTransitionsThroughErrorStates()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
$t = new Transaction($client, $request);
|
|
||||||
$c = [];
|
|
||||||
|
|
||||||
$fsm = new Fsm('begin', [
|
|
||||||
'begin' => [
|
|
||||||
'success' => 'end',
|
|
||||||
'error' => 'error',
|
|
||||||
'transition' => function (Transaction $trans) use ($t, &$c) {
|
|
||||||
$c[] = 'begin';
|
|
||||||
throw new \OutOfBoundsException();
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'error' => [
|
|
||||||
'success' => 'end',
|
|
||||||
'error' => 'end',
|
|
||||||
'transition' => function (Transaction $trans) use ($t, &$c) {
|
|
||||||
$c[] = 'error';
|
|
||||||
$this->assertInstanceOf('OutOfBoundsException', $t->exception);
|
|
||||||
$trans->exception = null;
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'end' => [
|
|
||||||
'transition' => function (Transaction $trans) use ($t, &$c) {
|
|
||||||
$c[] = 'end';
|
|
||||||
}
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$fsm->run($t);
|
|
||||||
$this->assertEquals(['begin', 'error', 'end'], $c);
|
|
||||||
$this->assertNull($t->exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testThrowsTerminalErrors()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
$t = new Transaction($client, $request);
|
|
||||||
|
|
||||||
$fsm = new Fsm('begin', [
|
|
||||||
'begin' => [
|
|
||||||
'transition' => function (Transaction $trans) use ($t) {
|
|
||||||
throw new \OutOfBoundsException();
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$fsm->run($t);
|
|
||||||
$this->fail('Did not throw');
|
|
||||||
} catch (\OutOfBoundsException $e) {
|
|
||||||
$this->assertSame($e, $t->exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \RuntimeException
|
|
||||||
* @expectedExceptionMessage Too many state transitions
|
|
||||||
*/
|
|
||||||
public function testThrowsWhenTooManyTransitions()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
$t = new Transaction($client, $request);
|
|
||||||
$fsm = new Fsm('begin', ['begin' => ['success' => 'begin']], 10);
|
|
||||||
$fsm->run($t);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedExceptionMessage Foo
|
|
||||||
* @expectedException \GuzzleHttp\Exception\StateException
|
|
||||||
*/
|
|
||||||
public function testThrowsWhenStateException()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
$t = new Transaction($client, $request);
|
|
||||||
$fsm = new Fsm('begin', [
|
|
||||||
'begin' => [
|
|
||||||
'transition' => function () use ($request) {
|
|
||||||
throw new StateException('Foo');
|
|
||||||
},
|
|
||||||
'error' => 'not_there'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
$fsm->run($t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCanInterceptTransitionStates()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
$t = new Transaction($client, $request);
|
|
||||||
$called = false;
|
|
||||||
$fsm = new Fsm('begin', [
|
|
||||||
'begin' => [
|
|
||||||
'transition' => function () { return true; },
|
|
||||||
'intercept' => 'end'
|
|
||||||
],
|
|
||||||
'end' => [
|
|
||||||
'transition' => function () use (&$called) { $called = true; }
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
$fsm->run($t);
|
|
||||||
$this->assertTrue($called);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedExceptionMessage Invalid intercept state transition from begin
|
|
||||||
* @expectedException \GuzzleHttp\Exception\StateException
|
|
||||||
*/
|
|
||||||
public function testEnsuresInterceptStatesAreDefined()
|
|
||||||
{
|
|
||||||
$client = new Client();
|
|
||||||
$request = $client->createRequest('GET', 'http://httpbin.org');
|
|
||||||
$t = new Transaction($client, $request);
|
|
||||||
$fsm = new Fsm('begin', [
|
|
||||||
'begin' => [
|
|
||||||
'transition' => function () { return true; }
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
$fsm->run($t);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
namespace GuzzleHttp\Tests;
|
namespace GuzzleHttp\Tests;
|
||||||
|
|
||||||
use GuzzleHttp\Exception\RequestException;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
|
use GuzzleHttp\Message\MessageFactory;
|
||||||
use GuzzleHttp\Message\Response;
|
use GuzzleHttp\Message\Response;
|
||||||
use GuzzleHttp\RequestFsm;
|
use GuzzleHttp\RequestFsm;
|
||||||
use GuzzleHttp\Subscriber\Mock;
|
use GuzzleHttp\Subscriber\Mock;
|
||||||
@ -19,21 +20,28 @@ use React\Promise\Deferred;
|
|||||||
|
|
||||||
class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
|
private $mf;
|
||||||
|
|
||||||
|
public function setup()
|
||||||
|
{
|
||||||
|
$this->mf = new MessageFactory();
|
||||||
|
}
|
||||||
|
|
||||||
public function testEmitsBeforeEventInTransition()
|
public function testEmitsBeforeEventInTransition()
|
||||||
{
|
{
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||||
$c = false;
|
$c = false;
|
||||||
$t->request->getEmitter()->on('before', function (BeforeEvent $e) use (&$c) {
|
$t->request->getEmitter()->on('before', function (BeforeEvent $e) use (&$c) {
|
||||||
$c = true;
|
$c = true;
|
||||||
});
|
});
|
||||||
$fsm->run($t, 'send');
|
$fsm($t, 'send');
|
||||||
$this->assertTrue($c);
|
$this->assertTrue($c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testEmitsCompleteEventInTransition()
|
public function testEmitsCompleteEventInTransition()
|
||||||
{
|
{
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||||
$t->response = new Response(200);
|
$t->response = new Response(200);
|
||||||
$t->state = 'complete';
|
$t->state = 'complete';
|
||||||
@ -41,13 +49,13 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
$t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
|
$t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
|
||||||
$c = true;
|
$c = true;
|
||||||
});
|
});
|
||||||
$fsm->run($t, 'end');
|
$fsm($t, 'end');
|
||||||
$this->assertTrue($c);
|
$this->assertTrue($c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDoesNotEmitCompleteForFuture()
|
public function testDoesNotEmitCompleteForFuture()
|
||||||
{
|
{
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
$t->response = new FutureResponse($deferred->promise());
|
$t->response = new FutureResponse($deferred->promise());
|
||||||
@ -56,13 +64,13 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
$t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
|
$t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
|
||||||
$c = true;
|
$c = true;
|
||||||
});
|
});
|
||||||
$fsm->run($t, 'end');
|
$fsm($t, 'end');
|
||||||
$this->assertFalse($c);
|
$this->assertFalse($c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDoesNotEmitEndForFuture()
|
public function testDoesNotEmitEndForFuture()
|
||||||
{
|
{
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
$t->response = new FutureResponse($deferred->promise());
|
$t->response = new FutureResponse($deferred->promise());
|
||||||
@ -71,7 +79,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
$t->request->getEmitter()->on('end', function (EndEvent $e) use (&$c) {
|
$t->request->getEmitter()->on('end', function (EndEvent $e) use (&$c) {
|
||||||
$c = true;
|
$c = true;
|
||||||
});
|
});
|
||||||
$fsm->run($t);
|
$fsm($t);
|
||||||
$this->assertFalse($c);
|
$this->assertFalse($c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +95,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testTransitionsThroughErrorsInBefore()
|
public function testTransitionsThroughErrorsInBefore()
|
||||||
{
|
{
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
$request = $client->createRequest('GET', 'http://ewfewwef.com');
|
$request = $client->createRequest('GET', 'http://ewfewwef.com');
|
||||||
$t = new Transaction($client, $request);
|
$t = new Transaction($client, $request);
|
||||||
@ -97,7 +105,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
throw new \Exception('foo');
|
throw new \Exception('foo');
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
$fsm->run($t, 'send');
|
$fsm($t, 'send');
|
||||||
$this->fail('did not throw');
|
$this->fail('did not throw');
|
||||||
} catch (RequestException $e) {
|
} catch (RequestException $e) {
|
||||||
$this->assertContains('foo', $t->exception->getMessage());
|
$this->assertContains('foo', $t->exception->getMessage());
|
||||||
@ -125,7 +133,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testTransitionsThroughErrorInterception()
|
public function testTransitionsThroughErrorInterception()
|
||||||
{
|
{
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
$request = $client->createRequest('GET', 'http://ewfewwef.com');
|
$request = $client->createRequest('GET', 'http://ewfewwef.com');
|
||||||
$t = new Transaction($client, $request);
|
$t = new Transaction($client, $request);
|
||||||
@ -134,10 +142,10 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
$t->request->getEmitter()->on('error', function (ErrorEvent $e) {
|
$t->request->getEmitter()->on('error', function (ErrorEvent $e) {
|
||||||
$e->intercept(new Response(200));
|
$e->intercept(new Response(200));
|
||||||
});
|
});
|
||||||
$fsm->run($t, 'send');
|
$fsm($t, 'send');
|
||||||
$t->response = new Response(404);
|
$t->response = new Response(404);
|
||||||
$t->state = 'complete';
|
$t->state = 'complete';
|
||||||
$fsm->run($t);
|
$fsm($t);
|
||||||
$this->assertEquals(200, $t->response->getStatusCode());
|
$this->assertEquals(200, $t->response->getStatusCode());
|
||||||
$this->assertNull($t->exception);
|
$this->assertNull($t->exception);
|
||||||
$this->assertEquals(['before', 'complete', 'error', 'complete', 'end'], $calls);
|
$this->assertEquals(['before', 'complete', 'error', 'complete', 'end'], $calls);
|
||||||
@ -158,4 +166,27 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
|||||||
$calls[] = 'end';
|
$calls[] = 'end';
|
||||||
}, RequestEvents::EARLY);
|
}, RequestEvents::EARLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \GuzzleHttp\Exception\RequestException
|
||||||
|
* @expectedExceptionMessage Too many state transitions
|
||||||
|
*/
|
||||||
|
public function testDetectsInfiniteLoops()
|
||||||
|
{
|
||||||
|
$client = new Client([
|
||||||
|
'fsm' => $fsm = new RequestFsm(
|
||||||
|
function () {},
|
||||||
|
new MessageFactory(),
|
||||||
|
3
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
$request = $client->createRequest('GET', 'http://foo.com:123');
|
||||||
|
$request->getEmitter()->on('before', function () {
|
||||||
|
throw new \Exception('foo');
|
||||||
|
});
|
||||||
|
$request->getEmitter()->on('error', function ($e) {
|
||||||
|
$e->retry();
|
||||||
|
});
|
||||||
|
$client->send($request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ use GuzzleHttp\Message\Response;
|
|||||||
use GuzzleHttp\Ring\Client\MockAdapter;
|
use GuzzleHttp\Ring\Client\MockAdapter;
|
||||||
use GuzzleHttp\Exception\RequestException;
|
use GuzzleHttp\Exception\RequestException;
|
||||||
use GuzzleHttp\Event\ErrorEvent;
|
use GuzzleHttp\Event\ErrorEvent;
|
||||||
use GuzzleHttp\Fsm;
|
|
||||||
use GuzzleHttp\RequestFsm;
|
use GuzzleHttp\RequestFsm;
|
||||||
|
|
||||||
class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
||||||
@ -26,7 +25,7 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
|||||||
$request->getConfig()->set('foo', 'bar');
|
$request->getConfig()->set('foo', 'bar');
|
||||||
$trans = new Transaction(new Client(), $request);
|
$trans = new Transaction(new Client(), $request);
|
||||||
$factory = new MessageFactory();
|
$factory = new MessageFactory();
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||||
$r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
|
$r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
|
||||||
$this->assertEquals('http', $r['scheme']);
|
$this->assertEquals('http', $r['scheme']);
|
||||||
$this->assertEquals('1.1', $r['version']);
|
$this->assertEquals('1.1', $r['version']);
|
||||||
@ -48,7 +47,7 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
|||||||
$request = new Request('GET', 'http://httpbin.org');
|
$request = new Request('GET', 'http://httpbin.org');
|
||||||
$trans = new Transaction(new Client(), $request);
|
$trans = new Transaction(new Client(), $request);
|
||||||
$factory = new MessageFactory();
|
$factory = new MessageFactory();
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||||
$r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
|
$r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
|
||||||
$this->assertNull($r['query_string']);
|
$this->assertNull($r['query_string']);
|
||||||
$this->assertEquals('/', $r['uri']);
|
$this->assertEquals('/', $r['uri']);
|
||||||
@ -117,7 +116,7 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
|||||||
});
|
});
|
||||||
$f = new MessageFactory();
|
$f = new MessageFactory();
|
||||||
$res = ['status' => 200, 'headers' => []];
|
$res = ['status' => 200, 'headers' => []];
|
||||||
$fsm = new RequestFsm(function () {});
|
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||||
RingBridge::completeRingResponse($trans, $res, $f, $fsm);
|
RingBridge::completeRingResponse($trans, $res, $f, $fsm);
|
||||||
$this->assertInstanceOf(
|
$this->assertInstanceOf(
|
||||||
'GuzzleHttp\Message\ResponseInterface',
|
'GuzzleHttp\Message\ResponseInterface',
|
||||||
@ -203,16 +202,12 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$trans = new Transaction(new Client(), new Request('GET', 'http://f.co'));
|
$trans = new Transaction(new Client(), new Request('GET', 'http://f.co'));
|
||||||
$f = new MessageFactory();
|
$f = new MessageFactory();
|
||||||
$called = false;
|
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||||
$fsm = new Fsm('foo', [
|
try {
|
||||||
'error' => [
|
RingBridge::completeRingResponse($trans, [], $f, $fsm);
|
||||||
'transition' => function ($trans) use (&$called) {
|
} catch (RequestException $e) {
|
||||||
$called = true;
|
$this->assertSame($trans->request, $e->getRequest());
|
||||||
$this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $trans->exception);
|
$this->assertContains('Guzzle-Ring', $e->getMessage());
|
||||||
}
|
}
|
||||||
]
|
|
||||||
]);
|
|
||||||
RingBridge::completeRingResponse($trans, [], $f, $fsm);
|
|
||||||
$this->assertTrue($called);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user