Installing Laravel Pennant0:00
Welcome back. Alright, next up on the list is Laravel Pennant. This is a first-party package, which means, yeah, it doesn't ship with Laravel by default. Instead, you have to install it as a package. But we call it a first-party package because it is maintained by the Laravel core team. Okay, so let's have a look. I think you're going to like it. I will begin by installing it through Composer: composer require laravel/pennant.
I will begin by installing it through Composer. Composer, require, Laravel, Pennant. Alright, next up, let's publish the configuration files. So I can say vendor:publish. And yeah, here's all the tags for a fresh application. In our case, here's the provider, and the number is 5. So let's do that now. Alright, so we can see that we have a new config/pennant.php file, as well as a new migration. Let's go ahead and run that migration.
as well as a new migration. Let's go ahead and run that migration. However, right now, you'll see that because it's a fresh install of Laravel, we don't yet have a database to go along with it. But again, as we talked about an episode or two ago, in recent months, Laravel added support for automatically creating this database if it does not yet exist. So let's do that now. Okay, so here we have our usual tables, but also this new features table.
Feature Flags Overview2:04
can opt in to try out the new dashboard. Or maybe administrators or people who work at your company can try out the new dashboard, or the new design, or layout, but everyone else still sees the old layout, or the old API, or whatever it happens to be. Have you ever found yourself in that situation? And if so, well, you're working with feature flags, and this package can help you. Alright, so let's have a look. I'm going to go into PhpStorm. I'll visit my app Service
Defining a Feature2:28
Alright, so let's have a look. I'm going to go into PhpStorm. I'll visit my AppServiceProvider, and let's define our first feature. I will say feature, Laravel Pennant feature, and let's define a new one. The first argument here will be some kind of identifier that represents and describes the feature itself. I will call ours, you know, newDesign, whatever format you want. Next, we'll have a closure here that will determine whether or not each individual
format you want. Next, we'll have a closure here that will determine whether or not each individual User has access to this feature, or if the feature is active for each individual User. So if I return true here, oh yeah, that would mean everyone has access to this feature. And I don't know, I'm not sure how useful that would be. I don't know, maybe as a toggle, if you want to say, alright, well for the next couple of hours, let's turn on this feature for everybody.
more practically, you would say, well, these users can access the new dashboard, or whatever the feature is, but these other users cannot access it. So again, it would be contention on a per-user basis. Okay. So maybe what we could do is type user, and then you can inspect this user to determine if the new design feature should be active for them. And yeah, maybe if you have a really small site, you can just do something like this.
the new design feature should be active for them. And yeah, maybe if you have a really small site, you can just do something like this. Like the User with an ID of 1 is the owner, or the administrator. They can see it, but nobody else can. This would be fine. Another option, maybe a little more realistically, is you have some kind of method on your User model. Maybe it's isAdmin, isModerator, maybe, you know, whatever you want. Whatever makes sense for your application. In this case, I'm just
Using Blade Feature Directive4:52
Return user isAdmin. Okay. So I think we're ready to try this out. I'm going to go into my routes file. And let's see. We have, again, a fresh application. This loads the welcome page. All right. Let's see. Yeah. How about this? We will say, for demonstration purposes, it's kind of lame, but it'll work. If the new design feature is activated for your account, we will load the style tag here. And that seems to inline the Tailwind library. So I guess otherwise,
we will load the style tag here. And that seems to inline the Tailwind library. So I guess otherwise, you get an unstyled site. So it really is a new design that you're testing out. Okay. Let's give it a shot. So when you pull in the pennant package, that also includes a new Blade directive called feature. And you can use this just like all the Blade directives you're used to in the past. So we're going to give it the identifier. Ours was newDesign. And then don't forget to close out the block and
we don't. Okay. And what is the logic again? Just as a reminder, the logic is if you're an Administrator, you can see it. Otherwise, you don't see it. All right. Let's give this a shot in the browser. So I give this a reload and it should be unstyled. And good. I did it correctly. We get a totally unstyled splash page. And this is the current design, I guess, at least for the example. Okay. But now let's set an authenticated user. So here's what I'll do. I will open up TablePlus.
Okay. But now let's set an authenticated user. So here's what I'll do. I will open up TablePlus. And yeah, here's the Laravel 10 database. But you'll notice that I don't have any users at the moment. So here's what we can do. Let's open up my database/seeds/DatabaseSeeder.php that you'll find in database/seeds, database/seeds/DatabaseSeeder.php. And yeah, they have a little snippet here that I can uncomment. So when I run the php artisan db:seed command, this will create 10 dummy users. And that's what we need.
db seed command, this will create 10 dummy Users. And that's what we need. Okay. Let's give that a run. php artisan db seed. All right. And now if I switch back to TablePlus and give this a refresh, yeah, now we have 10 dummy Users that we can work with. Okay. So let's decide that that Beau Sanford is the administrator. All right. If I switch back, we'll just load this in the routes file. In real life, you might have an authentication
All right. If I switch back, we'll just load this in the routes file. In real life, you might have an authentication system. You might pull in Laravel Breeze, but we don't have any of that. So I will temporarily define it here. I can do that by saying auth and I could say login using id. But why don't we stick with once using id? It does the exact same thing, but it doesn't set any session or cookie. So it's great for just a little quick test examples. So this
Understanding Feature Caching8:04
can see this new design, but everyone else still sees that ugly splash page, the unstyled splash page. Okay. Very cool. But again, my next question would be, but how is this working? Well, let's have a look. If I come back to TablePlus and visit the features table, you'll see two records here for this particular feature. Notice the scope. One of them is user/1. So of course, that's the user with an ID of 1 and the value.
One of them is User 1. So of course, that's the User with an ID of 1 and the value is set to true because for that scope, that particular User can see the feature or the feature is active for that User. This other one, Laravel null, that would be like your global scope, guest users, things like that. By default, that is set to false. Okay. So that means, let's try this out. What if I log in the User with an ID of 2? Well, I want you
means, let's try this out. What if I log in the User with an ID of 2? Well, I want you to notice that if I come back to AppServiceProvider, this logic here, this closure, is never triggered until you check the feature. So for example, I want to make this crystal clear. If I don't have any of this here, if I never check the feature using that directive, well, that means this closure will never be called. So if I come back and give it
directive, well, that means this closure will never be called. So if I come back and give it a bunch of refreshes and go to TablePlus, notice we don't end up with a new record because once again, you don't call that closure, you don't run that closure unless the feature is being checked. Okay. So back to PHPStorm, let's bring this back and now if I come back to Firefox and give it a refresh, we are checking the feature which means there will be a new record in the features
give it a refresh, we are checking the feature which means there will be a new record in the features table for that particular User. So in this case, the User with an ID of 1 has it turned on but the User with an ID of 2, who is not an administrator, has it turned off. Okay. But now the next thing I want to show you, something you might run into, is what if we decide that the User with an ID of 2 gets promoted and now they are an administrator or a moderator.
with an ID of 2 gets promoted and now they are an administrator or a moderator. So you might do something like this. This is fine. Or if you want an array, this doesn't matter. Why am I doing this? But check the ID and see if it's 1 or 2. And those are your administrators. Okay. Well now notice that in our routes file the user with an ID of 2 is logged in but they will not see the new design.
the User with an ID of 2 is logged in but they will not see the new design even though they do qualify. Alright, this is a key thing to understand. If I switch back, access for this particular User has already been saved or stored. And what this effectively means is this closure is not rerunning for every single request. It wouldn't make sense. Instead, it has already been cached in the database. So for example, if I were to delete this outright and then retry,
Otherwise, in situations like this, how might we deal with it? Well, one option is to do what I just did, manually delete it. It's fine for small stuff. But otherwise, more practically you could, whenever a relevant action takes place, you could activate the feature for the User or update its status. So I could say feature activate and then I provide the name of the feature, new design. So yeah, if the User does something, if they're upgraded, as part of that
and then I provide the name of the feature, newDesign. So yeah, if the User does something, if they're upgraded, as part of that you could run code like this. Just keep in mind what this code says is activate the newDesign feature for the current scope. And in our case the scope is the User. So for the current User. This does not say activate newDesign for everybody that visits the application. It is still scoped. So in our case, notice that the User
application. It is still scoped. So in our case, notice that the User with an ID of 2 does not have access. But when I load the homepage and that User is signed in, we activate it for them alone. And actually, just to make this crystal clear, let's cache this and say the User with an ID of 1 is false. Just to show you that we're not in a blanket update changing every single record. Come back, give it a refresh, and
Rolling Out With Lottery13:16
I don't care which users access it, I just don't want everyone accessing this at the exact same time. Well, in those situations, you can reach for a lottery. How about 1 out of 10 users, or 1 out of 5 users can access the new dashboard. And as always, Laravel has our back. We can pull in Illuminate\Support\Lottery, and let's declare our odds. I said 1 out of 5, there you go. Add your fraction there, and you're all set. Okay, so now I don't even need this scope, and I think we're all set.
there, and you're all set. Okay, so now I don't even need this scope, and I think we're all set. Let's go to my routes file, and let's declare a dashboard. And I'm just going to return a string here, dashboard. But then I'm going to have the new dashboard, so we'll have one here. Alright, so now we have the old dashboard and the new one that you want to load. Okay, so here
Alright, so now we have the old dashboard and the new one that you want to load. Okay, so here you might check if the, what did we call it, dashboardV2 feature is active, maybe we redirect them somewhere else. That would be fine. Redirect them to the new dashboard. Otherwise, stick with the old dashboard. Alright, let's give this a try. Let's visit our dashboard, and we get the current
new dashboard. Okay, so let's come back and hopefully this won't take too long. We switch our current user, give it a refresh, and here we go. It did work. Okay, so now we can see a relationship between any scope, in this case a User, and all of the features associated with it. So in this case, the User with an ID of 1 does not have access to the new design, but they do have access to this dashboard v2. Alright, so now we can see that the User with an ID of 1 has
Protecting Routes Middleware15:40
were still able to access that new dashboard. Okay, so that's another thing we should check for. And again, we can solve that problem. I will visit my Http\Kernel, and yeah, we can add a new one that's available through this package. So here is our route middleware aliases. We're going to call it feature, and that will be ensure, there it is, features are active. And real quick, if we have a look there, here's the middleware. Notice that you can
are active. And real quick, if we have a look there, here's the middleware. Notice that you can pass in the name of the feature. Okay, cool. This is all we have to do. So now, for any pages that need protection, you can use your new middleware. feature: and then the name of the feature. Dashboard v2. Alright, cool. So now, yeah, if I come back and refresh, we don't have access to this page, so an exception
