Bright Ideas

— Description —

It's a holiday miracle–we recorded another episode! Listen in as we talk through the new Filament Ambassadors program, some major performance improvements, and a bunch of quality-of-life changes that are coming in the ever-closer launch of Filament v4.

— Links — 

Ambassadors:

PHP Generator Package: https://github.com/nette/php-generator

Laravel Policy Responses: https://laravel.com/docs/11.x/authorization#policy-responses


— The Usual Stuff —

Thank you to our Filament Partner, Kirschbaum Development Group for their support of Bright Ideas! Find more about them here: https://kirschbaumdevelopment.com/solutions/filament-development

Follow us on Bluesky:

Follow us on Twitter:

Join the Filament Discord server: https://discord.gg/filament

Have questions, topics, or people you’d like us to talk about in a future episode? Reach out to us on Bluesky, Twitter, or in the Filament Discord server with your ideas!

Creators & Guests

Host
Alex Six
Senior Software Engineer at Zillow, head of DevRel for Filament.
Host
Dan Harrin
Lead developer at Kirschbaum, creator of Filament.

What is Bright Ideas?

Join Dan and Alex on 'Bright Ideas' for all things Filament! From product updates to community news, we've got you covered.

[00:00:00]

Alex: Hey everybody. Welcome back to another episode of bright ideas. Uh the podcast where my co host Dan and I get together to talk about all things Filament. It's been a little while, but we're back again. I think we haven't recorded since close to the first half of the year, but we're back in the saddle and we're prepping and getting ready for the next release of Filament.

Alex: Before we start want to jump in really quickly and say a big thank you to our Filament partner Kirschbaum development group for their support of the Bright Ideas podcast and of Filament as a whole. if you want to find out more information about them, we will have a link to their website down in the show notes.

Alex: So definitely check them out if you need any Filament work done. Dan, how are you?

Dan: Hey Alex, how have you been doing?

Alex: Good. Good. Been busy. But what else is new, I guess?

Dan: Me too.

Alex: We're still getting settled in the new house. Just it always takes longer. You know, we decided we wanted to paint the whole downstairs, which was a great idea in theory, until we actually started painting. And then it was just

Dan: Yeah.

Alex: a long time. We have a two story foyer in the front too, which is really pretty.

Alex: It's really nice. [00:01:00] Um, But that was not easy to paint. So we, we hired somebody in to do that, thankfully, because I was not about to go fall off a ladder and break my back or something while trying to, while trying to paint. So

Dan: We did basically all the painting ourselves this year for our new house as well, and it was really a back breaker and a neck breaker. Um, was really underestimating how much like it would tire me out. Um, but I'm, I'm very glad that I haven't done any painting in a good few months.

Dan: The listeners at home can appreciate your new background if you start streaming again because Alex's room has been repainted since I last saw it and it looks very very nice

Alex: It was, for the listener, uh, it was a bright, bright yellow for a long Well, since we moved in.

Dan: Filament that's what it was

Alex: It was, yeah, it was on brand, I guess. But it was a lot, since my desk, you know, the back of it bumps up against the wall. I would just be staring at this bright yellow wall all day. Got a little old. So I painted it a dark matte green.

Alex: So it [00:02:00] is, it is like a cave in here now, but it's cozy. Right? I've got like the warm incandescent lights.

Dan: Yeah, like your lighting is very good still though It doesn't feel like it's absorbing all of the light from your face. Like it's it's it's nice

Alex: I did have to jack my key lights up quite a bit. They were, I think beforehand they were pointed at the wall and bouncing off the wall at like 10%. And I think now they're sitting around like 50 just, just to light me up. And actually , it's funny you say that about streaming.

Alex: I, just the other day was talking to my wife and I was like, I think I'm going to start streaming a little bit again, we'll see.

Dan: I remember that, like, for the listeners, the reason me and Alex started talking was because he streamed and, he used to dabble with Filament in some of those streams, and I used to watch them in the mornings when I woke back up, and message Alex and all the things he was doing.

Alex: doing wrong.

Dan: doing. Yeah, doing more, I guess,

Alex: I, yeah, I, I, every, every time I do a Filament stream, I would end the stream and I'd wake up to a DM from Dan on Twitter with like, Hey, here are the 10 things I noticed that you can [00:03:00] actually do differently about Filament. I remember there was one stream where I say I, but it was really chat and I were working on some sort of like page builder functionality. Like I wanted to build web pages with Filament and I only got a DM with Dan or from Dan. That was just like, Hey, your whole stream, uh, just use the builder component. And I was like, Oh, well, there goes my plan for the next stream.

Dan: It's quitee incredible on how streaming can unlock those sorts of doors though, in relationships. So I'm

Dan: where that goes for you. And, um,

Alex: And when you start streaming too, you can have the same.

Dan: honestly, I'm not opposed to streaming at all. And I just don't want to do all the setup for actually getting the video online.

Dan: that's, if I could just hit play and do it, I would do it, but

Alex: We'll get you set up.

Dan: like overwhelming, even starting to think about that.

Alex: Yeah. Thankfully. Are you still running one of the M series MacBook pros?

Dan: [00:04:00] Yes.

Alex: you're. Yeah. So thankfully that chip has enough oomph to where you can just run OBS on your own computer and

Alex: you don't have to do the whole like dual PC. Yeah.

Dan: fighting with OBS and then that's why people go to these hosted streaming

Alex: Yeah. Well, I'll get you set up. We'll make it work.

Dan: Yeah, we could leech off of your stream.

Alex: I'm always happy to co stream. It's way more fun to stream with people.

Dan: Well, I'd be happy to do that if you wanted me to.

Alex: Cool. You heard it here first, folks. Hold them to it. But should we dive in?

Dan: yeah, no, we probably should.

Alex: It's cool. All right. Well, I've got one rather large note about something we, we launched a couple of months ago. Um, just want to cover it in case people haven't seen or heard, um, of the program. But a few months ago we did launch the Filament Ambassadors program. And I mean, so far, I think it's been a roaring success, but effectively what, what we did was we, we as a Filament core team [00:05:00] looked around the Filament community.

