Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Geo Fence Laravel Package

salmanzafar/laravel-geo-fence

A Laravel package for geo-fencing: define geographic boundaries and test whether coordinates or models fall inside them. Useful for location-based access rules, delivery zones, and region restrictions, with a simple, framework-friendly API.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it like any other small Laravel utility:

composer require salmanzafar/laravel-geo-fence

The package is auto-discovered on Laravel 5.5+ (there is a single service provider). If you're on an older app or have discovery disabled, add the provider to config/app.php.

Where to look first. The whole package is roughly one class plus a service provider, so spend sixty seconds in src/ and the README rather than guessing. You are looking for exactly one thing: the distance method signature and its unit convention. Everything else in this assessment depends on that method, and the surface area is small enough that reading the source is the documentation.

First use case. The realistic day-one need is a radius check: "is this address inside the delivery zone for this store?" Typical shape:

use Salmanzafar\GeoFence\GeoFence;

$km = app(GeoFence::class)->distance(
    51.5074, -0.1278,   // London
    48.8566,  2.3522,   // Paris
);

Some versions expose a facade (GeoFence::distance(...)); check config/app.php aliases or the README before wiring that in.

Smoke test immediately. Don't trust the unit silently — verify against a known pair before shipping:

// London -> Paris is ~343.5 km (213.4 mi)
$this->assertEqualsWithDelta(343.5, $km, 5.0);

If you get ~213, the package returns miles and you need a conversion at your boundary, not scattered through the codebase.

Implementation Patterns

Wrap it behind your own interface

This is the single highest-value pattern for a 14-star package. Never let Salmanzafar\GeoFence\GeoFence appear in a controller or model. Bind a small contract so you can swap in brick/geo, PostGIS, or a fork without touching call sites.

// app/Geo/DistanceCalculator.php
interface DistanceCalculator
{
    public function kilometers(Coordinates $from, Coordinates $to): float;
}

// app/Geo/PackageDistanceCalculator.php
final class PackageDistanceCalculator implements DistanceCalculator
{
    public function __construct(private GeoFence $geo) {}

    public function kilometers(Coordinates $from, Coordinates $to): float
    {
        return $this->geo->distance(
            $from->lat, $from->lng,
            $to->lat,   $to->lng,
        );
    }
}

Bind it in a service provider, and the package becomes an implementation detail.

Coordinates as a value object

The number-one source of bugs here is argument order. A tiny value object removes the ambiguity permanently:

final readonly class Coordinates
{
    public function __construct(
        public float $lat,
        public float $lng,
    ) {
        if ($lat < -90 || $lat > 90)   throw new InvalidArgumentException('lat');
        if ($lng < -180 || $lng > 180) throw new InvalidArgumentException('lng');
    }

    public static function fromArray(array $c): self
    {
        return new self((float) $c['lat'], (float) $c['lng']);
    }
}

Cast at the boundary (request validation, model casts) and the package call is always correct.

Bounding-box prefilter, then exact distance

The package does math in PHP; it cannot filter your table. Pulling 50k rows into a collection and sorting by distance will fall over. Do a cheap box query in SQL first, then let the package do precise work on the survivors:

$latDelta = $radiusKm / 111.045;
$lngDelta = $radiusKm / (111.045 * cos(deg2rad($lat)));

$candidates = Store::query()
    ->whereBetween('lat', [$lat - $latDelta, $lat + $latDelta])
    ->whereBetween('lng', [$lng - $lngDelta, $lng + $lngDelta])
    ->get();

return $candidates
    ->map(fn (Store $s) => [
        'store' => $s,
        'km' => $calc->kilometers($origin, $s->coordinates()),
    ])
    ->filter(fn ($r) => $r['km'] <= $radiusKm)
    ->sortBy('km')
    ->values();

The box is intentionally generous (it's a square around a circle); the package call does the trimming. This is the pattern that makes the package viable at any real scale.

Model accessor and scope

Store coordinates as two decimal(10,7) columns (not floats) and expose them cleanly:

protected function casts(): array
{
    return ['lat' => 'float', 'lng' => 'float'];
}

public function coordinates(): Coordinates
{
    return new Coordinates($this->lat, $this->lng);
}

Then $order->coordinates() and $store->coordinates() read symmetrically everywhere.

Bulk and queued work

For "recalculate delivery fee for 10,000 orders," don't loop in a request. Chunk and dispatch:

Order::query()->chunkById(500, function ($orders) use ($calc) {
    RecalculateDistance::dispatch($orders->pluck('id'));
});

Resolve DistanceCalculator from the container inside the job — never serialize the package object.

Gotchas and Tips

Confirm the unit and radius constant before anything else. Most implementations of this formula use 6371 km or 3959 mi as the Earth radius. Getting this wrong is silent — the numbers look plausible. The one-degree-of-latitude check (~111.2 km) is your fastest diagnostic.

Haversine, not ellipsoidal. Expect 0.3–0.5% error versus Vincenty/WGS-84. Fine for "is it in the zone," unacceptable for billing by the metre or legal boundary claims. If accuracy matters, the package is the wrong tool.

Argument order is unenforced. distance($lat1, $lng1, $lat2, $lng2) is conventional here, but GeoJSON, Google Maps, and many JS libs use [lng, lat]. One transposed pair gives a plausible-but-wrong distance that can pass a loose test. The value object above is the fix.

No spatial indexing, no WHERE clause. You cannot express distance in SQL through this package. If you find yourself doing ->get()->filter(...) over large tables, stop and add the bounding-box prefilter. If the app is genuinely geo-heavy, PostGIS (ST_DWithin) or a dedicated library will outperform this by orders of magnitude.

The name oversells it. Despite "geo-fence," expect point-to-point distance only — no polygon containment, no point-in-radius helper, no multi-vertex fence. Build those yourself on top of the distance primitive, and name them accordingly.

Antimeridian and polar edge cases. A naive longitude delta breaks across ±180°, and the Haversine formula degrades near the poles. If your domain includes Fiji or Svalbard, test explicitly; otherwise note the limitation and move on.

Package maturity is the real risk. 14 stars, zero dependents, low opportunity score — treat it as unmaintained glue. Pin the exact version in composer.json, and because it's MIT and tiny, consider vendoring or forking the single class into app/Geo/ so a Laravel upgrade can't strand you. This is one of the rare cases where copying ten lines of source is more defensible than a dependency.

Floating-point assertions. Use assertEqualsWithDelta($expected, $actual, 0.5) in tests, never assertEquals. And test with a real pair or two, not just zero-distance and identical coordinates — those pass even when the formula is inverted.

Boundary semantics. Decide whether distance == radius is inside or outside and enforce it in your wrapper. Different packages use < versus <=, and "free delivery at exactly 5 km" disputes are real.

Extension point. If the provider registers a singleton, you can swap the binding in a test to return fixed distances and make radius logic deterministic — much better than constructing real coordinates in every test.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource