holicz/simple-exception
Lightweight base exception for PHP/Laravel with structured context: a safe public message for users, a private debug message for logs, and an HTTP status code. Create your own exceptions by extending BaseException and pass an ExceptionContext.
Start by installing the package via Composer:
composer require holicz/simple-exception
First Use Case: Create a custom exception for a business-specific error (e.g., CouldNotRemoveArticleException) to standardize error handling in your Laravel application.
Example:
// app/Exceptions/CouldNotRemoveArticleException.php
namespace App\Exceptions;
use holicz\SimpleException\BaseException;
use holicz\SimpleException\ExceptionContext;
class CouldNotRemoveArticleException extends BaseException
{
public function __construct(int $articleId)
{
$exceptionContext = new ExceptionContext(
'Failed to remove the article. Please try again.',
sprintf('Article with ID %d could not be deleted due to a database constraint.', $articleId),
422 // HTTP status code
);
parent::__construct($exceptionContext);
}
}
Where to Look First:
BaseException and ExceptionContext classes in vendor/holicz/simple-exception/src/ to understand the structure.App\Exceptions\Handler in Laravel to see how to integrate the custom exception into your error-handling pipeline.Extending BaseException:
Always extend BaseException for custom exceptions. Pass an ExceptionContext object in the constructor to define the public message, debug message, and HTTP status code.
class PaymentFailedException extends BaseException
{
public function __construct(string $paymentId, string $error)
{
$context = new ExceptionContext(
'Payment processing failed. Please contact support.',
"Payment ID: {$paymentId}. Error: {$error}",
402 // HTTP status code
);
parent::__construct($context);
}
}
Handling Exceptions: Catch exceptions and use the provided methods to access messages and status codes.
try {
// Business logic that may throw an exception
} catch (CouldNotRemoveArticleException $e) {
return response()->json([
'message' => $e->getPublicMessage(),
'debug' => app()->debug() ? $e->getDebugMessage() : null,
], $e->getStatusCode());
}
Logging:
Use getDebugMessage() for logging detailed error information.
catch (PaymentFailedException $e) {
Log::error($e->getDebugMessage(), [
'context' => 'payment_processing',
'payment_id' => $paymentId,
]);
return response()->json(['error' => $e->getPublicMessage()], $e->getStatusCode());
}
API Responses:
Standardize API error responses using BaseException in App\Exceptions\Handler.
public function render($request, Throwable $exception)
{
if ($exception instanceof BaseException) {
return response()->json([
'error' => $exception->getPublicMessage(),
'debug' => app()->debug() ? $exception->getDebugMessage() : null,
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
Validation Errors:
Extend BaseException for validation errors to maintain consistency.
class ValidationException extends BaseException
{
public function __construct(array $errors)
{
$context = new ExceptionContext(
'Validation failed. Please correct the errors below.',
json_encode($errors),
422
);
parent::__construct($context);
}
}
Middleware for Debugging: Use middleware to log exceptions with debug messages in development.
public function handle($request, Closure $next)
{
try {
return $next($request);
} catch (BaseException $e) {
Log::debug($e->getDebugMessage());
throw $e;
}
}
Use with Laravel’s HTTP Exceptions:
Convert BaseException to Laravel’s HttpResponseException for seamless integration.
catch (BaseException $e) {
throw new HttpResponseException(
response()->json([
'error' => $e->getPublicMessage(),
'debug' => app()->debug() ? $e->getDebugMessage() : null,
], $e->getStatusCode())
);
}
Localization:
Wrap public messages in Laravel’s __() function for multilingual support.
$context = new ExceptionContext(
__('errors.payment_failed'),
"Payment ID: {$paymentId}. Error: {$error}",
402
);
Inconsistent Adoption:
BaseException, error responses will be inconsistent.Debug Messages Leaking:
getDebugMessage() to end-users in production.app()->debug() before exposing debug details.return response()->json([
'error' => $e->getPublicMessage(),
'debug' => app()->debug() ? $e->getDebugMessage() : null,
]);
HTTP Status Code Mismatch:
500 for client errors).422 for validation, 500 for server errors).Overhead in Simple Projects:
Logging Context:
Log the full ExceptionContext for debugging complex issues.
Log::error('Exception occurred', [
'public_message' => $e->getPublicMessage(),
'debug_message' => $e->getDebugMessage(),
'status_code' => $e->getStatusCode(),
'context' => $e->getContext(), // If extended
]);
Testing Exceptions: Write unit tests to verify public/debug message separation and status codes.
public function test_exception_messages()
{
$exception = new CouldNotRemoveArticleException(1);
$this->assertEquals('Failed to remove the article. Please try again.', $exception->getPublicMessage());
$this->assertEquals(422, $exception->getStatusCode());
}
Extending ExceptionContext:
Add custom properties to ExceptionContext for additional metadata (e.g., timestamps, user IDs).
$context = new ExceptionContext(
'User not found.',
'User with ID 123 does not exist.',
404,
['user_id' => 123, 'timestamp' => now()]
);
Then extend BaseException to support the new property:
class BaseException extends \Exception
{
protected $context;
public function getContext(): array
{
return $this->context['extra'] ?? [];
}
}
No Built-in Localization:
The package does not support localization out of the box. Use Laravel’s __() function or a decorator pattern.
$context = new ExceptionContext(
__('errors.generic_error'),
$debugMessage,
500
);
No Event Dispatching: The package does not integrate with Laravel’s event system. Manually dispatch events if needed.
catch (BaseException $e) {
event(new ExceptionOccurred($e));
throw $e;
}
PHP Version Compatibility: Ensure your project uses PHP 7.4+ (or PHP 8) to avoid compatibility issues.
Custom ExceptionContext:
Extend ExceptionContext to add domain-specific properties.
class CustomExceptionContext extends ExceptionContext
{
public function __construct(
string $publicMessage,
string $debugMessage,
int $statusCode,
public array $metadata
) {
parent::__construct($publicMessage, $debugMessage, $statusCode);
}
}
Exception Handlers:
Create a base handler for all BaseException instances in App\Exceptions\Handler.
public function render($request, BaseException $exception)
{
return response()->json([
'error' => $exception->getPublicMessage(),
'debug' => app()->debug() ? $exception->getDebugMessage() : null,
'code' => $exception->getStatusCode(),
], $exception->getStatusCode());
}
Middleware for Global Handling: Use middleware to catch and log exceptions globally.
public function handle($request
How can I help you explore Laravel packages today?