Alex: And I guess I should start by saying we're always so grateful and very honored and very flattered with just kind of the wild and crazy things that people come up with in the community, whether it's plugins or content or even just like full projects that people have built on Filament. And we really wanted to take the opportunity that we have now, since we've got more Core Team members, and we've got a rather large community using Filament.

Alex: To help support people in the community who are doing good work and like bringing the word of Filament to the masses, I guess you could say. Um, and so as a core team, we kind of looked around and we, noticed that there were a handful of people, at least for this first round that really stood out kind of in our discord community and in the content generation, like YouTube communities, um, who just do a really good job of explaining how Filament works and. Even more than that, just like showing themselves using Filament. Um, and it [00:06:00] went into the goods and bad of Filament. And so we, we kind of grouped them up and we said, Hey, we'd love for you guys to be, ambassadors for Filament, and, effectively, what that gets people is the ability to promote content if they so choose on the website and we just want to support these folks and make sure that they can continue doing what they're already doing and have all sorts of success and fame and fortune and whatever they want. Uh, with it as kind of a, a big thank you to them for, for all the good work they've done.

Alex: So, um,

Dan: Yeah, and from our perspective as well, um, it's great that we were able to also forward the people who use Filament onto these great resources that we trust. and they're able to, uh, consume that content and learn a bit.

Alex: Yeah. And, and the stuff that's out there from our ambassadors is honestly just kind of incredible. Like we, we have everything from videos to blog posts to like, um, Oh, what's the word? Like, like not flow charts, but like data visualizations, but not data. I don't know. I'm losing the word, but [00:07:00] like actual drawings and note taking PDFs of just all the different methods, like in the form builder.

Alex: It's awesome. There's some, some amazing stuff. Um, I'll link the blog post announcing the ambassadors that then also links to all their socials in the show notes. If you're looking for more Filament content, if you're looking for more frequent Filament content than a podcast, we put out three times a year.

Alex: Um, definitely give them a look because there's some incredible stuff there. That that's my big announcement really with the ambassador program. There's other stuff in the pipes, not quite ready to talk about it just yet, but probably hopefully early 2025, um, we'll be able to. Get some more details on the, the big stuff coming for Filament.

Alex: Um, but I know there are some, well there's actually quite a lot of technical updates for the next version of Filament that I'm sure people are itching to get into.

Alex: So, the first one I definitely wanted to talk about. Because you've been working like mad on it. Is this idea of table performance. And I want to start [00:08:00] that with the question of why are tables slow?

Dan: Tables are slow, at least slower than you would expect them to be if you try to render your own table outside of Filament, mainly because of how we've structured the blade views that actually render the table. So the actual data that gets into, into the table is all, um, done through eloquent and eloquent is not really something that really slows your, um, app down unless you use it badly. However, when we're rendering the tables in Filament v3, we're using a lot of, um, blade views to render that table. So for example, each cell in the table is its own blade view. And that renders like the actual cell container and the cell element. And that's reused across a lot of different parts of Filament. We also have an element within the cell that then adds a, URL or a button to that cell [00:09:00] because you can't add a URL to an entire row in a table in HTML, you have to do it per cell. And also then you have the content of each cell in the table builder. So that's also another blade view that you have to render.

Dan: So that's about three views per cell that you're rendering in a table. You then also have actions, so, each action has its own view that, decides which type of button you want to render. So maybe that's a link or a button or an icon button. And that view then renders the actual view for the button . So that's two views. And then you also have for each action cell, you then have that same cell element, and an additional element inside that actually renders the actions in a loop. So that's then at least four views per action cell. You then have the selection cells, which are , a checkbox view and another cell view and a check and like the thing that actually renders the checkbox.

Dan: That's [00:10:00] also a view. So essentially for the more, the more columns that you add to a Filament table and the more rows, the more blade views that you're rendering. And we like to follow the Laravel best practices when we are extracting out, blade components to make sure that we're not duplicating HTML across multiple parts of our code base that's the same. And this is especially important when you're using something like Tailwind CSS, because the CSS class strings can get quite long, and when you update the CSS clustering in one place, you want to update it elsewhere as well to make sure that all of your styling is consistent. So that wasn't too brief but hopefully you understand a little bit more about why I believe the table builder is relatively slow compared to a custom table you might build in Filament version 3.

Alex: So it really comes down to the nesting of these, uh, these blade views, right? Because we're just rendering view after view after view more than it would [00:11:00] appear in the UI.

Dan: Specifically, it's not actually rendering the view. The blade compiler will compile the blade code down into raw PHP code. So running that is not really the slow part. Cause once you've actually compiled the view, once it then gets cached by blade. The slow part that I've found is actually the include the PHP include that actually imports the PHP file from the view cache and renders it. So it passes in all the data it needs to render it and then gets the HTML and puts it on the page. So it's not really the actual process of generating the HTML from the PHP code. It's more about the file operations that are required.

Alex: Interesting. So then I guess theoretically, the faster the file system, the less noticeable this would be, but.

Dan: Yeah, and that's why I think there's quite a lot, uh, a large, like, discretion between different environments when rendering a Filament table. I've also heard reports that on Windows, for example, um, Windows Defender will [00:12:00] sometimes scan every single file before it gets opened. And you have to disable that on your code bases, because otherwise, like before each blade view gets rendered, it has to be scanned. so yeah, I think it's very OS specific, but generally it's still pretty slow to have to then render these blade components because of the fact that they are each a PHP include.

Alex: Mm hmm.

Dan: That's why I think it's slow. And obviously that just grows depending on how many columns and rows you have in the table.

Alex: Right, because even if the view itself is cached, PHP still has to run the operation. Yeah. Okay. Interesting. So then if the problem is that PHP is including file after file after file after file with all these nested blade views, are we just rendering fewer views now? Is that, is that the idea?

