Viewing file: RequestFsm.php (8.04 KB) -rwxrwxr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php namespace GuzzleHttp;
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 { private $handler; private $mf; private $maxTransitions;
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 RingPHP request, so we exit the FSM. 'send' => ['error' => 'error'], 'complete' => [ 'success' => 'end', 'intercept' => 'retry', 'error' => 'error' ], 'error' => [ 'success' => 'complete', 'intercept' => 'retry', 'error' => 'end' ], 'retry' => ['success' => 'before'], 'end' => [], ];
public function __construct( callable $handler, MessageFactoryInterface $messageFactory, $maxTransitions = 200 ) { $this->mf = $messageFactory; $this->maxTransitions = $maxTransitions; $this->handler = $handler; }
/** * 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));
// When a response is set during the before event (i.e., a mock), then // we don't need to send anything. Skip ahead to the complete event // by returning to to go to the intercept state. return (bool) $trans->response; }
private function retry(Transaction $trans) { $trans->response = $trans->exception = null; $trans->retries++; }
private function send(Transaction $trans) { $fn = $this->handler; $trans->response = FutureResponse::proxy( $fn(RingBridge::prepareRingRequest($trans)), function ($value) use ($trans) { RingBridge::completeRingResponse($trans, $value, $this->mf, $this); // Resolve deep futures if this is not a future transaction. // This accounts for things like retries that would otherwise // not have an immediate side-effect. if (!$trans->future && $trans->response instanceof FutureInterface) { $trans->response = $trans->response->wait(); } 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, * then the exception is thrown and we transition to the "end" event. This * event also allows requests to be retried, and when retried, transitions * to the "before" event. Otherwise, when no retries, and the exception is * intercepted, transition to the "complete" event. */ private function error(Transaction $trans) { // Convert non-request exception to a wrapped exception if (!($trans->exception instanceof RequestException)) { $trans->exception = RequestException::wrapException( $trans->request, $trans->exception ); }
// Dispatch an event and allow interception $event = new ErrorEvent($trans); $trans->request->getEmitter()->emit('error', $event);
if ($trans->state === 'retry') { return true; }
if ($trans->exception) { throw $trans->exception; }
return false; }
/** * Emits a complete event, and if a request is marked for a retry during * the complete event, then the "before" state is transitioned to. */ private function complete(Transaction $trans) { // Futures will have their own end events emitted when dereferenced. if ($trans->response instanceof FutureInterface) { return false; }
$trans->response->setEffectiveUrl($trans->request->getUrl()); $trans->request->getEmitter()->emit('complete', new CompleteEvent($trans));
// Return true to transition to the 'before' state. False otherwise. return $trans->state === 'retry'; }
/** * Emits the "end" event and throws an exception if one is present. */ 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. if (!$trans->exception && $trans->response instanceof FutureInterface) { return; }
$trans->request->getEmitter()->emit('end', new EndEvent($trans));
// Throw exceptions in the terminal event if the exception was not // handled by an "end" event listener. if ($trans->exception) { if (!($trans->exception instanceof RequestException)) { $trans->exception = RequestException::wrapException( $trans->request, $trans->exception ); } throw $trans->exception; } } }
|