Laravel Migrations can be used to update the database or uploaded files during an upgrade. The most common use of a migration is to modify the database schema from version to version.
Create a new class for each migration and name it after the issue it is related to.
namespace PKP\migration\upgrade\v3_4_0;
class I7265_EditorialDecisions extends \PKP\migration\Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Allow null values in two columns of the `edit_decisons` table
Schema::table('edit_decisions', function (Blueprint $table) {
$table->bigInteger('review_round_id')->nullable()->change();
$table->bigInteger('round')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Force null values when reversing the migration,
// to set the database back to its original state
Schema::table('edit_decisions', function (Blueprint $table) {
$table->bigInteger('review_round_id')->nullable(false)->change();
$table->bigInteger('round')->nullable(false)->change();
});
}
}
Once the migration has been created, add it to the dbscripts/xml/upgrade.xml
for each application where the migration should be run.
The XML file is organized by version descriptors like the following.
<upgrade minversion="3.1.0.0" maxversion="3.3.9.9">
...
<migration class="PKP\migration\upgrade\v3_4_0\I7265_EditorialDecisions" />
...
</upgrade>
Add your migration to the <upgrade>
block that matches the maxversion
before the migration should be run. For example, if the migration should be applied when upgrading to 3.4, add it to the end of the block with maxversion="3.3.9.9"
, right before the <note>
line.
<upgrade minversion="3.1.0.0" maxversion="3.3.9.9">
...
<migration class="PKP\migration\upgrade\v3_4_0\I7265_EditorialDecisions" />
<note file="docs/release-notes/README-3.4.0" />
</upgrade>
Sometimes a migration is nearly identical across all applications, except for a few differences. For example, the table for a context is called journals
, presses
, or servers
, depending on the application.
Use an abstract class in the lib/pkp
library.
<?php
namespace PKP\migration\upgrade\v3_4_0;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use PKP\migration\Migration;
abstract class I5555_Example extends Migration
{
abstract protected function getContextTable(): string;
abstract protected function getContextSettingsTable(): string;
abstract protected function getContextIdColumn(): string;
public function up(): void
{
Schema::table($this->getContextTable(), function (Blueprint $table) {
//
});
}
}
Extend that class in the APP
namespace and implement the required methods.
<?php
namespace APP\migration\upgrade\v3_4_0;
class I5555_Example extends \PKP\migration\upgrade\v3_4_0\I5555_Example
{
protected function getContextTable(): string
{
return 'journals';
}
protected function getContextSettingsTable(): string
{
return 'journal_settings';
}
protected function getContextIdColumn(): string
{
return 'journal_id';
}
}
Then add the child class to the upgrade.xml
file.
<upgrade minversion="3.1.0.0" maxversion="3.3.9.9">
...
<migration class="APP\migration\upgrade\v3_4_0\I5555_Example" />
<note file="docs/release-notes/README-3.4.0" />
</upgrade>
The down()
method should be implemented to support downgrades. When that is not possible, use the DowngradeNotSupportedException
.
public function down(): void
{
throw new DowngradeNotSupportedException('Downgrade unsupported, you will need to restore from backup');
}
Migrations should never rely on Repositories, DAOs, or other application code. Always write the migrations as isolated calls to the database or files.
// DON'T DO THIS
$submissionTable = Repo::submissions()->dao->table;
// DO THIS
$submissionsTable = 'submissions';
That’s because a Migration for one version may be run in another version’s code base. For example, when upgrading from 3.3 to 3.5, a migration for 3.4 will be run in the code for 3.5. If the submissions table was changed to all_submissions
in 3.5, the value of $submissionsTable
would change and our migration would fail.
Every upgrade will have a PreflightCheckMigration
. This should be the first migration listed in the upgrade.xml
file. Use the up()
method of this migration to catch potential migration errors before any modifications are made.
public function up(): void
{
try {
// Check that a foreign key exists before it is
// added to the tables
$count = DB::table('...')
->whereNull('id')
->count();
if ($count) {
throw new Exception('One or more items are missing an id. You must remove these items before upgrading.');
}
} catch (Throwable $e) {
$this->_installer->log('A pre-flight check failed.);
throw $e;
}
}