Dan: So I have refactored the table builder already to not render blade components unless it really, really has to. So, for example, instead of having a [00:13:00] cell view and a link view and the actual content of the cell it's all just done within one blade view and that's all raw within the view of the actual table. The different columns have different contents, so it might be text, icon, color, image That is all dictated by code within the column class itself now so the text column has a method on it that generates HTML and returns it back to the main blade view. So you're not really including a new file, you are just running a method that generates the HTML instead. So this is really not a Laravel best practice. It is definitely not as maintainable as using blade views in the traditional way. However, it's allowed us to really speed up the rendering of the table. Now there aren't any extra blade views being rendered for each cell, unless you have a custom column with a blade view. And for the actions, the only blade components that being rendered there are the actual blade components that render the [00:14:00] button, link, icon button. So from my experience, the tables are now rendering about twice as fast, which is, think, a good improvement.

Alex: you said that pretty casually.

Dan: yeah, I think that I was hoping to get a little bit faster, but, um, just because of how much complexity there is within the table builder and how many options there are. Um, I don't think I was really able to, get that much faster than it is now. One thing we have had to do is start early on our CSS refactor. And as a reminder for the audience, this is to do with removing tailwind classes from the views and actually putting them in CSS files under semantic class names. So the CSS files contain the Filament specific semantic classes, and then they use the apply directive in tailwind to apply the different tailwind utilities to it. That allows us then to simplify the HTML that we're sending. So it makes me much more comfortable to render things like cells in multiple places in the [00:15:00] Filament code base without having to update multiple places of the styling changes, for example, because they're all sharing a CSS class. It's also providing a little bit of a performance boost because there is less HTML being sent over the wire each time something changes, especially when you filter or search on a table, we're not sending through the tailwind classes a ton That isn't really much of a performance boost because responses from server are usually compressed anyway, and repetitive things like tailwind classes are usually quite effective at being compressed, so, it's not that impressive, but it does reduce the response size quite a lot.

Alex: At this point, are we happy with the level of performance for tables? At least for this version?

Dan: Yeah I, I think so. I think it'd be difficult to go further than this. I've done my best at reducing the number of file includes and reusing objects and, not including extra view files where I don't need them. And I think that over time, as we convert more of the code base to use [00:16:00] these sorts of standards, that will also impact things like the forms potentially, if we choose to go down that route, I am kind of thinking at the moment to still render blade views for forms, because there are not that many blade views that you have to render in a form. However, um, that's still up for discussion really.

Alex: Yeah, it feels like it would be, just in general, orders of magnitude less than on a table, right? Because for a table, you're rendering rows and rows and rows. Yeah. A form is usually just one piece of data.

Dan: Yeah.

Alex: That's awesome. I think I know table performance has been a big sticking point for at least a handful of people in the Filament community and people who have tried Filament when they have lots and lots of data or lots and lots of columns. So I think this is going to be a very, very welcome change for folks.

Dan: Yeah. And as a reminder, we've still got all of the improvements we had earlier on in the year, with things like partial rendering that we're implementing in version four as well, which will also improve table performance, especially when using things like actions and deferred filters. [00:17:00] So hopefully you'll see a large improvement, especially if you opt in to a lot of these new settings that we're providing you with, to really help you with those large data sets and the clients that want to see 200 records on one page without any pagination.

Alex: Why paginate when you can just load it all with the new Filament table improvements? We should put a, put like an Instagram ad out or something. Why paginate? Use Filament. Is there anything else that you want to touch on with tables?

Dan: Um, I think that's it from me for tables. Um, but I'm interested to hear some feedback from when people start using it to see how fast the tables are compared to before. Because obviously I've only got a limited number of test cases that I can put this through.

Alex: Right. Yeah, benchmarks are nice, but real world use cases are definitely preferred. So listener, if you're listening to this and you feel brave, Pull down the, uh, most recent commit of, of Filament v4 and, uh,

Dan: wait a little bit.

Alex: see how fast you can make your tables.

Dan: [00:18:00] yeah,

Alex: Well, then with that, let's, let's move on to a feature that you reached out to the community for about, for some feedback and some, some direction on, that I genuinely did not expect just in Filament core at all, let alone this version of Filament. Let's talk about two factor auth. What has been going on with two factor auth in Filament?

Dan: Yeah, so the story of two factor auth is that I think since version one, but at least version two, we've had a few plugins that offer two factor auth and they seem to be very popular and a lot of people implement those plugins in their Filament apps, especially when they're building software as a service apps with Filament.

Dan: It's quite important, but also with admin panels, authenticating your admins is also important. So we've decided to bring that functionality into Filament core and provide a, an official implementation and not just one driver, but, the ability to add any other two factor authentication driver that you wish [00:19:00] through either your own code or a plugin.

Dan: So Filament will ship a Google driver, and an email code driver. So both of those use the Google two factor authentication protocol to send codes either to your phone or your email. And when you sign into Filament, it will then, ask you for that code before it signs you in and verify it. It also supports recovery codes. So when you generate the two factor auth, code initially, and you set it up, you can save the recovery codes and then use those codes if you ever get locked out of your, phone or email. There is the ability to enable multiple drivers at once, and at the moment that does if you have two drivers enabled, you have to complete both of them before signing in.

Dan: But I think I might change that to, only require you to choose one to sign in with. So it's only two factor and not really multifactor, but, um, I'm quite excited for this. I'm, I'm quite happy with how like the UI has turned out [00:20:00] and how the driver systems turned out. I think that hopefully it's going to be quite extensible for other platforms to then hook into for this.

Alex: Yeah, I get the feeling we're going to be seeing a lot of plugins around to factor off here pretty shortly after this next version launches. I am curious about the, the multi factor part. Of this really not two factor auth, but multi factor auth implementation. You know you said, right now, let's just say you have Google and email both enabled.

Alex: And you say, currently, you have to run through both of those authentication schemes in order to get access to the app. So you'd have to authenticate with Google and then authenticate with the email driver. I'm curious if removing that is the right answer. And I say that because I, I can see a world where somebody would want multiple factors to be run for every login,

Dan: Yeah. Especially with pass keys.

Dan: I don't know if anyone ever made a pass key driver, I think that would be like one step and then maybe email address as well would be [00:21:00] then another step.

