PHP ile HTTP Proxy Kullanımı: cURL, Guzzle, Symfony ve Laravel Entegrasyonu

PHP projelerinizde HTTP proxy kullanımı için kapsamlı rehber. Raw cURL, Guzzle, Symfony HTTP Client ve Laravel entegrasyonu ile production-ready örnekler.

PHP ile HTTP Proxy Kullanımı: cURL, Guzzle, Symfony ve Laravel Entegrasyonu

PHP geliştiricileri için web scraping, üçüncü parti API entegrasyonları ve otomasyon süreçlerinde proxy kullanımı kritik bir gereklilik haline gelmiştir. Hedef siteler IP tabanlı rate limiting uygular, coğrafi kısıtlamalar getirir veya şüpheli trafiği engeller. Bu rehberde, PHP ekosisteminde HTTP proxy kullanımının her katmanını — raw cURL'den modern framework entegrasyonlarına kadar — pratik kod örnekleriyle ele alacağız.

Neden PHP Projesinde Proxy Kullanmalısınız?

Web scraping veya yoğun API kullanımı yapan PHP uygulamaları, kısa sürede hedef sunucular tarafından engellenebilir. Tek bir IP'den gelen binlerce istek, anormal davranış olarak algılanır ve HTTP 429 (Too Many Requests) veya HTTP 403 (Forbidden) yanıtlarıyla karşılaşırsınız.

Proxy kullanımının temel faydaları:

  • IP rotasyonu: Engellenen IP'leri atlayarak kesintisiz erişim
  • Coğrafi hedefleme: Ülke/bazlı içerik kısıtlamalarını aşma
  • Rate limit yönetimi: İstekleri birden fazla IP'ye dağıtma
  • Anonimlik: Gerçek sunucu IP'nizi gizleme

Bu rehberde ProxyHat residential proxy servisini örnek olarak kullanacağız. Gateway adresi: gate.proxyhat.com, HTTP portu: 8080, SOCKS5 portu: 1080.

Raw cURL ile Proxy Kullanımı

PHP'nin yerleşik cURL eklentisi, proxy desteği için en temel ve en hızlı yöntemdir. CURLOPT_PROXY ve CURLOPT_PROXYUSERPWD seçenekleriyle proxy konfigürasyonu yapılır.

<?php

declare(strict_types=1);

/**
 * Basit cURL proxy isteği örneği
 * ProxyHat residential proxy kullanarak HTTP isteği yapar
 */
function fetchWithProxy(string $url, string $username, string $password): string
{
    $ch = curl_init();
    
    // Proxy konfigürasyonu
    $proxyHost = 'gate.proxyhat.com';
    $proxyPort = 8080;
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        
        // Proxy ayarları
        CURLOPT_PROXY => $proxyHost,
        CURLOPT_PROXYPORT => $proxyPort,
        CURLOPT_PROXYUSERPWD => "$username:$password",
        CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
        
        // SSL/TLS konfigürasyonu
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt', // Linux yolı
        
        // User-Agent (önemli: bot tespitini zorlaştırır)
        CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        
        // Header'ları al
        CURLOPT_HEADER => false,
        CURLOPT_HTTPHEADER => [
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language: en-US,en;q=0.5',
            'Accept-Encoding: gzip, deflate',
        ],
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    $errno = curl_errno($ch);
    
    curl_close($ch);
    
    if ($errno !== 0) {
        throw new RuntimeException("cURL Error [$errno]: $error");
    }
    
    if ($httpCode >= 400) {
        throw new RuntimeException("HTTP Error: $httpCode");
    }
    
    return $response;
}

// Kullanım örneği
try {
    $html = fetchWithProxy(
        'https://httpbin.org/ip',
        'user-country-US',      // ProxyHat username (geo-targeting)
        'your-password'          // ProxyHat password
    );
    echo $html;
} catch (RuntimeException $e) {
    error_log("Proxy request failed: " . $e->getMessage());
}

ProxyHat'ta geo-targeting ve sticky session kullanıcı adı içinde belirtilir. Örneğin:

  • user-country-US:pass — ABD IP'si
  • user-country-DE-city-berlin:pass — Berlin, Almanya IP'si
  • user-session-abc123:pass — Sticky session (aynı IP)

cURL ile SOCKS5 Proxy

SOCKS5 proxy kullanımı için CURLOPT_PROXYTYPE değerini değiştirmeniz yeterlidir:

<?php

function fetchWithSocks5(string $url, string $username, string $password): string
{
    $ch = curl_init();
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_PROXY => 'gate.proxyhat.com',
        CURLOPT_PROXYPORT => 1080, // SOCKS5 portu
        CURLOPT_PROXYUSERPWD => "$username:$password",
        CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt',
    ]);
    
    $response = curl_exec($ch);
    
    if (curl_errno($ch)) {
        throw new RuntimeException('SOCKS5 proxy error: ' . curl_error($ch));
    }
    
    curl_close($ch);
    return $response;
}

