Data Migration

Dealing with Data Migrations on Multi Tenant — Laravel

Pinterest LinkedIn Tumblr
Image for post

I work on a project that sometimes require data change for all customers. Since each customer have it’s own database, we always end up working on a script to run it across the whole client-base.

Another common thing during these script development is that you want to run for a specific customer (testing database) first, see the results before you roll it out for every customer. On this article, I’m going to show how I built an abstract command that makes these processes easier.

1- The Abstract Command

The abstract command will be the one actually implementing the handle method. It consists in running a job (synchronously) for a specific tenant or dispatching one job per tenant. This allows to check the result of the script immediately during tests while being able to run multiple workers to process every tenant in parallel when it’s ready to roll out.

2- The Concrete Command

A concrete command will have to do 2 things:

  • Setup the command signature;
  • Instantiate the Job to be executed;

The following snippet is an example of how simple it is to create a new upgrade command.

3- The Abstract Job

Just like the upgrade command, every job class will have some pieces of code that I don’t want to duplicate. Let’s extract all of that into an abstract Job. It is the Job’s responsibility to:

  • Make sure a Tenant is provided.
  • Run the migration process inside a database transaction.
  • Define the amount of attempt to only once.

namespace App\Jobs;

use Illuminate\Support\Facades\DB;
use Throwable;
use App\Customer;

abstract class UpgradeJob
     * Make sure that the job can only be executed once.
     * @var int
    public $tries = 1;

     * @var Customer
    protected $customer;

     * Create a new job instance.
     * @param Customer $customer
    public function __construct(Customer $customer)
        $this->customer = $customer;

     * Performs the script upgrade on a customer wrapped in a database transaction.
     * In case of error, automatically rolls back and re-throws the exception.
     * @throws Throwable
    public function handle()
        // Establish a database connection with the provided customer (Tenant).

        try {


        } catch (Throwable $t) {

            throw $t;

    abstract protected function run();

If you want to see how I establish the database connection, check out this article: Multi Tenancy with Laravel.

4- The Concrete Job

The Concrete Job should extend the abstract (obviously) and implement the run method, which should contain the data migration logic.

5- Wrapping it up

To get everything up and running, just make sure to register the commands on App\Console\Kernel.php class and run it through artisan.

php artisan upgrade:menu --database=my_test_database

Once the script is ready to go live, just run it with the flag --all and get the workers up.

php artisan upgrade:menu --all
php artisan queue:work

Of course, you can always run multiple queue:work to run it in parallel.

Author Bio:

Marco Aurélio Deleu

PHP developer for 9 years. Passionate about Laravel. AWS evangelist.