Alex: Exactly. And then I can also see a world where, oh, I want to have Google and email, active and enabled, but the user gets to choose which of those they want to access. Right? So more of like a boolean and instead of a boolean or, or no, well, flip those around, but you know what I mean?

Dan: Yeah. But should that be on the user or should that be on the developer to decide that, because obviously like the developer can tell the app which drivers are available. So the developer can say, uh, Google's available and the email code's available. But that doesn't really enforce the user to set that up unless the developer wants to add a middleware to make sure that they have to set it up before they can access the app, but also it's the user's choice then do they do Google?

Dan: Do they do email or do they do both? So in that case, like, should we make it easier for the user? So they only have to, if they enable both. They only have to do one and they can choose which one they want to do. Or should we allow them to configure that? So they have to do both. Is that on the user? Is that on the developer?

Dan: Like what's the, I'm, [00:22:00] I'm struggling to see what the story is there because I only considered this previously with like forcing the user to go and to go all of the factors that they enabled, but like the user could just choose not to do all of that and just do one of them. And at that point, like, we don't want to make their life harder for them.

Alex: Mm hmm.

Dan: Yeah, I'm interested in your thoughts there.

Alex: Yeah, I mean, I definitely agree that we don't want to make a user's life more difficult unless that's the business objective, right? Like if the business is like, no, you have to go through these two levels of authentication.

Alex: Right, right. If,

Dan: just making users lives hell.

Alex: if we are making the worst app possible and we have 10 different authentication factors you have to go through.

Alex: And that's like fully within the business's purview to make that, to make that call, right? But at the same time, I'm thinking back to like G suite, like Google business, whatever is a great example for me. Whenever I log into YouTube or Gmail or Google drive or just my Google business [00:23:00] accounts, I always get presented with a list of like five or six different ways.

Alex: Do you want to use your security key? Do you want to press yes on your phone or tablet? Do you want to enter your, your password? Like they give me all these different options for how to log in. But I only have to do one. And on top of that, I get to choose which of those options are even available to me, right?

Alex: Like if I didn't have a USB security key, I wouldn't see that option. I wouldn't be able to choose that option, even though it's technically enabled, like Google allows it. I have not provided that information. Or if I didn't have like a phone or a tablet that I could go hit yes on in the YouTube app, like it always takes me to, You know, that I, that would still be an option.

Alex: Google still has that enabled for users to make use of. I just wouldn't be making use of it. So that's kind of where I'm unsure of the best way to proceed, because that's how I typically like multi factor auth to be is where I, as the user, get the opportunity to choose which factors I want to use, and then the business can say like, Oh, well you need two separate factors of authentication.

Alex: You just need one separate factor or one individual authentication factor [00:24:00] type to log in.

Dan: So basically, we could say, because I don't think the user is ever going to turn around and say, like, I want to have to do both at once, I think it's going to be more like the actual app enforcing you to do that. So, potentially like maybe we have like a setting, which is like minimum number of factors that you have to have to have to do to, like, and they can choose which combinations you want or something like that,

Dan: Also like, you don't have a minimum, we don't enforce people even set it up in the first place.

Dan: So like, maybe the user does that, like the user currently is even responsible for enabling it or disabling it. So I don't think the next step on that is to allow then the developer to enforce that every single factor that they've set up has to be done at once.

Dan: I think next step for that is then allowing the user to choose that every single factor that they've set up can be done, uh, that should be done.

Alex: Right.

Dan: Um,

Alex: That feels like the most flexible and less, least intrusive of, of the options. But listener, if, uh, you have [00:25:00] strong feelings about multi factor auth and its flows, let us know, hit us up.

Dan: Yeah. So we haven't got anything else on that topic, we can probably move on a little bit.

Alex: Yeah, let's do it. Talk to me about this, uh, ongoing effort, like we talked about to convert everything to be a schema.

Dan: Yeah. So, um, I think last episode I talked about combining the different form, layout components and info list components into the same type of component, which is just a schema component and schemas is now a package in Filament that handles the underlying logic for both forms and info lists and allows them to be combined. One part of that is that I wanted to allow people to customize the content of pages within a Filament panel using a schema. So, at the moment, the part that the schema covers is just the form or the info list, within a page. but what happens if you want to change how a page is structured? For example, add some more content, add two forms, add relation [00:26:00] managers above the form, or change how the tabs work, or maybe combine the form tabs with the relation manager tabs in a different way than the default.

Dan: Like at the moment, if you combine them, then the form tab always comes first before the relation managers, maybe you want to change that. So the goal there is to basically use a schema for rendering all of the page content for every single Filament page, and that's what I've done. So I've allowed the user now to define page content in a schema. There's a content method on every page and that defines what is rendered and you can change which components get rendered within that schema. And that will allow you to then use all those built in layout components like tabs and cards and, wizards and everything. And in the same way that you would a form or an info list, but you're also rendering out the entire page content.

Alex: So, so theoretically I could render a wizard on a page where the first page or the first, whatever step of the wizard is a form. And the second step is a relation manager. And the third step is another [00:27:00] form.

Dan: Yes. Yes, you could, but, like, there are, there will be some limitations there, like, obviously the relation manager needs to be associated with an existing record.

Dan: So like, you need to make sure the record's created by that point, by the time you render the wizard and stuff like that.

Dan: But technically, yes. This is the sort of flexibility you can do. Yeah. And you could add extra content like above and below the form and things like that without having to override the view. That's what we don't want people to do. We don't have to want people to have to like copy the default edit record, create record, view record, view, from the Filament package into their own view and then set the view property on their own page, they can still do that if they want, but a lot of the customization should be done. Hopefully in the future with this new schema that we're rendering, the page content schema.

Alex: So obviously we're adding a lot of customizability to these, I'm going to call them default pages for Filament, but like the view, the edit pages. So. The idea then is custom pages aren't going away, but this is [00:28:00] giving people who would typically have to reach for a custom page to make these small tweaks a much easier outlet than rendering a whole custom page, wiring it up and doing all that.