Guzzle HTTP Client ile Proxy Konfigürasyonu

Guzzle, PHP'de en yaygın kullanılan HTTP client kütüphanesidir. Laravel ve Symfony projelerinde standart haline gelmiştir. Proxy konfigürasyonu request options üzerinden yapılır.

<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

class ProxyScraper
{
    private Client $client;
    private string $proxyHost = 'gate.proxyhat.com';
    private int $proxyPort = 8080;
    
    public function __construct(
        private string $proxyUser,
        private string $proxyPass
    ) {
        $this->client = new Client([
            'timeout' => 30,
            'connect_timeout' => 10,
            'verify' => true,
            'headers' => [
                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            ],
        ]);
    }
    
    /**
     * Tek istek - proxy ile
     */
    public function fetch(string $url, ?string $country = null): string
    {
        $username = $this->buildUsername($country);
        
        try {
            $response = $this->client->get($url, [
                'proxy' => [
                    'http' => "http://{$username}:{$this->proxyPass}@{$this->proxyHost}:{$this->proxyPort}",
                    'https' => "http://{$username}:{$this->proxyPass}@{$this->proxyHost}:{$this->proxyPort}",
                ],
            ]);
            
            return $response->getBody()->getContents();
            
        } catch (RequestException $e) {
            // Retry logic veya fallback
            if ($e->hasResponse()) {
                $statusCode = $e->getResponse()->getStatusCode();
                throw new RuntimeException("HTTP {$statusCode} error for {$url}");
            }
            throw new RuntimeException("Network error: " . $e->getMessage());
        }
    }
    
    /**
     * Per-request IP rotasyonu ile çoklu istek
     */
    public function fetchMultiple(array $urls, ?string $country = null): array
    {
        $results = [];
        $promises = [];
        
        foreach ($urls as $i => $url) {
            // Her istek için farklı session ID = farklı IP
            $sessionId = 'session-' . uniqid($i, true);
            $username = $this->buildUsername($country, $sessionId);
            
            $promises[$i] = $this->client->getAsync($url, [
                'proxy' => [
                    'http' => "http://{$username}:{$this->proxyPass}@{$this->proxyHost}:{$this->proxyPort}",
                    'https' => "http://{$username}:{$this->proxyPass}@{$this->proxyHost}:{$this->proxyPort}",
                ],
            ]);
        }
        
        // Wait for all promises
        $responses = \GuzzleHttp\Promise\Utils::settle($promises)->wait();
        
        foreach ($responses as $i => $result) {
            if ($result['state'] === 'fulfilled') {
                $results[$urls[$i]] = $result['value']->getBody()->getContents();
            } else {
                $results[$urls[$i]] = ['error' => $result['reason']->getMessage()];
            }
        }
        
        return $results;
    }
    
    /**
     * ProxyHat username builder
     * Format: user-country-US-session-abc123
     */
    private function buildUsername(?string $country = null, ?string $session = null): string
    {
        $parts = [$this->proxyUser];
        
        if ($country) {
            $parts[] = "country-{$country}";
        }
        
        if ($session) {
            $parts[] = "session-{$session}";
        }
        
        return implode('-', $parts);
    }
}

// Kullanım
$scraper = new ProxyScraper('user', 'your-password');

// Tek istek (ABD IP'si)
$html = $scraper->fetch('https://httpbin.org/ip', 'US');

// Çoklu istek (her biri farklı IP)
$results = $scraper->fetchMultiple([
    'https://httpbin.org/ip',
    'https://httpbin.org/user-agent',
    'https://httpbin.org/headers',
], 'US');

Guzzle ile Pool ve Concurrency

Yüksek hacimli scraping işlemlerinde Guzzle Pool kullanarak concurrency kontrolü yapabilirsiniz:

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

class ConcurrentScraper
{
    public function __construct(
        private Client $client,
        private string $proxyUser,
        private string $proxyPass
    ) {}
    
    public function scrapeUrls(array $urls, int $concurrency = 10): array
    {
        $results = [];
        $proxyHost = 'gate.proxyhat.com:8080';
        
        $requests = function () use ($urls, $proxyHost) {
            foreach ($urls as $i => $url) {
                $sessionId = "pool-session-{$i}";
                $proxyAuth = "{$this->buildUsername($sessionId)}:{$this->proxyPass}";
                
                yield new Request('GET', $url, [
                    'Proxy-Authorization' => 'Basic ' . base64_encode($proxyAuth),
                ]);
            }
        };
        
        $pool = new Pool($this->client, $requests(), [
            'concurrency' => $concurrency,
            'fulfilled' => function ($response, $index) use ($urls, &$results) {
                $results[$urls[$index]] = $response->getBody()->getContents();
            },
            'rejected' => function ($reason, $index) use ($urls, &$results) {
                $results[$urls[$index]] = ['error' => $reason->getMessage()];
            },
            'options' => [
                'proxy' => "http://{$this->proxyUser}:{$this->proxyPass}@{$proxyHost}",
                'timeout' => 30,
            ],
        ]);
        
        $promise = $pool->promise();
        $promise->wait();
        
        return $results;
    }
    
