Skip to main content

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