Dan: Yeah. Um, so if you're, if you have a view defined on your page, you will still render that. So there's no real, there's no real breaking change here. Um, like if you have a custom page, you have a view, we will render that. If you have one of the default pages and you've overridden the view, we'll still render that. In the same way, like going back to the table performance stuff. If you have a column with a view, it will still render that view. Um, we were not making a breaking change there. If you have like a custom column or a column in a plugin, it will still support blade views. They're just going to be slower then the built in columns. And you can obviously go forth and use the same, API that the built in columns use, but it's, it's opt in and we're not going to break your, um, custom columns for that in the same way that we're not going to break your custom pages to force you to use a schema if you don't want to. I just like the idea of being able to use a schema to define the default [00:29:00] content then you can still go for, uh, Like go through and have your own blade view if you wanted that.

Alex: And so going forward, the, this schema method of defining these pages is going to be the standard

Dan: I think so. We'll document both but like, when Filament needs to add something to one of the built in pages that's what they're going to add because all the content is defined there. Like a lot of the pages now literally are just one line, but the page view is just one line rendering the schema That's literally it. So, yeah, it'll be It will hopefully be like that, moving forward.

Alex: Well, on the topic of rendering things, without moving on too fast. I know this next one I'm very excited about because we've, you and I have talked kind of at length about, about this idea and it's honestly something that I would love to see become more prevalent in the Laravel ecosystem.

Alex: Anyways, talk to me about stubs.

Dan: Yeah.

Alex: What are our on stubs?

Dan: our thoughts on stubs are that stubs scale relatively well. So, when you're building a package or using a package in the Laravel ecosystem, you're probably going to stumble across stubs. [00:30:00] And, when you're writing artisan commands that create files, usually you're going to reach to a stub to actually decide what content gets rendered in those files that get generated. So a stub file is just like a template file that, has a series of template strings within them that get replaced with actual content. So for example, in Laravel, in the Laravel framework, There is a model stub and a controller stub and, um, actually several different controller stubs for different usages different migration stubs and things like that.

Dan: And those files are just plain text files with, strings that get replaced by the command with actual content. So maybe the name of the model, the name of the migration, or the different, table names inside the migration file as well as the file name and things like that. So you can have multiple replacements within a file, but that is literally all it is.

Dan: Like a stub file is static. It will just allow you to do a find and replace on that file content and then copy that file content into the user's [00:31:00] app. And that, that's pretty good. Like, a lot of commands, aren't generating anything too fancy. They're just generating boilerplate. So that works. pretty well, and, I mean, you can start to see where it gets a little bit more complex in Laravel, because Laravel has many stubs for the same resources, like many migration stubs, many controller stubs, because there are many different configuration options on the Controller make command and the migration make command. So that's kind of where we're at now Filament v3 has a lot of stubs in it and a lot of the stubs are quite complex with a lot of template strings and we're passing through a lot of the code that is in the stub actually from the command so it's not even really stored in the stub anymore.

Dan: It's kind of generated in the command and then just passed. The good thing about stubs is that users can customize them. So users can publish stubs into their own app. And then each time they run those make commands, the package will use the stubs in the user's app instead of the default one, so you can customize what's actually [00:32:00] in the stubs.

Dan: You can change things like adding properties, adding methods, changing types, adding return types, if there aren't any, changing the method bodies and things like that, but it's still not that flexible because you still have to work around the constraints of having a find and replace system with a static file and dealing with the fact that the the framework might be adding new properties and things dynamically to the file when you have to account for that,

Alex: So then we have some very high complexity stubs in Filament. And I imagine, especially with the changes, think things like schemas and whatnot, these are only getting more and more complex. How are we handling complexity with regards to stubs?

Dan: Yeah, so I'll just go through the complexity at the moment. So for example, resources are one of the most complex stubs we have in Filament at the moment. Resources have quite a few different options that require us to do some really nasty things with stubs. Like you don't have to have a resource within a cluster, for example, but if you have it [00:33:00] within the cluster, we have to add a cluster property and we don't have to have resources with a view page.

Dan: If you do, then we have to add a view page registration and an info list. We don't have to have it so that. You have soft deletes, like the soft deletes have to be added in a separate method. And all of these things that are optional cannot be in the stub, because we cannot find and replace those things. in the stub, we have to like, have like a template string and then just generate that code within the, within the make command and then pass it into the stub, which is not nice. And that's only getting worse. So, there's a few things in Filament v4. So nested resources, if your resource is nested, you then have to pass in a parent resource to even register that. In Filament v4, we're changing the file system, and that's also something we should talk about actually after the CLI.

Dan: We should talk about how the file system is changing with resources. Anyway, the resource is going to change, file system wise. And, that's going to allow people to have a lot more options. and it's going to make the stubs a lot more [00:34:00] complex. And also we're going to allow people to turn off the partial imports feature. So at the moment, when you generate a resource or a relation manager, it will import the entire Filament forms, namespace and Filament tables, namespace. And then when PHP storm or whatever your IDE is imports a new file, it will actually, it imports a new class. It will actually like do a partial import instead of a full import.

Dan: And a lot of people don't like that because they think that it's way too long. And I used to like it and I'm coming around to their viewpoint now. So, I'm given that I'm given the option to do the partial imports, but then if we do the partial imports. it basically makes the stubs impossible because there's going to be nothing left in the stubs.

Dan: It's just going to be a collection of template strings. Like all the imports will then have to be dynamic passed in from the command anyway. So it's just, even if we did stick with stubs, we probably could, but it would, they would, it would just make stubs useless because you would never be able to customize them because all the stuff would be coming from the command anyway. So that's the problem with stubs. And how we're dealing with that is, [00:35:00] I found a package. called net or netty PHP generator, and it is able to generate PHP files using PHP code. So you can actually tell the generator, generate me a PHP class, add these properties, add these methods. And, these methods need to have this return type, this name, body. like you can tell it to add imports and you can tell it to either like add a full import or a partial import. You can tell it to like add an extend to the class and add an, interface and traits to the class and things like that. So what I've done with that package is I've wrapped around it and I've made a system where you can, override a generator class in your own app, which extends our generator classes. So for example, there's a resource class generator. you can extend that, bind a new one to the container and then use that instead of a stub. And that will then allow you to add whatever properties and methods you want into that generated file and change and override [00:36:00] any of those methods or properties you want. But it still gives us a ton of flexibility with generating, and we aren't having to do find and replace template strings at all. Like this has completely replaced stubs for PHP classes. There still are some stubs for things like views and, the, JS and CSS files for themes and things like that. But, most of the, like all of the stubs for actual classes have now been replaced with this new system.