    private function buildUsername(string $sessionId): string
    {
        return "{$this->proxyUser}-session-{$sessionId}";
    }
}

Symfony HTTP Client ile Proxy ve AsyncResponse

Symfony HTTP Client, Guzzle'a modern bir alternatiftir. Async desteği ve curl wrapper olarak çalışır. Laravel projelerinde de kullanılabilir.

<?php

require 'vendor/autoload.php';

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Exception\TransportException;

class SymfonyProxyClient
{
    private HttpClient $client;
    private string $proxyHost = 'gate.proxyhat.com';
    private int $proxyPort = 8080;
    
    public function __construct(
        private string $proxyUser,
        private string $proxyPass
    ) {
        $this->client = HttpClient::create([
            'timeout' => 30,
            'max_redirects' => 5,
            'verify_peer' => true,
            'verify_host' => true,
            'cafile' => '/etc/ssl/certs/ca-certificates.crt',
        ]);
    }
    
    /**
     * Senkron istek
     */
    public function fetch(string $url, array $options = []): string
    {
        $options = array_merge($this->getDefaultProxyOptions(), $options);
        
        try {
            $response = $this->client->request('GET', $url, $options);
            
            $statusCode = $response->getStatusCode();
            
            if ($statusCode >= 400) {
                throw new RuntimeException("HTTP {$statusCode} error");
            }
            
            return $response->getContent();
            
        } catch (TransportException $e) {
            throw new RuntimeException("Transport error: " . $e->getMessage());
        }
    }
    
    /**
     * Asenkron istekler - birden fazla URL aynı anda
     */
    public function fetchAsync(array $urls, ?string $country = null): array
    {
        $responses = [];
        $results = [];
        
        // İstekleri başlat
        foreach ($urls as $i => $url) {
            $sessionId = "async-" . bin2hex(random_bytes(8));
            $username = $this->buildUsername($country, $sessionId);
            
            $responses[$i] = $this->client->request('GET', $url, [
                'proxy' => "http://{$username}:{$this->proxyPass}@{$this->proxyHost}:{$this->proxyPort}",
            ]);
        }
        
        // Tüm yanıtları bekle
        foreach ($responses as $i => $response) {
            try {
                $results[$urls[$i]] = $response->getContent();
            } catch (TransportException $e) {
                $results[$urls[$i]] = ['error' => $e->getMessage()];
            }
        }
        
        return $results;
    }
    
    /**
     * Streaming response - büyük dosyalar için
     */
    public function fetchStream(string $url, string $outputPath): int
    {
        $response = $this->client->request('GET', $url, $this->getDefaultProxyOptions());
        
        $handle = fopen($outputPath, 'w');
        $bytesWritten = 0;
        
        foreach ($this->client->stream($response) as $chunk) {
            $bytesWritten += fwrite($handle, $chunk->getContent());
        }
        
        fclose($handle);
        return $bytesWritten;
    }
    
    private function getDefaultProxyOptions(): array
    {
        $username = $this->buildUsername();
        
        return [
            'proxy' => "http://{$username}:{$this->proxyPass}@{$this->proxyHost}:{$this->proxyPort}",
            'headers' => [
                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            ],
        ];
    }
    
    private function buildUsername(?string $country = null, ?string $session = null): string
    {
        $parts = [$this->proxyUser];
        
        if ($country) {
            $parts[] = "country-{$country}";
        }
        
        if ($session) {
            $parts[] = "session-{$session}";
        }
        
        return implode('-', $parts);
    }
}

// Kullanım
$client = new SymfonyProxyClient('user', 'your-password');

// Tek istek
$content = $client->fetch('https://httpbin.org/ip', ['country' => 'DE']);

// Asenkron çoklu istek
$results = $client->fetchAsync([
    'https://httpbin.org/ip',
    'https://httpbin.org/headers',
    'https://httpbin.org/user-agent',
], 'US');

Laravel Entegrasyonu: Residential Proxy Pool Service

Laravel projelerinde proxy yönetimini merkezi bir service class ile ele almak, queue job'larından ve controller'lardan kolay erişim sağlar. Aşağıdaki örnekte, residential proxy pool'u yöneten bir service sınıfı ve bunu job içinde kullanan bir yapı göreceksiniz.

<?php

namespace App\Services;

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use RuntimeException;

class ResidentialProxyService
{
    private Client $client;
    private array $config;
    
    // ProxyHat gateway
    private const PROXY_HOST = 'gate.proxyhat.com';
    private const PROXY_PORT = 8080;
    
    public function __construct()
    {
        $this->config = [
            'username' => config('services.proxyhat.username'),
            'password' => config('services.proxyhat.password'),
            'default_country' => config('services.proxyhat.default_country', 'US'),
            'retry_attempts' => config('services.proxyhat.retry_attempts', 3),
            'timeout' => config('services.proxyhat.timeout', 30),
        ];
        
        $this->client = new Client([
            'timeout' => $this->config['timeout'],
            'connect_timeout' => 10,
            'verify' => true,
        ]);
    }
    
    /**
     * Proxy ile istek at
     */
    public function request(
        string $method,
        string $url,
        array $options = [],
        ?string $country = null,
        ?string $sessionId = null
    ): array {
        $attempts = 0;
        $maxAttempts = $this->config['retry_attempts'];
        $lastException = null;
        
        while ($attempts < $maxAttempts) {
            $attempts++;
            
            // Her denemede yeni session = yeni IP
            $currentSession = $sessionId ?? $this->generateSessionId();
            $username = $this->buildUsername($country, $currentSession);
            
            try {
                $response = $this->client->request($method, $url, array_merge($options, [
                    'proxy' => $this->getProxyUrl($username),
                    'headers' => array_merge(
                        $this->getDefaultHeaders(),
                        $options['headers'] ?? []
                    ),
                ]));
                
                return [
                    'success' => true,
                    'status' => $response->getStatusCode(),
                    'body' => $response->getBody()->getContents(),
                    'session_id' => $currentSession,
                    'attempts' => $attempts,
                ];
                
            } catch (RequestException $e) {
                $lastException = $e;
                $statusCode = $e->hasResponse() ? $e->getResponse()->getStatusCode() : 0;
                
                Log::warning("Proxy request attempt {$attempts} failed", [
                    'url' => $url,
                    'status' => $statusCode,
                    'session' => $currentSession,
                    'error' => $e->getMessage(),
                ]);
                
                // Rate limit veya block - yeni IP ile retry
                if (in_array($statusCode, [429, 403, 503])) {
                    sleep(min($attempts, 5)); // Exponential backoff
                    continue;
                }
                
                // Diğer hatalar - retry yok
                break;
            }
        }
        
        return [
            'success' => false,
            'error' => $lastException?->getMessage() ?? 'Unknown error',
            'attempts' => $attempts,
        ];
    }
    
    /**
     * GET isteği shorthand
     */
    public function get(string $url, ?string $country = null): array
    {
        return $this->request('GET', $url, [], $country);
    }
    
    /**
     * POST isteği shorthand
     */
    public function post(string $url, array $data, ?string $country = null): array
    {
        return $this->request('POST', $url, [
            'json' => $data,
        ], $country);
    }
    
    /**
     * Sticky session - aynı IP ile birden fazla istek
     */
    public function withSession(string $sessionId, callable $callback): mixed
    {
        return $callback($sessionId, fn($method, $url, $options = []) => 
            $this->request($method, $url, $options, null, $sessionId)
        );
    }
    
    /**
     * Batch istekler - her URL için farklı IP
     */
    public function batch(array $urls, ?string $country = null): array
    {
        $results = [];
        
        foreach ($urls as $key => $url) {
            $results[$key] = $this->get($url, $country);
            
            // Rate limiting için küçük gecikme
            usleep(100000); // 100ms
        }
        
        return $results;
    }
    
    /**
     * Proxy pool istatistikleri
     */
    public function getStats(): array
    {
        return Cache::remember('proxy_pool_stats', 300, function () {
            $response = $this->client->get('https://api.proxyhat.com/v1/stats', [
                'auth' => [$this->config['username'], $this->config['password']],
            ]);
            
            return json_decode($response->getBody()->getContents(), true);
        });
    }
    
    private function buildUsername(?string $country = null, ?string $session = null): string
    {
        $parts = [$this->config['username']];
        
        $country = $country ?? $this->config['default_country'];
        if ($country) {
            $parts[] = "country-{$country}";
        }
        
        if ($session) {
            $parts[] = "session-{$session}";
        }
        
        return implode('-', $parts);
    }
    
    private function getProxyUrl(string $username): string
    {
        return "http://{$username}:{$this->config['password']}@" . self::PROXY_HOST . ':' . self::PROXY_PORT;
    }
    
    private function generateSessionId(): string
    {
        return 'laravel-' . bin2hex(random_bytes(8));
    }
    
    private function getDefaultHeaders(): array
    {
        return [
            'User-Agent' => config('services.proxyhat.user_agent', 
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'),
            'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language' => 'en-US,en;q=0.9',
        ];
    }
}

Laravel Job ile Kullanım

<?php

namespace App\Jobs;

use App\Services\ResidentialProxyService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;

class ScrapeProductJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    public int $tries = 3;
    public int $maxExceptions = 3;
    public int $timeout = 120;
    
    public function __construct(
        private string $productUrl,
        private string $country = 'US'
    ) {}
    
