Error Handling
Comprehensive guide to handling errors and exceptions in the Trading Card API PHP SDK.
Exception Hierarchy
The SDK provides specific exception classes for different types of errors:
TradingCardApiException (Base exception)
├── AuthenticationException // Authentication/authorization errors
├── ValidationException // Request validation errors
├── RateLimitException // Rate limiting errors
├── CardNotFoundException // Resource not found errors
├── SetNotFoundException // Set-specific not found
├── PlayerNotFoundException // Player-specific not found
├── TeamNotFoundException // Team-specific not found
└── NetworkException // Network/connection errors
Exception Classes
Base Exception
All SDK exceptions extend the base TradingCardApiException
:
use CardTechie\TradingCardApiSdk\Exceptions\TradingCardApiException;
try {
// API call
} catch (TradingCardApiException $e) {
echo "API Error: " . $e->getMessage();
echo "HTTP Status: " . $e->getHttpStatusCode();
echo "Request ID: " . $e->getRequestId();
}
Authentication Exception
Thrown when authentication fails or tokens are invalid:
use CardTechie\TradingCardApiSdk\Exceptions\AuthenticationException;
try {
$cards = TradingCardApiSdk::card()->getList();
} catch (AuthenticationException $e) {
echo "Authentication failed: " . $e->getMessage();
// Common causes:
// - Invalid client credentials
// - Expired access token
// - Insufficient permissions
// Recommended actions:
// - Verify client_id and client_secret
// - Check token expiration
// - Refresh authentication
}
Validation Exception
Thrown when request data fails validation:
use CardTechie\TradingCardApiSdk\Exceptions\ValidationException;
try {
$card = TradingCardApiSdk::card()->create([
'name' => '', // Invalid: empty name
'invalid_field' => 'value' // Invalid: unknown field
]);
} catch (ValidationException $e) {
echo "Validation failed: " . $e->getMessage();
// Get detailed validation errors
$errors = $e->getValidationErrors();
foreach ($errors as $field => $fieldErrors) {
echo "Field '$field': " . implode(', ', $fieldErrors) . "\n";
}
// Example output:
// Field 'name': The name field is required.
// Field 'invalid_field': The invalid field field is not allowed.
}
Rate Limit Exception
Thrown when API rate limits are exceeded:
use CardTechie\TradingCardApiSdk\Exceptions\RateLimitException;
try {
$cards = TradingCardApiSdk::card()->getList();
} catch (RateLimitException $e) {
echo "Rate limit exceeded: " . $e->getMessage();
// Get retry information
$retryAfter = $e->getRetryAfter(); // Seconds to wait
$rateLimit = $e->getRateLimit(); // Requests per period
$remaining = $e->getRemaining(); // Remaining requests
echo "Retry after: {$retryAfter} seconds\n";
echo "Rate limit: {$rateLimit} requests per period\n";
echo "Remaining: {$remaining} requests\n";
// Implement retry logic (see examples below)
}
Resource Not Found Exceptions
Specific exceptions for different resource types:
use CardTechie\TradingCardApiSdk\Exceptions\{
CardNotFoundException,
SetNotFoundException,
PlayerNotFoundException,
TeamNotFoundException
};
try {
$card = TradingCardApiSdk::card()->get('invalid-card-id');
} catch (CardNotFoundException $e) {
echo "Card not found: " . $e->getMessage();
echo "Card ID: " . $e->getResourceId();
}
try {
$set = TradingCardApiSdk::set()->get('invalid-set-id');
} catch (SetNotFoundException $e) {
echo "Set not found: " . $e->getMessage();
echo "Set ID: " . $e->getResourceId();
}
try {
$player = TradingCardApiSdk::player()->get('invalid-player-id');
} catch (PlayerNotFoundException $e) {
echo "Player not found: " . $e->getMessage();
}
try {
$team = TradingCardApiSdk::team()->get('invalid-team-id');
} catch (TeamNotFoundException $e) {
echo "Team not found: " . $e->getMessage();
}
Network Exception
Thrown for network-related errors:
use CardTechie\TradingCardApiSdk\Exceptions\NetworkException;
try {
$cards = TradingCardApiSdk::card()->getList();
} catch (NetworkException $e) {
echo "Network error: " . $e->getMessage();
// Common causes:
// - Connection timeout
// - DNS resolution failure
// - SSL certificate issues
// - Network connectivity problems
// Recommended actions:
// - Check network connection
// - Verify API endpoint URL
// - Check firewall settings
// - Implement retry logic
}
Error Handling Patterns
Basic Error Handling
Always wrap API calls in try-catch blocks:
use CardTechie\TradingCardApiSdk\Exceptions\TradingCardApiException;
function getCard(string $cardId): ?array
{
try {
return TradingCardApiSdk::card()->get($cardId);
} catch (TradingCardApiException $e) {
// Log the error
Log::error('Failed to fetch card', [
'card_id' => $cardId,
'error' => $e->getMessage(),
'http_status' => $e->getHttpStatusCode(),
'request_id' => $e->getRequestId()
]);
return null;
}
}
Comprehensive Error Handling
Handle different exception types appropriately:
use CardTechie\TradingCardApiSdk\Exceptions\{
CardNotFoundException,
ValidationException,
RateLimitException,
AuthenticationException,
NetworkException,
TradingCardApiException
};
function createCard(array $cardData): array
{
try {
return TradingCardApiSdk::card()->create($cardData);
} catch (ValidationException $e) {
// Handle validation errors - return to user
return [
'success' => false,
'errors' => $e->getValidationErrors(),
'message' => 'Please fix the validation errors and try again.'
];
} catch (AuthenticationException $e) {
// Handle authentication errors - refresh token or redirect
Log::warning('Authentication failed during card creation', [
'error' => $e->getMessage()
]);
return [
'success' => false,
'message' => 'Authentication required. Please log in again.',
'action' => 'redirect_to_login'
];
} catch (RateLimitException $e) {
// Handle rate limiting - implement retry or queue
Log::info('Rate limit hit during card creation', [
'retry_after' => $e->getRetryAfter()
]);
return [
'success' => false,
'message' => 'Too many requests. Please try again in ' . $e->getRetryAfter() . ' seconds.',
'retry_after' => $e->getRetryAfter()
];
} catch (NetworkException $e) {
// Handle network errors - retry or queue
Log::error('Network error during card creation', [
'error' => $e->getMessage()
]);
return [
'success' => false,
'message' => 'Network error occurred. Please try again later.',
'action' => 'retry'
];
} catch (TradingCardApiException $e) {
// Handle any other API errors
Log::error('Unexpected API error during card creation', [
'error' => $e->getMessage(),
'http_status' => $e->getHttpStatusCode(),
'request_id' => $e->getRequestId()
]);
return [
'success' => false,
'message' => 'An unexpected error occurred. Please try again later.'
];
}
}
Retry Logic
Simple Retry
Implement basic retry logic for transient errors:
function fetchCardWithRetry(string $cardId, int $maxRetries = 3): ?array
{
$attempt = 0;
while ($attempt < $maxRetries) {
try {
return TradingCardApiSdk::card()->get($cardId);
} catch (RateLimitException $e) {
$attempt++;
if ($attempt >= $maxRetries) {
throw $e; // Re-throw if max retries reached
}
// Wait for the retry delay
sleep($e->getRetryAfter());
} catch (NetworkException $e) {
$attempt++;
if ($attempt >= $maxRetries) {
throw $e;
}
// Exponential backoff for network errors
sleep(pow(2, $attempt));
}
}
return null;
}
Advanced Retry with Exponential Backoff
use Illuminate\Support\Facades\Log;
class ApiRetryHandler
{
public static function withRetry(callable $operation, array $config = []): mixed
{
$maxRetries = $config['max_retries'] ?? 3;
$baseDelay = $config['base_delay'] ?? 1;
$maxDelay = $config['max_delay'] ?? 60;
$retryableExceptions = $config['retryable'] ?? [
RateLimitException::class,
NetworkException::class
];
$attempt = 0;
while ($attempt <= $maxRetries) {
try {
return $operation();
} catch (Exception $e) {
$attempt++;
// Check if this exception type is retryable
$isRetryable = false;
foreach ($retryableExceptions as $retryableClass) {
if ($e instanceof $retryableClass) {
$isRetryable = true;
break;
}
}
if (!$isRetryable || $attempt > $maxRetries) {
throw $e;
}
// Calculate delay
if ($e instanceof RateLimitException) {
$delay = $e->getRetryAfter();
} else {
$delay = min($baseDelay * pow(2, $attempt - 1), $maxDelay);
}
Log::info("Retrying API call", [
'attempt' => $attempt,
'max_attempts' => $maxRetries,
'delay' => $delay,
'exception' => get_class($e)
]);
sleep($delay);
}
}
throw new Exception('Max retries exceeded');
}
}
// Usage
$card = ApiRetryHandler::withRetry(function() {
return TradingCardApiSdk::card()->get('card-123');
}, [
'max_retries' => 5,
'base_delay' => 2
]);
Laravel Integration
Global Exception Handler
Add API exception handling to Laravel's exception handler:
// app/Exceptions/Handler.php
use CardTechie\TradingCardApiSdk\Exceptions\{
TradingCardApiException,
ValidationException,
AuthenticationException,
RateLimitException
};
public function render($request, Throwable $exception)
{
// Handle Trading Card API exceptions
if ($exception instanceof ValidationException) {
return response()->json([
'error' => 'Validation failed',
'errors' => $exception->getValidationErrors()
], 422);
}
if ($exception instanceof AuthenticationException) {
return response()->json([
'error' => 'Authentication required'
], 401);
}
if ($exception instanceof RateLimitException) {
return response()->json([
'error' => 'Rate limit exceeded',
'retry_after' => $exception->getRetryAfter()
], 429);
}
if ($exception instanceof TradingCardApiException) {
Log::error('Trading Card API error', [
'message' => $exception->getMessage(),
'http_status' => $exception->getHttpStatusCode(),
'request_id' => $exception->getRequestId()
]);
return response()->json([
'error' => 'API error occurred'
], 500);
}
return parent::render($request, $exception);
}
Service Layer Error Handling
Create a service layer that handles errors consistently:
// app/Services/CardService.php
namespace App\Services;
use CardTechie\TradingCardApiSdk\Facades\TradingCardApiSdk;
use CardTechie\TradingCardApiSdk\Exceptions\{
CardNotFoundException,
ValidationException,
TradingCardApiException
};
use Illuminate\Support\Facades\Log;
class CardService
{
public function getCard(string $cardId): array
{
try {
$card = TradingCardApiSdk::card()->get($cardId, [
'include' => 'set,player,prices'
]);
return [
'success' => true,
'data' => $card
];
} catch (CardNotFoundException $e) {
return [
'success' => false,
'error' => 'card_not_found',
'message' => "Card with ID '{$cardId}' was not found."
];
} catch (TradingCardApiException $e) {
Log::error('Failed to fetch card', [
'card_id' => $cardId,
'error' => $e->getMessage(),
'request_id' => $e->getRequestId()
]);
return [
'success' => false,
'error' => 'api_error',
'message' => 'Failed to fetch card data.'
];
}
}
public function createCard(array $cardData): array
{
try {
$card = TradingCardApiSdk::card()->create($cardData);
return [
'success' => true,
'data' => $card
];
} catch (ValidationException $e) {
return [
'success' => false,
'error' => 'validation_failed',
'errors' => $e->getValidationErrors()
];
} catch (TradingCardApiException $e) {
Log::error('Failed to create card', [
'card_data' => $cardData,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => 'creation_failed',
'message' => 'Failed to create card.'
];
}
}
}
Logging and Debugging
Structured Logging
Log API errors with structured data:
use Illuminate\Support\Facades\Log;
try {
$card = TradingCardApiSdk::card()->get($cardId);
} catch (TradingCardApiException $e) {
Log::error('Trading Card API Error', [
'exception' => get_class($e),
'message' => $e->getMessage(),
'http_status' => $e->getHttpStatusCode(),
'request_id' => $e->getRequestId(),
'card_id' => $cardId,
'user_id' => auth()->id(),
'timestamp' => now()->toISOString(),
'trace' => $e->getTraceAsString()
]);
}
Debug Mode
Enable debug logging in development:
// config/tradingcardapi.php
return [
'debug' => env('TRADINGCARDAPI_DEBUG', env('APP_DEBUG', false)),
// ... other config
];
Custom Error Reporting
Implement custom error reporting for production:
use Illuminate\Support\Facades\Mail;
class ApiErrorReporter
{
public static function report(TradingCardApiException $exception, array $context = []): void
{
// Don't report in testing
if (app()->environment('testing')) {
return;
}
// Log locally
Log::error('API Error Report', [
'exception' => $exception,
'context' => $context
]);
// Send to external service (Sentry, Bugsnag, etc.)
if (app()->bound('sentry')) {
app('sentry')->captureException($exception, $context);
}
// Email alerts for critical errors
if ($exception->getHttpStatusCode() >= 500) {
Mail::to('[email protected]')->send(
new ApiErrorAlert($exception, $context)
);
}
}
}
Testing Error Scenarios
Unit Testing Exceptions
Test error handling in your unit tests:
// tests/Unit/CardServiceTest.php
use Tests\TestCase;
use App\Services\CardService;
use CardTechie\TradingCardApiSdk\Facades\TradingCardApiSdk;
use CardTechie\TradingCardApiSdk\Exceptions\CardNotFoundException;
class CardServiceTest extends TestCase
{
public function test_get_card_handles_not_found_exception()
{
// Mock the SDK to throw an exception
TradingCardApiSdk::shouldReceive('card->get')
->with('invalid-id', ['include' => 'set,player,prices'])
->andThrow(new CardNotFoundException('Card not found'));
$service = new CardService();
$result = $service->getCard('invalid-id');
$this->assertFalse($result['success']);
$this->assertEquals('card_not_found', $result['error']);
$this->assertStringContains('invalid-id', $result['message']);
}
}
Best Practices
1. Always Handle Exceptions
Never make API calls without proper exception handling:
// ❌ Bad - No error handling
$card = TradingCardApiSdk::card()->get($id);
// ✅ Good - Proper error handling
try {
$card = TradingCardApiSdk::card()->get($id);
} catch (TradingCardApiException $e) {
// Handle error appropriately
}
2. Use Specific Exception Types
Catch specific exceptions for better error handling:
// ❌ Less specific
try {
$card = TradingCardApiSdk::card()->get($id);
} catch (Exception $e) {
// Too broad
}
// ✅ More specific
try {
$card = TradingCardApiSdk::card()->get($id);
} catch (CardNotFoundException $e) {
// Handle not found
} catch (ValidationException $e) {
// Handle validation
} catch (TradingCardApiException $e) {
// Handle other API errors
}
3. Log Errors Appropriately
Include relevant context in error logs:
try {
$card = TradingCardApiSdk::card()->get($id);
} catch (TradingCardApiException $e) {
Log::error('Card fetch failed', [
'card_id' => $id,
'user_id' => auth()->id(),
'error' => $e->getMessage(),
'request_id' => $e->getRequestId()
]);
}
4. Implement Graceful Degradation
Design your application to handle API errors gracefully:
function getCardOrDefault(string $cardId): array
{
try {
return TradingCardApiSdk::card()->get($cardId);
} catch (CardNotFoundException $e) {
// Return a default card structure
return [
'id' => $cardId,
'name' => 'Unknown Card',
'error' => 'Card data unavailable'
];
} catch (TradingCardApiException $e) {
// Return cached data if available
return Cache::get("card_{$cardId}", [
'id' => $cardId,
'name' => 'Cached Card',
'cached' => true
]);
}
}
Next Steps
- Examples - Real-world implementation examples
- Installation & Setup - Installation and configuration guide
- Quick Start Guide - Basic usage patterns