Download the PHP package mpge/php-country-block without Composer
On this page you can find all versions of the php package mpge/php-country-block. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download mpge/php-country-block
More information about mpge/php-country-block
Files in mpge/php-country-block
Package php-country-block
Short Description Block or allow web traffic by country, with pluggable IP geolocation providers.
License MIT
Homepage https://github.com/mpge/PHP-Country-Block
Informations about the package php-country-block
PHP-Country-Block
Block or allow web traffic by country, with pluggable IP geolocation providers.
Version 2, twelve years later
Version 1 shipped in 2014 and was built on ipinfodb.com, whose IP location API is no longer something you can point a library at. That would have been reason enough for a new release. Reading the old code back was reason for a rewrite.
What version 1 did, and what version 2 does instead:
| Version 1 | Version 2 |
|---|---|
| One API call per blocked country: five countries meant five lookups | One lookup per request, then a set membership check |
| Scraped a raw text response with a regular expression | Typed JSON handling per provider |
The constructor made HTTP calls, set a cookie, and wrote to $_COOKIE |
The constructor wires dependencies; check() returns a value object |
An ip_not_allowed cookie anyone could forge, which blocked forever once set |
A PSR-16 cache of the country code; the verdict is recomputed every request |
Trusted HTTP_CLIENT_IP and X-Forwarded-For unconditionally |
REMOTE_ADDR only, unless you name your proxies |
| Failed open silently when the API errored | An explicit FailureMode, plus an error callback you can log |
Plain http:// |
HTTPS everywhere; the one provider that cannot do HTTPS free must be opted into by name |
| One provider, hard-wired | Six, in a fallback chain, behind one interface |
Version 1 is still installable. It is tagged v1.0.0,
and a countryBlock shim keeps old call sites running under version 2. See UPGRADING.md.
Requirements
PHP 8.2 or newer, with ext-curl and ext-json. The only required package is psr/simple-cache,
and that is interfaces only.
Install
Quick start
check() takes an optional address. Pass one to check somebody other than the current visitor; leave it
off and the client address is worked out for you.
Allowlist instead
Most geo-fencing is really an allowlist. A country you forgot to think about should fail closed, not open:
Providers
Every provider implements one interface:
| Resolver | Key | Free tier | Transport | Worth knowing |
|---|---|---|---|---|
CloudflareHeaderResolver |
none | unlimited | no request at all | Only if your origin is locked to Cloudflare |
MaxMindResolver |
account for the download | unlimited, local | local file | You own the update cycle |
Ip2LocationResolver |
optional | 1,000/day keyless, 50,000/month keyed | HTTPS | The direct replacement for version 1's provider |
IpInfoResolver |
required | Lite is uncapped | HTTPS | Country and ASN only on Lite, which is all this needs |
IpApiCoResolver |
optional | 1,000/day | HTTPS | Keyless and still encrypted, so a good last resort |
IpApiComResolver |
optional | 45/minute | plain HTTP unless you pay | See the warning below |
StaticResolver |
none | n/a | none | Forces a country. For local development and tests |
About ip-api.com
Its free tier has no HTTPS. A network attacker between your server and theirs can rewrite the country in the response, and you would then admit exactly the traffic you set out to block. Because that is a real downgrade from every other option here, the constructor refuses to build until you say so out loud:
About the Cloudflare header
CF-IPCountry costs nothing and adds no latency, because Cloudflare resolved the country before your PHP
process started. It is also just a header. If your origin server is reachable directly, anyone can set it.
Restrict your origin to Cloudflare's IP ranges, or use authenticated origin pulls, before trusting it.
The resolver also declines to answer for any address other than the one the request came from, rather than confidently returning the wrong country.
MaxMind
No network call, no rate limit, and no third party learning your visitors' addresses. Keep the database
updated; a stale .mmdb gets quietly less accurate.
Chaining providers
Order them cheap to expensive. The first real answer wins, and a provider that is down is stepped over rather than allowed to end the request:
Silent fallback is how an expired API key goes unnoticed for a year, so wire the error callback to your logger:
Caching
Cache the country and the verdict stays live. That distinction is what version 1's cookie got wrong: it cached the answer, so changing your country list left old visitors on the old verdict.
Any PSR-16 pool works: symfony/cache, cache/redis-adapter, Laravel's
Cache::store()->getStore(), whatever you already run. Two are bundled for projects that have none:
ArrayCachelives for one request, enough to stop a page that checks the same visitor three times from making three API calls.FileCachepersists to disk. Writes go via a temporary file and a rename, and reads unserialize withallowed_classesdisabled.
Misses are cached too, on the shorter negativeTtl, so a run of unknown addresses cannot burn a day's
quota in a minute.
Behind a proxy or load balancer
By default only REMOTE_ADDR is believed, so nobody can choose their own country by sending a header.
That is deliberate, and it is the single biggest behavioural change from version 1.
When you really do sit behind a proxy, name it:
Forwarding headers are then read only when the request actually arrived from one of those ranges. The chain is walked from the socket end inwards and the first hop that is not one of yours wins, so entries a client invented and prepended are never reached.
Reading a different header instead:
When nothing can resolve the country
Providers go down, rate-limit, and draw a blank on addresses they have never seen. Decide what that means for you:
Either way the Decision tells you it happened, so you can tell a policy verdict from a real match:
A rise in Reason::Unresolved usually means a provider is failing, not that your traffic changed. It is
worth a metric.
Using it in a framework
A front controller, the version 1 pattern brought forward:
Laravel middleware:
Blockers are immutable, so one configured instance is safe to bind in a container and share.
See the examples directory for complete, runnable files.
Local development
StaticResolver saves you from the fact that no provider has anything useful to say about 127.0.0.1:
Testing
Nothing in the suite touches the network.
Upgrading from version 1
See UPGRADING.md. The short version: countryBlock still exists, still sets $isBlocked,
and now emits a deprecation notice. It no longer sets the ip_not_allowed cookie, and it no longer uses
$path_to_script.
License
MIT. See LICENSE.
The version 1 implementation bundled a copy of IP-User-Location by Tom Green, also MIT. Version 2 no longer includes it.
Support
All versions of php-country-block with dependencies
ext-curl Version *
ext-json Version *
psr/simple-cache Version ^2.0 || ^3.0