├── .gitignore ├── venture_configs ├── id.php ├── ph.php ├── th.php ├── my.php ├── sg.php └── vn.php ├── README.md ├── app ├── Bootstrap │ └── ConfigLoader.php ├── Console │ └── Migration │ │ ├── MigrateCommand.php │ │ ├── InstallCommand.php │ │ ├── StatusCommand.php │ │ ├── RollbackCommand.php │ │ ├── FreshCommand.php │ │ ├── ResetCommand.php │ │ ├── RefreshCommand.php │ │ └── MigrationTrait.php └── Providers │ ├── VentureServiceProvider.php │ └── VentureMigrationServiceProvider.php └── bootstrap └── app.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /venture_configs/id.php: -------------------------------------------------------------------------------- 1 | 'Asia/Jakarta' 5 | ]; 6 | -------------------------------------------------------------------------------- /venture_configs/ph.php: -------------------------------------------------------------------------------- 1 | 'Asia/Manila' 5 | ]; 6 | -------------------------------------------------------------------------------- /venture_configs/th.php: -------------------------------------------------------------------------------- 1 | 'Asia/Bangkok' 5 | ]; 6 | -------------------------------------------------------------------------------- /venture_configs/my.php: -------------------------------------------------------------------------------- 1 | 'Asia/Kuala_Lumpur' 5 | ]; 6 | -------------------------------------------------------------------------------- /venture_configs/sg.php: -------------------------------------------------------------------------------- 1 | 'Asia/Singapore' 5 | ]; 6 | -------------------------------------------------------------------------------- /venture_configs/vn.php: -------------------------------------------------------------------------------- 1 | 'Asia/Ho_Chi_Minh' 5 | ]; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Laravel Multi-tenant 2 | 3 | This repo contains code samples used in the series of article I published on [medium](https://medium.com) to build multi-tenant application with laravel and postgres. 4 | 5 | **Link to posts:** 6 | 1. [Multi-tenant Application with Laravel and Postgres](https://medium.com/hackernoon/multi-tenant-application-with-laravel-and-postgres-abbb137bdbc8) 7 | 2. [Extending Laravel’s migration command to add new options](https://medium.com/hackernoon/extending-laravels-migration-command-to-add-new-options-90b5a0fc4ef4) 8 | -------------------------------------------------------------------------------- /app/Bootstrap/ConfigLoader.php: -------------------------------------------------------------------------------- 1 | runningInConsole()) { 19 | $app['venture']->loadVentureConfigs(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Console/Migration/MigrateCommand.php: -------------------------------------------------------------------------------- 1 | signature .= " 16 | {--all : Run migrations in all available schemas.} 17 | {--schema= : Run migrations in given schemas.} 18 | "; 19 | 20 | parent::__construct($migrator); 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function handle() 27 | { 28 | if ($this->option('all')) { 29 | $this->runFor(Venture::ALL_VENTURES); 30 | } elseif ($schemas = $this->option('schema')) { 31 | $this->runFor($this->getValidSchemas($schemas)); 32 | } else { 33 | parent::handle(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Console/Migration/InstallCommand.php: -------------------------------------------------------------------------------- 1 | option('all')) { 19 | $this->runFor(Venture::ALL_VENTURES); 20 | } elseif ($schemas = $this->option('schema')) { 21 | $this->runFor($this->getValidSchemas($schemas)); 22 | } else { 23 | parent::handle(); 24 | } 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function getOptions() 31 | { 32 | $options = parent::getOptions(); 33 | 34 | array_push($options, 35 | ['all', null, InputOption::VALUE_NONE, 'Rollback migrations from all available schemas.'], 36 | ['schema', null, InputOption::VALUE_OPTIONAL, 'Rollback migrations from given schemas.'] 37 | ); 38 | 39 | return $options; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Console/Migration/StatusCommand.php: -------------------------------------------------------------------------------- 1 | option('all')) { 19 | $this->runFor(Venture::ALL_VENTURES); 20 | } elseif ($schemas = $this->option('schema')) { 21 | $this->runFor($this->getValidSchemas($schemas)); 22 | } else { 23 | parent::handle(); 24 | } 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function getOptions() 31 | { 32 | $options = parent::getOptions(); 33 | 34 | array_push($options, 35 | ['all', null, InputOption::VALUE_NONE, 'Show migrations status from all available schemas.'], 36 | ['schema', null, InputOption::VALUE_OPTIONAL, 'Show migrations status from given schemas.'] 37 | ); 38 | 39 | return $options; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Console/Migration/RollbackCommand.php: -------------------------------------------------------------------------------- 1 | option('all')) { 19 | $this->runFor(Venture::ALL_VENTURES); 20 | } elseif ($schemas = $this->option('schema')) { 21 | $this->runFor($this->getValidSchemas($schemas)); 22 | } else { 23 | parent::handle(); 24 | } 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function getOptions() 31 | { 32 | $options = parent::getOptions(); 33 | 34 | array_push($options, 35 | ['all', null, InputOption::VALUE_NONE, 'Rollback migrations from all available schemas.'], 36 | ['schema', null, InputOption::VALUE_OPTIONAL, 'Rollback migrations from given schemas.'] 37 | ); 38 | 39 | return $options; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Console/Migration/FreshCommand.php: -------------------------------------------------------------------------------- 1 | option('all')) { 19 | $this->runFor(Venture::ALL_VENTURES); 20 | } elseif ($schemas = $this->option('schema')) { 21 | $this->runFor($this->getValidSchemas($schemas)); 22 | } else { 23 | parent::handle(); 24 | } 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function getOptions() 31 | { 32 | $options = parent::getOptions(); 33 | 34 | array_push($options, 35 | ['all', null, InputOption::VALUE_NONE, 'Rollback all database migrations in all available schemas.'], 36 | ['schema', null, InputOption::VALUE_OPTIONAL, 'Rollback all database migrations in given schemas.'] 37 | ); 38 | 39 | return $options; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Console/Migration/ResetCommand.php: -------------------------------------------------------------------------------- 1 | option('all')) { 19 | $this->runFor(Venture::ALL_VENTURES); 20 | } elseif ($schemas = $this->option('schema')) { 21 | $this->runFor($this->getValidSchemas($schemas)); 22 | } else { 23 | parent::handle(); 24 | } 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function getOptions() 31 | { 32 | $options = parent::getOptions(); 33 | 34 | array_push($options, 35 | ['all', null, InputOption::VALUE_NONE, 'Rollback all database migrations in all available schemas.'], 36 | ['schema', null, InputOption::VALUE_OPTIONAL, 'Rollback all database migrations in given schemas.'] 37 | ); 38 | 39 | return $options; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Console/Migration/RefreshCommand.php: -------------------------------------------------------------------------------- 1 | option('all')) { 19 | $this->runFor(Venture::ALL_VENTURES); 20 | } elseif ($schemas = $this->option('schema')) { 21 | $this->runFor($this->getValidSchemas($schemas)); 22 | } else { 23 | parent::handle(); 24 | } 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function getOptions() 31 | { 32 | $options = parent::getOptions(); 33 | 34 | array_push($options, 35 | ['all', null, InputOption::VALUE_NONE, 'Reset and re-run all migrations in all available schemas.'], 36 | ['schema', null, InputOption::VALUE_OPTIONAL, 'Reset and re-run all migrations in given schemas.'] 37 | ); 38 | 39 | return $options; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Console/Migration/MigrationTrait.php: -------------------------------------------------------------------------------- 1 | $schema]); 17 | 18 | $this->getLaravel()['db']->purge(); 19 | } 20 | 21 | /** 22 | * Get valid schemas from option. 23 | * 24 | * @param string $schemas 25 | * 26 | * @return array 27 | */ 28 | protected function getValidSchemas(string $schemas) 29 | { 30 | $schemas = explode(',', $schemas); 31 | 32 | return array_intersect(Venture::ALL_VENTURES, $schemas); 33 | } 34 | 35 | /** 36 | * Run migrations in given ventures. 37 | * 38 | * @param array $ventures 39 | * 40 | * @return void 41 | */ 42 | protected function runFor(array $ventures) 43 | { 44 | $defaultSchema = config('database.connections.pgsql.schema'); 45 | 46 | foreach ($ventures as $venture) { 47 | $this->connectUsingSchema($venture); 48 | 49 | $this->comment("\nSchema: " . $venture); 50 | 51 | parent::handle(); 52 | } 53 | 54 | // Reset schema. 55 | $this->connectUsingSchema($defaultSchema); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Providers/VentureServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('db', function ($app) { 20 | config(['database.connections.pgsql.schema' => $app['venture']->resolveVenture()]); 21 | 22 | return new DatabaseManager($app, $app['db.factory']); 23 | }); 24 | 25 | /* 26 | * Create db and config instance for each venture. 27 | */ 28 | foreach ($this->app['venture']->getAllVentures() as $venture) { 29 | // Venture specific dbs. 30 | $this->app->singleton('db.' . $venture, function ($app) use ($venture) { 31 | config(['database.connections.pgsql.schema' => $venture]); 32 | 33 | return new DatabaseManager($app, $app['db.factory']); 34 | }); 35 | 36 | // Venture specific configs. 37 | $this->app->singleton('config.' . $venture, function ($app) use ($venture) { 38 | $config = new Repository($app['config']->all()); 39 | 40 | $app['venture']->loadVentureConfigs($venture, $config); 41 | 42 | return $config; 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | $app->alias(\App\Library\Venture::class, 'venture'); 45 | 46 | $app->singleton( 47 | \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 48 | \App\Bootstrap\ConfigLoader::class 49 | ); 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Return The Application 54 | |-------------------------------------------------------------------------- 55 | | 56 | | This script returns the application instance. The instance is given to 57 | | the calling script so we can separate the building of the instances 58 | | from the actual running of the application and sending responses. 59 | | 60 | */ 61 | 62 | return $app; 63 | -------------------------------------------------------------------------------- /app/Providers/VentureMigrationServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerMigrateCommand(); 26 | 27 | $this->registerMigrateRollbackCommand(); 28 | 29 | $this->registerMigrateStatusCommand(); 30 | 31 | $this->registerMigrateRefreshCommand(); 32 | 33 | $this->registerMigrateResetCommand(); 34 | 35 | $this->registerMigrateFreshCommand(); 36 | 37 | $this->registerMigrateInstallCommand(); 38 | } 39 | 40 | /** 41 | * Register the command. 42 | * 43 | * @return void 44 | */ 45 | protected function registerMigrateCommand() 46 | { 47 | $this->app->singleton('command.migrate', function ($app) { 48 | return new MigrateCommand($app['migrator']); 49 | }); 50 | } 51 | 52 | /** 53 | * Register the command. 54 | * 55 | * @return void 56 | */ 57 | protected function registerMigrateRollbackCommand() 58 | { 59 | $this->app->singleton('command.migrate.rollback', function ($app) { 60 | return new RollbackCommand($app['migrator']); 61 | }); 62 | } 63 | 64 | /** 65 | * Register the command. 66 | * 67 | * @return void 68 | */ 69 | protected function registerMigrateStatusCommand() 70 | { 71 | $this->app->singleton('command.migrate.status', function ($app) { 72 | return new StatusCommand($app['migrator']); 73 | }); 74 | } 75 | 76 | /** 77 | * Register the command. 78 | * 79 | * @return void 80 | */ 81 | protected function registerMigrateRefreshCommand() 82 | { 83 | $this->app->singleton('command.migrate.refresh', function () { 84 | return new RefreshCommand; 85 | }); 86 | } 87 | 88 | /** 89 | * Register the command. 90 | * 91 | * @return void 92 | */ 93 | protected function registerMigrateResetCommand() 94 | { 95 | $this->app->singleton('command.migrate.reset', function ($app) { 96 | return new ResetCommand($app['migrator']); 97 | }); 98 | } 99 | 100 | /** 101 | * Register the command. 102 | * 103 | * @return void 104 | */ 105 | protected function registerMigrateFreshCommand() 106 | { 107 | $this->app->singleton('command.migrate.fresh', function () { 108 | return new FreshCommand; 109 | }); 110 | } 111 | 112 | /** 113 | * Register the command. 114 | * 115 | * @return void 116 | */ 117 | protected function registerMigrateInstallCommand() 118 | { 119 | $this->app->singleton('command.migrate.install', function ($app) { 120 | return new InstallCommand($app['migration.repository']); 121 | }); 122 | } 123 | } 124 | --------------------------------------------------------------------------------