-
-
Notifications
You must be signed in to change notification settings - Fork 94
S7k/non tenant side #169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
S7k/non tenant side #169
Changes from all commits
9d92fa9
f4f872f
796ec5d
affa3e1
7a2f1bc
131913a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,161 @@ | ||||||
--- | ||||||
title: Non-Tenant Site Setup | ||||||
icon: fal fa-gears | ||||||
excerpt: > | ||||||
How to setup goabal and admin sites in the same application | ||||||
tags: | ||||||
- tenancy | ||||||
- tutorial | ||||||
- non-tenant | ||||||
--- | ||||||
|
||||||
## Introduction | ||||||
When building a SaaS there tends to be a need for a "sub-site" such as an admin panel, or a public facing website describing your SaaS. | ||||||
|
||||||
This tutorial will go though the basics of setting these "sub-sites" within the same codebase without the need for creating a tenant do display these sites. | ||||||
|
||||||
## Requirements | ||||||
This tutorial will assume that you have followed the [basic setup tutorial](tutorial-basic-setup). | ||||||
|
||||||
## Routes | ||||||
The first thing we want to do is configure the [Routes Affect](affacts-routes). | ||||||
|
||||||
First start by creating a new file for all tenant specific routes. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is repeated from above. First we do, first we do that. A more fluid way of saying this is something like
Suggested change
|
||||||
|
||||||
### Tenant Route File | ||||||
For this example it will be stored at `routes\tenant.php` | ||||||
```php | ||||||
use Illuminate\Support\Facades\Route; | ||||||
|
||||||
/* | ||||||
|-------------------------------------------------------------------------- | ||||||
| Tenant Web Routes | ||||||
|-------------------------------------------------------------------------- | ||||||
| | ||||||
| Here is where you can register web routes for your tenant application. | ||||||
| They are not loaded by default, and will be loaded thought the Routes Affect. | ||||||
| | ||||||
*/ | ||||||
|
||||||
Route::get('/', function () { | ||||||
//This is the "root" route for all tenants | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd never use a callable in an example for routes. Callables cause routes files to be no longer cacheable. So.. either just remove the non-working example or provide a controlller from the get-go. |
||||||
}); | ||||||
|
||||||
``` | ||||||
|
||||||
### Routes Affect Listener | ||||||
Next we need to create the listener for the [Routes Affect]() that will get rid of the system (non-tenant) routes and load our tenant route file instead. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Link to the right page please |
||||||
|
||||||
`app\Listeners\ConfigureTenantRoutes.php` | ||||||
```php | ||||||
namespace App\Listeners; | ||||||
|
||||||
use Tenancy\Affects\Routes\Events\ConfigureRoutes; | ||||||
|
||||||
class TenantRoutes | ||||||
{ | ||||||
public function handle(ConfigureRoutes $event) | ||||||
{ | ||||||
if($event->event->tenant) | ||||||
{ | ||||||
$event | ||||||
->flush() | ||||||
->fromFile( | ||||||
['middleware' => ['web']], | ||||||
base_path('/routes/tenant.php') | ||||||
); | ||||||
} | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
#### Registering the listener | ||||||
Now update your `app\Providers\EventServiceProvider.php` to include your new listener. | ||||||
|
||||||
```php | ||||||
protected $listen = [ | ||||||
|
||||||
... | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Tenancy\Affects\Routes\Events\ConfigureRoutes::class => [ | ||||||
App\Listeners\ConfigureTenantRoutes::class, | ||||||
] | ||||||
|
||||||
... | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
]; | ||||||
``` | ||||||
|
||||||
## Status | ||||||
|
||||||
At this point you application is configured to load any tenant routes when a tenant is identified. When a tenant is not identified, the routes located in `routes/web.php` will be used instead. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## Tips and Reminders | ||||||
### Namespaceing | ||||||
As with any Laravel application; if you are storing controllers in a different location than in `app\Http\Controllers` you will need to use the namespace option within your routes. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this line and the usage of namespace, please. I don't personally recommend using namespaces, rather classes to reference the controllers |
||||||
|
||||||
`routes\tenant.php` | ||||||
```php | ||||||
Route::namespace('Tenant')->group(function () { | ||||||
// Controllers Within The "App\Http\Controllers\Tenant" Namespace | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to provide commented examples than commented explanations.. |
||||||
}); | ||||||
``` | ||||||
|
||||||
### Sub-site by domains | ||||||
When using subdomains to identify your non-tenant sub-sites you will need to use the route option within your routes file. | ||||||
`routes\web.php` | ||||||
```php | ||||||
Route::domain('admin.myapp.com')->group(function () { | ||||||
Route::get('/', function () { | ||||||
// This is the root route for the admin site | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No callables please. |
||||||
}); | ||||||
}); | ||||||
|
||||||
Route::domain('www.myapp.com')->group(function () { | ||||||
Route::get('/', function () { | ||||||
// This is the root route for the public site | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No callables. |
||||||
}); | ||||||
}); | ||||||
|
||||||
Route::get('/shared', function() { | ||||||
//This is a route that is shared with both the admin site and the public site | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No callables. |
||||||
}) | ||||||
``` | ||||||
|
||||||
### Accessing `OnTenant` models from a "sub-site" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we rename "sub-site" to tenant site or something else? And make it consistent in the whole tutorial. |
||||||
Before a model that uses the `OnTenant` trait can be used a tenant needs to be identified. Because of this it is best practice to avoid using tenant models on a non-tenant site. | ||||||
|
||||||
In events where this access is still needed, a tenant will first need to be identified within your code. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could need it, or not, please try to make it look more like an option. Maybe showcase both the ConnectionResolver way and the setting tenant way There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't even think this warning is necessary. Especially on the tenant website you will need to know who the tenant is.. In fact I would even enable eager tenant identification for this. |
||||||
|
||||||
The following example assumes you are trying to create an admin view that lists all products a specific tenant has in their store. | ||||||
|
||||||
`app\Http\Controllers\TenantProducts.php` | ||||||
```php | ||||||
public function index(Request $request) | ||||||
{ | ||||||
// We currently do not have a tenant identified as this is the "admin sub-site" | ||||||
|
||||||
// Get our tenant (A Customer) | ||||||
$customer = App\Models\Customer::firstOrFail($request->get('tenant_id')); | ||||||
// Tell tenancy, that we want to access the above tenant | ||||||
Tenancy::setTenant($customer) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would recommend using the |
||||||
|
||||||
// We are still in the "admin sub-site", but we now also have an active tenant | ||||||
// And we can access the information stored in their database. | ||||||
|
||||||
// We will assume that Products uses the `OnTenant` Trait | ||||||
$tenant_products = Products::all(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ``$tenantProducts` please :D |
||||||
|
||||||
// Return the products that belong to the tenant still within our "admin sub-site" | ||||||
return $tenant_products | ||||||
|
||||||
} | ||||||
``` | ||||||
|
||||||
## Considerations to take into account | ||||||
1. **Storing users in tenant databases** | ||||||
When the User Model has the `OnTenant` trait applied to it, you will not be able to have those users login to any "non-tenant" routes. Because there is no tenant identified, it will not be possible to authenticate the user. | ||||||
To overcome this you will need to either create two different User models (Admin & User), or will need to store all users in the system database. The option you choose will depend on youre goals and objectives. | ||||||
Comment on lines
+156
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not true and reminds me a lot about the restrictions that hyn/multi-tenant had. If you would want an admin dashboard that allows authenticating users from a tenant, you can create a log in page that asks for their credentials and one more field with a slug, unique hash or something else to identify the tenant with. Then, modify the authentication layer to see whether the identifier is in the payload and set up a tenant database connection on the fly.. t/t is built to be as flexible as possible, the only limitation is your own imagination. |
||||||
|
||||||
2. **Subdomain identification** | ||||||
When using subdomain identification, it is best practice to preform an additional check when registering a new tenant that they do not try to use a subdomain you have configured for a "non-tenant site". | ||||||
I.E. Do not allow users to register the subdomains (`www`, `admin`, etc...) | ||||||
Comment on lines
+160
to
+161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without providing an actual implementation for this, I'm not sure this advice is appreciated. Even mentioning this might sound condescending because it seems pretty obvious you want this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.