ijeffro / laravel-airports
IATA airport codes for Laravel: an in-memory lookup API, validation rule and optional database table.
Requires
- php: ^8.2
- illuminate/console: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- illuminate/validation: ^12.0|^13.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.5|^12.0|^13.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel Airports — Over 10,000 IATA airport codes (LHR, JFK, DXB, …) for Laravel 12 and 13.
- No database required. Look airports up through a facade backed by an in-memory dataset.
- Typed results. Every lookup returns readonly
Airportobjects in Laravel collections keyed by code. - Locations. City, coordinates, type, scheduled service and ICAO code for over 8,400 of them, from OurAirports, and a
nearest()lookup. - Validation rule.
new AirportCodeaccepts known codes only. - Optional table. Publish a migration and seed an
airportstable when other tables need to reference airports.
📦 Installation · 🚀 Usage · 💾 Database table · 🗂️ Dataset · 🔄 Upgrading
📦 Installation
composer require flairuk/laravel-airports
Requires PHP 8.2 or later with Laravel 12, or PHP 8.3 or later with Laravel 13.
Laravel discovers the service provider and the Airports facade automatically.
🚀 Usage
use FLAIRUK\Airports\Facades\Airports; Airports::find('lhr'); // Airport { id: 4329, code: "LHR", name: "Heathrow", countryCode: "GB", city: "London", … } Airports::findOrFail('LHR'); // throws ItemNotFoundException for unknown codes Airports::exists('JFK'); // true Airports::findById(4329); Airports::all(); // Collection<string, Airport> keyed by code Airports::inCountry('GB'); // airports in the UK Airports::search('heathrow'); // matches on name, city or exact code Airports::codes(); // ['AAA', 'AAB', ...]
Nearest airports
Airports::nearest(51.754, -1.254); // the 5 nearest to Oxford, nearest first Airports::nearest(51.754, -1.254, limit: 8, scheduledOnly: true); // LTN, LHR, BHX, … with passenger flights Airports::nearest(51.754, -1.254, withinKm: 50); // only those within 50 km Airports::nearest(51.754, -1.254, types: ['large', 'medium']); Airports::find('LHR')->distanceTo(51.754, -1.254); // 63.2 (km, great circle)
nearest() returns a collection keyed by code and skips airports without coordinates and closed ones. Each Airport carries city, latitude, longitude, type (large, medium, small, heliport, seaplane, closed or null), scheduled and icao; any of them is null (or false) when OurAirports has no match for the code.
Select options
Airports::options(); // ['LHR' => 'Heathrow', ...] sorted by name Airports::options('id'); // [4329 => 'Heathrow', ...]
Validation
use FLAIRUK\Airports\Rules\AirportCode; $request->validate([ 'origin' => ['required', new AirportCode], 'destination' => ['required', new AirportCode, 'different:origin'], ]);
AirportCode ignores case but different does not, so LHR and lhr would pass as different airports. Uppercase both fields first (for example in a form request's prepareForValidation()) when that matters.
Dependency injection
The facade resolves a singleton FLAIRUK\Airports\Airports, which you can type-hint instead.
💾 Database table (optional)
php artisan airports:install # publish config + migration, then ask to migrate and seed php artisan airports:install --migrate # migrate and seed without asking php artisan airports:seed # insert / update (safe to re-run) php artisan airports:seed --prune # also delete rows no longer in the dataset
You can also call the seeder from your own DatabaseSeeder:
$this->call(\FLAIRUK\Airports\Database\AirportsSeeder::class);
Query the table through the bundled Eloquent model:
use FLAIRUK\Airports\Models\Airport; Airport::code('LHR')->first(); Airport::inCountry('GB')->scheduled()->orderBy('name')->get();
The table name and connection come from AIRPORTS_TABLE and AIRPORTS_DB_CONNECTION, or from the published config.
🗂️ Rebuilding the dataset
data/airports.php keeps the original IATA rows (id, code, name, country_code) and adds the location fields from OurAirports (public domain), matched on IATA code and country. To refresh them from a newer export:
curl -sSfLo /tmp/airports.csv https://davidmegginson.github.io/ourairports-data/airports.csv
php bin/build-dataset.php /tmp/airports.csv
composer test
The script only reads the original four fields, so re-running it is safe; it prints how many rows matched.
🔄 Upgrading
From 1.0 to 1.1
1.1 is additive. If you seeded the table, add the new columns and re-seed:
php artisan airports:install # publishes the upgrade migration next to your 1.0 one
php artisan migrate
php artisan airports:seed
Until you migrate, airports:seed keeps filling the 1.0 columns only.
From dev-master
Version 1.0 is a rewrite. Breaking changes:
| dev-master | 1.0 |
|---|---|
Package ijeffro/laravel-airports |
flairuk/laravel-airports |
ijeffro\Airports\… namespace |
FLAIRUK\Airports\… |
Facade ijeffro\Airports\AirportsFacade |
FLAIRUK\Airports\Facades\Airports (auto-discovered) |
Airports::getList($sort) (array) |
Airports::all()->sortBy($property, SORT_NATURAL | SORT_FLAG_CASE) (Collection of Airport; properties are camelCase, e.g. countryCode) |
Airports::getOne($id) |
Airports::findById($id) or Airports::find($code) |
Airports::getListForSelect() (keyed by id) |
Airports::options('id') |
php artisan airports:migration |
php artisan airports:install / airports:seed |
Config key airports.table_name |
airports.table |
Row ids and columns (code, name, country_code) are unchanged, so existing tables and foreign keys stay valid.
🧪 Testing
composer test
📄 License
MIT. See LICENSE.