tastyigniter/ti-ext-local
TastyIgniter Local extension adds location-based features to your application: manage multiple locations, define delivery areas and zones, set opening hours, enable customer reviews, add custom location fields, and let customers search for the nearest location to their address.
Installation:
composer require tastyigniter/ti-ext-local
Publish the extension’s assets and configurations:
php artisan vendor:publish --provider="TastyIgniter\Local\LocalServiceProvider"
First Use Case:
Location model’s built-in query scopes:
use TastyIgniter\Local\Models\Location;
$nearestLocation = Location::nearBy($latitude, $longitude, $distanceInKm)
->where('status', 'active')
->first();
$location = Location::find($locationId);
$isInDeliveryArea = $location->isInDeliveryArea($customerLatitude, $customerLongitude);
$slots = $location->getTimeslots(); // now correct even with weekly closing days
Where to Look First:
app/Models/TastyIgniter/Local/Location.php for core functionality (e.g., nearBy(), isInDeliveryArea(), timeslot resolution).database/migrations/ for schema changes (e.g., delivery area boundaries, opening hours, schedule periods).Location-Based Queries:
nearBy() scope with optional filters:
Location::nearBy($lat, $lng, 10) // 10km radius
->with(['reviews', 'deliveryAreas']) // Eager load relationships
->get();
$cart = Cart::with('items')->find($cartId);
foreach ($cart->items as $item) {
$location = $item->location;
if (!$location->isInDeliveryArea($customerLat, $customerLng)) {
throw new \Exception("Item not available for delivery in this area.");
}
}
Opening Hours & Schedule Logic:
$location = Location::find($id);
$isOpen = $location->isOpenNow(); // Uses Carbon for time comparison
$location->openingHours()->update([
'monday_open' => '09:00',
'monday_close' => '18:00',
// ... other days
]);
future_order_days setting as a workaround:
// Prefer this over manual date math — the search budget is now correct
$slots = $location->getTimeslots(\Illuminate\Support\Carbon::now());
Geocoder Integration:
.env:
LOCAL_GEOCODE_DRIVER=google // or 'osm'
GOOGLE_MAPS_API_KEY=your_key_here
$geocoder = app(\TastyIgniter\Local\Services\Geocoder::class);
$result = $geocoder->geocode('1600 Amphitheatre Parkway, Mountain View');
Custom Location Fields:
Location model with custom attributes:
// In a service provider or model observer
Location::created(function ($location) {
$location->custom_fields = json_encode([
'has_wifi' => true,
'parking_available' => false,
]);
$location->save();
});
Admin Workflow:
SettingsEditor to configure location-specific settings (e.g., delivery fees, opening hours) via the TastyIgniter admin panel.LocationArea model:
$location->deliveryAreas()->create([
'name' => 'Downtown Zone',
'radius' => 5, // km
'fee' => 2.99,
'color' => '#FF0000', // For UI visualization
]);
Frontend Integration:
@foreach($nearbyLocations as $location)
<div data-lat="{{ $location->latitude }}" data-lng="{{ $location->longitude }}">
{{ $location->name }}
</div>
@endforeach
const locations = @json($nearbyLocations);
locations.forEach(loc => {
L.marker([loc.latitude, loc.longitude]).addTo(map)
.bindPopup(`<b>${loc.name}</b><br>Distance: ${loc.distance} km`);
});
Performance Optimization:
Location::with(['reviews', 'deliveryAreas'])->nearBy($lat, $lng, 20)->get();
latitude, longitude) for faster geospatial queries.Event Listeners:
Location::updated(function ($location) {
if ($location->isDirty('opening_hours')) {
event(new \TastyIgniter\Local\Events\OpeningHoursUpdated($location));
}
});
API Endpoints:
Route::get('/api/locations/nearby', function (Request $request) {
$lat = $request->query('lat');
$lng = $request->query('lng');
$distance = $request->query('distance', 10);
return Location::nearBy($lat, $lng, $distance)->get();
});
Testing:
$this->partialMock(\TastyIgniter\Local\Services\Geocoder::class, function ($mock) {
$mock->shouldReceive('geocode')
->once()
->andReturn(['lat' => 37.422, 'lng' => -122.084]);
});
$this->travelTo(now()->setTime(23, 59));
$slots = $location->getTimeslots();
$this->assertNotEmpty($slots);
Caching:
$nearby = Cache::remember("nearby_locations_{$lat}_{$lng}", now()->addHours(1), function () use ($lat, $lng) {
return Location::nearBy($lat, $lng, 10)->get();
});
Namespace References:
Location model references use short class names. If your code explicitly uses fully qualified namespaces, it may break.// Before (may break)
$location = new \App\Models\Location;
// After (recommended)
$location = new \TastyIgniter\Local\Models\Location;
Timeslot Lookback Regression (fixed in v4.3.1):
getTimeslots() could incorrectly return an empty window when a location was actually open — most visibly when future orders were disabled and a weekly closing day fell inside the two-day lookback the schedule period search used. The bug surfaced around midnight and self-healed the next day, making it hard to reproduce.future_order_days. You can delete any padding workarounds you added around closing days or future_order_days.Type-Safety Changes on Location Methods:
Location methods. Passing loosely-typed values (string carbon timestamps, nulls, ints where a CarbonInterface is expected) can now trigger TypeError where it previously coerced silently.$location->getTimeslots(\Illuminate\Support\Carbon::parse($when));
Run your test suite against the new version to surface any hidden coercions.Geocoder Driver Configuration:
LOCAL_GEOCODE_DRIVER is not set in .env, the package will throw an exception..env:
LOCAL_GEOCODE_DRIVER=google
GOOGLE_MAPS_API_KEY=your_api_key_here
Delivery Area Validation:
isInDeliveryArea() may return false positives if the geocoder returns imprecise coordinates.$isInArea = $location->isInDeliveryArea($lat, $lng, 0.1); // Add 100m buffer
Opening Hours Edge Cases:
isOpenNow() assumes 24-hour time format and may fail if opening_hours is malformed (e.g., missing values for certain days). The v4.3.1 fix addresses lookback correctness, not malformed data — validation is still your responsibility.if (!$location->openingHours->isFilled()) {
throw new \InvalidArgumentException("Opening hours not fully configured.");
}
Custom Fields Serialization:
protected $casts = [
'custom_fields' => 'array',
];
Geocoding Failures:
config(['services.google.maps_key' => env('GOOGLE_MAPS_API_KEY')]);
Log::debug('Geocode response', ['payload' => $geocoder->geocode($address)]);
Timeslot Debugging:
$when = now();
Log::info('slot-check', [
'when' => $when->toIso8601String(),
'is_open' => $location->isOpenNow(),
'future_days' => $location->future_order_days ?? null,
'slots' => $location->getTimeslots($when),
]);
future orders off + weekly close day inside two-day lookback + now() just before midnight.Query Debugging:
nearBy() SQL to confirm indexes are used:
DB::listen(fn ($q) => dump($q->sql, $q->bindings));
Location::nearBy($lat, $lng, 10)->get();
latitude/longitude indexes aren’t used, check your DB collation and that the migration from the correct package version has run.Upgrading from pre-v4.3.1:
composer update, run php artisan migrate (no new migrations in v4.3.1, but the type-safety changes may surface through dependent code) and run your test suite. Remove any workarounds that padded future_order_days or disabled weekly closes to get slots to appear — they’re no longer needed and can mask genuine configuration errors.How can I help you explore Laravel packages today?