Skip to main content

Error handling

Catching exceptions​

Every exception the package throws implements Dirthara\Http\Exception\HttpException, so one catch covers them all. Each also extends the SPL exception that describes the failure: invalid input throws an InvalidArgumentException, and an operation that failed while running throws a RuntimeException.

use Dirthara\Http\Exception\HttpException;
use Dirthara\Http\Exception\InvalidRequestException;

try {
$request = $request->withMethod($method);
} catch (InvalidRequestException $exception) {
// the method was invalid
} catch (HttpException $exception) {
// anything else this package raised
}
ExceptionExtendsThrown for
InvalidMessageExceptionInvalidArgumentExceptionInvalid header names and values, and protocol versions.
InvalidRequestExceptionInvalidArgumentExceptionInvalid methods, request targets, uploaded file trees, and parsed bodies.
InvalidResponseExceptionInvalidArgumentExceptionInvalid status codes and reason phrases.
InvalidUriExceptionInvalidArgumentExceptionURIs and components that are not valid.
InvalidStreamExceptionInvalidArgumentExceptionInvalid resources, modes, filenames, and read lengths.
InvalidUploadedFileExceptionInvalidArgumentExceptionInvalid uploaded file details and target paths.
StreamExceptionRuntimeExceptionStream operations that fail, and files the stream factory cannot open.
UploadedFileExceptionRuntimeExceptionUploaded files that failed, were already moved, or could not be moved.

The pages for each class list which situation throws which exception. All exception classes are final; catch them by class or by HttpException.

Context​

Every exception carries diagnostic metadata in its public, read-only context property, an array<string, mixed>.

use Dirthara\Http\Exception\InvalidResponseException;

try {
$response->withStatus(600);
} catch (InvalidResponseException $exception) {
$context = $exception->context;
}

$context is ['statusCode' => 600]. Code that catches an exception and knows more can add to it with addContext(), which merges the given array into the context, replacing matching keys, and returns the exception:

throw $exception->addContext(['route' => 'users.store']);
Constructor parameterTypeDefaultMeaning
messagestring''The exception message.
codeint0The exception code.
previous?ThrowablenullThe exception this one wraps.
contextarray<string, mixed>[]The diagnostic context.

Sensitive values​

Some values an HTTP message carries are credentials or personal data, so the package leaves them out:

  • Header values are never in a message or context, only the header name.
  • Request targets are left out entirely, because a query string can hold tokens.
  • Parsed bodies are reduced to their type.

Other exceptions do include what they rejected, in both the message and the context:

  • InvalidUriException::forInvalidUri() includes the whole URI, which can hold a password in its user info or a token in its query.
  • StreamException::unableToOpen() includes the whole filename, which can be a URL such as ftp://user:password@host/.
  • The uploaded file exceptions include file paths.
caution

The first two can put credentials in a log. Do not log them unfiltered when the URI or filename can come from outside the application.

When a message quotes a rejected value, control characters in it are escaped, so a line break shows as \n and cannot forge a line in a log. The context holds the value as it was given.

Logging​

Exceptions do not log themselves or depend on a logger. An application handler can pass the context to a PSR-3 logger. Add the caught exception under the exception key after reading the context, so an exception entry already in the context cannot replace it.

use Dirthara\Http\Exception\HttpException;

try {
$response = $response->withHeader($name, $value);
} catch (HttpException $exception) {
$context = $exception->context;
$context['exception'] = $exception;

$logger->warning($exception->getMessage(), $context);
}