Skip to content

Instantly share code, notes, and snippets.

@jamiehannaford
Created April 30, 2014 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamiehannaford/0a085d4b1507308b0190 to your computer and use it in GitHub Desktop.
Save jamiehannaford/0a085d4b1507308b0190 to your computer and use it in GitHub Desktop.
<?php
namespace OpenStack\Common\Transport\Exception;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
use OpenStack\Common\Exception;
class RequestException extends Exception
{
/** @var \GuzzleHttp\Message\RequestInterface */
protected $request;
/** @var \GuzzleHttp\Message\ResponseInterface */
protected $response;
/**
* Construct this exception like any other, but also inject Request and
* Response objects in case the user needs them for debugging.
*
* @param string $errorMessage Human-readable explanation of error
* @param RequestInterface $request The failed request
* @param ResponseInterface $response The API's response
*/
public function __construct($errorMessage, RequestInterface $request, ResponseInterface $response)
{
parent::__construct($errorMessage);
$this->request = $request;
$this->response = $response;
}
/**
* Factory method that creates an appropriate Exception object based on the
* Response's status code. The message is constructed here also.
*
* @param RequestInterface $request The failed request
* @param ResponseInterface $response The API's response
* @return self
*/
public static function create(RequestInterface $request, ResponseInterface $response)
{
$label = 'A HTTP error occurred';
$status = $response->getStatusCode();
switch ($status) {
case '401':
$class = self::prependNamespace('UnauthorizedException');
break;
case '403':
$class = self::prependNamespace('ForbiddenException');
break;
case '404':
$class = self::prependNamespace('ResourceNotFoundException');
break;
case '405':
$class = self::prependNamespace('MethodNotAllowedException');
break;
case '409':
$class = self::prependNamespace('ConflictException');
break;
case '411':
$class = self::prependNamespace('LengthRequiredException');
break;
case '422':
$class = self::prependNamespace('UnprocessableEntityException');
break;
case '500':
$class = self::prependNamespace('ServerException');
break;
}
$message = sprintf(
"%s\n[Status] %s (%s)\n[Message] %s", $label,
$status, $response->getReasonPhrase(), (string) $response->getBody()
);
// For all other errors, throw a generic Exception
if (!isset($class)) {
throw new Exception($message);
}
return new $class($message, $request, $response);
}
protected static function prependNamespace($class)
{
return sprintf("%s\\%s", __NAMESPACE__, $class);
}
public function getResponse()
{
return $this->response;
}
public function getRequest()
{
return $this->request;
}
}
@jamiehannaford
Copy link
Author

One of the problems I've just noticed is that the $response and $request properties reference Guzzle classes, which is incorrect. I can change this to point to our own message classes.

@ycombinator
Copy link

Minor suggestion: when I first read self::prependNamespace('UnauthorizedException'), I interpreted it as "prepend the UnauthorizedException namespace (to something)". Consider renaming prependNamespace to something like createFullyQualifiedClassNameFrom or prependNamespaceTo.

@ycombinator
Copy link

Lines 74 - 82: It might be clearer if the creation of $message was done before the switch and a default case was added in the switch to throw the generic Exception.

@ycombinator
Copy link

I imagine each of the *Exception classes mentioned in the switch cases will extend this RequestException class. I also imagine you would want to disallow instantiation of the RequestException class itself (that is, one of its subclasses must be instantiated instead). If so, consider making the RequestException class abstract.

@jamiehannaford
Copy link
Author

I agree about the method name, I'll change it to self::prependNamespaceTo.

Adding a default block won't work because the generic exception class has different constructor args - so it needs to be instantiated in a different way. Another option might be:

return isset($class) ? new $class($message, $request, $response) : Exception($message);

I see what you mean about making the RequestException abstract - but I'm not sure we'll need to do it. Say for example we encounter a HTTP 4xx or 5xx error that does not have its own concrete exception, we'd probably throw a generic RequestException.

@ycombinator
Copy link

In that case, what about creating a default case that returns new self($message, $request, $response);?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment