Base Exceptions
Throw provides three base exception classes that categorize errors by their nature, making error handling more semantic and maintainable.
Overview
Section titled “Overview”The base exceptions help you organize errors into clear categories:
- DomainException - Business logic violations
- InfrastructureException - External system failures
- ValidationException - Input validation errors
All base exceptions include the ConditionallyThrowable, HasErrorContext, and WrapsErrors traits.
DomainException
Section titled “DomainException”Use DomainException for business rule violations and domain-specific errors.
Creating Domain Exceptions
Section titled “Creating Domain Exceptions”use Cline\Throw\Exceptions\DomainException;
final class OrderCannotBeCancelledException extends DomainException{ public static function alreadyShipped(): self { return new self('Order cannot be cancelled after shipping'); }
public static function alreadyDelivered(): self { return new self('Order has already been delivered'); }}Usage Examples
Section titled “Usage Examples”// Guard against invalid state transitionsOrderCannotBeCancelledException::alreadyShipped() ->throwIf($order->status === 'shipped');
// Business rule enforcementInsufficientFundsException::forAmount($amount) ->withContext(['balance' => $account->balance, 'required' => $amount]) ->throwIf($account->balance < $amount);
// Invalid operationsSubscriptionNotActiveException::cannotAccess() ->throwUnless($subscription->isActive());Common Domain Exception Patterns
Section titled “Common Domain Exception Patterns”// State machine violationsfinal class InvalidStateTransitionException extends DomainException{ public static function from(string $from, string $to): self { return new self("Cannot transition from {$from} to {$to}"); }}
// Business constraintsfinal class MaximumRetriesExceededException extends DomainException{ public static function forOperation(string $operation, int $max): self { return new self("Maximum {$max} retries exceeded for {$operation}"); }}
// Invariant violationsfinal class AccountOverdraftException extends DomainException{ public static function amount(Money $overdraft): self { return new self("Account would be overdrawn by {$overdraft->format()}"); }}InfrastructureException
Section titled “InfrastructureException”Use InfrastructureException for failures in external dependencies like databases, APIs, file systems, or caches.
Creating Infrastructure Exceptions
Section titled “Creating Infrastructure Exceptions”use Cline\Throw\Exceptions\InfrastructureException;
final class DatabaseException extends InfrastructureException{ public static function connectionFailed(string $host): self { return new self("Failed to connect to database at {$host}"); }
public static function queryFailed(): self { return new self('Database query failed'); }}Usage Examples
Section titled “Usage Examples”// Database operationstry { $db->query($sql);} catch (PDOException $e) { throw DatabaseException::queryFailed() ->wrap($e) ->withContext(['query' => $sql, 'bindings' => $bindings]);}
// External API callstry { $response = $client->post('/payments', $data);} catch (RequestException $e) { throw PaymentGatewayException::requestFailed() ->wrap($e) ->withTags(['payment', 'stripe', 'critical']) ->withMetadata(['data' => $data, 'response' => $e->getResponse()]);}
// File system operationsFileSystemException::cannotWrite($path) ->throwIf(!is_writable($path));Common Infrastructure Exception Patterns
Section titled “Common Infrastructure Exception Patterns”// API failuresfinal class ApiException extends InfrastructureException{ public static function timeout(string $endpoint): self { return new self("API request to {$endpoint} timed out"); }
public static function rateLimited(): self { return new self('API rate limit exceeded'); }}
// Cache failuresfinal class CacheException extends InfrastructureException{ public static function connectionFailed(string $driver): self { return new self("Failed to connect to {$driver} cache"); }}
// Queue failuresfinal class QueueException extends InfrastructureException{ public static function jobFailed(string $job): self { return new self("Queue job {$job} failed to process"); }}ValidationException
Section titled “ValidationException”Use ValidationException for input validation failures and data constraint violations.
Creating Validation Exceptions
Section titled “Creating Validation Exceptions”use Cline\Throw\Exceptions\ValidationException;
final class InvalidEmailException extends ValidationException{ public static function format(string $email): self { return new self("Invalid email format: {$email}"); }}
final class RequiredFieldException extends ValidationException{ public static function missing(string $field): self { return new self("Required field missing: {$field}"); }}Usage Examples
Section titled “Usage Examples”// Email validationInvalidEmailException::format($email) ->throwIf(!filter_var($email, FILTER_VALIDATE_EMAIL));
// Required fieldsRequiredFieldException::missing('email') ->throwIf(empty($data['email']));
// Type validationInvalidTypeException::expectedArray('settings') ->withContext(['actual_type' => get_debug_type($value)]) ->throwIf(!is_array($value));
// Range validationOutOfRangeException::forField('age', 0, 120) ->throwIf($age < 0 || $age > 120);Common Validation Exception Patterns
Section titled “Common Validation Exception Patterns”// Format validationfinal class InvalidFormatException extends ValidationException{ public static function forField(string $field, string $expectedFormat): self { return new self("{$field} must be in {$expectedFormat} format"); }}
// Length validationfinal class StringTooLongException extends ValidationException{ public static function forField(string $field, int $max, int $actual): self { return new self("{$field} exceeds maximum length of {$max} (got {$actual})"); }}
// Constraint validationfinal class UniqueConstraintException extends ValidationException{ public static function forField(string $field, mixed $value): self { return new self("{$field} value '{$value}' already exists"); }}Choosing the Right Base Exception
Section titled “Choosing the Right Base Exception”| Exception Type | Use When | Examples |
|---|---|---|
| DomainException | Violating business rules or domain constraints | Order already shipped, insufficient funds, subscription expired |
| InfrastructureException | External systems fail | Database down, API timeout, file not writable |
| ValidationException | Input data is invalid | Invalid email, missing required field, number out of range |
Combined Features
Section titled “Combined Features”All base exceptions support the full Throw feature set:
// Combine all featuresPaymentFailedException::insufficientFunds() ->withContext([ 'user_id' => $user->id, 'amount' => $amount, 'balance' => $account->balance, ]) ->withTags(['payment', 'critical', 'stripe']) ->withMetadata([ 'gateway_response' => $response, 'attempt_number' => 3, ]) ->throwIf($account->balance < $amount);
// Wrap lower-level exceptionstry { $db->query($sql);} catch (PDOException $e) { throw DatabaseException::queryFailed() ->wrap($e) ->withContext(['query' => $sql]) ->withTags(['database', 'critical']);}Best Practices
Section titled “Best Practices”- Choose the right base - Use the exception type that best describes the error’s nature
- Create specific exceptions - Extend base exceptions for each distinct error case
- Use static factories - Provide named constructors for common scenarios
- Add context - Include relevant data to aid debugging
- Tag appropriately - Use tags for filtering in monitoring systems
- Wrap low-level errors - Preserve original exceptions while providing domain context
Next Steps
Section titled “Next Steps”- Learn about Error Context for adding debugging information
- Explore Error Wrapping for preserving exception chains
- See Assertions for the
ensure()helper pattern