Running migrations while changing database - php

I'm making a multi tenancy application. For reasons I've chosen to go with a database for each of the tenants, and then a "master" database which contains meta information about the different tenants, and such.
I've therefore grouped my migrations into two directories:
Master - which contains the migrations for the master database.
Tenants - contains the migrations for each tenant database.
Instead of having to specify the path to the migration folders and the database to run on, each time I migrate, I've created a console command instead. However this is where the issue occurs.
I handle tenants using the subdomain as an identifier for which database to load from, using a middleware like this:
public function handle($request, Closure $next)
{
$domains = explode('.', parse_url($request->url())['host']);
if (count($domains) < 3)
return app()->abort(403, "A valid subdomain is required");
\Config::set('database.connections.tenant.database', $domains[0]);
return $next($request);
}
This works fine for web.
However when I use Config::set() within my console command, it's being ignored, and Laravel is just using the one from my .env file.
database config file:
return [
'default' => env('DB_CONNECTION', 'tenant'),
'connections' => [
'tenant' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'database' => env('DB_TENANT_DATABASE'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', '')
],
'master' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'database' => env('DB_MASTER_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
],
]
];
(I've cleaned it up a bit, so only the essential is shown).
In my .env file I've then specified a default tenant database, which is used when I'm using the cli.
DB_HOST=127.0.0.1
DB_MASTER_DATABASE=master
DB_TENANT_DATABASE=company
DB_USERNAME=root
DB_PASSWORD=
Within my TenantMigrater I iterate through each tenant, and run migrate for each of them with a different connection and path.
foreach(Tenant::all() as $tenant)
{
$this->info("Running migrations for the tenant: {$tenant->name}");
\Config::set('database.connections.tenant.database', $tenant->database);
$this->call('migrate', [
'--database' => 'tenant',
'--path' => 'database/migrations/tenants/'
]);
}
Although I'm setting a new database for the tenant connection, it's still falling back to the one specified in the .env file.
I tried going through Laravel's migrater, and as far I could see, the name was being set in the config correctly, so I'm feeling a bit confused. Any ideas?
Edit:
I think I've gotten one step closer to the issue. It seems like, when running php artisan *, the cli starts a connection to the database under the name specified in the config/database.php file. When i then try to override this, the connection is already open to the connection (or so laravel thinks), and it just hand my command the same connection, without setting the new database, hence why it keeps using the same database. However, I have no idea as to how to force it to create a new connection each time I iterate through the tenant.

Running \DB::reconnect('tenant'); before each migration seemed to make it work. This is a bit counter intuitive I think though.

Related

Laravel - How to share the session between two or more Web Application?

I have two web Applications. I will login in to one Web Application and will navigate to another by links or redirection from the first Application. Lastly after completing some steps in Application two, I will be redirected to Application one. How can I implement this?
Create a new database called sessions.
Configure a connection profile for the session database secondary to your apps primary databases for both apps.
And then they should be syncing up in storing the data for sessions, being able to share them etc...
config/database.php
'app_main_database' => [
'driver' => env('DB_CONNECTION'),
'host' => env('DB_HOST'),
'port' => env('DB_PORT'),
'database' => env('DB_DATABASE'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
],
'sessions_database' => [
'driver' => env('DB_CONNECTION_SESSION'),
'host' => env('DB_HOST_SESSION'),
'port' => env('DB_PORT_SESSION'),
'database' => env('DB_DATABASE_SESSION'),
'username' => env('DB_USERNAME_SESSION'),
'password' => env('DB_PASSWORD_SESSION'),
],
Configure session.connection to the name for your session driver
config/session.php
<?php
use Illuminate\Support\Str;
return [
'driver' => env('SESSION_DRIVER', 'database'),
'connection' => env('SESSION_CONNECTION', 'sessions_database'),
Your question starts with "Laravel - How do..." which leads me to believe both of your applications are using Laravel. You said they are both on the same server and same domain so you could simply expand the default PHP Session cookie from the SLD (Second Level Domain) scope of yourdomain.com to be a cross subdomain cookie: *.yourdomain.com and then set the same application key and your applications will magically start using the and sharing the data. Just make sure that you did the right work in sharing the user database because Laravel default Auth will take the User ID column and log the user into the same ID.
Make sure the environment variables in the .env for both installations have the same identical settings:
#APP_KEY is used to encrypt the session data so must be the same
APP_KEY=base64:'YOUR KEY '
#SESSION_COOKIE must be the same, this is normally not set and defaults to APP_NAME+"_session' which is a problem if your apps have different names
SESSION_COOKIE=laravel_session
#The SESSION_DOMAIN defaults to null which causes the CORS scope to limit to the subdomain level, in my testing must start with leading dot.
SESSION_DOMAIN=.rootdomain.com

Two tables for one model in Laravel / Default (undeletable) rows

I have two databases for my Laravel 8 project: DB_COMMON and DB_SYSTEM. The point is, that I want to separate sensitive data to avoid deleting, so I'll use one db_user (with all privileges) for DB_COMMON and another (without deleting or updating permissions) for DB_SYSTEM.It will be something like additional security layer.
And there are table/(-s), for exapmle, rbac_role. I want to store two roles as "system" (S_ADMIN and CUSTOMER) in DB_SYSTEM and allow user to create new roles in same table name (rbac_role), but in DB_COMMON. And usually I want to work with that roles in one place (one model).
Are there any way to do it?
Or, maybe, I can "push" this two roles in model? I mean merge rows from database table with my (maybe even hardcoded) rows in model?
First of all you have to create two separate connection in your config/database.php file.
mysql connection is default if you are using mysql database.
Copy that params and create new connection:
'connection2' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
...
],
Then you can specify any model to run on specific connection:
class RoleModel extends Model {
protected $connection = 'connection2';
...
}
When you call RoleModel it will connect to second database.
I don't fully get what you want, But if you want to select from rows from two database you can set connection dynamically and select rows from any table.
$first_rows = RoleModel::setConnection('mysql')->select('id', 'role')->get();
$second_rows = RoleModel::setConnection('connection2')->select('id', 'role')->get();
$combined_rows = $first_rows->merge($second_rows);
If you want to create new row in second connection depending whether same record exists in first connection:
if(RoleModel::setConnection('mysql')->where('role', 'somerole')->exists()){
RoleModel::setConnection('connection2')->create([
'role' => 'somerole'
]);
}
Hope this answer helps you

Getting trouble when sending http request from one laravel project to another in same machine

I have strange issue. I have two laravel projects on the same server(local server) and I want to retrieve data from one project into another. Each project has its own database and credentials. Now about problem: When I am sending GET/POST request using curl(), another project(destination project) uses credentials of the project I am sending request from. So select statement does not work due credentials and table names mismatch. When I dumped uri and pasted it into web browser it works as expected. Now question: Why second project using database credentials(or maybe other configs too) of the project I am sending request from? What is this shared session or something like this?
I am having a little trouble getting your question, but I am assuming you are using credentials from one project and are somehow getting a return in your other project which has different credentials. On top of that, you also have different databases. How could that be? Well to put it short, it shouldn't be. Can it be done? Yes, but you would have to do it on purpose. There is no shared session. So I would check your database connections in both projects to see if there is some overlap. If there is, check those out as your issue may be in there. If that is not the case, then check your curl request and see if you are passing the correct project credentials and are therefore getting the right result. Other than that, I would need more information to help you out.
Thanks!
And oh, are you using Laravel Passport? If not, what are you using/doing?
Did you solve this problem ?
I faced the same situation and found solution about this.
Solution is to change the .env file of one project as follows.
change .env as follows.
DB_X_HOST="localhost"
DB_X_DATABASE="other_project"
DB_X_USERNAME="homestead"
DB_X_PASSWORD="secret"
DB_X_PORT="3306"
change config\database.php as follows.
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_X_HOST', '127.0.0.1'),
'port' => env('DB_X_PORT', '3306'),
'database' => env('DB_X_DATABASE', 'forge'),
'username' => env('DB_X_USERNAME', 'forge'),
'password' => env('DB_X_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
]
It worked fine for me.

How to use orchestral/tenanti in Laravel 5 to build a multi tenant application with multiple databases?

I am trying to build and application using Laravel 5. It is supposed to be a multi tenant database architecture using multiple databases. My employer requires this for security purposes.
I have tried manually managing the main DB migrations and the Tenant migrations but failed. So I decided to take the help of a Laravel specific package which is supposedly what I require.
Tenanti provides a way to have my purpose solved but the problem is that me being a novice developer, am not able to fully understand how to use it in my application.
I have installed it correctly I believe doing:
composer require "orchestra/tenanti=~3.0"
Adding these providers and aliases in the config app file:
'providers' => [
// ...
Orchestra\Tenanti\TenantiServiceProvider::class,
Orchestra\Tenanti\CommandServiceProvider::class,
],
'aliases' => [
'Tenanti' => Orchestra\Support\Facades\Tenanti::class,
],
Finally publishing the config and tweaking it according to the documentation for multiple databases:
php artisan vendor:publish
return [
'drivers' => [
'user' => [
'model' => App\User::class,
'migration' => 'tenant_migrations',
'path' => database_path('tenanti/user'),
],
],
];
At this point I am still blurry what to do next?
My doubts are as follows:
Where will the migration files be generated and stored? I mean there are two kinds of databases in my application obviously. One set of files is for the main DB which will store all the tenant information and the other files will be for the tenant DB. So how and where will these be stored?
I see the word 'driver' a lot in the documentation but I am not sure what driver is exactly.
How will I handle the authentication for the application? I mean whenever a tenant logs in, I will have to make sure the connection to the database changes dynamically. How will I accomplish this?
I tried to go through the repository of the package itself and make sense of the code inside but in vain. I am not very good when it comes to design patters like facades, command bus, service provider and so on, which is why I am not able to understand the flow of the package or make sense of it.
I tried to run some of the artisan commands which come with the package like:
php artisan tenanti:install {driver}
php artisan tenanti:make {driver} {name}
But I am getting an error like so:
[InvalidArgumentException] Database connection
[tenants] is not available.
Where can I find the resources to understand how to proceed with this?
+1 to #morphatic answer, it quiet accurate on most of the stuff.
Migration
One set of files is for the main DB which will store all the tenant information and the other files will be for the tenant DB. So how and where will these be stored?
For your main database you should be able to use the default database/migration and utilize php artisan make:migration and php artisan migrate.
Tenanti however will use the migration path set under the "driver" configuration. e.g:
'path' => database_path('tenanti/user'),
In this case the migration will be created/migrated from database/tenanti/user (you can choose other folder and it will use that folder). Once you set this up you can create new migration file for the user tenant via php artisan tenanti:make user create_blogs_table (as an example) and run migration via php artisan tenanti:migrate user (see the similarity between Laravel migration command and Tenanti?).
Driver
Driver is just the grouping of a tenant, you maybe grouping it by users, companies, or team etc. And there is possibility that you may require more than one type of group per project, otherwise most of the time you only be using single "group" or "driver".
Authentication or Accessing DB
How will I handle the authentication for the application? I mean whenever a tenant logs in, I will have to make sure the connection to the database changes dynamically. How will I accomplish this?
First of all, you need to consider how you're planning to distinguish each tenant. Most of the time I would see people tend to opt for subdomain. So in this case you need to check if the subdomain belongs to any of the user (by querying the main database) using a middleware and then connect to the database that belongs to the user.
Tenanti doesn't manage that part of the process, because everyone has different style on that aspect, but we do provide a code to dynamically connect to your database tenant from a base database configuration.
Let say you have the following config:
<?php
return [
'fetch' => PDO::FETCH_CLASS,
'default' => 'primary',
'connections' => [
'primary' => [
//
],
'tenants' => [
'driver' => 'mysql',
'host' => 'dbhost', // for user with id=1
'username' => 'dbusername', // for user with id=1
'password' => 'dbpassword', // for user with id=1
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
],
],
'migrations' => 'migrations',
'redis' => [ ... ],
];
You can follow the step available in https://github.com/orchestral/tenanti#multi-database-connection-setup and add the following code.
<?php namespace App\Providers;
use Orchestra\Support\Facades\Tenanti;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Tenanti::setupMultiDatabase('tenants', function (User $entity, array $template) {
$template['database'] = "tenant_{$entity->getKey()}";
return $template;
});
}
}
This would ensure that you be using tenant_1 database for user=1, tenant_2 database for user=2 and so on.
So how does Tenanti detect which user if active?
This is where you need to add logic in your middleware.
$user = App\User::whereSubdomain($request->route()->parameter('tenant'))->first();
Tenanti::driver('user')->asDefaultDatabase($user, 'tenants_{id}');
I've never used this package, but using the code you submitted above here's what I think is probably close to the right solution. You will probably still need to play with some of these values to get them correct:
Migration Paths
Since you're using the multi-database configuration, I believe you should be able to keep your migrations in the normal location, i.e. database/migrations. Tenanti will then create an exact replica of the database for each tenant in a different database. However, when you run php artisan tenanti:install user it might actually create a folder under database/ that indicates where you should put your migrations.
What is a "driver"?
The driver describes whether Tenanti will use a single or multiple databases, what models to use for determining different tenants, and where to store migrations. It is what you identified in the Tenanti config file you used above.
Database Connection Selection
You need to update config/database.php as follows. In a normal Laravel app, you would have the DB connection setup as follows:
<?php
return [
'fetch' => PDO::FETCH_CLASS,
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'sqlite' => [ ...DB connection info... ],
'mysql' => [ ...DB connection info... ],
'pgsql' => [ ...DB connection info... ],
'sqlsrv' => [ ...DB connection info... ],
],
'migrations' => 'migrations',
'redis' => [ ... ],
];
However, in the case of Tenanti multi-database setup, you need to add in different connection info for each tenant's database. To do this you would add a new level to your database.php config file (this example assumes you're using mysql, but you could use any DB, or even different database engines for different tenants):
<?php
return [
'fetch' => PDO::FETCH_CLASS,
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'tenants' => [
'user_1' => [
'driver' => 'mysql',
'host' => 'dbhost', // for user with id=1
'database' => 'dbname', // for user with id=1
'username' => 'dbusername', // for user with id=1
'password' => 'dbpassword', // for user with id=1
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
'user_2' => [
'driver' => 'mysql',
'host' => 'dbhost', // for user with id=2
'database' => 'dbname', // for user with id=2
'username' => 'dbusername', // for user with id=2
'password' => 'dbpassword', // for user with id=2
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
],
],
'migrations' => 'migrations',
'redis' => [ ... ],
];
As you can see, each tenant has its own database instance that can be located on a different host and have a different username/password. Tenanti needs to be told how to figure out which database to use. This is what the documentation on Database Connection Resolver describes. In their example, they've named their tenant databases using acme_{$user->id} whereas in my example above I used user_{$user->id}.
Like I said, I've never actually set this up myself, but these are my best guesses based on the docs, and having used other packages by this same developer. Hope this helps!

Laravel DB Config, Adding Periods to Prefix

In my Laravel 5 config/database.php file I have the following driver defined.
'Portal' => [
'driver' => 'sqlsrv',
'host' => env('DB_HOST', 'localhost'),
'database' => env('DB_PORTAL', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'prefix' => 'Portal.dbo.',
],
When I add a period . in the prefix (currently set to Portal.dbo.), I get a white screen with no exception or stack trace information. I have enabled DB logging, but the page stops loading before any logging can take place. When I remove the . from the prefix, everything appends correctly to the query as it should.
I need to prefix my queries in such a way because I have more than one databases on the same SQL Server that I want to run Joins between. How can I use periods in my prefix without Laravel crashing?

Categories