    public function handle(ResidentialProxyService $proxy): void
    {
        Log::info("Starting product scrape", ['url' => $this->productUrl]);
        
        $response = $proxy->get($this->productUrl, $this->country);
        
        if (!$response['success']) {
            Log::error("Product scrape failed", [
                'url' => $this->productUrl,
                'error' => $response['error'],
            ]);
            
            // Retry with different IP
            $this->release(30); // 30 saniye sonra retry
            return;
        }
        
        // HTML parsing
        $data = $this->parseProduct($response['body']);
        
        // Veritabanına kaydet
        // Product::updateOrCreate(['url' => $this->productUrl], $data);
        
        Log::info("Product scraped successfully", [
            'url' => $this->productUrl,
            'session_id' => $response['session_id'],
        ]);
    }
    
    private function parseProduct(string $html): array
    {
        // DOM parsing logic
        return [
            'title' => 'Product Title', // Extract from HTML
            'price' => 99.99,
            'availability' => true,
            'scraped_at' => now(),
        ];
    }
}

// Dispatch
class ProductController extends Controller
{
    public function scrape(Request $request, ResidentialProxyService $proxy)
    {
        $urls = $request->input('urls', []);
        $country = $request->input('country', 'US');
        
        foreach ($urls as $url) {
            ScrapeProductJob::dispatch($url, $country);
        }
        
        return response()->json([
            'message' => 'Scraping jobs queued',
            'count' => count($urls),
        ]);
    }
}

Laravel Config Dosyası

config/services.php dosyasına ProxyHat konfigürasyonunu ekleyin:

<?php

// config/services.php

return [
    // ... diğer servisler
    
    'proxyhat' => [
        'username' => env('PROXYHAT_USERNAME'),
        'password' => env('PROXYHAT_PASSWORD'),
        'default_country' => env('PROXYHAT_DEFAULT_COUNTRY', 'US'),
        'retry_attempts' => env('PROXYHAT_RETRY_ATTEMPTS', 3),
        'timeout' => env('PROXYHAT_TIMEOUT', 30),
        'user_agent' => env('PROXYHAT_USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'),
    ],
];

PHP multi_curl ile Eşzamanlı İstekler

Yüksek performans gerektiren scraping işlemlerinde curl_multi_* fonksiyonları kullanarak paralel istekler yapabilirsiniz. Bu yöntem, Guzzle Pool'dan daha düşük seviyeli ve daha fazla kontrol sunar.

<?php

declare(strict_types=1);

class MultiCurlProxyClient
{
    private string $proxyHost = 'gate.proxyhat.com';
    private int $proxyPort = 8080;
    private string $proxyUser;
    private string $proxyPass;
    
    public function __construct(string $proxyUser, string $proxyPass)
    {
        $this->proxyUser = $proxyUser;
        $this->proxyPass = $proxyPass;
    }
    
    /**
     * Çoklu paralel istek
     * 
     * @param array $urls URL'ler array'i
     * @param int $concurrency Eşzamanlı istek sayısı
     * @param string|null $country Coğrafi hedefleme
     * @return array Her URL için sonuç
     */
    public function fetchMultiple(
        array $urls, 
        int $concurrency = 10, 
        ?string $country = null
    ): array {
        $results = [];
        $handles = [];
        $sessions = [];
        
        // Multi curl başlat
        $mh = curl_multi_init();
        
        // İlk batch curl handle'larını oluştur
        $urlKeys = array_keys($urls);
        $urlValues = array_values($urls);
        $urlCount = count($urls);
        $running = 0;
        
        // İlk batch'i başlat
        for ($i = 0; $i < min($concurrency, $urlCount); $i++) {
            $key = $urlKeys[$i];
            $url = $urlValues[$i];
            $sessionId = $this->generateSessionId();
            $sessions[$key] = $sessionId;
            
            $ch = $this->createCurlHandle($url, $sessionId, $country);
            $handles[$key] = $ch;
            curl_multi_add_handle($mh, $ch);
        }
        
        $currentIndex = $concurrency;
        
        // İstekleri çalıştır
        do {
            curl_multi_exec($mh, $running);
            
            // Tamamlanan istekleri kontrol et
            while ($info = curl_multi_info_read($mh)) {
                $ch = $info['handle'];
                $key = array_search($ch, $handles, true);
                
                if ($key !== false) {
                    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                    $error = curl_error($ch);
                    
                    if ($info['result'] === CURLE_OK && $httpCode >= 200 && $httpCode < 400) {
                        $results[$key] = [
                            'success' => true,
                            'body' => curl_multi_getcontent($ch),
                            'http_code' => $httpCode,
                            'session_id' => $sessions[$key],
                        ];
                    } else {
                        $results[$key] = [
                            'success' => false,
                            'error' => $error ?: "HTTP {$httpCode}",
                            'http_code' => $httpCode,
                        ];
                    }
                    
                    // Handle'ı temizle
                    curl_multi_remove_handle($mh, $ch);
                    curl_close($ch);
                    unset($handles[$key]);
                    
                    // Yeni URL ekle (varsa)
                    if ($currentIndex < $urlCount) {
                        $newKey = $urlKeys[$currentIndex];
                        $newUrl = $urlValues[$currentIndex];
                        $sessionId = $this->generateSessionId();
                        $sessions[$newKey] = $sessionId;
                        
                        $newCh = $this->createCurlHandle($newUrl, $sessionId, $country);
                        $handles[$newKey] = $newCh;
                        curl_multi_add_handle($mh, $newCh);
                        $currentIndex++;
                    }
                }
            }
            
            // Kısa bekleme (CPU kullanımını azalt)
            curl_multi_select($mh, 0.1);
            
        } while ($running > 0 || !empty($handles));
        
        curl_multi_close($mh);
        
        return $results;
    }
    
