mirror of
https://github.com/guzzle/guzzle.git
synced 2025-02-24 10:03:27 +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 */
|
||||
private $defaults;
|
||||
|
||||
/** @var Fsm Request state machine */
|
||||
/** @var callable Request state machine */
|
||||
private $fsm;
|
||||
|
||||
/**
|
||||
@ -63,7 +63,10 @@ class Client implements ClientInterface
|
||||
* - message_factory: Factory used to create request and response object
|
||||
* - defaults: Default request options to apply to each request
|
||||
* - 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 = [])
|
||||
{
|
||||
@ -77,12 +80,15 @@ class Client implements ClientInterface
|
||||
$this->messageFactory = isset($config['message_factory'])
|
||||
? $config['message_factory']
|
||||
: new MessageFactory();
|
||||
$adapter = isset($config['adapter'])
|
||||
? $config['adapter']
|
||||
: self::getDefaultAdapter();
|
||||
$this->fsm = isset($config['fsm'])
|
||||
? $config['fsm']
|
||||
: $this->createDefaultFsm($adapter, $this->messageFactory);
|
||||
|
||||
if (isset($config['fsm'])) {
|
||||
$this->fsm = $config['fsm'];
|
||||
} else {
|
||||
$this->fsm = new RequestFsm(
|
||||
isset($config['adapter']) ? $config['adapter'] : self::getDefaultAdapter(),
|
||||
$this->messageFactory
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,11 +233,12 @@ class Client implements ClientInterface
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$trans = new Transaction($this, $request);
|
||||
$fn = $this->fsm;
|
||||
|
||||
// Ensure a future response is returned if one was requested.
|
||||
if ($request->getConfig()->get('future')) {
|
||||
try {
|
||||
$this->fsm->run($trans);
|
||||
$fn($trans);
|
||||
// Turn the normal response into a future if needed.
|
||||
return $trans->response instanceof FutureInterface
|
||||
? $trans->response
|
||||
@ -242,7 +249,7 @@ class Client implements ClientInterface
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$this->fsm->run($trans);
|
||||
$fn($trans);
|
||||
return $trans->response instanceof FutureInterface
|
||||
? $trans->response->wait()
|
||||
: $trans->response;
|
||||
@ -368,23 +375,6 @@ class Client implements ClientInterface
|
||||
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.
|
||||
* @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\CompleteEvent;
|
||||
use GuzzleHttp\Event\EndEvent;
|
||||
use GuzzleHttp\Exception\StateException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Message\FutureResponse;
|
||||
use GuzzleHttp\Message\MessageFactoryInterface;
|
||||
use GuzzleHttp\Ring\Future\FutureInterface;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$this->sendFn = $sendFn;
|
||||
parent::__construct('before', [
|
||||
// When a mock intercepts the emitted "before" event, then we
|
||||
// transition to the "complete" intercept state.
|
||||
'before' => [
|
||||
'success' => 'send',
|
||||
'intercept' => 'complete',
|
||||
'error' => 'error',
|
||||
'transition' => [$this, 'beforeTransition']
|
||||
],
|
||||
// The complete and error events are handled using the "then" of
|
||||
// the Guzzle-Ring request, so we exit the FSM.
|
||||
'send' => [
|
||||
'error' => 'error',
|
||||
'transition' => $this->sendFn
|
||||
],
|
||||
'complete' => [
|
||||
'success' => 'end',
|
||||
'intercept' => 'before',
|
||||
'error' => 'error',
|
||||
'transition' => [$this, 'completeTransition']
|
||||
],
|
||||
'error' => [
|
||||
'success' => 'complete',
|
||||
'intercept' => 'before',
|
||||
'error' => 'end',
|
||||
'transition' => [$this, 'ErrorTransition']
|
||||
],
|
||||
'end' => [
|
||||
'transition' => [$this, 'endTransition']
|
||||
]
|
||||
]);
|
||||
private $states = [
|
||||
// When a mock intercepts the emitted "before" event, then we
|
||||
// transition to the "complete" intercept state.
|
||||
'before' => [
|
||||
'success' => 'send',
|
||||
'intercept' => 'complete',
|
||||
'error' => 'error'
|
||||
],
|
||||
// The complete and error events are handled using the "then" of
|
||||
// the Guzzle-Ring request, so we exit the FSM.
|
||||
'send' => ['error' => 'error'],
|
||||
'complete' => [
|
||||
'success' => 'end',
|
||||
'intercept' => 'before',
|
||||
'error' => 'error'
|
||||
],
|
||||
'error' => [
|
||||
'success' => 'complete',
|
||||
'intercept' => 'before',
|
||||
'error' => 'end'
|
||||
],
|
||||
'end' => []
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
callable $adapter,
|
||||
MessageFactoryInterface $messageFactory,
|
||||
$maxTransitions = 200
|
||||
) {
|
||||
$this->mf = $messageFactory;
|
||||
$this->maxTransitions = $maxTransitions;
|
||||
$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));
|
||||
|
||||
@ -61,6 +131,18 @@ class RequestFsm extends Fsm
|
||||
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
|
||||
* 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
|
||||
* intercepted, transition to the "complete" event.
|
||||
*/
|
||||
protected function errorTransition(Transaction $trans)
|
||||
private function error(Transaction $trans)
|
||||
{
|
||||
// Convert non-request exception to a wrapped exception
|
||||
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
|
||||
* 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.
|
||||
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.
|
||||
*/
|
||||
protected function endTransition(Transaction $trans)
|
||||
private function end(Transaction $trans)
|
||||
{
|
||||
// Futures will have their own end events emitted when dereferenced,
|
||||
// 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 array $response Ring response array
|
||||
* @param MessageFactoryInterface $messageFactory Creates response objects.
|
||||
* @param Fsm $fsm State machine.
|
||||
* @param callable $fsm Request FSM function.
|
||||
*/
|
||||
public static function completeRingResponse(
|
||||
Transaction $trans,
|
||||
array $response,
|
||||
MessageFactoryInterface $messageFactory,
|
||||
Fsm $fsm
|
||||
callable $fsm
|
||||
) {
|
||||
$trans->state = 'complete';
|
||||
$trans->transferInfo = isset($response['transfer_info'])
|
||||
@ -118,7 +118,7 @@ class RingBridge
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Message\MessageFactory;
|
||||
use GuzzleHttp\Message\Response;
|
||||
use GuzzleHttp\RequestFsm;
|
||||
use GuzzleHttp\Subscriber\Mock;
|
||||
@ -19,21 +20,28 @@ use React\Promise\Deferred;
|
||||
|
||||
class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $mf;
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$this->mf = new MessageFactory();
|
||||
}
|
||||
|
||||
public function testEmitsBeforeEventInTransition()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||
$c = false;
|
||||
$t->request->getEmitter()->on('before', function (BeforeEvent $e) use (&$c) {
|
||||
$c = true;
|
||||
});
|
||||
$fsm->run($t, 'send');
|
||||
$fsm($t, 'send');
|
||||
$this->assertTrue($c);
|
||||
}
|
||||
|
||||
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->response = new Response(200);
|
||||
$t->state = 'complete';
|
||||
@ -41,13 +49,13 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||
$t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) {
|
||||
$c = true;
|
||||
});
|
||||
$fsm->run($t, 'end');
|
||||
$fsm($t, 'end');
|
||||
$this->assertTrue($c);
|
||||
}
|
||||
|
||||
public function testDoesNotEmitCompleteForFuture()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||
$deferred = new Deferred();
|
||||
$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) {
|
||||
$c = true;
|
||||
});
|
||||
$fsm->run($t, 'end');
|
||||
$fsm($t, 'end');
|
||||
$this->assertFalse($c);
|
||||
}
|
||||
|
||||
public function testDoesNotEmitEndForFuture()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$t = new Transaction(new Client(), new Request('GET', 'http://foo.com'));
|
||||
$deferred = new Deferred();
|
||||
$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) {
|
||||
$c = true;
|
||||
});
|
||||
$fsm->run($t);
|
||||
$fsm($t);
|
||||
$this->assertFalse($c);
|
||||
}
|
||||
|
||||
@ -87,7 +95,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testTransitionsThroughErrorsInBefore()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$client = new Client();
|
||||
$request = $client->createRequest('GET', 'http://ewfewwef.com');
|
||||
$t = new Transaction($client, $request);
|
||||
@ -97,7 +105,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||
throw new \Exception('foo');
|
||||
});
|
||||
try {
|
||||
$fsm->run($t, 'send');
|
||||
$fsm($t, 'send');
|
||||
$this->fail('did not throw');
|
||||
} catch (RequestException $e) {
|
||||
$this->assertContains('foo', $t->exception->getMessage());
|
||||
@ -125,7 +133,7 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testTransitionsThroughErrorInterception()
|
||||
{
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, $this->mf);
|
||||
$client = new Client();
|
||||
$request = $client->createRequest('GET', 'http://ewfewwef.com');
|
||||
$t = new Transaction($client, $request);
|
||||
@ -134,10 +142,10 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||
$t->request->getEmitter()->on('error', function (ErrorEvent $e) {
|
||||
$e->intercept(new Response(200));
|
||||
});
|
||||
$fsm->run($t, 'send');
|
||||
$fsm($t, 'send');
|
||||
$t->response = new Response(404);
|
||||
$t->state = 'complete';
|
||||
$fsm->run($t);
|
||||
$fsm($t);
|
||||
$this->assertEquals(200, $t->response->getStatusCode());
|
||||
$this->assertNull($t->exception);
|
||||
$this->assertEquals(['before', 'complete', 'error', 'complete', 'end'], $calls);
|
||||
@ -158,4 +166,27 @@ class RequestFsmTest extends \PHPUnit_Framework_TestCase
|
||||
$calls[] = 'end';
|
||||
}, 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\Exception\RequestException;
|
||||
use GuzzleHttp\Event\ErrorEvent;
|
||||
use GuzzleHttp\Fsm;
|
||||
use GuzzleHttp\RequestFsm;
|
||||
|
||||
class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
||||
@ -26,7 +25,7 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
||||
$request->getConfig()->set('foo', 'bar');
|
||||
$trans = new Transaction(new Client(), $request);
|
||||
$factory = new MessageFactory();
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||
$r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
|
||||
$this->assertEquals('http', $r['scheme']);
|
||||
$this->assertEquals('1.1', $r['version']);
|
||||
@ -48,7 +47,7 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
||||
$request = new Request('GET', 'http://httpbin.org');
|
||||
$trans = new Transaction(new Client(), $request);
|
||||
$factory = new MessageFactory();
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||
$r = RingBridge::prepareRingRequest($trans, $factory, $fsm);
|
||||
$this->assertNull($r['query_string']);
|
||||
$this->assertEquals('/', $r['uri']);
|
||||
@ -117,7 +116,7 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
||||
});
|
||||
$f = new MessageFactory();
|
||||
$res = ['status' => 200, 'headers' => []];
|
||||
$fsm = new RequestFsm(function () {});
|
||||
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||
RingBridge::completeRingResponse($trans, $res, $f, $fsm);
|
||||
$this->assertInstanceOf(
|
||||
'GuzzleHttp\Message\ResponseInterface',
|
||||
@ -203,16 +202,12 @@ class RingBridgeTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$trans = new Transaction(new Client(), new Request('GET', 'http://f.co'));
|
||||
$f = new MessageFactory();
|
||||
$called = false;
|
||||
$fsm = new Fsm('foo', [
|
||||
'error' => [
|
||||
'transition' => function ($trans) use (&$called) {
|
||||
$called = true;
|
||||
$this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $trans->exception);
|
||||
}
|
||||
]
|
||||
]);
|
||||
RingBridge::completeRingResponse($trans, [], $f, $fsm);
|
||||
$this->assertTrue($called);
|
||||
$fsm = new RequestFsm(function () {}, new MessageFactory());
|
||||
try {
|
||||
RingBridge::completeRingResponse($trans, [], $f, $fsm);
|
||||
} catch (RequestException $e) {
|
||||
$this->assertSame($trans->request, $e->getRequest());
|
||||
$this->assertContains('Guzzle-Ring', $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user