geocoder-php/common-http
Common HTTP layer for Geocoder PHP providers. Includes shared HTTP client abstractions, request/response handling, and helpers to integrate PSR-18 clients and PSR-7 messages, keeping geocoding providers lightweight and consistent across transports.
Installation:
composer require geocoder-php/common-http
This package is designed to be a dependency for other HTTP-based geocoders (e.g., geocoder-php/google-map, geocoder-php/openstreetmap). Install the appropriate provider package based on your needs.
First Use Case:
Where to Look First:
AbstractHttpProvider: The core class that defines HTTP request/response handling. Extend this for custom providers.HttpClientInterface) for HTTP requests. Laravel's built-in GuzzleHttp\Client or Symfony\Contracts\HttpClient\HttpClient will work out of the box.use Geocoder\Provider\AbstractHttpProvider;
use Psr\Http\Client\ClientInterface;
$httpClient = new \GuzzleHttp\Client();
$provider = new CustomProvider($httpClient, $apiKey);
Psr\Http\Message\RequestFactoryInterface and Psr\Http\Message\StreamFactoryInterface for request/response creation. Laravel's Symfony\Component\HttpFoundation\Request or GuzzleHttp\Psr7 factories work well.AbstractHttpProvidergetUrl(): Define the API endpoint.getRequest(): Customize request headers/body (e.g., add authentication).getParsedResponse(): Parse provider-specific responses (e.g., JSON/XML).class CustomProvider extends AbstractHttpProvider {
protected function getUrl(): string {
return 'https://api.example.com/geocode?q=' . urlencode($this->query);
}
protected function getParsedResponse(ResponseInterface $response): array {
$data = json_decode($response->getBody(), true);
return $data['results'] ?? [];
}
}
public function register() {
$this->app->singleton(ClientInterface::class, function ($app) {
return new \GuzzleHttp\Client();
});
$this->app->bind(CustomProvider::class, function ($app) {
return new CustomProvider(
$app->make(ClientInterface::class),
config('services.custom_geocoder.api_key')
);
});
}
config/services.php:
'custom_geocoder' => [
'api_key' => env('CUSTOM_GEOCODE_API_KEY'),
'endpoint' => 'https://api.example.com',
],
getParsedResponse() to transform raw responses into geocoder-compatible formats (e.g., arrays with latitude, longitude).$provider = new CustomProvider($httpClient, $apiKey);
$results = $provider->geocode('1600 Amphitheatre Parkway, Mountain View');
foreach ($results as $result) {
echo $result->getCoordinates(); // Returns a \Geocoder\Coordinate object
}
handleResponse() to manage provider-specific errors (e.g., rate limits, invalid queries):
protected function handleResponse(ResponseInterface $response): void {
if ($response->getStatusCode() === 429) {
throw new \RuntimeException('Rate limit exceeded');
}
parent::handleResponse($response);
}
HttpClient or MessageFactory directly may break. Use dependency injection for ClientInterface, RequestFactoryInterface, and StreamFactoryInterface.Http facade (Guzzle) or Symfony\Component\HttpClient\HttpClient are drop-in replacements. Example:
use Symfony\Contracts\HttpClient\HttpClientInterface;
$httpClient = \Symfony\Contracts\HttpClient\HttpClient::create();
$client = new \GuzzleHttp\Client([
'handler' => \GuzzleHttp\HandlerStack::create([
new \GuzzleHttp\Middleware::tap(function ($request, $options) {
\Log::debug('Geocoder Request:', [
'url' => (string) $request->getUri(),
'headers' => $request->getHeaders(),
]);
}),
]),
]);
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
try {
$response = $httpClient->request('GET', $url);
} catch (ClientExceptionInterface | ServerExceptionInterface $e) {
if ($e->getCode() === 429) {
sleep(2); // Retry after delay
return $this->geocode($query);
}
throw $e;
}
$cacheKey = 'geocode_' . md5($query);
$results = \Cache::remember($cacheKey, now()->addHours(1), function () use ($provider, $query) {
return $provider->geocode($query);
});
use Psr\Http\Message\ResponseInterface;
$mockResponse = $this->createMock(ResponseInterface::class);
$mockResponse->method('getBody')->willReturn(json_encode(['results' => []]));
$mockClient = $this->createMock(ClientInterface::class);
$mockClient->method('sendRequest')->willReturn($mockResponse);
$provider = new CustomProvider($mockClient, 'api_key');
$results = $provider->geocode('test');
$this->assertCount(0, $results);
getUrl() to ensure correct URL construction:
protected function getUrl(): string {
return sprintf(
'%s/reverse?lat=%s&lon=%s',
$this->endpoint,
$this->query->getLatitude(),
$this->query->getLongitude()
);
}
// Bad: Creates a new client every time
$provider = new CustomProvider(new \GuzzleHttp\Client(), $apiKey);
getRequest():
protected function getRequest(): RequestInterface {
$request = $this->requestFactory->createRequest('GET', $this->getUrl());
$request = $request->withHeader('X-Custom-Header', 'value');
return $request;
}
$client = new \GuzzleHttp\Client([
'timeout' => 10.0, // 10 seconds
]);
How can I help you explore Laravel packages today?