Last active
September 18, 2019 15:37
-
-
Save safoorsafdar/c6c623f9ec7b440f563d76995faf7aec to your computer and use it in GitHub Desktop.
Multiple Tenancy (with separate database) Migration Management for Laravel 5.*
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Illuminate\Database\Schema\Blueprint; | |
use Illuminate\Database\Migrations\Migration; | |
class CreateUsersTable extends Migration | |
{ | |
/** | |
* Migration type to syncronized with multiple/single database connection | |
*/ | |
public $type = ["default","tenant"]; | |
/** | |
* Run the migrations. | |
* | |
* @return void | |
*/ | |
public function up() | |
{ | |
Schema::create('users', function (Blueprint $table) { | |
$table->increments('id'); | |
$table->string('name'); | |
$table->string('email')->unique(); | |
$table->string('password', 60); | |
$table->rememberToken(); | |
$table->timestamps(); | |
}); | |
} | |
/** | |
* Reverse the migrations. | |
* | |
* @return void | |
*/ | |
public function down() | |
{ | |
Schema::drop('users'); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Symfony\Component\Console\Input\InputOption; | |
trait BatchMigrationTrait { | |
/** | |
* @var string | |
*/ | |
protected $migrationType = 'default'; | |
/** | |
* Extends the default options by type-option and db-option | |
* | |
* @return array | |
*/ | |
protected function getOptions() { | |
$optExtend = array( | |
array('type', null, InputOption::VALUE_OPTIONAL, 'The migration type to be executed.', 'default'), | |
array('db', null, InputOption::VALUE_OPTIONAL, 'The migration DataBase Name to be executed.', 'default'), | |
); | |
return array_merge(parent::getOptions(), $optExtend); | |
} | |
/** | |
* Filters the connections and only returns the ones that match the migration type | |
* | |
* @param array $connection The database connections | |
* | |
* @return bool Returns TRUE on a match, else FALSE | |
*/ | |
protected function filterConnections($connection) { | |
switch ($this->migrationType) { | |
case 'default': | |
return ( empty($connection['migration_type']) || $connection['migration_type'] == 'default' ); | |
break; | |
default: | |
return (!empty($connection['migration_type']) && $connection['migration_type'] == $this->migrationType ? true : false ); | |
break; | |
} | |
} | |
/** | |
* Returns the default DB connection | |
* | |
* @return array | |
*/ | |
protected function getDefaultConnection() { | |
$defaultConnection = app('db')->getDefaultConnection(); | |
$connection = config()->get('database.connections.' . $defaultConnection); | |
return ( empty($connection) ? array() : array($defaultConnection => $connection) ); | |
} | |
/** | |
* Returns the default Tenant DB connection | |
* | |
* @return array | |
*/ | |
protected function getTenantConnection() { | |
$defaultTenantConnectionName = "tenant_mysql"; | |
$connection = config()->get('database.connections.' . $defaultTenantConnectionName); | |
return ( empty($connection) ? array() : array($defaultTenantConnectionName => $connection) ); | |
} | |
/** | |
* Retrieves database connections by type | |
* | |
* @param null|string $filter When specified (--database option), only this connection will be checked | |
* | |
* @return array An array containing the matching connections | |
*/ | |
protected function getConnectionsByType($filter = null) { | |
config()->set('database.connections.' . $filter . '.database', $this->option('db')); | |
$connections = array(); | |
if ($this->migrationType == "default" && empty($filter)) { | |
return $this->getDefaultConnection(); | |
} elseif (!empty($filter)) { | |
$connections = config()->get('database.connections.' . $filter); | |
if (!empty($connections)) { | |
$connections = array($filter => $connections); | |
} | |
if (empty($connections)) { | |
$connection = $this->getTenantConnection(); | |
if (!is_null($this->option('db'))) | |
//if database name not null override the database for tenant connection. | |
// db option could be *: for all tenant, tenant_id:for specific tenant database | |
$connection['tenant_mysql']['database'] = $this->option('db'); | |
$connections = array($filter => $connection['tenant_mysql']); | |
} | |
} else { | |
//get all connection from database and prepare `$connections` arrays by preparing the database with tenant_mysql database connection | |
//Otherwise | |
//$connections = config()->get('database.connections'); | |
} | |
if (!empty($connections)) { | |
$connections = array_filter($connections, array($this, 'filterConnections')); | |
} | |
return (array) $connections; | |
} | |
/** | |
* Retrieves and sets the migration type | |
*/ | |
protected function setMigrationType() { | |
$this->migrationType = $this->input->getOption('type'); | |
} | |
/** | |
* Run a batch migration on the specified connections | |
* | |
* @param array $connections | |
*/ | |
protected function runMigrationsOnConnections($connections) { | |
foreach ($connections as $name => $connection) { | |
$this->input->setOption('database', $name); | |
if (isset($this->migrator)) { | |
$this->migrator->setMigrationType(array_get($connection, 'migration_type', 'default')); | |
} | |
parent::fire(); | |
} | |
} | |
/** | |
* Default command override | |
*/ | |
public function fire() { | |
$this->setMigrationType(); | |
$connections = $this->getConnectionsByType($this->input->getOption('database')); | |
if (empty($connections)) { | |
$this->info("specified migration type not available for any connection"); | |
} else { | |
$this->runMigrationsOnConnections($connections); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
'default' => env( 'DB_DRIVER', 'mysql' ), | |
'connections' => [ | |
'mysql' => [ | |
'driver' => 'mysql', | |
'host' => env( 'DB_HOST', 'localhost' ), | |
'database' => env( 'DB_DATABASE', 'forge' ), | |
'username' => env( 'DB_USERNAME', 'forge' ), | |
'password' => env( 'DB_PASSWORD', '' ), | |
'charset' => 'utf8', | |
'collation' => 'utf8_unicode_ci', | |
'prefix' => 'tbl_', | |
'strict' => false, | |
'migration_type' => 'default' | |
], | |
'tenant_mysql' => [ | |
'driver' => 'mysql', | |
'host' => env( 'DB_HOST', 'localhost' ), | |
'database' => "database_prefix_", | |
'username' => env( 'DB_USERNAME', 'forge' ), | |
'password' => env( 'DB_PASSWORD', '' ), | |
'charset' => 'utf8', | |
'collation' => 'utf8_unicode_ci', | |
'prefix' => 'tbl_', | |
'strict' => false, | |
'migration_type' => 'tenant' | |
], | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use BatchMigrationTrait; | |
use Symfony\Component\Console\Input\InputOption; | |
class MigrateCommand extends \Illuminate\Database\Console\Migrations\MigrateCommand { | |
use BatchMigrationTrait; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class Migrator | |
* The custom Migrator filters migrations of a specific type | |
*/ | |
class Migrator extends \Illuminate\Database\Migrations\Migrator { | |
/** | |
* @var string | |
*/ | |
protected $migrationType = 'default'; | |
/** | |
* Sets the migration type filter | |
* | |
* @param string $type | |
*/ | |
public function setMigrationType( $type ) { | |
$this->migrationType = $type; | |
} | |
/** | |
* Returns the migration type filter | |
* | |
* @return string | |
*/ | |
public function getMigrationType() { | |
return $this->migrationType; | |
} | |
/** | |
* Resolves the migration and filters those that don't match the migration type | |
* | |
* @param string $migration | |
* | |
* @return bool Returns TRUE on a match, else FALSE | |
*/ | |
protected function filterMigrations( $migration ) { | |
$instance = $this->resolve( $migration ); | |
if ( empty( $instance->type ) ) { | |
$instance->type = 'default'; | |
} | |
if ( ! is_array( $instance->type ) && $this->migrationType != $instance->type ) { | |
return false; | |
} else if ( is_array( $instance->type ) && in_array( $this->migrationType, $instance->type ) ) { | |
return true; | |
} else { | |
return true; | |
} | |
} | |
/** | |
* Gets a filtered list of migrations and runs them | |
* | |
* @param array $migrations | |
* @param bool $pretend | |
*/ | |
public function runMigrationList( $migrations, $pretend = false ) { | |
$this->note( "Running " . ( $this->migrationType == "default" ? "default" : "custom" ) . " migrations for DB " . $this->connection ); | |
$migrations = array_filter( $migrations, array( $this, "filterMigrations" ) ); | |
parent::runMigrationList( $migrations, $pretend ); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use BatchMigrationTrait; | |
class RefreshCommand extends \Illuminate\Database\Console\Migrations\RefreshCommand { | |
use BatchMigrationTrait; | |
public function call( $command, array $arguments = array() ) { | |
if ( $command === 'migrate' || $command === 'migrate:reset' ) { | |
$arguments['--type'] = $this->input->getOption( 'type' ); | |
} | |
return parent::call( $command, $arguments ); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use BatchMigrationTrait; | |
class ResetCommand extends \Illuminate\Database\Console\Migrations\ResetCommand { | |
use BatchMigrationTrait; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use BatchMigrationTrait; | |
class RollbackCommand extends \Illuminate\Database\Console\Migrations\RollbackCommand { | |
use BatchMigrationTrait; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Tenant\Migrations\MigrateCommand; | |
use Tenant\Migrations\RefreshCommand; | |
use Tenant\Migrations\ResetCommand; | |
use Tenant\Migrations\RollbackCommand; | |
use Tenant\Migrations\Migrator; | |
use Illuminate\Database\MigrationServiceProvider as ServiceProvider; | |
class TenancyServiceProvider extends ServiceProvider { | |
/** | |
* Register the migrator service. | |
* | |
* @return void | |
*/ | |
protected function registerMigrator() { | |
// The migrator is responsible for actually running and rollback the migration | |
// files in the application. We'll pass in our database connection resolver | |
// so the migrator can resolve any of these connections when it needs to. | |
$this->app->singleton( 'migrator', function ( $app ) { | |
$repository = $app['migration.repository']; | |
return new Migrator( $repository, $app['db'], $app['files'] ); | |
} ); | |
} | |
/** | |
* Register the "migrate" migration command. | |
* | |
* @return void | |
*/ | |
protected function registerMigrateCommand() { | |
$this->app->singleton( 'command.migrate', function ( $app ) { | |
$packagePath = $app['path.base'] . '/vendor'; | |
return new MigrateCommand( $app['migrator'], $packagePath ); | |
} ); | |
} | |
/** | |
* Register the "rollback" migration command. | |
* | |
* @return void | |
*/ | |
protected function registerRollbackCommand() { | |
$this->app->singleton( 'command.migrate.rollback', function ( $app ) { | |
return new RollbackCommand( $app['migrator'] ); | |
} ); | |
} | |
/** | |
* Register the "reset" migration command. | |
* | |
* @return void | |
*/ | |
protected function registerResetCommand() { | |
$this->app->singleton( 'command.migrate.reset', function ( $app ) { | |
return new ResetCommand( $app['migrator'] ); | |
} ); | |
} | |
/** | |
* Register the "refresh" migration command. | |
* | |
* @return void | |
*/ | |
protected function registerRefreshCommand() { | |
$this->app->singleton( 'command.migrate.refresh', function () { | |
return new RefreshCommand; | |
} ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment