Error Handling
How validation errors and exceptions are caught and handled gracefully
Catching Errors Gracefully
After routing, the framework wraps everything in a try-catch block to handle errors elegantly.
Think of it like a safety net:
- If something goes wrong, catch it
- Show a helpful message
- Don't crash the entire app
The Try-Catch Block
try {
$router->route($uri, $method, $request);
} catch (ValidationException $exception) {
// Handle form validation errors
} catch (\Throwable $e) {
// Handle all other errors
}Two types of errors are handled differently:
- Validation errors - Expected, user-fixable
- Other errors - Unexpected, developer needs to fix
Handling Validation Errors
catch (ValidationException $exception) {
Session::flash('errors', $exception->errors);
Session::flash('old', $exception->old);
redirect($router->previousUrl());
}What is a ValidationException?
When a form has invalid input, controllers throw this special exception:
// In a controller
if (!Validator::email($email)) {
ValidationException::throw(
['email' => 'Invalid email format'],
['email' => $email]
);
}The Flow
- User submits form with bad data
- Controller validates and throws
ValidationException - Entry point catches it here
- Errors stored in flash (temporary session data)
- Old input stored in flash (so user doesn't retype everything)
- Redirect back to the form page
Why Flash?
Flash data exists for exactly one request:
// Request 1: Form submission fails
Session::flash('errors', ['email' => 'Invalid']);
redirect('/register');
// Request 2: Show form again
$errors = Session::get('errors'); // Available
echo $errors['email'];
// Request 3: Any other page
$errors = Session::get('errors'); // Gone (cleaned up)This prevents errors from showing up on unrelated pages.
The User Experience
Without this handling:
Fatal error: Uncaught ValidationException
Stack trace: ...With this handling:
[User sees the form again]
❌ Invalid email format
[Email field still has their input]Much better!
Handling Other Errors
catch (\Throwable $e) {
app_log(get_class($e) . ': ' . $e->getMessage());
throw $e;
}What is Throwable?
In PHP, \Throwable catches everything:
Exception(expected errors)Error(fatal errors like syntax errors)
This is the broadest catch possible.
What Happens Here
- Log the error to
storage/logs/app.log - Re-throw it so developers see it
Why Log Then Re-throw?
Logging helps in production:
- Errors are recorded even if users don't report them
- You can debug issues after they happen
Re-throwing helps in development:
- You see the full error message and stack trace
- You can fix bugs immediately
The Logging Behavior
The app_log() function always logs errors to storage/logs/app.log, regardless of whether you are in development (APP_DEBUG=true) or production (APP_DEBUG=false).
This is the standard, expected behavior because:
- In dev, you see the error on screen and have a written record.
- In production, errors are hidden from the user but still written to the log file so you can debug them later.
Cleaning Up Flash Data
Session::unflash();This is the last line in the file. It runs after the response is sent.
What Does It Do?
Removes flash data from the session:
unset($_SESSION['_flash']);Why at the End?
Flash data should exist for exactly one request:
- Request 1: Store flash data
- Request 2: Read flash data, then delete it
- Request 3: Flash data is gone
By calling unflash() at the end of every request, we ensure flash data never lives longer than one request.
The Complete Error Flow
Validation Error Flow
User submits form
↓
Controller validates
↓
Validation fails
↓
throw ValidationException
↓
Entry point catches it
↓
Store errors in flash
Store old input in flash
↓
Redirect back to form
↓
Form shows errors
Form pre-fills old input
↓
End of request
↓
Clean up flash dataOther Error Flow
Something breaks
↓
throw Exception/Error
↓
Entry point catches it
↓
Log to file
↓
Re-throw
↓
PHP shows error (dev)
or error page (production)Key Takeaways
- Two error types - Validation (expected) vs others (unexpected)
- Flash data for forms - Errors and old input survive one redirect
- Logging for production - Track errors even when hidden from users
- Clean up after yourself - Flash data is removed at request end
What's Good Here
✅ Validation errors provide great UX (redirect back with errors)
✅ Flash data is automatically cleaned up
✅ Errors are logged for debugging
✅ Separation between validation and fatal errors
✅ Simple enough to understand and extend yourself
Design Note
Error handling is intentionally minimal for learning purposes. You can see exactly how validation errors flow through the system. For production apps, you'd add custom error pages and error reporting services (Sentry, Bugsnag, etc.), but those would hide the underlying mechanics that DALT is trying to teach.
Part 1 Complete!
You now understand the entry point:
- How requests enter the application
- How sessions are managed
- How the framework bootstraps itself
- How errors are caught and handled
This is the foundation. Everything else builds on this.