Dan: And I'm really happy with how the refactors turned out. It's allowed us to implement a lot of, UX improvements for commands as well, so you can now generate things that you couldn't generate before. Like you can generate. relation manager forms and tables from, the database, like you can with resources, you can also generate settings pages with, Spatie settings from the settings classes. Like the settings pages from the classes, sorry, like from the properties that are defined on the settings classes. And there are a lot of other options that you can add now that this has allowed us to do because the stubs are, gone and the new system is [00:37:00] more flexible as to what we can put into the files.

Alex: I admittedly haven't looked much at the actual, like the final code that you settled on with, with generators. But what I'm imagining is that it looks an awful lot like the panel provider classes do with chained methods, or is it closer to something like a config file?

Dan: Not quite. The thing with that is, for example, if I had a chained method approach. And I had things like, an add properties and an add methods method that was chained. You would then have to override the entire, like, builder method to be able to override anything. Like, if I wanted to add a property in between two other properties, you would have to override everything. Like everything, the entire configuration and not just that bit. So what's actually, there is multiple methods in the class. One is called like, get extends. And that is the name, the class name that is used to extend. the class from, and it automatically imports that and things like that. It also has like an add traits to class method, [00:38:00] which has multiple lines, which is just like, add interactive forms trait to class, add interactive tables trait to class, and then each of those methods can be overridden as well.

Dan: So you override only the methods you want to in that class generator to add your own functionality. So for example, if you wanted to add your own method to every page to change the redirect, you would extend the generator class, bind the generator class, the app service provider, override the add methods to class method on the generator class. Add your own line after that just says, add redirect methods to class define that method in the class, which is like, a few lines, which just tells nette or net PHP generator to add the methods. And then it just does it, and it does that within the flow of the rest of the generator.

Dan: So it's kind of like, completely in line, and it follows just PHP principles with how you're overriding and how you expect things to work. Because you can actually go up a level and go straight to the generator and [00:39:00] see exactly what's happening. And I think the code's pretty clean, to do that.

Alex: That's really nice. Just the ability to be able to go to definition and

Dan: And you don't have to see any of the command stuff, which no one cares about. No one cares about how you're accepting, user input. All anyone cares about is the input that gets passed into the class generator, which you can see. And then how the class actually gets generated. No one cares about the bit before, but like the bit that's important is in its own class.

Dan: It's really nice a, if a command is generating multiple files, there are multiple classes. Like for a resource, there is a resource class generator, an edit page class generator, a, create page class generator, things like that. Like you can just override the ones you need.

Alex: I like that a lot. I like that it's, it's pretty surgical with what you have to override to get your application in the format that you want, right? Whether it's, you know, just wanting to extend a different class for all of your resources or all the way down to, I want to customize the heck out of this thing.

Alex: Make sure all my clustered resources are extending from this class and have these traits. that's going to be really [00:40:00] big. I, I like that kind of stuff that gives us the flexibility to kind of manipulate Filament, specifically the panel, to more easily fit a wider array of applications as opposed to for me, like having to spin up an entire TALL stack application and then install the packages that I want. Both very valid options. I just always appreciate adding customizability to the panel because I think that's where most people are going to start. Um, at least in the people that I've talked to about Filament, right. Going to like the local meetups or even at Laracon, having talked to some folks about Filament, most people tend to start with the panel provider and then realize, Oh, you know, I need, I need more customizability than what the panel can offer at this moment.

Alex: So then they, you know, refactor out to a tall stack app, but that's, that's never a small lift, so I think this will help the runway between the two be a little smoother, right? Like, Oh, I need some more customizability within my panel, but not so much to warrant. Ripping everything apart, starting a new tall stack application and installing Filament packages.

Alex: But yeah, very excited for all the, the customizability changes that are coming in, in V4.

Dan: Yeah, I'm really happy with it

Alex: Uh, [00:41:00] you said we should talk about something and I promptly did not write it down and have forgotten. What, what did you want to talk about?

Dan: So I want to talk about, the way that we want to structure, resources in version four.

Alex: Yes, I

Dan: I can't remember if we've discussed this on the podcast before, but I don't think we have.

Alex: I think we I think we've touched on it as like a man, it'd be really nice to do this thing. So let's let's go back over the basics of it again and then talk about what we've done.

Dan: this, this isn't something we could have done without refactoring the CLI because there's so many different combinations of things you can do now. the main idea is that forever you've been able to share forms and tables in Filament between different resources, different relation managers, things like that. We've never taken a stance on how you do it. So you've always been able to define your own class and have that class return your table or, accept access the table objects and then add the columns to it and return it. And then you call that from your class. And I've never told anyone to, uh. call the method this, or call the class this, or put the class this in this place, or use a [00:42:00] trait, or instantiate a page object, and then grab the form from that, and things like that.

Dan: Like, there are many ways you can do it, but I think it, now it's time for us to start pushing our opinions on the community a little bit with regards to this, because I think there are definitely right and wrong ways to do this.

Dan: So the idea is that in version 4, if you don't decide to configure this, You will have a new form class and a new table class inside your resource directory. And this is, this is essentially going to, hold your form schema and your table schema. So there'll be, a schemas directory aside from your pages directory inside the resource, there'll be a tables directory aside from the pages and schemas directory, and that's where those classes will be and the classes will contain just one method, which is configure and how this will work is the configure method will have a parameter, which is your form. And that is where you define your form in the same way that you wouldn't a [00:43:00] resource or, the same for the table.

