در حال بارگذاری ...

Defining Multi-Tenancy Goals0:00

Alright, let's build a multi-tenant application using a single database. So let's talk about what we mean by a multi-tenant application. You can think of this like a SaaS application. This is generally you have one application, one set of features, but lots of different people or groups or tenants are using that application. So some of the key features that we need to make sure that we build into this application are listed here. The data must be segmented by tenant in the database. And basically what that means is a user should only be able to see the data that belongs to his or her tenant.

Creating Laravel App1:28

Their employees, their contractors, I need to know information about them, what's their start date, what's their name, phone number, and more features that we're going to build later on. But first, let's spin up our app. I'm going to call it TeamZ. So laravel new TeamZ. Okay, so now that that's done, let's go into TeamZ. So the first thing I want to do now that I have my new Laravel app is to install the tall stack preset. So that is the Tailwind, Alpine, Laravel, and Livewire stack.

Installing TALL Preset1:51

tall stack preset. So that is the Tailwind, Alpine, Laravel, and Livewire stack. I love this stack. This is what I build all my new apps with. So we're going to get started with this really easily. We don't have to do any of the configuration. It's all done for us. So let's scroll down and look at how to install this. We're going to want authentication. So we'll follow these instructions here.

We're going to want authentication. So we'll follow these instructions here. Let's grab this, go to our terminal and require it. Okay, and now that that's done, let's go back and grab this command and run that here in our terminal. Okay, and now it is asking us to run npm install and run npm run dev. So let's do that. Okay, we should be good to go. That just finished up. If we go to our browser, and now it looks like we're done here.

That just finished up. If we go to our browser, and now it looks like we're done here. Let's just go to TeamZ.test. And there we go. We have our Laravel app with the tall stack installed. Now I don't like seeing this in my browser all the time. So I'm just going to valet secure. And now we've fixed the SSL. And the last thing I want to install is the Laravel Debugbar. I use this in every project.

Configuring Environment and DB2:55

And the last thing I want to install is the Laravel debug bar. I use this in every project. You probably do too. But let's just get this installed here. Okay, that's installed. Now just a little bit more to get this set up. Let's go into phpStorm and open our project. Okay, we've got our project here. And if we take a look at our .env file, let's make some updates here. We've got TeamZ.test.

And if we take a look at our .env file, let's make some updates here. We've got TeamZ.test. Our database is going to be TeamZ. And I'm going to set my mailer to log. We're not going to send any mail just yet, but I like to do that. I think we're good here. Our app key is already set. Now let's go into SQL Pro and create that TeamZ database. Just go to add database, TeamZ. And now if we go to our terminal, we should be able to run php artisan migrate.

Just go to add database, TeamZ. And now if we go to our terminal, we should be able to run php artisan migrate. And everything seems to have worked. So if we go to our browser and pull up our app, we can get rid of that. Refresh. There's our debug bar. And if we register, we should be able to make this work. And let's click register. I'm now logged in. If I look at the debug bar, you can actually see the query that was run here as a logged

Adding Tenant Model4:08

I'm now logged in. If I look at the debug bar, you can actually see the query that was run here as a logged in user. So this is great. Everything is working. Now let's go back into the code. And if we're going to be building a multi-tenant application, I like to create a Tenant model. Now in a lot of cases, that's actually your customer. It could be an organization. But in my brain, I think that I'm building a multi-tenant app.

It could be an organization. But in my brain, I think that I'm building a multi-tenant app. So I like to actually name it the Tenant model. So let's go create a new model. We'll make a model for Tenant. And we want a migration and a factory when we create this. Okay, so if we go to phpStorm, create the tenants table, we're going to make it really simple to start off. All I want is a name of whatever the organization or the tenant is that is purchasing the software from us.

All I want is a name of whatever the organization or the tenant is that is purchasing the software from us. Now because this is a multi-tenant application, like I said, we need to make sure we associate all of our data, including our users to a tenant. So let's find our create_users_table migration. And let's actually make some changes here. I want to add a unsignedInteger. And this is going to be tenant_id. And we are going to make this nullable. And the reason for that is we may want to create a like super admin user that has access.

And we are going to make this nullable. And the reason for that is we may want to create a like super admin User that has access to all the data from every tenant. And in that case, we would leave their tenant_id as null. But in general, all of our customers and all of the users of our customers are going to have a tenant_id. Lastly, I do want to put another column here as the role of the user. Now you could use a package like spatie/permission package. But in our case, this is going to be a pretty simple app. So we're just going to have a couple of roles and we can just use it directly in the users.

But in our case, this is going to be a pretty simple app. So we're just going to have a couple of roles and we can just use it directly in the users table here. Okay. So once I've changed this table, I need to actually go ahead and rerun my migrations. So php artisan migrate:fresh, there we go. And now let's go back to our app. And let's go look at our factories and make sure that the factories are all set up so that we can start looking at some data. If we go into database/factories, we've got the UserFactory that currently exists here.

that we can start looking at some data. If we go into database/factories, we've got the UserFactory that currently exists here. So we're going to have to add a role here. And let's just have this default for now to admin. One thing I always like to do for the password, that is weird to me. So let's just add this bcrypt helper password. And that makes it really clear to me that every User I create with the factory has a password of password. Now the other thing we need to add here is the tenant_id. So let's do that tenant_id.

