Basically we can distinguish between two types of database updates: Schema updates that change the structure of the database and migrations that move around data from one place to the other.
Schema updates need to be performed after an update, before the installation goes into production again, because ILIAS relies on the database to have a certain structure. So these updates should be light and run quickly. Migrations are concerned with potentially heavy tasks on the database, that might be performed in the background while the system already is productive again.
Both variants are triggered by the setup, so make sure you have a basic understanding of how the setup works before looking into updating the database.
Previous versions of ILIAS supported the so called db-update-files
. These files
will keep on working for some time technically, but are deprecated as decided by
the Jour Fixe on 2021-06-08.
General directions on how to use the database in ILIAS are to be found in the according readme of Services/Database.
To create a schema update, you first need an integration with the setup. Create
a class that implements ILIAS\Setup\Agent
, you MUST put it in the subfolder
classes/Setup
in your component. If you only want to introduce some update steps
you could just extend from the NullAgent
.
use ILIAS\Setup;
class MySetupAgent extends Setup\Agent\NullAgent
{
}
Your actual updates of the database go into another file, which implements the
ilDatabaseUpdateSteps
interface. The name SHOULD always start with il$COMPONENT
and end with Steps
. You will want to put something descriptive in between, e.g.
ilMyComponentSettingsTableSteps
. The file SHOULD always be put into the same folder
as the agent. You MAY put your steps in a Steps
folder in the Setup
-folder, if
you need further order in your folder. In the class, you need to implement one
prepare
method that will be called before the steps actually get executed. The
setup will pass an ilDBInterface
-instance to be used by the steps and it is recommended
to store the ilDBInterface
into a property as shown below.
class ilMyDBUpdateSteps implements ilDatabaseUpdateSteps
{
protected \ilDBInterface $db;
public function prepare(\ilDBInterface $db)
{
$this->db = $db;
}
}
This new class then needs to be wired into the Agent:
use ILIAS\Setup;
class MySetupAgent extends Setup\Agent\NullAgent
{
public function getUpdateObjective(Setup\Config $config = null) : Setup\Objective
{
return new ilDatabaseUpdateStepsExecutedObjective(new ilMyDBUpdateSteps());
}
}
To ensure that the setup/status command will output the current database step status
of your component add the method getStatusObjective
to your Agent.
use ILIAS\Setup;
class MySetupAgent extends Setup\Agent\NullAgent
{
public function getStatusObjective(Setup\Metrics\Storage $storage) : Setup\Objective
{
return new ilDatabaseUpdateStepsMetricsCollectedObjective($storage, new MyDBUpdateSteps());
}
}
In the MyDBUpdateSteps you can add your consecutive steps by adding methods according to this schema:
class ilMyDBUpdateSteps implements ilDatabaseUpdateSteps
{
protected \ilDBInterface $db;
public function prepare(\ilDBInterface $db)
{
$this->db = $db;
}
public function step_1()
{
//... your code using $this->db
}
public function step_2()
{
//... your code using $this->db
}
}
The setup mechanism will call your step-methods in ascending order. It will keep track about which method was already called and takes care that the methods are only called once.
A few words of warning:
- Make sure to understand, that this mechanism really is about schema updates.
Do not perform other kinds of updates (e.g. the migrations, creating files, ...)
with this. There is a more general mechanism (the
Objectives
) to do this. - Only use the provided
\ilDBInterface
in the methods. Do not use other things from the environment or the globals, they might not be there if you need them. - It will be easier if one of your database update steps takes care of one table
or a set of closely related tables. You can have multiple classes with database update
steps by using multiple
ilDatabaseUpdateStepsExecutedObjective
s bundled via anObjectiveCollection
.
To create migrations, you start similar to the database update steps. Create a class
that derives from ILIAS\Setup\Agent
. The class MUST be in a subfolder Setup
of
your component, maybe just extend from NullAgent
for simplicity. Implement the
method getMigrations
.
class MySetupAgent extends NullAgent
{
public function getMigrations() : array
{
return [
"my_migration" => new MyMigration()
];
}
}
The migration then needs to implement ILIAS\Setup\Migration
:
class MyMigration implements Setup\Migration
{
/**
* @return string - a meaningful and concise description for your migration.
*/
public function getLabel() : string
{
return "Your migration label here.";
}
/**
* Tell the default amount of steps to be executed for one run of the migration.
* Return Migration::INFINITE if all units should be migrated at once.
*/
public function getDefaultAmountOfStepsPerRun() : int
{
return 10;
}
/**
* Objectives the migration depends on.
*
* @throw UnachievableException if the objective is not achievable
* @return Objective[]
*/
public function getPreconditions(Environment $environment) : array
{
return [];
}
/**
* Prepare the migration by means of some environment.
*
* This is not supposed to modify the environment, but will be run to prime the
* migration object to run `step` and `getRemainingAmountOfSteps` afterwards.
*/
public function prepare(Environment $environment) : void
{
// Prepare the environment for the following steps here.
}
/**
* Run one step of the migration.
*/
public function step(Environment $environment) : void
{
// Perform one step of the migration here.
}
/**
* Count up how many "things" need to be migrated. This helps the admin to
* decide how big he can create the steps and also how long a migration takes
*/
public function getRemainingAmountOfSteps() : int
{
// Make some calculation to return the remaining amount of steps
}
}
The Migration
-interface makes it possible to break down a migration into distinct
steps. This allows administrators to control and monitor the migrations, which
potentially take a lot of time, closely. When the migration is executed, prepare
will be called first and allows the migration to pull required resources from the
environment. Via getPreconditions
, the migration can announce which other
Objective
s need to be achieved first to fill the environment with the required
resources. With getRemainingAmountOfSteps
you can tell the setup, how many steps
still need to be performed to finish the migration. When the administrator requests
migration steps to be performed, the step
method will be called to perform the
single steps.