Dan: So the table will have a table parameter and it will modify the table and then return it. In the resource that you want to use that form or your table or the table. You just call return. And then the name of the form class, and then colon, colon, configure, and then passing the form object. So it's one line to import it. You can do the same in relation managers, and you can do the same in pages. So pages also allow you to override the form per page if you wanted to, or the table. So you can have a different create form, different edit form, and Pass those, through on the page itself. You can also pass these into actions.

Dan: So you could use a schema, in an action, if you pass a function to the action and then you call the configure method and pass through the form object or the schema object. Sorry. I'm also thinking about adding a shortcut to do that by just passing in the class name of the schema. And then I'm doing that for you.

Dan: So it's a little bit, shorter, but I think that's like, a, very minor detail. On top of that, I've also changed the way [00:44:00] that resource files, look. So I didn't really like that the resource class itself was outside of its directory. So like, there'll be like a user resource file and then there'll be a user resource directory next to it.

Dan: And it's not really the same, like, you then have to look through the list of directories and then look through the list of classes. So it didn't feel nice. It didn't feel like they were all close together and portable. So I've changed it so that the directories are now just called things like users instead of user resource. And then the users directory contains the user resource file. So every, so it's like, um, Filament. And then resources and then users and then user resource. So it's slightly longer for just that file, but the rest of them, it feels a little bit nicer. So then you've got like users slash pages slash edit user users slash schemas slash, user form. And then you can kind of use that user form across multiple places without worrying about the fact that it's like [00:45:00] tied to a resource. It's still in the resource directory, but it's not quite the same. So that's kind of where I'm heading with that. And I've, I'm able to do that much easier in version four by refactoring the CLI, because I can actually give people the option to turn that off if they want to.

Dan: If they prefer the old style, then they can do that with the config option. In the same way that the partial imports can be preserved. I do think I can imagine that people upgrading from version three to version four, won't want to use the new version for location, at least initially until they convert all the other resources to do it.

Dan: And I don't want that to be a blocker for them. So I don't want them to have like an inconsistent code base. This is completely, backwards compatible with, the old way of doing it, like you can use a mixture if you wanted to, and it will still generate the URLs and things in the same way.

Alex: I'm very excited just about the, the reuse in general. When I was at Kirshbaum and even now I still work with some folks, um, just as nature by nature of what I do with, questions about how to use Filament and what to build their Filament apps with and, and how to [00:46:00] build them, how to structure them.

Alex: And the, the, the biggest and most frequent question that I get is, Hey, I already declared this user form. Why do I have to declare it again? Like, is there some way I could just tell Filament across the app to use this user form over and over and over again? And kind of like you said, my answer is always like, there's like 500 ways you can do it.

Alex: Um, here's my favorite 10 of them. Pick your favorite, everyone suits your, your use case the best right now and just use that.

Dan: As long as you know basic PHP, you should be able to work out how to do it. Like it's

Dan: just basic like refactoring, ability, really. So it's good that we've now got a way of doing it, an official way of doing it.

Alex: Yeah, it's, it's just being paralyzed by the choices. Right? Like, like you said, there's so many different ways you could do it. And now just having an ordained way to do it, I think will be ideal. So you, you did mention that this will work for things like forms and tables. Does this also apply to other packages?

Alex: So like info lists?

Dan: info lists. Yeah. So, um, info lists are all going to [00:47:00] be in the same directory as forms because they're both schemas. It's going to be the

Dan: schemas directory containing the user form and the user info list. And you could just call it user schema if you wanted to. It's not like something we declare, but like, you could do whatever you want with that. Actions as well. I want to create a command that generates action classes because a lot of the time actions. end up, being quite long as like a definition, especially if you're doing them in like in line. So defining actions within like your resource file or your table file or your page file, like that can really improve, like increase the number of lines of that file and make it difficult to navigate.

Dan: So I want to create a command that generates a class with just a make method and the make method will just return the action object, which you can then, customize to whatever you want. And then just pull in that make method anywhere else in your app and use the action. And obviously, cause now actions are also consolidated.

Dan: you can now use the action anywhere. You could use it on a form. You could use it on a table. You could use it in a [00:48:00] page, like all of them are the same. So it's kind of all coming together really with like all the decisions we've made. On top of that, currently you can generate, form fields and table columns and things like that.

Dan: Um, and they're like custom columns, so you can define the view and everything. I want to add an option to that command to allow you to generate in a similar way, file with a make method, and then you just return. Whatever custom configuration you want from that as well. So also allowing you to extract out those really long field definitions into their own files, if you wanted to do that.

Dan: So it does apply to basically all the packages to be able to do this.

Alex: That's awesome. Again, I think just that added bit of optional structure, I think it's going to help a lot of people a lot. I was going to say on, you know, when they're onboarding and starting their small projects, but even I could see that being really nice with the enterprise level too, of just. Allowing people like this is the, this is the convention.

Alex: We're going to follow this convention. Even if you have 30 developers, this is the way you do it.

Dan: And, one thing that I've learned, working on large [00:49:00] Filament projects, like in the last year or two, is that there are some cases where you actually need to pass in specific data, to be able to even render an action. So for example, like an action might be. It might be configurable to be able to be used in different places, and you might need to actually pass in some things to the constructor that are required. So maybe you might need, like, the name of something as configuration, but the action won't work without that thing. So, if you went with, our traditional advice, which is, like, override the action class, like, extend it, and then just add your own methods. You wouldn't be able to override the make method because it already is defined in the parent class in the action class, and you can't add your own parameters to it.

Dan: So you can still technically instantiate the action class without its required parameters. But with the new way of just creating a, an empty class with just a make method that just returns it and doesn't extend the action class itself. You can add whatever arguments you want to the method signature so that you can require things when people instantiate your actions. [00:50:00] So I think it's quite nice to then enforce that and, allow your actions to be a little bit more, safe when, when they're being used by, by people.

Alex: Strong typing of everything is, is really nice. That's really cool. I I'm, I'm very excited about. Honestly, all of that. I just, I can already see the number of questions I have to field about, where to put these things decreasing, which is, which is really nice.

