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.
Pros:
500 for server errors), integrating seamlessly with Laravel’s HttpException or JsonResponse.ExceptionContext allow attaching metadata (e.g., IDs, timestamps) without bloating exception classes, improving debugging.ValidationException) without conflicts.Cons:
Handler class or logging systems (e.g., Log::error()), requiring manual adaptation.throwing events in Symfony).Laravel Compatibility:
throw new HttpException($e->getStatusCode(), $e->getDebugMessage())).Migration Path:
BaseException in new code.ExceptionContext (requires constructor refactoring).trait UsesSimpleException).Technical Risk:
getDebugMessage() isn’t accidentally exposed in production (validate app()->debug()).Why adopt this over Laravel’s native exceptions?
How will this integrate with App\Exceptions\Handler?
render() or report() to check for BaseException and format responses/logs accordingly:
public function render($request, Throwable $exception)
{
if ($exception instanceof BaseException) {
return response()->json([
'message' => $exception->getPublicMessage(),
'errors' => $exception->getDebugMessage(), // Only in debug mode
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
Will this work with Laravel’s validation errors?
ValidationException is framework-specific. Use this package only for custom business exceptions.How will localization be handled?
__() or build a decorator:
$context = new ExceptionContext(
__('errors.generic.user'), // Localized public message
__('errors.generic.debug', ['id' => $id]), // Localized debug
500
);
What’s the fallback if the package is abandoned?
Exception class with similar properties:
class CustomException extends \Exception
{
public function __construct(
public string $publicMessage,
public string $debugMessage,
int $code = 0,
?\Throwable $previous = null
) {
parent::__construct($debugMessage, $code, $previous);
}
}
Ideal Use Cases:
ErrorHandler for simplicity).Poor Fit:
Symfony\Component\HttpKernel\Exception\HttpException).ValidationException, HttpException) are sufficient.ErrorHandler: May duplicate functionality (e.g., ErrorHandler::render() already separates public/debug messages).Assessment Phase:
Implementation:
composer require holicz/simple-exception
// app/Exceptions/BaseCustomException.php
namespace App\Exceptions;
use holicz\SimpleException\BaseException;
use holicz\SimpleException\ExceptionContext;
abstract class BaseCustomException extends BaseException
{
protected function createContext(string $public, string $debug, int $status): ExceptionContext
{
return new ExceptionContext($public, $debug, $status);
}
}
// app/Exceptions/PaymentFailedException.php
namespace App\Exceptions;
class PaymentFailedException extends BaseCustomException
{
public function __construct(int $amount, string $gateway)
{
$context = $this->createContext(
'Payment failed. Please try again.',
sprintf('Gateway %s rejected amount %d: insufficient funds.', $gateway, $amount),
422
);
parent::__construct($context);
}
}
App\Exceptions\Handler
public function render($request, Throwable $exception)
{
if ($exception instanceof BaseCustomException) {
return response()->json([
'message' => $exception->getPublicMessage(),
'debug' => app()->debug() ? $exception->getDebugMessage() : null,
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
422 for validation-like errors).getDebugMessage() is captured by Log::error()).Advanced: Backport to Legacy Exceptions
// app/Traits/UsesSimpleException.php
trait UsesSimpleException
{
protected function getExceptionContext(string $public, string $debug, int $status): ExceptionContext
{
return new ExceptionContext($public, $debug, $status);
}
}
// Legacy exception
class LegacyException extends \Exception
{
use UsesSimpleException;
public function __construct(int $id)
{
$context = $this->getExceptionContext(
'Legacy error occurred.',
sprintf('Failed to process ID %d.', $id),
500
);
parent::__construct($context->getDebugMessage(), $context->getStatusCode());
}
public function getPublicMessage(): string
{
return $this->context->getPublicMessage();
}
}
How can I help you explore Laravel packages today?