maxmind/geoip
Laravel wrapper for MaxMind GeoIP databases that resolves IP addresses to geographic data such as country, region, city, and coordinates. Includes configuration, a service provider, and a simple API for GeoIP lookups, with support for local GeoIP2/MMDB files.
This bundle wraps MaxMind's legacy GeoIP C-API (the .dat binary databases), not GeoIP2 .mmdb. Before writing any code, confirm the package name in composer.json — the repository is IDCI-Consulting/Maxmind-GeoIp but the Packagist name and the Symfony bundle alias may differ (conventionally idci/maxmind-geoip-bundle with service alias idci_maxmind_geoip). Also check the require block: it targets PHP 5.3+/Symfony 2.x, so you'll want to know exactly which Symfony constraints it declares before dropping it into anything modern.
Minimal path:
composer require idci/maxmind-geoip-bundle (verify the real name first).app/AppKernel.php: new IDCI\Bundle\MaxmindGeoIpBundle\IDCIMaxmindGeoIpBundle().GeoIP.dat for country, GeoLiteCity.dat for city) and point the bundle config at it, e.g. idci_maxmind_geoip: { database: "%kernel.root_dir%/../data/GeoLiteCity.dat", mode: memory }.DependencyInjection/Configuration.php and Resources/config/services.yml inside the bundle — that's the ground truth for the config keys, the service id, and which lookup methods (country code, record, org, city) are actually exposed. Don't trust README snippets from 2016.kernel.request listener and resolve the visitor's country code from $request->getClientIp() to drive a geo-redirect, a shipping/currency banner, or a default locale.If you can't get a legacy .dat file at all, stop here — the bundle has no database and won't do anything useful; go straight to geoip2/geoip2.
Wrap the vendor service, don't call it directly. The bundle returns raw arrays/false-y values in legacy MaxMind style. Put a thin GeoIpResolver of your own in front of it that returns a small DTO (countryCode, city, lat/lon, isResolved) and normalizes failure to null. That gives you one place to swap in GeoIP2 later without touching call sites.
Request-scoped lookup, decorator cache. Resolve the IP once per request (a listener that stashes the result on the request attributes), and memoize per-IP in a small array cache or PSR-6 pool for request-scoped and long-running (worker/queue) processes. Repeat lookups in the same request are common — e.g. locale detection, currency, tax, and Twig helpers all wanting the country.
kernel.request listener for geo-detection. Resolve early, store on the request, and let downstream services (locale negotiator, price resolver) read it. Keep the lookup non-fatal: an unresolvable IP must never 500 the page.
Twig extension. A geoip_country() function is the single most-requested thing from product/marketing. Back it with the request attribute so templates never trigger a DB lookup.
Bulk enrichment as a command. For ETL/analytics, write a console command that chunks rows, uses memory mode once per process (not per row), and writes results incrementally. This is where the package is actually fast; per-request lookups are dominated by mode choice, not the lookup itself.
Integration with Symfony's trusted proxies. If you're behind a load balancer, configure framework.trusted_proxies and read the IP through getClientIp() — never parse X-Forwarded-For yourself for a geo decision.
Testing seam. Because the underlying library is a static/binary-backed API, define an interface (GeoIpLookupInterface) in your app and bind the bundle's service as the implementation. Tests then use a fixture-backed fake and never need a .dat file or the extension.
.dat, your accuracy decays every month. Treat this bundle as a stopgap and plan the migration to geoip2/geoip2 + .mmdb (the GeoIP2 PHP reader works on PHP 7+/8 and needs no extension).127.0.0.1, 10.x, 192.168.x, and often IPv6-on-IPv4-edition DBs give false/empty. Every call site must handle "unknown country" explicitly; don't let it silently become US.GeoIP.dat for city/coordinates returns nothing rather than throwing. Match the lookup method to the file you shipped, and assert this in a smoke test.memory loads the whole file into each PHP-FPM worker. A city DB is tens of MB — multiply by worker count before enabling it in production. shared/standalone modes trade CPU for memory.Configuration.php and services.yml. If the alias isn't what you expected, debug:container | grep -i geoip finds it fast.GeoIpLookupInterface, not the bundle's internals.How can I help you explore Laravel packages today?