okay. So we are, we have a decision to make here. We are at just about an hour of recording time. I don't know how long it's going to be an edited time, but just about an hour of recording time. Uh, we have one more bullet point to cover. It's a bit of a beefy one. Do you want to try to roll it in to this episode?

Dan: I'm pretty sure that our next episode will probably have even more in

Alex: Yeah.

Dan: I think that we probably should, yeah.

Alex: All right, let's do it. So one of the things that you and I talked about before the show, is that really since V1 of Filament, we have had the same simple authorization [00:51:00] story. Default Laravel policies, plus a delete any method added on, uh, just for our use. But that, that way of doing things is kind of like with stubs.

Alex: It's starting to, crack a little bit under the, the weight of how much Filament is growing. So talk to me about the big issue with our current authorization path and then what we're doing to fix it.

Dan: So I think that some of the issues, that we're facing at the moment with authorization are to do with, the user experience side of it. Users want to know, a lot of the time, why they can't do something. So for example, it might not be that they have, don't have permission to do it. Like that is a possibility.

Dan: Like users may not have a correct role, for example, to actually do something, but there might be cases where a policy decides that a post on your blog cannot be deleted if it's published. And a user with permission to delete posts might be quite confused that they can't see a delete button [00:52:00] on some posts and they kind of want to know why so that's one of the problems. And the other problem is to do with bulk actions. So bulk actions obviously Alex mentioned, The delete any method.

Dan: So the delete any method is one that we've added to policies that control if you can see the bulk delete button. And we check that policy and then we don't check individual records because that can be quite slow. And I think for a lot of cases that's going to be absolutely fine. If a person can delete one record, they can usually delete a lot of them, But I think some people want us to be able to authorize each record individually before deleting it and potentially give feedback on that.

Alex: So what are we doing then? How are we solving this problem? Because right now, when I write a policy and it's, you know, uh, can user delete any, I'm returning a true yes, they can, or a false, no, they can't. And there's not much else to it.

Dan: So with regards to like the individual actions, so like a delete action on an edit page, we want to give the users some feedback as to why they can't access that [00:53:00] potentially. In Laravel, there is a feature called policy responses and policy responses are an alternative to returning just true or false from your policy method.

Dan: And they allow you to also attach a message, along with either an acceptance or a denial. And the message in an API would actually be returned to the user. And they're actually told why they can't do something. So for example, in our example with the blog posts, the message might be this post cannot be deleted.

Dan: Because it is, published, and the user is unable to know from the API that they can't access that for that reason, but Filament doesn't show those messages anywhere. So one feature I've been working on is the ability to opt in to showing those messages either as a tool tip on the button, so instead of hiding the button completely, we just disable it and we show a tool tip with the message from the policy, or a notification. So you can click the button and it will then send you a [00:54:00] notification with the reasoning as to why you cannot do that. And you have to manually dismiss that notification. So you know, you've read it and you understand why. So those are two things that you can opt into.

Dan: Now, Filament will behave the same, as before if you don't, but that's two optional things. And with the bulk actions, we've added quite a lot of improvements with regards to authorizing individual records. So you can now do that. You can tell a delete bulk action, for example, to authorize each individual record against the delete policy, as well as the delete any.

Dan: So the delete any is run, to show the user, the check boxes and show the user the button in the first place. If they have the delete any, we then also go through and check each individual record and make sure that they can delete that or not. So the way that we handle that is if the user has access to all the records, then they get notification at the end in the same way as we do now with just like a deleted message with a green checkbox. If the user can delete some of them, but not others, we delete the ones [00:55:00] they can. And we tell them that there are some records they cannot delete. And we tell them how many. And if the user can't delete any of the records they've selected, with the policy, then we tell them that as well, but with regards to the, messages that you get in a policy, we actually allow the user to see why certain records in the bulk delete. failed, and we do that by summarizing and grouping together messages with the same text. So, maybe you have, um, multiple reasons why a user can't delete blog posts. One of them is that they can't delete published blog posts. Another one is that they can't delete blog posts, by other users. So there are two different reasons.

Dan: And as a user, I want to know exactly how many posts failed for what reason. So Filament is extending the Laravel policy response object with its own and allowing the user to customize the message based on how many records that message applies to. I'll give you an [00:56:00] example. You select. 10 posts to delete, you delete them.

Dan: And then it says three posts deleted four posts could not be deleted because they were published and three posts. could not be deleted because they're owned by other users. And you actually know how many of each post failed because of those reasons. And you also know how many passed, so that's kind of how we're laying it out. These extended policy responses from Filament are compatible with the default Laravel, controller responses and things like that. So it's just going to assume that you're only talking about one record. If you, try and use that, but if you pass in more than one record, then it will change the message based on, how many there are.

I think. the extra clarity that comes from the messages coming back is going to be worth its weight in gold. Um, I didn't even know about policy responses until today. That was a brand new thing for me. I literally just returned true or false in like, all my policies. And then I leveraged the front end, whatever I made the front end in.

Alex: To do the [00:57:00] determining the determination of why did this fail, which sometimes is a lot more difficult than it probably should be.

Dan: And you're doing the checks twice. That's the the problem as well, like a lot of the time, like you're, you're maybe you're making your database query, you have to make that query twice and you don't know what happened in the policy and you don't have to like concatenate your own strings together with the messages and things like that.

Dan: So just trying to make it really easy for people to provide that feedback to the users, cause it really helps in the bulk situations.

Alex: For sure. is there anything else you want to run over before we call it quits?

Dan: I think that's me and I think we need to give our listeners ears a little bit of a break.

Alex: We're back in the saddle now. I promised this in the discord channel, which if you're not in discord, definitely, get in there. But we are, we will have a much more frequent and regular cadence of releases in 2025 once the world, well, really Dan and I's worlds stop being as crazy as they are. but yeah, thanks for, for listening in.

Alex: We really appreciate it. If you have any feedback on the show or anything, we've talked about the show. feel free to reach out to us on Bluesky now, which I'll leave links in the description or on Twitter [00:58:00] as well.

Alex: We'd love to hear from y'all. And make sure to rate us five stars in your podcatcher of choice. Other than that, we'll see you next time.

Thank you very much for joining us