
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.https://medium.com/media/2ac45a6b1e37173033cf6383f41db357
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.https://medium.com/media/996cbf8dd19736dc37224acf3ae8d62e
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.
<?php 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). $this->customer->connect(); try { DB::connection('tenant')->beginTransaction(); $this->run(); DB::connection('tenant')->commit(); } catch (Throwable $t) { DB::connection('tenant')->rollback(); 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.https://medium.com/media/6c000e0e500fad10c266c01e26191532
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:

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