This is the continuation of my previous article where we talked about creating a multi-tenant app using Laravel and Postgres. I recommend you to read that article before moving forward with this one.
Since our multi-tenant app is ready, we want to utilize Laravel’s console commands to handle our migrations. In this article, we will see how we can extend Laravel’s migration implementation to gracefully handle migrations for all schemas of our multi-tenant app.
Laravel provides easy console commands within
migrate namespace to handle migrations. eg:
$ php artisan migrate
$ php artisan migrate:rollback
But, as you might have noticed, these commands will only run migrations in default schema, which in our case needs to be run for all schemas. We will extend commands provided by Laravel to add 2 new options:
So, we will be able to:
# Run migrations in all available schemas.
$ php artisan migrate --all
# Run migrations in given schemas.
$ php artisan migrate --schema=th,vn,ph
# Rollback migrations in all available schemas.
$ php artisan migrate:rollback --all
# Rollback migrations in given schemas.
$ php artisan migrate:rollback --schema=th,vn,ph
I am using Laravel 5.5 and PostgreSQL 9.5 for my setup.
Console commands implementation in Laravel is basically an implementation of a command design pattern. Every command has its own
handle method which is fired whenever a command is dispatched.
The command pattern is a behavioral design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters. — Wikipedia
Database commands in Laravel can be found in
Illuminate\Database\Console\Migrations namespace. We just need to override them with our own implementations.
Create a folder called
app/Console. We will place all of our migration commands inside this folder. Create a file called
This class extends
Illuminate\Database\Console\Migrations\MigrateCommand which is basically the class that implements
migrate command (
php artisan migrate). Notice that in the constructor we are extending its signature by appending our options
--schema. Then we call the parent constructor with required parameter.
We now need to register it as Laravel’s default
migrate command. For this, create a service provider called
app/Http/Providers directory and register it inside
Note that this service provider extends Laravel’s migration service provider
Illuminate\Database\MigrationServiceProvider instead of the default
registerMigrateCommand extends the singleton object available in alias
command.migrate and returns our own MigrateCommand.
command.migrate alias points to
Illuminate\Database\Console\Migrations\MigrateCommand object. So overriding
command.migrate will override Laravel’s default MigrateCommand as well.
php artisan migrate --help. You can see the two new options we added earlier.
handle method inside
MigrateCommand gets executed whenever we run
php artisan migrate. Let’s make it respond to our newly added options.
handle method is straight forward. If
--all option is passed, we run migrations for all ventures available. If
--schema option is passed, we run it for given schemas otherwise we will just call default parent handler. The
runFor method exists in
MigrationTrait which I will explain shortly.
Since we will use some common methods quite often when we override commands like
migrate:status, we will extract some of the common methods inside a trait. Let’s name it
Note that you can create an AbstractClass for this as well. How you want to implement this is up to you. I will go with trait in this tutorial.
connectUsingSchema method switches schema based on the parameter passed. First line inside this method overrides global config and the next line will purge current database connection. Disconnecting the database is necessary as Laravel caches the connection and use it all over again. So changing the global config only will not work in this case. Reconnection is made by Laravel itself so we don’t need to reconnect the database in our method. During reconnection Laravel will use schema based on the config which is currently the schema we passed.
getValidSchemas, as the name suggests, will return all valid schemas from the argument passed. Remember we will pass schema from command line in
--schema=th,vn,sg format. We first need to explode the arguments with comma and return valid schemas from the list of our ventures.
runFor method loops through all ventures that we pass in the argument and runs the command (migrate, rollback, etc.). First line of the method saves default schema which is used to reset the schema at the end of the method. We then loop through the ventures, switch schema, and run parent handler for current schema.
At this stage, our
MigrateCommand is ready to handle migrations for all ventures. Let’s run some migrations using
Note that running
php artisan migrate will only run the migrations that are in your default schema which can be defined in
.env as mentioned in the previous article.
Now, try running migration for specific schemas with
php artisan migrate --schema=th,sg,vn. This will run migrations for Thailand, Singapore and Vietnam ventures.
The main advantage of extending migration this way is that we can still chain all other options provided by commands in Laravel migrate namespace. For instance, you can run
php artisan migrate --all --seed. Cool, huh!
Now let’s extend
migrate:rollback command. Create a file called
app/Console. This class will extend
Illuminate\Database\Console\Migrations\RollbackCommand, which is Laravel’s implementation of
handle method is same as we discussed earlier.
MigrateCommand class, we need to override
getOptions method in
RollbackCommand class to add new options to the list.
You might be thinking why we need to override
RollbackCommand and not in
MigrateCommand. Well, it is implemented that way. Looks like Taylor Otwell changed it in recent version for some reason. Check out this commit. If you any idea on why he did so, please do answer in comments ;)
At this point our rollback command is almost ready. All we need to do now is to tell Laravel to use this class instead of the default one. We can do this in our
VentureMigrationServiceProvider that we created earlier.
registerMigrateRollbackCommand extends the singleton object available in alias
command.migrate.rollback and returns our own
We can now rollback all with
php artisan migrate:rollback --all.
To rollback specific schemas use
php artisan migrate:rollback --schema=th,sg,vn.
Status command shows the status of your migrations. This command is usually helpful when you want to check which migrations already ran and which one is not executed yet. We can extend
StatusCommand in same way as we extended
App\Console\Migration\StatusCommand as Laravel’s default status command.
registerMigrateStatusCommand extends the singleton object available in alias
command.migrate.status and returns our own
To view the status of all of our schemas, run
php artisan migrate:status --all.
To view the status of specific schemas, run
php artisan migrate:status --schema=th,my,vn.
In similar way, we can override remaining commands:
migrate:install. One of the main advantage of extending default migration instead of creating a new one is that we still get the benefit of chaining other useful options like
Next step: Can you make
--path option work to define different migration path for ventures? Let me know your implementation in comments :)
That’s all! Happy Coding!
Create your free account to unlock your custom reading experience.