Exception Handling
try / catch
Wrap risky code in try and handle errors in catch:
ZYMBA
try {
$data = @Var.fromJSON($rawInput);
echo "Parsed successfully";
} catch ($e) {
echo "Error: " . $e.getMessage();
}
The caught exception is always an object with getMessage() and getTracesAsString() methods, even when a plain string is thrown:
ZYMBA
try {
throw "something went wrong";
} catch ($e) {
echo $e.getMessage(); // "something went wrong"
echo $e.getTracesAsString(); // "#0 throw -> guideapp.inline [1]"
}
throw
Throw an exception to interrupt normal flow:
ZYMBA
// Throw with @Exception (preferred)
throw new @Exception("Division by zero");
// Throw a string (also works — gets wrapped in an exception object)
throw "Invalid input";
Use exceptions for validation and error signalling:
ZYMBA
function $divide($a, $b) {
if ($b == 0) {
throw new @Exception("Cannot divide by zero");
}
return $a / $b;
}
try {
$result = $divide(10, 0);
} catch ($e) {
echo $e.getMessage(); // "Cannot divide by zero"
}
finally
The finally block executes regardless of whether an exception was thrown:
ZYMBA
$result = "";
try {
$result .= "try ";
} catch ($e) {
$result .= "catch ";
} finally {
$result .= "finally";
}
echo $result; // "try finally"
Use finally for cleanup code that must run regardless of success or failure:
ZYMBA
$tempFile = null;
try {
$tempFile = $createTempFile();
$processData($tempFile);
} catch ($e) {
$logError($e.getMessage());
throw $e; // Re-throw after logging
} finally {
if (exists $tempFile) {
@IO.delete($tempFile);
}
}
Nested exception handling
Try-catch blocks can be nested for granular error handling:
ZYMBA
try {
try {
$data = @Var.fromJSON($input);
} catch ($e) {
throw new @Exception("Invalid JSON: " . $e.getMessage());
}
try {
$result = $db.fetchOne(@SQL.prepare($query, $data.id));
} catch ($e) {
throw new @Exception("Database error: " . $e.getMessage());
}
} catch ($e) {
echo $e.getMessage();
}
Re-throwing exceptions
Catch, handle (e.g., log), then re-throw:
ZYMBA
try {
$processPayment($order);
} catch ($e) {
@Console.log("Payment failed: " . $e.getMessage());
throw $e; // Re-throw to caller
}
Practical patterns
Guard clause pattern
Use early returns or throws to handle edge cases first:
ZYMBA
function $processUser($user) {
unless ($user is object) {
throw new @Exception("Invalid user");
}
unless (exists $user.email) {
throw new @Exception("User has no email");
}
if (empty $user.name) {
throw new @Exception("User name is empty");
}
// Main logic here — all preconditions met
return [success: true, name: $user.name];
}
Safe JSON parsing
ZYMBA
function $parseJSON($input) {
try {
return @Var.fromJSON($input);
} catch ($e) {
return null;
}
}
$data = $parseJSON($rawInput);
if ($data is null) {
echo "Invalid JSON input";
}
Retry with backoff
ZYMBA
$maxRetries = 3;
$lastError = null;
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
$response = @HTTP.request($url, "GET");
break; // Success — exit loop
} catch ($e) {
$lastError = $e;
if ($attempt < $maxRetries) {
@Date.sleep($attempt * 1000); // Wait 1s, 2s, 3s
}
}
}
if (exists $lastError) {
throw new @Exception("Failed after $maxRetries attempts: " . $lastError.getMessage());
}
Accumulating errors
ZYMBA
$errors = [];
for ($items as $item) {
try {
$validateItem($item);
} catch ($e) {
$errors[] = "Item $item.id: " . $e.getMessage();
}
}
if (count $errors > 0) {
throw new @Exception("Validation errors:\n" . @Array.joinValues($errors, "\n"));
}