In this lesson, we will show the list of countries, will separate them in the Geography
navigation group, and will use a Relation Manager to show/manage cities for each country.
First, we need to create a Resource for the countries.
php artisan make:filament-resource Country
Now let's add a form and table columns. This is a very basic resource just with three values name
, lat
, and long
.
app/Filament/Resources/CountryResource.php:
class CountryResource extends Resource{ protected static ?string $model = Country::class; protected static ?string $navigationIcon = 'heroicon-o-globe'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->columnSpanFull(), Forms\Components\TextInput::make('lat') ->required(), Forms\Components\TextInput::make('long') ->required(), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('lat'), Tables\Columns\TextColumn::make('long'), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); } // ...}
For the lat
and long
fields, we will make a custom validation rule. Latitude ranges from -90.0 to 90.0, and longitude ranges from -180.0 to 180.0. We won't allow users to enter other values, and we will also reuse those rules later in lat/long of other geographical objects.
php artisan make:rule LatitudeRule
app/Rules/LatitudeRule.php:
class LatitudeRule implements ValidationRule{ public function validate(string $attribute, mixed $value, Closure $fail): void { $regex = '/^[-]?((([0-8]?[0-9])(\.(\d{1,8}))?)|(90(\.0+)?))$/'; if (! preg_match($regex, $value)) { $fail('The :attribute must be a valid latitude coordinate in decimal degrees format.'); } }}
php artisan make:rule LongitudeRule
app/Rules/LongtitudeRule.php:
class LongitudeRule implements ValidationRule{ public function validate(string $attribute, mixed $value, Closure $fail): void { $regex = '/^[-]?((((1[0-7][0-9])|([0-9]?[0-9]))(\.(\d{1,8}))?)|180(\.0+)?)$/'; if (! preg_match($regex, $value)) { $fail('The :attribute must be a valid longitude coordinate in decimal degrees format.'); } }}
And add them to the form.
app/Filament/Resources/CountryResource.php:
use App\Rules\LatitudeRule;use App\Rules\LongitudeRule; class CountryResource extends Resource{ protected static ?string $model = Country::class; protected static ?string $navigationIcon = 'heroicon-o-globe'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->columnSpanFull(), Forms\Components\TextInput::make('lat') ->required() ->rules([new LatitudeRule()]), Forms\Components\TextInput::make('long') ->required() ->rules([new LongitudeRule()]), ]); } // ...}
Next, let's add "Countries" menu to a group in the navigation.
app/Filament/Resources/CountryResource.php:
class CountryResource extends Resource{ protected static ?string $model = Country::class; protected static ?string $navigationIcon = 'heroicon-o-globe'; protected static ?string $navigationGroup = 'Geography'; // ...}
Every country has many cities, with a hasMany
relationship.
Now, we will use Relation Manager to show cities.
php artisan make:filament-relation-manager CountryResource cities name
class CountryResource extends Resource{ // ... public static function getRelations(): array { return [ RelationManagers\CitiesRelationManager::class, ]; } // ...}
If you go to the "Edit country" page, at the bottom you will see the list of cities that belong to that country.
For the cities form and table, we just need to add the lat
and long
fields.
app/Filament/Resources/CountryResource/RelationManagers/CitiesRelationManager.php:
use App\Rules\LatitudeRule;use App\Rules\LongitudeRule; class CitiesRelationManager extends RelationManager{ protected static string $relationship = 'cities'; protected static ?string $recordTitleAttribute = 'name'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255) ->columnSpanFull(), Forms\Components\TextInput::make('lat') ->required() ->rules([new LatitudeRule()]), Forms\Components\TextInput::make('long') ->required() ->rules([new LongitudeRule()]), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('lat'), Tables\Columns\TextColumn::make('long'), ]) ->filters([ // ]) ->headerActions([ Tables\Actions\CreateAction::make(), ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); }}
And we have lat
and long
fields.