    /**
     * Curl handle oluştur
     */
    private function createCurlHandle(
        string $url, 
        string $sessionId, 
        ?string $country
    ): \CurlHandle {
        $ch = curl_init();
        $username = $this->buildUsername($country, $sessionId);
        
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 5,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            
            // Proxy
            CURLOPT_PROXY => $this->proxyHost,
            CURLOPT_PROXYPORT => $this->proxyPort,
            CURLOPT_PROXYUSERPWD => "{$username}:{$this->proxyPass}",
            CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
            
            // SSL
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt',
            
            // Headers
            CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            CURLOPT_HTTPHEADER => [
                'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Accept-Language: en-US,en;q=0.9',
            ],
        ]);
        
        return $ch;
    }
    
    private function buildUsername(?string $country, string $sessionId): string
    {
        $parts = [$this->proxyUser];
        
        if ($country) {
            $parts[] = "country-{$country}";
        }
        
        $parts[] = "session-{$sessionId}";
        
        return implode('-', $parts);
    }
    
    private function generateSessionId(): string
    {
        return bin2hex(random_bytes(8));
    }
}

// Kullanım
$client = new MultiCurlProxyClient('user', 'your-password');

$urls = [
    'page1' => 'https://httpbin.org/ip',
    'page2' => 'https://httpbin.org/user-agent',
    'page3' => 'https://httpbin.org/headers',
    'page4' => 'https://httpbin.org/get',
    'page5' => 'https://httpbin.org/xml',
];

$results = $client->fetchMultiple($urls, concurrency: 5, country: 'DE');

foreach ($results as $key => $result) {
    echo "[$key] ";
    echo $result['success'] ? "OK ({$result['http_code']})" : "FAILED: {$result['error']}";
    echo "\n";
}

TLS/SSL Konfigürasyonu ve CA Bundle Yönetimi

Proxy kullanırken SSL/TLS doğrulaması kritik bir güvenlik katmanıdır. Özellikle man-in-the-middle saldırılarına karşı CA bundle doğrulaması şarttır. Ancak bazı durumlarda (eski sistemler, self-signed sertifikalar) CA bundle yönetimi gerekir.

CA Bundle Konfigürasyonu

<?php

declare(strict_types=1);

class TlsProxyClient
{
    private string $proxyHost = 'gate.proxyhat.com';
    private int $proxyPort = 8080;
    
    // CA bundle yolları (önce gelen ilk bulunan kullanılır)
    private array $caPaths = [
        '/etc/ssl/certs/ca-certificates.crt',     // Debian/Ubuntu
        '/etc/pki/tls/certs/ca-bundle.crt',      // RHEL/CentOS
        '/etc/ssl/ca-bundle.pem',                  // Alpine
        '/usr/local/etc/openssl/cert.pem',         // macOS Homebrew
        '/etc/openssl/certs/ca-certificates.crt', // FreeBSD
    ];
    
    private ?string $caBundlePath = null;
    
    public function __construct(
        private string $proxyUser,
        private string $proxyPass
    ) {
        $this->detectCaBundle();
    }
    
    /**
     * Sistem CA bundle'ını tespit et
     */
    private function detectCaBundle(): void
    {
        foreach ($this->caPaths as $path) {
            if (file_exists($path) && is_readable($path)) {
                $this->caBundlePath = $path;
                break;
            }
        }
        
        // Composer CA bundle fallback
        if (!$this->caBundlePath) {
            $composerCa = dirname(__DIR__) . '/vendor/composer/ca-bundle/res/cacert.pem';
            if (file_exists($composerCa)) {
                $this->caBundlePath = $composerCa;
            }
        }
    }
    
    /**
     * Guzzle client oluştur (TLS sertifikalı)
     */
    public function createGuzzleClient(): \GuzzleHttp\Client
    {
        $options = [
            'timeout' => 30,
            'connect_timeout' => 10,
        ];
        
        if ($this->caBundlePath) {
            $options['verify'] = $this->caBundlePath;
        } else {
            // CA bundle bulunamadı - production'da önerilmez
            trigger_error(
                'CA bundle not found. SSL verification may fail. ' .
                'Install ca-certificates or use composer/ca-bundle.',
                E_USER_WARNING
            );
            $options['verify'] = true; // Sistem default'u dene
        }
        
        return new \GuzzleHttp\Client($options);
    }
    