Now the other thing we need to add here is the tenantId. So let's do that tenantId. And that's going to be our factory app tenant. And that looks good. Now we just have to get the tenant factory. And all we have here was the name. So we'll just set this to a faker company. And that should do it. So let's see if we can go into php artisan tinker and make this work.

So let's see if we can go into tinker and make this work. php artisan tinker. So we've created this User Alexa and she has a tenantId of one. So that tenant was created. Let's go ahead and make a few more. Let's make nine more. And this time let's set the tenantId to be one. Okay so now we have 10. If we go to our SQL database and take a look at what we have, we've got 10 Users. They all have the same tenant.

If we go to our SQL database and take a look at what we have, we've got 10 users. They all have the same tenant. If we look at our tenants table, we just have this one tenant here. Now let's create another 10 users, but this time we'll create them all in different tenants. So now if we go back to our database, we've got 11 tenants, 20 users. Finally let's go back to our code. Let's go to the welcome.blade.php and let's just spit out here. Let's say app user count. And let's also, if we are just going to get the users from this tenant, we might do something like where tenant_id = 1.

And let's also, if we are just going to get the users from this tenant, we might do something like where tenant_id is one. And let's just count that as well. So if we load our app here in the browser, we've got 20 and 10. And let's add a line break here so it's easier to see. Okay, let me log out here and I'm going to log back in using the top user from the database and password. And now we're signed in. So if this were working, as in if this app were functioning as a true multi-tenant application, then I should not see this 20.

So if this were working, as in if this app were functioning as a true multi-tenant application, then I should not see this 20. I should actually only see 10. If we go back and look at the code, when I get a count of the users, I shouldn't have to add this scope to it. I shouldn't say only give me the tenants that have an ID of one because if you're relying on the developer to do that on every call, all it takes is one mistake and you've got data leaking from one tenant to the other. So let's use what's called a global scope to make sure that we scope these calls. Let's look at the Laravel docs for global scopes.

Applying Global Tenant Scope9:37

So let's use what's called a global scope to make sure that we scope these calls. Let's look at the Laravel docs for global scopes. And if we see that, we've got an example right here of a global scope. What this does is it adds that where tenant_id is, whatever we want it to be, to every call. So let's just copy this exactly from the Laravel docs and go back to our code. And in the app directory, we're going to make a scopes directory. And we're going to make a new PHP class called TenantScope. And I'm just going to copy this full thing, we're going to name it TenantScope. And we want to add into this queryBuilder the same thing we were just referring to,

And I'm just going to copy this full thing, we're going to name it TenantScope. And we want to add into this query builder the same thing we were just referring to, which is the tenant ID. And for now, let's just hard code it to a value of one. So now we have our TenantScope. And all we have to do is apply it to the User model. So let's go into our User model here. And to do that, if we go back to the Laravel docs, you'll see applying it is just adding this booted method. So let's put it right here.

this booted method. So let's put it right here. We've got a booted method and we want the TenantScope. And if this all works, and we go back to our browser, and reload our page, you'll see the 10 is now correct. And if we look at the queries that were run here, you can actually see where the where tenant_id has been appended to this first call. And if that's a little bit confusing to you, let me just comment this one out entirely. All we're doing is saying give me the count of users. If we refresh the page, you'll see that query was run, but the global scope appended this

All we're doing is saying give me the count of users. If we refresh the page, you'll see that query was run, but the global scope appended this where the tenant ID is number one. So that's how we're ensuring that we're never going to see users from a different tenant while we're logged in as a tenant ID one, except you may remember, we actually hard coded that value. So instead of hard coding the value, what I actually want to do is put the tenant ID into the session. Right now we've got a couple things in session, but I want to put the tenant ID into the session. And then we can always use that session variable to determine how we should scope each of our

Storing Tenant in Session11:53

Right now we've got a couple things in session, but I want to put the tenantId into the session. And then we can always use that session variable to determine how we should scope each of our database queries. So let's go back to PhpStorm. So Laravel already provides a login event that's fired every time someone logs in. And so we just need to listen for that event and set the ID in the session. So let's go ahead and exit our tinker session. And php artisan will make a listener. This is going to be set tenantId in session. Okay, so we've created that.

This is going to be set tenantId in session. Okay, so we've created that. Let's go take a look. Here's our listener. And all we're going to do, we have an event, this event, actually, let's take a look at it just to understand what we're getting here. Now in order for us to actually get this event, we have to apply it in the EventServiceProvider. So we'll go here and we'll just add a login, we have this Illuminate\Foundation\Auth\Events\Login. And when we get this event, we're just going to set tenantId in session. Great.

And when we get this event, we're just going to set tenantId in session. Great. So now if we go back to our application, let's log out, let's log back in. And when I sign in, I get this event here, and it gives me the user. And so on the user, I have the tenantId. So let's go back to our code. And where we have this, we're just going to say, session->put(tenantId, event->user->tenantId); Okay, so one more time. Let's refresh this page.

Okay, so one more time. Let's refresh this page. And when we sign in, now if we look in our session, we have the tenantId of one right here. So now all we have to do is go back to our global scope. And instead of hard coding a one, we can say, session->get(tenantId). And we're probably going to want to make sure the session has that. So if session->has(tenantId), then we'll do this. So this should do it. If we go back to our page and refresh, then you'll actually see that the query is still

So this should do it. If we go back to our page and refresh, then you'll actually see that the query is still working. We're scoping it to the tenant ID based off of the variable that is in the session. And so we've still got a ways to go. But this is a really great first step where now we are scoping our model according to the tenant.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