- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
28 KiB
cURL OOP API
Object-orientierte cURL API Implementation basierend auf PHP RFC: Object-oriented curl API v2.
Übersicht
Die cURL OOP API bietet eine moderne, typsichere Alternative zu den prozeduralen curl_* Funktionen. Die Implementation folgt dem offiziellen PHP RFC und erweitert diesen um Framework-spezifische Patterns.
RFC Referenz: https://wiki.php.net/rfc/curl_oop_v2
Architektur
┌─────────────────────┐
│ HttpClient │ ← Public API (unchanged)
│ Interface │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ CurlHttpClient │ ← Uses Handle instead of curl_*
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ CurlRequestBuilder │ ← Returns HandleOption enums
└──────────┬──────────┘
│
┌──────────▼──────────────────────────────────┐
│ Curl\Handle │ ← OOP wrapper for curl_*
│ - fetch(): string │
│ - execute(resource|callable): void │
│ - setOption(HandleOption, mixed): self │
│ - getInfo(?Info): mixed │
│ - pause(Pause): void │
│ - reset(): void │
│ - escapeUrl(string): string │
│ - unescapeUrl(string): string │
│ - upkeep(): void │
└──────────────────────────────────────────────┘
│
├─── HandleOption enum (150+ cases)
├─── Info enum (50+ cases)
├─── Pause enum (6 cases)
└─── HandleException
Core Components
1. Curl\Handle
Location: src/Framework/HttpClient/Curl/Handle.php
Purpose: Object-orientierter Wrapper für einzelne cURL Handles
Key Features:
- Asymmetric Visibility:
public private(set)für Error-Properties - Exception-based: Keine
falsereturns, nur Exceptions - Fluent API: Method chaining für bessere Lesbarkeit
- Type Safety: Automatische Type-Validierung für Options
- Readonly: Unveränderliche Klasse nach Konstruktion
Usage:
// Simple GET Request
$handle = new Handle('https://api.example.com/users');
$handle->setOption(HandleOption::Timeout, 10);
$response = $handle->fetch();
// Fluent API
$response = (new Handle())
->setOption(HandleOption::Url, 'https://api.example.com')
->setOption(HandleOption::UserAgent, 'CustomPHP/1.0')
->setOption(HandleOption::Timeout, 10)
->fetch();
// Multiple Options
$handle->setOptions([
HandleOption::Url => 'https://api.example.com',
HandleOption::Timeout => 10,
HandleOption::FollowLocation => true,
]);
// Error Handling
try {
$response = $handle->fetch();
} catch (HandleException $e) {
// Access error details
$errorNumber = $handle->errorNumber; // public private(set)
$errorMessage = $handle->errorMessage; // public private(set)
}
Methods:
__construct(?string $uri = null)- Initialisiert Handle mit optionaler URLfetch(): string- Führt Request aus und gibt Response-Body zurückexecute(resource|callable $out): void- Führt Request aus und schreibt in Resource/CallbacksetOption(HandleOption $opt, mixed $value): self- Setzt einzelne OptionsetOptions(array $options): self- Setzt multiple OptionsgetInfo(?Info $option = null): mixed- Holt Request-Informationenpause(Pause $flag): void- Pausiert/Continued Transferreset(): void- Setzt alle Options zurückescapeUrl(string $string): string- URL-encoded StringunescapeUrl(string $string): string- URL-decoded Stringupkeep(): void- Connection upkeep checksgetResource(): CurlHandle- Holt underlying Resource (für Kompatibilität)
Properties:
public private(set) int $errorNumber- curl_errno() Equivalentpublic private(set) string $errorMessage- curl_error() Equivalentprivate readonly CurlHandle $handle- Underlying Resource
2. Curl\HandleOption
Location: src/Framework/HttpClient/Curl/HandleOption.php
Purpose: Type-safe Enum für alle CURLOPT_* Konstanten
Features:
- 150+ Option Cases: Alle CURLOPT_* Konstanten als Enum Cases
- Type Checking: Built-in Methoden für Type-Validierung
- IDE Support: Auto-completion für alle Options
- Kategorisiert: Grouped by functionality
Categories:
- URL & Request:
Url,Port,CustomRequest,UserAgent,Referer - HTTP Methods:
HttpGet,Post,Put,Upload,Nobody - Request Body:
PostFields,ReadData,InFile,InFileSize - Headers:
HttpHeader,Header,HeaderFunction - Response:
ReturnTransfer,File,WriteFunction - Authentication:
HttpAuth,UserPwd,Username,Password - SSL/TLS:
SslVerifyPeer,SslVerifyHost,CaInfo,SslCert - Timeouts:
Timeout,TimeoutMs,ConnectTimeout,ConnectTimeoutMs - Redirects:
FollowLocation,MaxRedirs,AutoReferer - Proxy:
Proxy,ProxyPort,ProxyType,ProxyAuth - Cookies:
Cookie,CookieFile,CookieJar - Network:
Interface,DnsServers,BufferSize - Debug:
Verbose,Stderr,FailOnError
Type Checking Methods:
$opt = HandleOption::Timeout;
$opt->expectsBoolean(); // false
$opt->expectsInteger(); // true
$opt->expectsString(); // false
$opt->expectsArray(); // false
$opt->expectsCallable(); // false
$opt->expectsResource(); // false
$opt->getName(); // "Timeout"
Usage:
// Instead of:
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // ❌ Magic constant
// Use:
$handle->setOption(HandleOption::Timeout, 10); // ✅ Type-safe enum
3. Curl\Info
Location: src/Framework/HttpClient/Curl/Info.php
Purpose: Type-safe Enum für alle CURLINFO_* Konstanten
Available Info Cases:
- Response:
ResponseCode,HttpCode,EffectiveUrl - Timing:
TotalTime,NameLookupTime,ConnectTime,PreTransferTime,StartTransferTime - Size:
SizeDownload,SizeUpload,HeaderSize,RequestSize - Speed:
SpeedDownload,SpeedUpload - SSL:
SslVerifyResult,ProxySslVerifyResult,CertInfo - Content:
ContentType,ContentLengthDownload,ContentLengthUpload - Network:
PrimaryIp,PrimaryPort,LocalIp,LocalPort - Redirects:
RedirectCount,RedirectUrl,RedirectTime
Usage:
// Single info
$statusCode = $handle->getInfo(Info::ResponseCode);
$totalTime = $handle->getInfo(Info::TotalTime);
// All info
$allInfo = $handle->getInfo();
4. Curl\Pause
Location: src/Framework/HttpClient/Curl/Pause.php
Purpose: Type-safe Enum für CURLPAUSE_* Konstanten
Cases:
Recv- Pause receivingRecvCont- Continue receivingSend- Pause sendingSendCont- Continue sendingAll- Pause bothCont- Continue both
Usage:
$handle->pause(Pause::Recv); // Pause receiving
$handle->pause(Pause::RecvCont); // Continue receiving
5. Curl\MultiHandle
Location: src/Framework/HttpClient/Curl/MultiHandle.php
Purpose: Parallele Ausführung mehrerer Requests
Key Features:
- Parallel Execution: Multiple Requests gleichzeitig
- High-Level API:
executeAll()für einfache Verwendung - Low-Level API:
execute(),select(),getInfo()für Fine-Tuning - Resource Management: Automatisches Cleanup
Usage:
// Variadic Constructor: Pass handles directly (NEW!)
$handle1 = new Handle('https://api.example.com/users/1');
$handle2 = new Handle('https://api.example.com/users/2');
$handle3 = new Handle('https://api.example.com/users/3');
$multi = new MultiHandle($handle1, $handle2, $handle3);
$completed = $multi->executeAll();
foreach ([$handle1, $handle2, $handle3] as $handle) {
$response = $handle->fetch(); // Already executed
$status = $handle->getInfo(Info::ResponseCode);
}
// Alternative: Add handles later
$multi = new MultiHandle();
$handles = [];
foreach ($urls as $url) {
$handle = new Handle($url);
$handles[] = $handle;
$multi->addHandle($handle);
}
$completed = $multi->executeAll();
foreach ($handles as $handle) {
$response = $handle->fetch(); // Already executed
$status = $handle->getInfo(Info::ResponseCode);
}
// Low-Level: Fine-Grained Control
$multi = new MultiHandle();
$stillRunning = 0;
do {
$multi->execute($stillRunning);
if ($stillRunning > 0) {
$multi->select();
}
while ($info = $multi->getInfo($messagesInQueue)) {
// Process completed transfers
}
} while ($stillRunning > 0);
Methods:
__construct(Handle ...$handles)- Erstellt MultiHandle mit optionalen Handles (NEW!)addHandle(Handle $handle): self- Fügt Handle hinzuremoveHandle(Handle $handle): self- Entfernt Handleexecute(int &$stillRunning): self- Führt alle Handles ausselect(float $timeout = 1.0): int- Wartet auf ActivitygetInfo(int &$messagesInQueue): array|false- Holt Completion-InfoexecuteAll(): array<Handle>- High-level: Führt alle aus und wartetgetHandles(): array<Handle>- Alle angehängten Handlescount(): int- Anzahl angehängter HandlessetOption(int $option, mixed $value): self- Setzt Multi-OptiononComplete(\Closure $callback): self- Callback für erfolgreiche Requests (NEW!)onError(\Closure $callback): self- Callback für fehlerhafte Requests (NEW!)onProgress(\Closure $callback): self- Callback für Progress Updates (NEW!)
Event Callbacks (NEW!):
Die MultiHandle-Klasse unterstützt jetzt Event-basierte Callbacks für real-time Processing während der parallelen Ausführung.
// onComplete Callback: Wird für jeden erfolgreichen Request aufgerufen
$multi = new MultiHandle();
$multi->onComplete(function(Handle $handle) {
$url = $handle->getInfo(Info::EffectiveUrl);
$status = $handle->getInfo(Info::ResponseCode);
$time = $handle->getInfo(Info::TotalTime);
echo "✓ {$url} - {$status} ({$time}s)\n";
});
foreach ($urls as $url) {
$multi->addHandle(new Handle($url));
}
$multi->executeAll(); // Callbacks werden während Execution getriggert
// onError Callback: Wird für Requests mit Fehlern aufgerufen
$multi->onError(function(Handle $handle, int $errorCode) {
$url = $handle->getInfo(Info::EffectiveUrl);
$error = curl_strerror($errorCode);
error_log("✗ {$url} - Error {$errorCode}: {$error}");
});
// onProgress Callback: Real-time Progress Updates
$totalRequests = 100;
$multi->onProgress(function(int $completed, int $total) {
$percent = round(($completed / $total) * 100);
echo "\rProgress: {$completed}/{$total} ({$percent}%)";
});
// Alle Callbacks kombinieren für comprehensive Monitoring
$multi = new MultiHandle();
$multi
->onComplete(function(Handle $handle) {
// Log successful requests
$this->logger->info('Request completed', [
'url' => $handle->getInfo(Info::EffectiveUrl),
'status' => $handle->getInfo(Info::ResponseCode),
'time' => $handle->getInfo(Info::TotalTime)
]);
})
->onError(function(Handle $handle, int $errorCode) {
// Alert on errors
$this->alerting->error('Request failed', [
'url' => $handle->getInfo(Info::EffectiveUrl),
'error_code' => $errorCode,
'error_msg' => curl_strerror($errorCode)
]);
})
->onProgress(function(int $completed, int $total) {
// Update progress bar
$this->progressBar->update($completed, $total);
});
foreach ($urlsToFetch as $url) {
$multi->addHandle(new Handle($url));
}
$results = $multi->executeAll();
Callback Signatures:
onComplete:function(Handle $handle): voidonError:function(Handle $handle, int $errorCode): voidonProgress:function(int $completed, int $total): void
Use Cases:
- Real-time Logging: Log jede Completion/Error während Execution
- Progress Tracking: CLI Progress Bars, UI Updates
- Error Handling: Immediate Retry-Logic oder Alerting
- Metrics Collection: Performance Monitoring während Batch-Processing
- Data Streaming: Process Response-Daten sofort statt am Ende
6. Curl\MultiHandlePool (NEW!)
Location: src/Framework/HttpClient/Curl/MultiHandlePool.php
Purpose: Pool für parallele Requests mit Concurrency Control
Key Features:
- Concurrency Limit: Limitiert parallele Requests (verhindert Resource Exhaustion)
- Automatic Batching: Führt Requests in Batches aus wenn Queue größer als Limit
- Progress Tracking: Real-time Progress und Status Monitoring
- Memory Efficient: Batch-wise Execution statt alle auf einmal
Warum MultiHandlePool statt MultiHandle?:
- Resource Control: Verhindert zu viele parallele Connections
- Production Ready: Sicher für große Request-Mengen
- Predictable Performance: Kontrollierbare Resource-Nutzung
- Auto-Batching: Keine manuelle Batch-Logik notwendig
Usage:
// Basic Usage: 100 URLs mit max 10 parallelen Requests
$pool = new MultiHandlePool(maxConcurrent: 10);
foreach ($urls as $url) {
$handle = new Handle($url);
$handle->setOption(HandleOption::Timeout, 10);
$pool->add($handle);
}
$completed = $pool->executeAll(); // Executes in batches of 10
foreach ($completed as $handle) {
$status = $handle->getInfo(Info::ResponseCode);
echo "Completed: {$status}\n";
}
// Progress Tracking
$pool = new MultiHandlePool(maxConcurrent: 5);
for ($i = 0; $i < 50; $i++) {
$pool->add(new Handle("https://api.example.com/items/{$i}"));
}
echo "Total: " . $pool->totalCount() . "\n"; // 50
echo "Queued: " . $pool->queueSize() . "\n"; // 50
$pool->executeAll();
echo "Progress: " . $pool->getProgress() . "%\n"; // 100.0%
echo "Completed: " . $pool->completedCount() . "\n"; // 50
// Reusable Pool
$pool = new MultiHandlePool(maxConcurrent: 10);
// First batch
foreach ($firstBatch as $url) {
$pool->add(new Handle($url));
}
$pool->executeAll();
// Clear and reuse
$pool->clear();
// Second batch
foreach ($secondBatch as $url) {
$pool->add(new Handle($url));
}
$pool->executeAll();
Methods:
__construct(int $maxConcurrent = 10)- Erstellt Pool mit Concurrency Limitadd(Handle $handle): self- Fügt Handle zur Queue hinzuexecuteAll(): array<Handle>- Führt alle Handles in Batches ausqueueSize(): int- Anzahl wartender HandlesactiveCount(): int- Anzahl aktuell ausgeführter HandlescompletedCount(): int- Anzahl abgeschlossener HandlestotalCount(): int- Gesamt-Anzahl (queued + active + completed)getProgress(): float- Progress in Prozent (0-100)isComplete(): bool- Sind alle Handles abgeschlossen?clear(): self- Löscht alle Handles (queue, active, completed)getMaxConcurrent(): int- Konfiguriertes Concurrency Limit
Performance Characteristics:
// Sequential (old approach - avoid!)
foreach ($urls as $url) {
$handle = new Handle($url);
$response = $handle->fetch();
}
// Time: N * request_time (z.B. 100 * 1s = 100s)
// MultiHandle (uncontrolled)
$multi = new MultiHandle();
foreach ($urls as $url) {
$multi->addHandle(new Handle($url));
}
$multi->executeAll();
// Time: ~request_time (z.B. ~1s)
// Risk: Too many connections, resource exhaustion
// MultiHandlePool (controlled, recommended!)
$pool = new MultiHandlePool(maxConcurrent: 10);
foreach ($urls as $url) {
$pool->add(new Handle($url));
}
$pool->executeAll();
// Time: (N / concurrency) * request_time (z.B. 100/10 * 1s = 10s)
// Safe: Controlled resource usage, predictable performance
Production Best Practices:
// Use appropriate concurrency based on server capacity
$pool = new MultiHandlePool(
maxConcurrent: match ($environment) {
'production' => 20, // Conservative for production
'staging' => 50, // More aggressive for testing
'local' => 5, // Low for local development
}
);
// Set timeouts on all handles
foreach ($urls as $url) {
$handle = new Handle($url);
$handle->setOption(HandleOption::Timeout, 30);
$handle->setOption(HandleOption::ConnectTimeout, 10);
$pool->add($handle);
}
// Execute and handle results
$completed = $pool->executeAll();
foreach ($completed as $handle) {
$status = $handle->getInfo(Info::ResponseCode);
if ($status >= 200 && $status < 300) {
// Success
$response = $handle->fetch();
} else {
// Handle error
$this->logger->warning("Request failed", [
'url' => $handle->getInfo(Info::EffectiveUrl),
'status' => $status
]);
}
}
7. Exception Handling
HandleException: src/Framework/HttpClient/Curl/Exception/HandleException.php
Static Factory Methods:
HandleException::initializationFailed(?string $uri)
HandleException::executionFailed(string $errorMessage, int $errorCode)
HandleException::invalidOutputTarget(mixed $output)
HandleException::setOptionFailed(HandleOption $opt, mixed $value, string $errorMessage)
HandleException::invalidOptionType(HandleOption $opt, mixed $value)
HandleException::pauseFailed(Pause $flag, string $errorMessage)
HandleException::escapeFailed(string $string)
HandleException::unescapeFailed(string $string)
HandleException::upkeepFailed(string $errorMessage)
Context-Rich Error Data:
try {
$handle->setOption(HandleOption::Timeout, 'invalid');
} catch (HandleException $e) {
$e->getMessage(); // "Invalid type for option Timeout: expected integer, got string"
$e->getData(); // ['option' => 'Timeout', 'expected_type' => 'integer', 'actual_type' => 'string']
}
MultiHandleException: src/Framework/HttpClient/Curl/Exception/MultiHandleException.php
Static Factory Methods:
MultiHandleException::initializationFailed()
MultiHandleException::addHandleFailed(int $errorCode)
MultiHandleException::removeHandleFailed(int $errorCode)
MultiHandleException::executionFailed(int $errorCode)
MultiHandleException::selectFailed()
MultiHandleException::setOptionFailed(int $option, mixed $value, int $errorCode)
Integration mit HttpClient
CurlRequestBuilder
Before (Procedural):
$options = [
CURLOPT_URL => $url,
CURLOPT_TIMEOUT => 10,
CURLOPT_RETURNTRANSFER => true,
];
After (OOP):
$options = [
HandleOption::Url => $url,
HandleOption::Timeout => 10,
HandleOption::ReturnTransfer => true,
];
CurlHttpClient
Before (Procedural):
$ch = curl_init();
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if ($response === false) {
throw new CurlExecutionFailed(curl_error($ch), curl_errno($ch));
}
curl_close($ch);
After (OOP):
$handle = new Handle();
$handle->setOptions($options);
$response = $handle->fetch(); // Throws HandleException on error
// Automatic cleanup via __destruct
AuthenticationHandler
Before (Procedural):
$curlOptions[CURLOPT_USERPWD] = "{$username}:{$password}";
$curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
After (OOP):
$curlOptions[HandleOption::UserPwd] = "{$username}:{$password}";
$curlOptions[HandleOption::HttpAuth] = CURLAUTH_BASIC;
Framework-Compliance
Die Implementation folgt allen Framework-Patterns:
✅ No Inheritance: Keine extends, nur Composition
✅ Readonly Everywhere: Klassen sind final readonly
✅ Final by Default: Alle Klassen sind final
✅ Value Objects: Enums statt Primitive Obsession
✅ Explicit DI: Keine globalen States
✅ Exception-based: Keine false returns
✅ Immutability: Properties readonly wo möglich
✅ Asymmetric Visibility: public private(set) für Error-Properties
Backward Compatibility
Die öffentliche HttpClient Interface bleibt unverändert:
interface HttpClient
{
public function send(ClientRequest $request): ClientResponse;
}
Alle bestehenden Aufrufe funktionieren weiterhin:
$client = new CurlHttpClient();
$response = $client->send($request); // ✅ Unchanged
Die OOP API wird intern verwendet, ist aber auch direkt nutzbar:
// Direct usage of new API
$handle = new Handle('https://api.example.com');
$handle->setOption(HandleOption::Timeout, 10);
$response = $handle->fetch();
Performance
Keine Performance-Regression:
- Enums sind compile-time optimiert
- Method calls werden inlined
- Gleiche underlying curl_* Funktionen
- Automatic cleanup via
__destruct
Vorteile:
- Type-Validierung verhindert Runtime-Fehler
- Better IDE support → weniger Debugging
- Exception-basierte Fehler → klarere Callstacks
Testing
Pest Tests (todo):
describe('Curl\Handle', function () {
it('can fetch from URL', function () {
$handle = new Handle('https://httpbin.org/get');
$response = $handle->fetch();
expect($response)->toContain('"url"');
expect($handle->getInfo(Info::ResponseCode))->toBe(200);
});
it('throws on invalid URL', function () {
$handle = new Handle('invalid-url');
$handle->fetch();
})->throws(HandleException::class);
it('validates option types', function () {
$handle = new Handle();
$handle->setOption(HandleOption::Timeout, 'invalid');
})->throws(HandleException::class, 'Invalid type for option Timeout');
});
describe('Curl\MultiHandle', function () {
it('can execute parallel requests', function () {
// New: variadic constructor accepts handles directly
$multi = new MultiHandle(
new Handle('https://httpbin.org/delay/1'),
new Handle('https://httpbin.org/delay/1')
);
$completed = $multi->executeAll();
expect($completed)->toHaveCount(2);
expect($multi->count())->toBe(2);
});
it('can add handles later', function () {
$multi = new MultiHandle();
$handles = [
new Handle('https://httpbin.org/delay/1'),
new Handle('https://httpbin.org/delay/1'),
];
foreach ($handles as $handle) {
$multi->addHandle($handle);
}
$completed = $multi->executeAll();
expect($completed)->toHaveCount(2);
});
});
Migration Guide
Von Procedural cURL zu OOP API
Step 1: Replace curl_init()
// Before
$ch = curl_init($url);
// After
$handle = new Handle($url);
Step 2: Replace curl_setopt()
// Before
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
// After
$handle->setOption(HandleOption::Timeout, 10);
Step 3: Replace curl_exec()
// Before
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
throw new Exception($error);
}
// After
try {
$response = $handle->fetch();
} catch (HandleException $e) {
// Automatic error handling
}
Step 4: Replace curl_getinfo()
// Before
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// After
$status = $handle->getInfo(Info::HttpCode);
Step 5: Replace curl_close()
// Before
curl_close($ch);
// After
// Automatic via __destruct
Von curl_multi_* zu MultiHandle
// Before
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh);
} while ($running > 0);
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);
// After
$multi = new MultiHandle();
$multi->addHandle($handle1);
$multi->addHandle($handle2);
$completed = $multi->executeAll();
// Automatic cleanup
Best Practices
1. Prefer Enums over Constants
// ❌ Avoid
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
// ✅ Prefer
$handle->setOption(HandleOption::Timeout, 10);
2. Use Fluent API for Multiple Options
// ❌ Avoid
$handle->setOption(HandleOption::Url, $url);
$handle->setOption(HandleOption::Timeout, 10);
$handle->setOption(HandleOption::FollowLocation, true);
// ✅ Prefer
$handle->setOption(HandleOption::Url, $url)
->setOption(HandleOption::Timeout, 10)
->setOption(HandleOption::FollowLocation, true);
// ✅ Or use setOptions()
$handle->setOptions([
HandleOption::Url => $url,
HandleOption::Timeout => 10,
HandleOption::FollowLocation => true,
]);
3. Use MultiHandle for Parallel Requests
// ❌ Avoid sequential requests
foreach ($urls as $url) {
$handle = new Handle($url);
$responses[] = $handle->fetch(); // Slow: sequential
}
// ✅ Prefer parallel execution
$multi = new MultiHandle();
$handles = [];
foreach ($urls as $url) {
$handle = new Handle($url);
$handles[] = $handle;
$multi->addHandle($handle);
}
$multi->executeAll(); // Fast: parallel
foreach ($handles as $handle) {
$responses[] = $handle->fetch();
}
4. Handle Exceptions Properly
// ❌ Avoid catching generic Exception
try {
$response = $handle->fetch();
} catch (\Exception $e) {
// Too broad
}
// ✅ Catch specific HandleException
try {
$response = $handle->fetch();
} catch (HandleException $e) {
// Access error details
$errorNumber = $handle->errorNumber;
$errorMessage = $handle->errorMessage;
// Log with context
$this->logger->error('cURL request failed', [
'error_number' => $errorNumber,
'error_message' => $errorMessage,
'exception_data' => $e->getData(),
]);
}
5. Use Type-Safe Info Retrieval
// ❌ Avoid magic constants
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// ✅ Use Info enum
$status = $handle->getInfo(Info::HttpCode);
$totalTime = $handle->getInfo(Info::TotalTime);
$downloadSize = $handle->getInfo(Info::SizeDownload);
Troubleshooting
Problem: Type Error beim setOption()
Symptom:
HandleException: Invalid type for option Timeout: expected integer, got string
Lösung:
// ❌ Wrong type
$handle->setOption(HandleOption::Timeout, '10');
// ✅ Correct type
$handle->setOption(HandleOption::Timeout, 10);
Problem: MultiHandle gibt keine Responses zurück
Symptom: executeAll() returned handles haben keinen Response
Lösung: Response ist bereits im Handle, nicht im Return-Wert
// ❌ Wrong
$completed = $multi->executeAll();
foreach ($completed as $handle) {
$response = $handle->fetch(); // ❌ Already executed
}
// ✅ Correct
$handles = [$handle1, $handle2];
$multi->addHandle($handle1);
$multi->addHandle($handle2);
$multi->executeAll();
foreach ($handles as $handle) {
// Response bereits vorhanden, Info abrufen
$status = $handle->getInfo(Info::ResponseCode);
$body = $handle->getInfo(Info::ResponseCode); // Use original handle reference
}
Problem: Memory Leak bei vielen Requests
Symptom: Memory usage steigt bei vielen Requests
Lösung: Unset handles nach Verwendung
// ❌ Handles bleiben im Speicher
foreach ($urls as $url) {
$handle = new Handle($url);
$response = $handle->fetch();
$results[] = $response;
}
// ✅ Explicit cleanup
foreach ($urls as $url) {
$handle = new Handle($url);
$response = $handle->fetch();
$results[] = $response;
unset($handle); // Force __destruct
}
// ✅ Or use MultiHandle with batching
Future Enhancements
Potentielle zukünftige Erweiterungen:
- HTTP/2 Support: Dedicated HTTP/2 methods und options
- Async/Await Integration: Integration mit Framework's Fiber Manager
- Connection Pooling: Wiederverwendung von Connections
- Metrics Integration: Automatische Performance-Metriken
- Retry Mechanisms: Built-in exponential backoff
- Circuit Breaker: Automatic failure handling
- Request/Response Middleware: Transformations vor/nach Request
Related Documentation
- PHP RFC: https://wiki.php.net/rfc/curl_oop_v2
- HttpClient Module:
/docs/claude/architecture.md#http--routing - Exception Handling:
/docs/claude/error-handling.md - Testing Guide:
/docs/claude/common-workflows.md - Examples:
/examples/curl-oop-usage.php
Summary
Die cURL OOP API bietet:
✅ Type Safety: Enums statt Magic Constants
✅ IDE Support: Auto-completion für alle Options
✅ Exception-based: Keine false returns
✅ Fluent API: Method chaining
✅ Parallel Execution: MultiHandle für Performance
✅ Framework-Compliance: Readonly, Final, Value Objects
✅ Backward Compatible: Bestehender Code funktioniert weiter
✅ Well-Documented: RFC-based mit Examples
Die Implementation ist production-ready und kann sofort verwendet werden!