    /**
     * cURL handle oluştur (TLS sertifikalı)
     */
    public function createCurlHandle(string $url): \CurlHandle
    {
        $ch = curl_init();
        
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            
            // Proxy
            CURLOPT_PROXY => $this->proxyHost,
            CURLOPT_PROXYPORT => $this->proxyPort,
            CURLOPT_PROXYUSERPWD => "{$this->proxyUser}:{$this->proxyPass}",
            
            // TLS/SSL - SNI (Server Name Indication)
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
            
            // TLS 1.3 desteği (PHP 7.4+, cURL 7.52+)
            CURLOPT_TLS13_CIPHERS => 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256',
        ];
        
        if ($this->caBundlePath) {
            $options[CURLOPT_CAINFO] = $this->caBundlePath;
            
            // CURLOPT_CAPATH de ekle (dizin bazlı)
            $caDir = dirname($this->caBundlePath);
            if (is_dir($caDir)) {
                $options[CURLOPT_CAPATH] = $caDir;
            }
        }
        
        curl_setopt_array($ch, $options);
        
        return $ch;
    }
    
    /**
     * SSL sertifikası bilgilerini al (debug için)
     */
    public function getSSLCertificateInfo(string $url): ?array
    {
        $ch = curl_init();
        
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_NOBODY => true, // HEAD request
            CURLOPT_CERTINFO => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_CAINFO => $this->caBundlePath,
        ]);
        
        curl_exec($ch);
        
        if (curl_errno($ch)) {
            curl_close($ch);
            return null;
        }
        
        $certInfo = curl_getinfo($ch, CURLINFO_CERTINFO);
        $sslVerifyResult = curl_getinfo($ch, CURLINFO_SSL_VERIFYRESULT);
        
        curl_close($ch);
        
        return [
            'cert_info' => $certInfo,
            'ssl_verify_result' => $sslVerifyResult,
            'ca_bundle_path' => $this->caBundlePath,
        ];
    }
    
    /**
     * TLS bağlantı testi
     */
    public function testTLSConnection(string $url): array
    {
        $ch = $this->createCurlHandle($url);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        
        curl_exec($ch);
        
        $info = [
            'ssl_verify_result' => curl_getinfo($ch, CURLINFO_SSL_VERIFYRESULT),
            'primary_ip' => curl_getinfo($ch, CURLINFO_PRIMARY_IP),
            'primary_port' => curl_getinfo($ch, CURLINFO_PRIMARY_PORT),
            'scheme' => curl_getinfo($ch, CURLINFO_SCHEME),
            'error' => curl_error($ch),
            'errno' => curl_errno($ch),
        ];
        
        curl_close($ch);
        
        return $info;
    }
}

// Kullanım
$client = new TlsProxyClient('user', 'your-password');

// TLS bağlantı testi
$testResult = $client->testTLSConnection('https://httpbin.org/get');
print_r($testResult);

// Sertifika bilgileri
$certInfo = $client->getSSLCertificateInfo('https://httpbin.org');
print_r($certInfo);

Sertifika Doğrulama Hatalarını Yönetme

<?php

/**
 * SSL hata yönetimi ve retry logic
 */
class RobustProxyClient extends TlsProxyClient
{
    public function fetchWithSSLErrorHandling(string $url, int $maxRetries = 3): array
    {
        $lastError = null;
        
        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            $ch = parent::createCurlHandle($url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            
            $response = curl_exec($ch);
            $errno = curl_errno($ch);
            $error = curl_error($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            
            curl_close($ch);
            
            // Başarılı
            if ($errno === 0 && $httpCode >= 200 && $httpCode < 400) {
                return [
                    'success' => true,
                    'body' => $response,
                    'http_code' => $httpCode,
                    'attempts' => $attempt,
                ];
            }
            
            $lastError = $error ?: "HTTP {$httpCode}";
            
            // SSL hata kodları
            $sslErrors = [
                CURLE_SSL_CACERT,      // 60
                CURLE_SSL_CACERT_BADFILE, // 77
                CURLE_SSL_CONNECT_ERROR,  // 35
                CURLE_SSL_CERTPROBLEM,    // 58
            ];
            
            if (in_array($errno, $sslErrors, true)) {
                // SSL hatası - CA bundle sorunu olabilir
                error_log("SSL error on attempt {$attempt}: [{$errno}] {$error}");
                
                // Fallback: sistem CA bundle'ını dene
                $ch = parent::createCurlHandle($url);
                curl_setopt_array($ch, [
                    CURLOPT_SSL_VERIFYPEER => true,
                    CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt',
                ]);
                
                $response = curl_exec($ch);
                if (curl_errno($ch) === 0) {
                    curl_close($ch);
                    return [
                        'success' => true,
                        'body' => $response,
                        'http_code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
                        'attempts' => $attempt,
                    ];
                }
                curl_close($ch);
            }
            
            // Exponential backoff
            if ($attempt < $maxRetries) {
                usleep($attempt * 500000); // 0.5s, 1s, 1.5s...
            }
        }
        
        return [
            'success' => false,
            'error' => $lastError,
            'attempts' => $maxRetries,
        ];
    }
}

PHP HTTP Client Karşılaştırması

Özellik Raw cURL Guzzle Symfony HTTP Client
Kurulum Yerleşik (eklenti gerekli) Composer ile Composer ile
Proxy Desteği Manuel (CURLOPT_PROXY) Otomatik (options array) Otomatik (options array)
Async/Concurrent curl_multi_* (karmaşık) Pool ile (orta) Native async (kolay)
Laravel Entegrasyonu Doğrudan kullanım Yerleşik destek Ek paket gerekli
Performans En yüksek Yüksek Yüksek
Hata Yönetimi Manuel Exception-based Exception-based
Öğrenme Eğrisi Düşük (basit) Orta Orta

Best Practices ve Production İpuçları

Logging ve Monitoring

<?php

use Psr\Log\LoggerInterface;

class LoggedProxyClient
{
    public function __construct(
        private ResidentialProxyService $proxy,
        private LoggerInterface $logger
    ) {}
    
