Filamentphp: a first look
Photo by Ryan Quintal on Unsplash
As Laravel developers we know how productive a framework can be: It provides a set of common components, reusable functionality and most importantly, a set of guidelines that make everyone using the framework more efficient.
Knowing this, the fine folks over at FilamentPHP created a framework-within-a-framework, consisting of a couple of parts: There's a Form Builder, a Table Builder and a Notifications package and there's also an Admin Panel that combines them. Using the admin panel, you can create Resources that enable you to easily add CRUD pages with navigation, tables and forms to your app. It has all been expertly designed, and is very flexible to make your own changes if you need them. Check out their demo here: Filament Demo.
In this article, we'll take a first look at Filament and we'll build upon what we make now in future articles. Prepare for liftoff!
The app
In this series, I'm going meta.
No, not the social media/virtual reality company. Over the course of this mini-series, I'll write an app that will show information about its own deployment on Fly.io. It'll have a Machine
model for every machine that's in your app, with a relation to a Region
model that specifies where that machine is running. This will enable us to do something very cool: we can let the machines ping each other and see how fast they respond. In this article, we'll lay the foundations for that app.
Setting up Filament
So, create a new Laravel project and install the Filament admin panel with composer require filament/filament:"^2.0"
. Now, run the migrations and create a user for yourself using php artisan make:filament-user
.
Filament has been built to be an admin panel, but I'll color outside of the lines and use Filament for the whole app. I've heard that Filament v3 will support this idea even better, but for now it also works well. To make it feel like Filament is the only thing in our app, I removed the default welcome page and changed the Filament URL from /admin
to the /
home url. You can do this by adding FILAMENT_PATH="/"
to your .env
file. After that, just remove the default route from web.php
and delete the resources/views/welcome.blade.php
file.
Now, onto the Filament admin panel itself. I'll be using two models here: a Region
model that has data about each region on Fly.io, and a Machine
model that contains data about each machine where this app will be deployed. Machines
will have a one-to-many relationship with Regions
, so each Machine
belongs to one Region
. I'll use this setup later on to illustrate the latency between different regions, but that's for a different post 😉.
If you want to follow along, here are the models:
class Region extends Model
{
protected $fillable = ['iata_code', 'name'];
public function machines(): \Illuminate\Database\Eloquent\Relations\hasMany
{
return $this->hasMany(Machine::class);
}
}
Both iata_code and name are normal strings.
class Machine extends Model
{
protected $fillable = ['machine_id', 'region_id'];
public function region(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Region::class);
}
}
Machine_id is a string and region_id is a foreign Id for the Region model.
Adding the RegionResource
Let's add a resource for our Region first, since that's the easiest. The model has only two properties: the iata_code
is the airport code of that region (LAX for Los Angeles) and the name
is well, the name of the region.
Resources in Filament are like superpowered classes that can handle all the CRUD operations using forms and tables. It's almost like magic! You can pick if they need to show separate pages for viewing and editing models or you can opt for a 'simple' single view and use modals for creating and updating. I'm a simple person, so I'll create a simple Resource for my Region
model using php artisan make:filament-resource Region --simple
. You're probably smarter than me so you would let Filament auto-generate the forms and tables as well. Install the doctrine/dbal
package using composer require doctrine/dbal --dev
and then add --generate
to the command: php artisan make:filament-resource Region --simple --generate
. Much easier!
Here's how it looks out of the box:
We actually have all we need already: we can do all the CRUD operations on our Region model, with validation on the form inputs! I'll change the label for the IATA code to have IATA in all caps by opening RegionResource
and adding ->label("IATA Code")
on the TextInput
for the iata_code
property. While we're at it we can also change the maxLength
from 255 to 3. Here's how the form()
looks now:
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('iata_code')
->label("IATA Code")
->required()
->maxLength(3),
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
]);
}
And we can do the same thing for the column in the table:
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('iata_code')
+ ->label('IATA Code'),
...
}
This is how almost all customization on tables and forms will be done: create the correct field and use the static methods to change it to whatever you need!
One last thing: I pulled a sneaky on ya! I used a different icon for the 'regions' navigation item. I wanted something more region-like than the default 'collection' icon, so I changed the $navigationIcon
on the RegionResource
class from heroicon-o-collection
to heroicon-o-globe
. It just makes more sense.
Adding the MachineResource
For the Machine resource, let's switch it up a bit and not use the 'simple' resource. This means we'll have different pages for index, edit, create and view operations. Run php artisan make:filament-resource Machine --generate
et voilà: a MachineResource! For the Icon I'd suggest the heroicon-o-chip
icon.
Let's now take a look at the MachineResource
. Something special happened here, and I need you to be as excited about it as I am: In the form
method, there is a TextInput
just like with the RegionResource
, but now there's also a Select
. This lets you attach a related Region
using the relationship on the Machine
model. Filament will preload the Regions and let the user pick them from a list when creating a Machine
. Pretty cool.
Let's make it even cooler: let's enable the user to filter the regions by typing in the field by adding ->searchable()
and ->preload()
on the Select
field. The preload isn't necessary here, but it does enhance the experience by showing results before the user has typed and filtering faster. Let's see it in action!
Now, one final change to make before I let you return to watching cat videos: The MachineResource
create form looks a bit weird, the input fields look like they've been thrown on the page. Let's group them together in a card! I'll also arrange both fields on one line, using ->columns(3)
on the Card and ->columnSpan(2)
on the Select
field.
public static function form(Form $form): Form
{
return $form
->schema([
+ Forms\Components\Card::make()
+ ->schema([
Forms\Components\TextInput::make('machine_id')
->required()
->maxLength(255),
Forms\Components\Select::make('region_id')
->relationship('region', 'name')
->required()
->searchable()
->preload()
+ ->columnSpan(2),
+ ])
]);
}
Here's how it looks now:
Bear this in mind: we have only scratched the surface of what Filament can do, but we've already made a lot of progress: We already have CRUD operations for two models of our simple app, including form validation and relationships. On top of that we have authentication and navigation that's built-in. Not bad, right? Later on we'll customize the look and feel of our app, and after that we'll add the dashboard that displays the ping times between the different deployments of our app.
And just between me and you: They don't even know I'm writing this article. I just genuinely enjoyed using Filament and I couldn't wait to tell you about it. Better yet, Filament v3 is right around the corner (check out v3when.com!) so they'll probably have a lot more in store for us in the future. Keep it up guys!
driesvints, dprinzensteiner liked this article