stevebauman/location
Retrieve a user’s geolocation from their IP in Laravel. Provides a simple Location facade to get city, region, country, coordinates, timezone and more, with multiple driver support (e.g., IP2Location, IP-API, MaxMind) plus caching and testing helpers.
Install the package:
composer require stevebauman/location
Publish the config (optional but recommended for customization):
php artisan vendor:publish --provider="Stevebauman\Location\LocationServiceProvider"
Configure your driver in .env (example for MaxMind):
LOCATION_DRIVER=maxmind
MAXMIND_LICENSE_KEY=your_license_key_here
Or for ipinfo.io:
LOCATION_DRIVER=ipinfo
IPINFO_TOKEN=your_token_here
First use case: Retrieve a visitor’s location in a controller or middleware:
use Stevebauman\Location\Facades\Location;
$position = Location::get();
$country = $position->country;
$city = $position->city;
config/location.php – Driver settings, timeouts, and fallback logic.Stevebauman\Location\Facades\Location – Primary entry point for all location queries.Stevebauman\Location\Position – Contains all location data (country, city, coordinates, etc.).Location::fake() – Simulate locations for development/testing.Use middleware to attach location data to requests globally:
// app/Http/Middleware/AttachGeoData.php
public function handle($request, Closure $next) {
$request->merge([
'geo' => Location::get(),
]);
return $next($request);
}
Register in app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\AttachGeoData::class,
];
Access in controllers/views:
$country = request('geo')->country;
Use the Position object to customize responses:
$position = Location::get();
if ($position->country === 'US') {
return view('pricing.us');
}
return view('pricing.international');
Cache responses to reduce API calls or database lookups:
$position = Cache::remember("geo_{$request->ip()}", now()->addHours(1), function () {
return Location::get();
});
Configure fallbacks in config/location.php:
'drivers' => [
'maxmind' => [
'fallback' => 'ipinfo',
],
'ipinfo' => [
'fallback' => 'ip2location',
],
],
This ensures graceful degradation if the primary driver fails.
Simulate locations in tests:
Location::fake([
'country' => 'DE',
'city' => 'Berlin',
'latitude' => 52.5200,
'longitude' => 13.4050,
]);
$position = Location::get();
$this->assertEquals('DE', $position->country);
Position ModelAdd custom methods via macros (e.g., to check if a user is in the EU):
// app/Providers/AppServiceProvider.php
use Stevebauman\Location\Position;
public function boot() {
Position::macro('isEu', function () {
$euCountries = ['DE', 'FR', 'IT', 'ES', 'PT', 'NL', 'BE', 'AT', 'SE', 'DK'];
return in_array($this->country, $euCountries);
});
}
Usage:
if (Location::get()->isEu()) {
// Apply VAT or EU-specific logic
}
Create a driver for a proprietary API:
// app/Services/CustomLocationDriver.php
namespace App\Services;
use Stevebauman\Location\Contracts\Driver;
class CustomLocationDriver implements Driver {
public function get($ip) {
$response = http_get("https://api.example.com/geo?ip={$ip}");
return new \Stevebauman\Location\Position($response->data);
}
}
Register in config/location.php:
'drivers' => [
'custom' => [
'class' => \App\Services\CustomLocationDriver::class,
],
],
For batch processing (e.g., importing user data):
$ips = ['192.168.1.1', '8.8.8.8'];
$positions = Location::getMultiple($ips);
MaxMind License Key
MAXMIND_LICENSE_KEY in .env will cause silent failures.bootstrap/app.php or a service provider:
if (empty(config('location.maxmind.license_key'))) {
throw new \RuntimeException('MaxMind license key not configured.');
}
IPv6 Support
ipapi.co) may not handle IPv6 addresses correctly.ipinfo or ip2location for broader support.Caching Headaches
Cache::remember("geo_{$ip}", now()->addMinutes(5), fn() => Location::get($ip));
Fallback Loops
null or a static default:
'drivers' => [
'maxmind' => [
'fallback' => 'ipinfo',
],
'ipinfo' => [
'fallback' => null, // No further fallbacks
],
],
MaxMind Database Updates
MAXMIND_DOWNLOAD_PATH=/custom/path/maxmind
Timezone Ambiguity
America/New_York, while others use offsets (e.g., -05:00).Position::macro('timezone', function () {
$tz = $this->timezone;
return \DateTimeZone::createFromIC($tz) ?: $tz;
});
Log Driver Responses
Enable debug mode in config/location.php:
'debug' => env('LOCATION_DEBUG', false),
This logs raw responses from drivers (useful for troubleshooting API issues).
Check IP Accuracy Use IPinfo’s tool or MaxMind’s demo to verify your IP’s expected location.
Validate Fallbacks Test fallback behavior by disabling drivers:
// Temporarily disable MaxMind
config(['location.drivers.maxmind.enabled' => false]);
$position = Location::get(); // Should use fallback
Handle Missing Data
Some drivers may return null for optional fields (e.g., postal_code). Use null-safe operators:
$postalCode = $position->postal ?? 'N/A';
Driver-Specific Settings
MAXMIND_LICENSE_KEY and MAXMIND_DATABASE_PATH (defaults to storage/app/maxmind).IPINFO_TOKEN and optionally IPINFO_SELECT_FIELDS to reduce payload size.IP2LOCATION_API_KEY and IP2LOCATION_LICENSE_KEY.Timeouts
Configure HTTP timeouts in config/location.php:
'timeout' => 5.0, // Seconds
Critical for API drivers
How can I help you explore Laravel packages today?