    public function fetchWithLogging(string $url, ?string $country = null): array
    {
        $startTime = microtime(true);
        
        $result = $this->proxy->request('GET', $url, [], $country);
        
        $duration = round((microtime(true) - $startTime) * 1000, 2);
        
        $this->logger->info('Proxy request completed', [
            'url' => $url,
            'country' => $country,
            'success' => $result['success'],
            'http_code' => $result['status'] ?? 0,
            'duration_ms' => $duration,
            'attempts' => $result['attempts'] ?? 1,
            'session_id' => $result['session_id'] ?? null,
        ]);
        
        return $result;
    }
}

Circuit Breaker Pattern

Art arda gelen hatalarda servisi geçici olarak devre dışı bırakarak sistem kaynaklarını koruyun:

<?php

class CircuitBreakerProxyClient
{
    private int $failureCount = 0;
    private ?int $lastFailureTime = null;
    private bool $isOpen = false;
    
    public function __construct(
        private ResidentialProxyService $proxy,
        private int $failureThreshold = 5,
        private int $resetTimeout = 60
    ) {}
    
    public function request(string $method, string $url, array $options = []): array
    {
        // Circuit açık mı kontrol et
        if ($this->isOpen) {
            $elapsed = time() - ($this->lastFailureTime ?? 0);
            
            if ($elapsed < $this->resetTimeout) {
                return [
                    'success' => false,
                    'error' => 'Circuit breaker is open',
                    'retry_after' => $this->resetTimeout - $elapsed,
                ];
            }
            
            // Reset
            $this->isOpen = false;
            $this->failureCount = 0;
        }
        
        $result = $this->proxy->request($method, $url, $options);
        
        if ($result['success']) {
            // Başarılı - sayacı sıfırla
            $this->failureCount = 0;
            return $result;
        }
        
        // Başarısız - sayacı artır
        $this->failureCount++;
        $this->lastFailureTime = time();
        
        if ($this->failureCount >= $this->failureThreshold) {
            $this->isOpen = true;
        }
        
        return $result;
    }
    
    public function isOpen(): bool
    {
        return $this->isOpen;
    }
}

Önemli Çıkarımlar

Key Takeaways:

  • Raw cURL en düşük seviyeli ve en performanslı seçenektir, ancak daha fazla kod gerektirir.
  • Guzzle modern PHP projeleri için önerilen seçenektir; async desteği ve exception-based hata yönetimi sunar.
  • Symfony HTTP Client native async desteği ile yüksek performanslı concurrent istekler için idealdir.
  • multi_curl maksimum kontrol ve performans gerektiren durumlarda kullanılmalıdır.
  • TLS/SSL doğrulaması production ortamlarında asla atlanmamalıdır; CA bundle yönetimi önemlidir.
  • Session ID kullanarak sticky sessions ile aynı IP'den birden fazla istek atılabilir (login flows için kritik).
  • Geo-targeting ile ülke/bazlı IP'ler kullanarak coğrafi kısıtlamalar aşılabilir.
  • Circuit breaker ve retry logic production sistemlerinde zorunludur.

PHP ile proxy kullanımı, modern web scraping ve API entegrasyon projelerinin vazgeçilmez bir parçasıdır. Bu rehberde ele aldığımız teknikleri uygulayarak, residential proxy servisleri ile güvenilir, ölçeklenebilir ve production-ready uygulamalar geliştirebilirsiniz.

Daha fazla kaynak için:

Başlamaya hazır mısınız?

148+ ülkede 50M+ konut IP'sine AI destekli filtreleme ile erişin.

Fiyatlandırmayı GörüntüleKonut Proxy'leri
← Bloga Dön