This document describes the Migration interface and Migration_Abstract base class.
All migrations must implement StellarWP\Migrations\Contracts\Migration.
Returns a human-readable label for the migration.
public function get_label(): string {
return 'Rename Meta Key';
}Returns a description of what the migration does.
public function get_description(): string {
return 'Renames the old_key meta key to new_key for all posts.';
}Returns the total number of items to process for the migration. Used for progress tracking.
The optional $operation parameter allows returning different counts for up vs down operations. When null is passed, Operation::UP() is assumed.
use StellarWP\Migrations\Enums\Operation;
public function get_total_items( ?Operation $operation = null ): int {
global $wpdb;
// Use Operation::UP() as default if null.
$operation = $operation ?? Operation::UP();
// Return different counts based on operation.
if ( $operation->equals( Operation::DOWN() ) ) {
// Count items that need to be rolled back.
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM %i WHERE meta_key = %s",
$wpdb->postmeta,
'new_key'
)
);
}
// Default: count items for migration (up).
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM %i WHERE meta_key = %s",
$wpdb->postmeta,
'old_key'
)
);
}For simple migrations where the count is the same for both operations, you can ignore the parameter:
public function get_total_items( ?Operation $operation = null ): int {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM %i WHERE meta_key = %s",
$wpdb->postmeta,
'old_key'
)
);
}Determines whether the migration should run on the current site. This should return a consistent value regardless of whether the migration has run.
public function is_applicable(): bool {
// Only run if a specific option exists.
return get_option( 'my_plugin_needs_migration' ) === 'yes';
}Determines whether the migration can currently run. Unlike is_applicable(), this can change based on runtime conditions.
public function can_run(): bool {
// Only run during off-peak hours.
$hour = (int) date( 'G' );
return $hour < 6 || $hour > 22;
}Default implementation returns true.
Returns the number of times to retry a failed batch before giving up.
public function get_number_of_retries_per_batch(): int {
return 3;
}Default implementation returns 0.
Returns the default number of items to process per batch. This value is used when the migration is initially scheduled.
public function get_default_batch_size(): int {
return 100;
}Returns an array of tags for categorizing or filtering migrations.
public function get_tags(): array {
return [ 'database', 'meta' ];
}Default implementation returns an empty array.
Returns true when the migration has fully completed. The library calls this after each batch to determine whether to continue.
public function is_up_done(): bool {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM %i WHERE meta_key = %s",
$wpdb->postmeta,
'old_key'
)
) === 0;
}Returns true when the rollback has fully completed.
public function is_down_done(): bool {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM %i WHERE meta_key = %s",
$wpdb->postmeta,
'new_key'
)
) === 0;
}Executes the migration logic for a single batch. Process a fixed number of records per call.
public function up( int $batch, int $batch_size ): void {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"UPDATE %i SET meta_key = %s WHERE meta_key = %s LIMIT %d",
$wpdb->postmeta,
'new_key',
'old_key',
$batch_size
)
);
}Reverts the migration logic for a single batch.
public function down( int $batch, int $batch_size ): void {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"UPDATE %i SET meta_key = %s WHERE meta_key = %s LIMIT %d",
$wpdb->postmeta,
'old_key',
'new_key',
$batch_size
)
);
}Called before each batch of the migration executes.
public function before_up( int $batch, int $batch_size ): void {
// Custom pre-batch logic for migrations.
}Called after each batch of the migration executes. The $is_completed parameter indicates whether the migration has finished.
public function after_up( int $batch, int $batch_size, bool $is_completed ): void {
if ( $is_completed ) {
// Cleanup or notification logic.
}
}Called before each batch of the rollback executes.
public function before_down( int $batch, int $batch_size ): void {
// Custom pre-batch logic for rollbacks.
}Called after each batch of the rollback executes. The $is_completed parameter indicates whether the rollback has finished.
public function after_down( int $batch, int $batch_size, bool $is_completed ): void {
if ( $is_completed ) {
// Cleanup or notification logic.
}
}Returns extra arguments to be passed to the up() method for a specific batch. This enables migrations to pass dynamic, batch-specific data to their processing methods.
The $batch parameter is the batch number about to be processed. The $batch_size parameter is the number of items to process in this batch. The returned array values are spread as additional arguments to the up() method.
public function get_up_extra_args_for_batch( int $batch, int $batch_size ): array {
// Return batch-specific data for the up migration.
return [ $this->get_items_for_batch( $batch, $batch_size ) ];
}Returns extra arguments to be passed to the down() method for a specific batch. This enables migrations to pass dynamic, batch-specific data to their rollback methods.
The $batch parameter is the batch number about to be processed. The $batch_size parameter is the number of items to process in this batch. The returned array values are spread as additional arguments to the down() method.
public function get_down_extra_args_for_batch( int $batch, int $batch_size ): array {
// Return batch-specific data for the down rollback.
return [ $this->get_items_for_batch( $batch, $batch_size ) ];
}When extra arguments are provided, your up() and down() methods should accept them as variadic parameters:
public function up( int $batch, int $batch_size, ...$extra_args ): void {
$items = $extra_args[0] ?? [];
foreach ( $items as $item ) {
// Process item.
}
}
public function down( int $batch, int $batch_size, ...$extra_args ): void {
$items = $extra_args[0] ?? [];
foreach ( $items as $item ) {
// Revert item.
}
}Returns the total number of batches for the migration. This is calculated from get_total_items() divided by the batch size, rounded up.
The optional $operation parameter is passed through to get_total_items() to support different batch counts for up vs down operations.
use StellarWP\Migrations\Enums\Operation;
// The default implementation in Migration_Abstract:
public function get_total_batches( int $batch_size, ?Operation $operation = null ): int {
return (int) ceil( $this->get_total_items( $operation ) / $batch_size );
}You typically don't need to override this method unless you have custom batching logic.
Returns the current status of the migration based on its most recent execution. Used by the CLI and for reporting.
The default implementation in Migration_Abstract queries the Migration_Executions table to find the latest execution for this migration and returns its status. If no executions exist, it returns Status::PENDING().
use StellarWP\Migrations\Enums\Status;
public function get_status(): Status {
// The default implementation queries the last execution.
// Returns Status::PENDING() if no executions exist.
}Available Status Values:
| Status | Description |
|---|---|
Status::PENDING() |
Migration has not started |
Status::SCHEDULED() |
Migration has been scheduled |
Status::RUNNING() |
Migration is currently running |
Status::COMPLETED() |
Migration finished successfully |
Status::FAILED() |
Migration failed (includes automatic rollback) |
Status::REVERTED() |
Migration was manually rolled back successfully |
Status::PAUSED() |
Migration is paused |
Status::CANCELED() |
Migration was canceled |
Status::NOT_APPLICABLE() |
Migration is not applicable to the current site |
Status Behavior Notes:
- Automatic Rollback: When a migration fails during
up(), an automatic rollback is triggered. After the rollback completes, the status remainsFAILEDto indicate the migration did not succeed. - Manual Rollback: When you manually trigger a rollback (via CLI, REST API, or Admin UI) on a completed migration, the status becomes
REVERTEDafter successful rollback. - The
get_status()method requires the migration ID to be set (via the constructor) to query the execution history. Ifis_applicable()returnsfalse, the status will beNOT_APPLICABLE.
Returns the most recent execution for this migration as an Execution model, or null if no executions exist.
use StellarWP\Migrations\Models\Execution;
$execution = $migration->get_latest_execution();
if ( $execution ) {
// Access execution data via getter methods.
$status = $execution->get_status(); // Status enum
$items_processed = $execution->get_items_processed();
$items_total = $execution->get_items_total();
$start_date = $execution->get_start_date(); // DateTimeInterface|null
$end_date = $execution->get_end_date(); // DateTimeInterface|null
}The Execution model provides a type-safe way to access execution data. See the Execution Model section for full details.
Converts the migration to an array representation. This is used by the CLI commands and implements JsonSerializable.
public function to_array(): array {
return [
'label' => $this->get_label(),
'description' => $this->get_description(),
'tags' => $this->get_tags(),
'total_batches' => $this->get_total_batches( $this->get_default_batch_size() ),
'can_run' => $this->can_run(),
'is_applicable' => $this->is_applicable(),
'status' => $this->get_status(),
];
}The Migration interface extends JsonSerializable, so migrations can be directly serialized to JSON via json_encode().
The Operation enum (StellarWP\Migrations\Enums\Operation) represents the migration direction:
| Value | Description |
|---|---|
Operation::UP() |
Migration operation (forward) |
Operation::DOWN() |
Rollback operation (reverse) |
Usage:
use StellarWP\Migrations\Enums\Operation;
$operation = Operation::UP();
// Check the operation type.
if ( $operation->equals( Operation::DOWN() ) ) {
// Handle rollback case.
}
// Get the string value ('up' or 'down').
$value = $operation->getValue(); // 'up'
// Get human-readable label.
$label = $operation->get_label(); // 'Up'StellarWP\Migrations\Abstracts\Migration_Abstract provides a base class that implements the Migration interface with sensible defaults.
The abstract class requires a migration ID to be passed to the constructor:
public function __construct( string $migration_id )This ID is used internally to query execution history and determine the current status. When using the Registry, the migration ID is automatically passed to the constructor when retrieving migrations.
Returns the migration ID that was passed to the constructor:
$migration = $registry->get( 'my_plugin_migration' );
echo $migration->get_id(); // 'my_plugin_migration'Migration_Abstract provides default implementations for the following methods:
| Method | Default Value |
|---|---|
before_up() |
No-op |
after_up() |
No-op |
before_down() |
No-op |
after_down() |
No-op |
can_run() |
true |
get_number_of_retries_per_batch() |
0 |
get_tags() |
[] |
get_up_extra_args_for_batch() |
[] |
get_down_extra_args_for_batch() |
[] |
get_total_batches() |
Calculated from items/batch_size |
get_status() |
Queries last execution or PENDING |
get_latest_execution() |
Returns Execution model or null |
to_array() |
Array of migration properties |
get_id() |
Returns the migration ID |
Extend this class to avoid implementing these methods when not needed.
use StellarWP\Migrations\Abstracts\Migration_Abstract;
class My_Migration extends Migration_Abstract {
// The constructor receives the migration ID from the Registry.
// You can add your own constructor if needed, but must call parent::__construct().
public function __construct( string $migration_id ) {
parent::__construct( $migration_id );
// Your initialization code here.
}
public function get_label(): string {
return 'My Migration';
}
public function get_description(): string {
return 'Performs data transformation.';
}
public function is_applicable(): bool {
return true;
}
public function is_up_done(): bool {
// Implementation.
}
public function is_down_done(): bool {
// Implementation.
}
public function get_total_items( ?Operation $operation = null ): int {
return 1000;
}
public function get_default_batch_size(): int {
return 100;
}
public function up( int $batch, int $batch_size ): void {
// Implementation.
}
public function down( int $batch, int $batch_size ): void {
// Implementation.
}
}Note: When extending Migration_Abstract, the constructor must accept the $migration_id parameter and pass it to the parent constructor. The Registry handles this automatically when retrieving migrations.
The Registry class stores and manages migrations. It implements ArrayAccess, Iterator, and Countable.
Migrations are registered using a unique ID and class name:
use StellarWP\Migrations\Config;
use StellarWP\Migrations\Registry;
$registry = Config::get_container()->get( Registry::class );
// Via register method.
$registry->register( 'my_plugin_migration', My_Migration::class );
// Via array access.
$registry['my_plugin_migration'] = My_Migration::class;
// Via constructor (for multiple migrations).
$registry = new Registry( [
'my_plugin_migration_1' => My_Migration::class,
'my_plugin_migration_2' => Another_Migration::class,
] );The registry returns a new instance of the migration class each time, automatically passing the migration ID to the constructor:
$migration = $registry->get( 'my_plugin_migration' );
// Equivalent to: new My_Migration( 'my_plugin_migration' )
// Or via array access.
$migration = $registry['my_plugin_migration'];The registry supports filtering migrations with a callback:
// Get only migrations with a specific tag.
$data_migrations = $registry->filter( function( Migration $migration ) {
return in_array( 'data', $migration->get_tags(), true );
} );
// Get only applicable migrations.
$applicable = $registry->filter( fn( Migration $m ) => $m->is_applicable() );The filter() method returns a new Registry instance containing only the matching migrations.
To retrieve all migrations as an array:
$all_migrations = $registry->all();
foreach ( $all_migrations as $migration_id => $migration ) {
// $migration is a Migration instance.
echo $migration->get_label();
}- Migration IDs must be strings with a maximum of 191 characters.
- Migration values must be class-strings (fully qualified class names).
- Migrations cannot be registered after the
stellarwp_migrations_{prefix}_schedule_migrationsaction has fired.
The library provides comprehensive logging capabilities for tracking migration execution and debugging issues.
The Log_Type enum provides the following log levels:
| Type | Description |
|---|---|
INFO |
Informational messages about migration progress. |
WARNING |
Warning messages for non-critical issues. |
ERROR |
Error messages for failures and exceptions. |
DEBUG |
Debug messages for troubleshooting. |
The Logger utility class makes it easy to add logs for a migration execution:
use StellarWP\Migrations\Utilities\Logger;
// Create a logger for an execution.
$logger = new Logger( $execution_id );
// Log messages at different levels.
$logger->info( 'Processing batch 1' );
$logger->warning( 'Skipped invalid record', [ 'record_id' => 123 ] );
$logger->error( 'Failed to update record', [ 'error' => $e->getMessage() ] );
$logger->debug( 'Query result', [ 'count' => 50 ] );info( string $message, ?array $data = null )- Log informational messageswarning( string $message, ?array $data = null )- Log warning messageserror( string $message, ?array $data = null )- Log error messagesdebug( string $message, ?array $data = null )- Log debug messages
The logger implements a high-pass filter system to control which log messages are written to the database. This prevents excessive logging in production environments while allowing detailed logging during development or troubleshooting.
Log levels are ordered by priority (from most verbose to least verbose):
debug- Detailed debugging informationinfo- Informational messages about migration progresswarning- Warning messages for non-critical issueserror- Error messages for failures and exceptions
When a minimum log level is set, only messages at that level or higher will be written to the database.
The minimum log level is automatically determined based on the WP_DEBUG constant:
WP_DEBUG = true: Minimum level isdebug(all messages are logged)WP_DEBUG = false: Minimum level isinfo(debug messages are not logged)
You can customize the minimum log level using the stellarwp_migrations_{prefix}_minimum_log_level filter, where {prefix} is your configured hook prefix:
use StellarWP\Migrations\Config;
use StellarWP\Migrations\Enums\Log_Type;
$prefix = Config::get_hook_prefix();
add_filter( "stellarwp_migrations_{$prefix}_minimum_log_level", function( Log_Type $minimum_log_level ) {
// Only log warnings and errors.
return Log_Type::WARNING();
} );The filter receives and should return a Log_Type enum instance. Available values:
Log_Type::DEBUG()- Most verboseLog_Type::INFO()- Informational messagesLog_Type::WARNING()- WarningsLog_Type::ERROR()- Errors only (least verbose)
Note: The filter name includes your configured hook prefix, allowing multiple instances of the library to run with independent log level configurations.
The logger provides a transparent API where you can call any log method without checking conditionals:
use StellarWP\Migrations\Utilities\Logger;
$logger = new Logger( $execution_id );
// These will always be called, but only written to DB if they meet the minimum level.
$logger->debug( 'Starting to process items.' );
$logger->info( 'Processing 100 items.' );
$logger->warning( 'Item 5 has invalid data.' );
$logger->error( 'Failed to process item 10.' );Each log entry contains:
id- Unique log entry IDmigration_execution_id- Reference to the migration executiontype- Log type (info, warning, error, debug)message- Human-readable log messagedata- Optional JSON data for additional contextcreated_at- Timestamp when the log was created
The Execution model (StellarWP\Migrations\Models\Execution) is a read-only Data Transfer Object (DTO) that represents a migration execution record.
Executions are typically retrieved from the database via the Migration_Executions table class or through the get_latest_execution() method on a migration:
use StellarWP\Migrations\Tables\Migration_Executions;
// Via the table class.
$execution = Migration_Executions::get_first_by( 'migration_id', 'my_migration' );
$executions = Migration_Executions::get_all_by( 'migration_id', 'my_migration' );
// Via the migration.
$migration = $registry->get( 'my_migration' );
$execution = $migration->get_latest_execution();The Execution model provides the following getter methods:
| Method | Return Type | Description |
|---|---|---|
get_id() |
int |
The unique execution ID |
get_migration_id() |
string |
The migration ID this execution belongs to |
get_start_date() |
?DateTimeInterface |
When the execution started (null if not yet started) |
get_end_date() |
?DateTimeInterface |
When the execution ended (null if still running) |
get_status() |
Status |
The current status as a Status enum |
get_items_total() |
int |
Total number of items to process |
get_items_processed() |
int |
Number of items processed so far |
get_created_at() |
DateTimeInterface |
When the execution record was created |
use StellarWP\Migrations\Models\Execution;
use StellarWP\Migrations\Enums\Status;
$execution = $migration->get_latest_execution();
if ( $execution ) {
// Get basic information.
$id = $execution->get_id();
$migration_id = $execution->get_migration_id();
// Check status.
$status = $execution->get_status();
if ( $status->equals( Status::COMPLETED() ) ) {
echo 'Migration completed successfully!';
}
// Calculate progress.
$total = $execution->get_items_total();
$processed = $execution->get_items_processed();
$percent = $total > 0 ? ( $processed / $total ) * 100 : 0;
echo sprintf( 'Progress: %d/%d (%.1f%%)', $processed, $total, $percent );
// Get timing information.
$start = $execution->get_start_date();
$end = $execution->get_end_date();
if ( $start ) {
echo 'Started: ' . $start->format( 'Y-m-d H:i:s' );
}
if ( $end ) {
echo 'Ended: ' . $end->format( 'Y-m-d H:i:s' );
}
}The to_array() method converts the execution to an associative array:
$data = $execution->to_array();
// Returns:
// [
// 'id' => 123,
// 'migration_id' => 'my_migration',
// 'start_date' => DateTimeInterface,
// 'end_date' => DateTimeInterface|null,
// 'status' => Status,
// 'items_total' => 100,
// 'items_processed' => 50,
// 'created_at' => DateTimeInterface,
// ]Note: The array keys use start_date and end_date (without the _gmt suffix) for consistency with the getter method names.
- Getting Started - Basic usage guide
- Admin UI Reference - Admin interface for managing migrations
- CLI Reference - WP-CLI commands for migrations
- REST API Reference - REST API endpoints for programmatic access
- Programmatic Scheduling - How to programmatically schedule migrations
- Hooks Reference - Available actions and filters