Laravel - Multi-tenancy - One database multiple tables - laravel

I want to create a multi tenancy application using Laravel. I am using the one database, multiple tenant tables database architecture.
I want to create a new set of tenant related tables dynamically, whenever someone registers a new tenant account.
Please consider the following:
tenants table holds all the client registrations. Each tenant will have dedicated tables only for them. In this example every tenant has their own dedicated customer table.
Ex:
tenant01's tables will have tenant01 prefix. (tenant01.customer - only tenant01's customers)
tenant02's tables will have tenant02 prefix. (tenant02.customer - only tenant02's customers)
I don't want to use multiple databases as they are costly and I don't want to use one table for all the tenants, as there will be lots of customers/products etc in the system.
I'm planning to identify the tenant at the logging process and set the tenant name(or a code/Id) in a session. ex: tenant440
After that, in all customer related eloquent model classes, I could dynamically append that prefix (ex: tenant440) into the table name like this:
<?php
class Customer extends Eloquent {
protected $tenant_name = //get name/code from the session. ex: tenant440
//table name will become tenant440.'customers'
protected $table = $tenant_name.'customers';
?>
Is this a correct way to achieve this? What is the simplest to do this? Do you know any kind of resources/packages/libraries for this?
Thanks in advance!

You can set tenant name as a prefix in your database file:
Config::set('database.connections.mysql.prefix',$tenantName); // assuming 'mysql' is the default database connection name
I recommend to put it inside a filter
// routes.php
Route::group(array('before'=>'setTablePrefix'), function($noob)
{
Route::resource('customers', 'CustomersController');
Route::controller('sales', 'SalesController');
});
Route::get('/login',array('as' => 'login', 'uses' => 'UserController#getLogin'));
// filters.php
Route::filter('setTablePrefix', function($route, $request)
{
if (!Session::has('prefixTable'))
App::abort(404);
Config::set('database.connections.mysql.prefix',Session::get('prefixTable'));
});
To get data from all tables, you probably need two queries (or one if you use Session)
$tenants = DB::table('tenants')->lists('name'); // you can set it in Session
if($tenants){
$allCustomers = DB::table($tenants[0].'.customers');
for ($i = 1; $i < count($tenants); $i++) {
$allCustomers->unionall(DB::table($tenants[$i].'.customers'));
}
var_dump($allCustomers->get());
}

Related

Laravel: sync many to many on a subset of rows

There is a multi-tenant laravel application that is implemented in a single database. There are models User and Role with the following relation in User model:
public function roles(): BelongsToMany
{
$relation = $this->belongsToMany(Roles::class, 'role_users', 'user_id', 'role_id')->withTimestamps()->withPivot('tenant_id');
$relation = $relation->where(function ($query) {
$query->where('tenant_id', Tenant::current()->id);
});
return $relation;
}
The role_users table contains user_id, role_id, tenant_id. A user may present in multiple tenants, when I update roles of a user in a tenant, I need to only update the roles of the current tenant, not the user roles on all tenants. This the code I write :
$user->roles()->syncWithPivotValues($roles, ['tenant_id' => Tenant::current()->id]);
But it doesn't work and syncs all roles not the roles of the current tenant. How can I solve the problem?
You can use the wherePivot() method passing the tenant id.
You can find the documentation here: https://laravel.com/docs/9.x/eloquent-relationships#filtering-queries-via-intermediate-table-columns
The code should then look like this:
$user->roles()->wherePivot('tenant_id', Tenant::current()->id)->sync($roles);

How to pass arguments to Laravel factories?

I have a users table and a one-to-zero/one relation with a businesses table (users.user_id => businesses.user_id). On my users table I have a discriminator which tells me if the user is of type business and therefore I need to have details on the businesses table as well.
I want to create my Users with my factory which currently is working and then only create business details where the discriminator points to a business account.
I have three options in my mind:
Create from users factory and then using '->each()' do some checks on the discriminator and create a new business user using a the factory. However I cannot pass to the business factory the user_id that the user was assigned.
First create the users. Then in my Business seeder, retrieve all Users that match a 'business' discriminator. Then for all of these users run a factory that creates the business details. But again, I would have to link somehow the user_id of the already create user with the business factory user_id.
In my business factory, create a new User and retrieve the id, thus making the link between users.user_id and business.user_id. However I am using a random generator for user.user_type so even if I have the businesses table filled it might be for users that have the discriminator as 'personal'.
Is there another way? Can I pass arguments from my Seeder to the factory?
The attributes you pass to the create function will be passed into your model definition callback as the second argument.
In your case you don't even need to access those attributes, since they'll automatically be merged in:
$business = factory(App\Business::class)->create();
factory(App\User::class, 5)->create([
'business_id' => $business->id,
]);
Adapt this to your needs.
My code for adding polymorphic 'Admin' users was:
// run model factory
factory(App\Admin::class, 3)->create()->each(function ($admin) {
$admin->user()->save(
// solved: https://laravel.com/docs/master/database-testing#using-factories (Overriding attributes)
factory(App\User::class)->make([
'userable_id' => $admin->id,
'userable_type' => App\Admin::class
])
);
});
Hope this helps.
Send attribute,
factory(App\User::class)->create(['businessId' => $businessId]);
Retrieve it,
$factory->define(App\User::class, function (Faker $faker, $businessInfo) {
//$businessInfo['businessId']
});

Laravel / Eloquent hasMany relationship with no foreign key

I have a model (Client) with a hasMany relationship to another (Client_option).
The two tables are in different databases (so there is a list of clients, and then each client has their own database with an options table within).
In my Client class I want my options() method to return the entire contents of the options table (it knows which client db to look for). As it is I get an error because the column client_id does not exist in the options table. I can of course create that column and populate every row with the client's id, but I'd only be doing it to keep Eloquent happy so would rather avoid that little messiness.
Thanks in advance for any input!
Geoff
This will allow you to work with it as a relation, call it as dynamic property $user->options, bulk save with push method and so on:
public function options()
{
// it will use the same connection as user model
$options = ClientOption::on($this->getConnectionName())->get();
// if options model has its own, then simply
// $options = ClientOption::get();
$this->setRelation('options', $options);
return $options;
}
public function getOptionsAttribute()
{
return (array_key_exists('options', $this->relations))
// get options from the relation, if already loaded
? $this->getRelation('options')
// otherwise call the method and load the options
: $this->options();
}

Can Laravel 4 relations use different databases

At work we use Nagios for our monitoring platform and we have an in house written configuration generator.
I am planning on re-writing this using Laravel 4. Our setup is that we support multiple clients who all will have their own configuration. For security reason we have each clients configuration stored in a separate database and the database credentials are supplied to the front end using environment variables in the Apache configuration (each client is using a different TCP port).
The problem is that for each client there are database tables that are common to every client i.e. Time Periods, Contacts, etc. At present if we want to change a common item we have to do this on all databases manually (although I have scripted this from the cli)
I wish to separate out the common elements into one database and and have the client specific items in their own database.
I know that Laravel is capable of easily having more than one database connection but my question is is it possible to create Eloquent relationships between tables in two different databases.
An example would be a Service in the client database has a timeperiod_id column which is a foreign key to the primary key in the timeperiod table in shared database.
I want to be able to seamlessly do something like below in the controller (with the approriate relations in the model.
$services = Service::all();
and in the blade template view
#foreach ($services as $service)
{{ $service->name }}
{{ $service->timeperiod->name }}
#endforeach
The Laravel documentation and google dont seem to shed any light on whether this is possible.
Any help greatly appreciated
I am currently doing this in one of my laravel projects.
you can specify a foreign key to match on and also a different primary key if you want.
class TimePeriod extends Eloquent {
protected $connection = 'some_Db';
protected $table = 'time_period';
protected $primaryKey = 'id';//can set this to anything, will default to auto incr id(optional)
public function Services() {
return $this->hasone('Services', 'time_period_id');//specify the foreign key as second param
}
}
class Services extends Eloquent {
protected $connection = 'client';
protected $table = 'services';
public function TimePeriod() {
return $this->belongsto('TimePeriod', 'id');//specify the foreign key as second param
}
}

delete multiple relationships in codeigniter datamapper orm

I have two models:
users and groups
user has many groups
and group has many users
I try to delete all groups of a user like this, which is not working
$user = new User(1);
$user->groups->delete_all();
Is there a solution to delete all multiple relationships for a model ?
if you want to delete data from multiple table and you have a relation like foreign key you can use this following function by just passing table names (array) and your field name..
function deleteMultiple($tables = array(),$condition = array())
{
if($condition)
$this->db->where($condition);
$this->db->delete($tables);
return;
}

Resources