-
Notifications
You must be signed in to change notification settings - Fork 0
Error Handling
Razy provides a structured exception hierarchy rooted in PHP's built-in RuntimeException. Each subsystem throws a domain-specific exception, making it straightforward to catch errors at different granularity levels.
RuntimeException (PHP built-in)
→?→ CacheException
→?→ ConfigurationException
→?→ ContainerException (PSR-11)
→ →?→ ContainerNotFoundException (PSR-11)
→?→ DatabaseException
→ →?→ ConnectionException
→ →?→ QueryException
→ →?→ TransactionException
→?→ FileException
→?→ HttpException
→ →?→ NotFoundException (404)
→ →?→ RedirectException (301/302)
→?→ MailerException
→?→ ModelNotFoundException
→?→ ModuleException
→ →?→ ModuleConfigException
→ →?→ ModuleLoadException
→?→ NetworkException
→ →?→ FTPException
→ →?→ SSHException
→?→ OAuthException
→?→ PipelineException
→?→ QueueException
→?→ RoutingException
→?→ TemplateException
All Razy exceptions extend RuntimeException, inheriting:
$e->getMessage(); // Human-readable message
$e->getCode(); // Error code (int)
$e->getPrevious(); // Wrapped previous exception
$e->getFile(); // File where thrown
$e->getLine(); // Line where thrown
$e->getTrace(); // Stack trace arrayThrown for DI container resolution failures. Implements PSR-11 Psr\Container\ContainerExceptionInterface.
use Razy\Exception\ContainerException;
try {
$service = $container->get(SomeService::class);
} catch (ContainerException $e) {
// Binding misconfiguration, circular dependency, etc.
echo $e->getMessage();
}Thrown when a requested binding is not found. Implements PSR-11 Psr\Container\NotFoundExceptionInterface.
use Razy\Exception\ContainerNotFoundException;
try {
$container->get('unregistered.service');
} catch (ContainerNotFoundException $e) {
echo "Service not registered: " . $e->getMessage();
}Base exception for all database errors.
use Razy\Exception\DatabaseException;
try {
$db->query('SELECT * FROM non_existent_table');
} catch (DatabaseException $e) {
// Catches Connection, Query, and Transaction errors
}Failed to connect or lost connection.
use Razy\Exception\ConnectionException;
try {
$db->connect();
} catch (ConnectionException $e) {
echo "Cannot connect: " . $e->getMessage();
// Maybe retry or fail fast
}SQL query execution failure. Includes query context:
use Razy\Exception\QueryException;
try {
$db->query('INSERT INTO users (email) VALUES (?)', ['duplicate@example.com']);
} catch (QueryException $e) {
echo "Query failed: " . $e->getMessage();
// Message typically includes the SQL and error details
}Transaction commit/rollback failure:
use Razy\Exception\TransactionException;
try {
$db->beginTransaction();
// ... operations ...
$db->commit();
} catch (TransactionException $e) {
echo "Transaction error: " . $e->getMessage();
}Base HTTP error with status code:
use Razy\Exception\HttpException;
throw new HttpException('Service unavailable', 503);404 Not Found — shorthand:
use Razy\Exception\NotFoundException;
// In a controller
$user = $db->find($id);
if (!$user) {
throw new NotFoundException("User {$id} not found");
}Triggers an HTTP redirect rather than rendering a response:
use Razy\Exception\RedirectException;
// 301 Permanent redirect
throw new RedirectException('/new-url', 301);
// 302 Temporary redirect (default)
throw new RedirectException('/login');When caught by the framework's error handler, a Location header is sent with the appropriate status code.
File system operations (read, write, permissions):
use Razy\Exception\FileException;
try {
$content = FileReader::read('/path/to/file');
} catch (FileException $e) {
echo "File error: " . $e->getMessage();
}Base for network I/O failures:
use Razy\Exception\NetworkException;
try {
// Any FTP or SFTP operation
} catch (NetworkException $e) {
// Catches both FTP and SSH errors
}FTP-specific failures (extends NetworkException):
use Razy\Exception\FTPException;
try {
$ftp->upload('/local/file', '/remote/path');
} catch (FTPException $e) {
echo "FTP error: " . $e->getMessage();
}SFTP/SSH-specific failures (extends NetworkException):
use Razy\Exception\SSHException;
try {
$sftp->loginWithPassword('user', 'wrong-password');
} catch (SSHException $e) {
echo "SSH error: " . $e->getMessage();
}Base for module system errors:
use Razy\Exception\ModuleException;
try {
$module->load();
} catch (ModuleException $e) {
// Config or load error
}Module configuration is invalid or missing required fields:
use Razy\Exception\ModuleConfigException;
try {
$module->loadConfig();
} catch (ModuleConfigException $e) {
echo "Bad module config: " . $e->getMessage();
}Module failed to load (missing files, dependency conflicts):
use Razy\Exception\ModuleLoadException;
try {
$distributor->loadModules();
} catch (ModuleLoadException $e) {
echo "Module load failed: " . $e->getMessage();
}Cache adapter failures:
use Razy\Exception\CacheException;
try {
Cache::get('key');
} catch (CacheException $e) {
// Redis connection lost, file permission denied, etc.
}Framework or application configuration errors:
use Razy\Exception\ConfigurationException;
// Thrown during bootstrap if config files are invalidEmail sending failures:
use Razy\Exception\MailerException;
try {
$mailer->send($message);
} catch (MailerException $e) {
echo "Email failed: " . $e->getMessage();
// SMTP connection error, invalid recipient, etc.
}Eloquent-style "find or fail" pattern:
use Razy\Exception\ModelNotFoundException;
try {
$user = User::findOrFail($id);
} catch (ModelNotFoundException $e) {
$model = $e->getModel(); // 'App\Models\User'
$ids = $e->getIds(); // [42]
echo "{$model} not found for IDs: " . implode(', ', $ids);
}Methods:
-
setModel(string $model, array $ids = []): static -
getModel(): string -
getIds(): array
OAuth 2.0 flow errors:
use Razy\Exception\OAuthException;
try {
$token = $oauth->requestAccessToken($code);
} catch (OAuthException $e) {
echo "OAuth error: " . $e->getMessage();
}Pipeline execution failures:
use Razy\Exception\PipelineException;
try {
$pipeline->send($data)->thenReturn();
} catch (PipelineException $e) {
echo "Pipeline error: " . $e->getMessage();
}Queue dispatch or worker errors:
use Razy\Exception\QueueException;
try {
$queue->push($job);
} catch (QueueException $e) {
echo "Queue error: " . $e->getMessage();
}Route resolution or URL generation failures:
use Razy\Exception\RoutingException;
try {
$router->resolve($request);
} catch (RoutingException $e) {
echo "Routing error: " . $e->getMessage();
}Template parsing or rendering errors:
use Razy\Exception\TemplateException;
try {
$engine->render('template-name', $data);
} catch (TemplateException $e) {
echo "Template error: " . $e->getMessage();
}// Granular → catch specific exceptions
try {
$db->beginTransaction();
$db->query($sql, $params);
$db->commit();
} catch (QueryException $e) {
$db->rollBack();
log("SQL error: {$e->getMessage()}");
} catch (TransactionException $e) {
log("TX error: {$e->getMessage()}");
}
// Broad → catch by subsystem
try {
$db->beginTransaction();
$db->query($sql, $params);
$db->commit();
} catch (DatabaseException $e) {
$db->rollBack();
log("DB error: {$e->getMessage()}");
}
// Broadest → catch any Razy runtime error
try {
// ...
} catch (\RuntimeException $e) {
// All Razy exceptions
}try {
$result = $controller->handle($request);
} catch (NotFoundException $e) {
return new Response(404, 'Not Found');
} catch (RedirectException $e) {
return new Response($e->getCode(), '', ['Location' => $e->getMessage()]);
} catch (HttpException $e) {
return new Response($e->getCode(), $e->getMessage());
} catch (\RuntimeException $e) {
return new Response(500, 'Internal Server Error');
}try {
$db->query($sql, $params);
} catch (QueryException $e) {
throw new \RuntimeException(
"Failed to process order #{$orderId}",
previous: $e
);
}| Exception | Parent | HTTP Code | Key Feature |
| --- | --- | --- | --- |
| CacheException | RuntimeException | — | Cache adapter errors |
| ConfigurationException | RuntimeException | — | Config file issues |
| ContainerException | RuntimeException | — | PSR-11 ContainerExceptionInterface |
| ContainerNotFoundException | ContainerException | — | PSR-11 NotFoundExceptionInterface |
| ConnectionException | DatabaseException | — | Database connection |
| DatabaseException | RuntimeException | — | Base DB error |
| FileException | RuntimeException | — | File I/O |
| FTPException | NetworkException | — | FTP operations |
| HttpException | RuntimeException | Any | HTTP status code |
| MailerException | RuntimeException | — | Email sending |
| ModelNotFoundException | RuntimeException | 404 | setModel() / getModel() / getIds() |
| ModuleConfigException | ModuleException | — | Module config |
| ModuleException | RuntimeException | — | Base module error |
| ModuleLoadException | ModuleException | — | Module loading |
| NetworkException | RuntimeException | — | Base network I/O |
| NotFoundException | HttpException | 404 | Page not found |
| OAuthException | RuntimeException | — | OAuth flow |
| PipelineException | RuntimeException | — | Pipeline execution |
| QueryException | DatabaseException | — | SQL execution |
| QueueException | RuntimeException | — | Job dispatch |
| RedirectException | HttpException | 301/302 | HTTP redirect |
| RoutingException | RuntimeException | — | Route resolution |
| SSHException | NetworkException | — | SFTP/SSH operations |
| TemplateException | RuntimeException | — | Template rendering |
| TransactionException | DatabaseException | — | TX commit/rollback |
Beyond the exception hierarchy, Razy provides three core classes that manage how errors are captured, configured, and rendered.
The Error class extends \Exception and serves as the primary error object for framework-level errors (404 pages, uncaught exceptions).
use Razy\Error;
// Create a custom error
$error = new Error(
message: 'Resource not found',
statusCode: 404,
heading: 'Not Found',
debugMessage: 'The requested URL /foo/bar was not found.',
);
$error->getStatusCode(); // 404
$error->getHeading(); // 'Not Found'
$error->getDebugMessage(); // Visible only in debug mode
// Static helpers
Error::show404(); // Throw NotFoundException (renders 404 page)
Error::showException($e); // Render an exception (web or CLI)// In a controller or routing callback
if (!$page) {
Error::show404();
// Throws NotFoundException internally
// Renders the 404 template or JSON response
}// Write to the debug console buffer (HTML debug panel)
Error::debugConsoleWrite('Query took 45ms', 'sql');
Error::debugConsoleWrite('Cache miss for key: user:42', 'cache');ErrorConfig is a static state manager that controls error behaviour globally.
use Razy\ErrorConfig;
// Enable/disable debug mode
ErrorConfig::setDebug(true);
// Check debug state
ErrorConfig::isDebug(); // true
// Enable cached error pages
ErrorConfig::setCached(true);
// Configure all settings at once
ErrorConfig::configure([
'debug' => true,
'cached' => false,
'display_errors' => true,
]);
// Get the accumulated debug console output
$console = ErrorConfig::getDebugConsole();
// Reset to defaults
ErrorConfig::reset();ErrorRenderer handles the visual output of errors. It operates differently in CLI vs web mode and supports debug backtraces in development.
use Razy\ErrorRenderer;
// Render a 404 page
ErrorRenderer::show404();
// In web mode: loads template from asset/exception/404.html
// In CLI mode: prints plain text
// Render an exception with full backtrace
ErrorRenderer::showException($exception);
// In debug mode: full backtrace with source code preview
// In production: generic error page
// Template resolution order:
// 1. PHAR_PATH/asset/exception/{statusCode}.html (e.g., 404.html, 500.html)
// 2. PHAR_PATH/asset/exception/default.html
// 3. Fallback plain HTMLWhen ErrorConfig::isDebug() is true, exceptions are rendered with:
-
Exception class name and message
-
Full stack trace with file paths and line numbers
-
Source code preview around the error line
-
Debug console output (SQL queries, cache operations, etc.)
When debug mode is off, errors display a generic user-friendly message without exposing internals:
ErrorConfig::setDebug(false);
// Now exceptions show:
// "An unexpected error occurred. Please try again later."
// No stack trace, no file paths, no SQL queriesuse Razy\Error;
use Razy\ErrorConfig;
// Bootstrap
ErrorConfig::setDebug(getenv('APP_DEBUG') === 'true');
// Register global exception handler
set_exception_handler(function (\Throwable $e) {
// Log the error
error_log($e->getMessage() . "\n" . $e->getTraceAsString());
// Render appropriate response
if ($e instanceof NotFoundException) {
Error::show404();
} elseif ($e instanceof HttpException) {
http_response_code($e->getCode());
Error::showException($e);
} else {
http_response_code(500);
Error::showException($e);
}
});