ijeffro/laravel-airlines
Laravel Airlines provides IATA codes, ISO 3166-3 and country codes for airlines, with a service provider, facade, migration generator, and seeder to populate an airlines table in your Laravel app.
Read the requirements first: The README now documents supported PHP/Laravel versions explicitly. Confirm your framework version before installing — the package's service provider and command signatures were written for older Laravel releases, so check the README rather than assuming.
Installation:
composer require ijeffro/laravel-airlines
Register the provider and alias (only needed on Laravel versions without package auto-discovery):
'providers' => [
ijeffro\Airlines\AirlinesServiceProvider::class,
],
'aliases' => [
'Airlines' => ijeffro\Airlines\AirlinesFacade::class,
]
Install and migrate in one step (new in v1.0.1):
php artisan airlines:install --migrate
The --migrate flag runs the published migrations immediately. Omit it if you want to inspect or edit the migration files before running php artisan migrate yourself.
First Use Case — look up an airline by IATA designator:
$airline = Airlines::findByIata('AA'); // American Airlines
echo $airline->name;
Where to look next: the README's requirements section, then Airlines::codes() for a cheap "is this a real designator?" check before writing anything more elaborate.
Fetching Airlines:
Airlines::findByIata('DL'); // Delta Air Lines
Airlines::findByName('United'); // partial match
Airlines::all(); // Collection of all airlines
Validating designators with codes() (v1.0.1):
codes() returns every designator as a string, including all-digit ones such as 00. Use it as the single source of truth for form validation:
use Illuminate\Validation\Rule;
$request->validate([
'iata_code' => ['required', 'string', Rule::in(Airlines::codes())],
]);
Or with the bundled rule:
use ijeffro\Airlines\Rules\ValidIataCode;
$request->validate(['iata_code' => ['required', new ValidIataCode]]);
Sorting results with sortBy:
The package's sortBy accepts camelCase keys and maps them to the underlying snake_case columns. Use the documented mapping rather than guessing column names:
Airlines::sortBy('iataCode')->get();
// maps to the `iata_code` column
Check the README table for the full camelCase → column mapping before passing arbitrary keys; unmapped keys are not silently corrected.
Eloquent relationship on an existing model (e.g. Flight):
public function airline()
{
return $this->belongsTo(Airlines::model(), 'airline_iata', 'iata');
}
$flight->airline->name;
API responses:
return Airlines::findByIata($request->iata)->toArray();
Custom queries via the model:
$active = Airlines::model()->where('active', 1)->get();
Caching the full list (these tables are effectively static, so cache hard):
$airlines = Cache::remember('all-airlines', now()->addDays(7), fn () => Airlines::all());
Caching codes() for validation — this runs on every form submit, so memoize it:
$codes = Cache::rememberForever('airline-codes', fn () => Airlines::codes());
Seeding / factories: rather than faking airlines, seed from the real table so FK constraints hold:
$factory->define(Flight::class, function (Faker $faker) {
return [
'airline_iata' => $faker->randomElement(Airlines::model()->pluck('iata')->all()),
];
});
Localization: add a translations table keyed on iata and join it in a repository wrapper; the package itself ships a single name column.
Numeric designators are now strings (v1.0.1 behaviour change):
codes() and findByIata() previously surfaced all-digit designators such as 00 as integers. They are now strings. Any strict comparison you wrote around the old behaviour will silently fail:
in_array('00', Airlines::codes()); // true
in_array('00', Airlines::codes(), true); // true now; was false before v1.0.1
Airlines::findByIata(00) === Airlines::findByIata('00'); // casts, avoids the issue
Audit any whereIn('iata', Airlines::codes()), array_diff, or in_array(..., true) calls after upgrading.
NOT NULL country columns break seeding:
Old migrations created country (and related) columns as NOT NULL. Making them nullable is a manual pre-step before seeding; airlines:install --migrate will not alter existing tables. Run:
Schema::table('airlines', function (Blueprint $table) {
$table->string('country')->nullable()->change();
});
Do this before airlines:update / seeding, otherwise the insert fails on airlines with no country data.
Case sensitivity: IATA lookups are case-sensitive. Uppercase user input at the edge:
Airlines::findByIata('aa'); // null — normalise first
$code = strtoupper(trim($request->input('iata_code')));
Installer vs. manual migration: airlines:install --migrate assumes a clean, default-configured table. If you have published and edited the config (custom table name), publish first, edit the migration, then run php artisan migrate manually.
Framework version: the package targets older Laravel conventions. Verify the README requirements before installing on Laravel 6+; command registration and provider binding signatures are the usual breakpoints.
Duplicate data: don't hand-insert airlines. Refresh through the package's own sync path (airlines:update) so codes() and the table stay consistent.
Confirm data loaded:
Airlines::all()->count(); // > 0
Check designator types when a lookup unexpectedly returns null:
dd(Airlines::codes(), array_map('gettype', Airlines::codes()));
// every entry should be 'string' on v1.0.1+
Log the underlying query for sortBy mapping surprises:
DB::enableQueryLog();
Airlines::sortBy('iataCode')->get();
dd(DB::getQueryLog()); // inspect the ORDER BY column
Facade vs. model: facade for the convenience API, model for anything with a where you need to compose.
Add custom columns and expose them via your own repository:
Schema::table('airlines', function (Blueprint $table) {
$table->string('hub_airport')->nullable();
});
Override the default repository in a subclassed service provider:
$this->app->singleton('airlines', fn () => new CustomAirlinesRepository());
Expose an endpoint:
Route::get('/api/airlines', fn () => Airlines::all()->map(fn ($a) => [
'iata' => $a->iata,
'name' => $a->name,
'active' => $a->active,
]));
Testing: bind a mock against the contract so tests don't need the migrated table:
$this->app->instance('Airlines', Mockery::mock('ijeffro\Airlines\AirlinesInterface'));
How can I help you explore Laravel packages today?