PHPackages                             restruct/silverstripe-database-migrations - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. restruct/silverstripe-database-migrations

ActiveSilverstripe-vendormodule[Database &amp; ORM](/categories/database)

restruct/silverstripe-database-migrations
=========================================

Database migration utilities for SilverStripe: table renames, classname remapping, column renames, and table merges during dev/build

0.2(3mo ago)0177↓33.3%MITPHP

Since Jan 9Pushed 3mo agoCompare

[ Source](https://github.com/restruct/silverstripe-database-migrations)[ Packagist](https://packagist.org/packages/restruct/silverstripe-database-migrations)[ RSS](/packages/restruct-silverstripe-database-migrations/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (1)Versions (3)Used By (0)

SilverStripe Database Migrations
================================

[](#silverstripe-database-migrations)

Database migration utilities for SilverStripe 5.
Handles table renames, classname value remapping, and column renames during `dev/build`.

[![Migrations applied during dev/build](https://private-user-images.githubusercontent.com/1005986/540942969-f471bbe4-9e91-414f-90ce-10d508c4aeea.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzM4MDA1NDQsIm5iZiI6MTc3MzgwMDI0NCwicGF0aCI6Ii8xMDA1OTg2LzU0MDk0Mjk2OS1mNDcxYmJlNC05ZTkxLTQxNGYtOTBjZS0xMGQ1MDhjNGFlZWEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMxOCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMThUMDIxNzI0WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZTUxNjg2MTQyNmE4ODk4MTc2ZDJjZmYyYzIxMWRiM2U3ZWJiODUzMTc1NzIwZGVjZGI5YTFjY2Y5MGE2ZDUyOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.FgSSpgKX_lfn8x_epEN5vHIL3xvlctq_T3eV92RltN4)](https://private-user-images.githubusercontent.com/1005986/540942969-f471bbe4-9e91-414f-90ce-10d508c4aeea.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzM4MDA1NDQsIm5iZiI6MTc3MzgwMDI0NCwicGF0aCI6Ii8xMDA1OTg2LzU0MDk0Mjk2OS1mNDcxYmJlNC05ZTkxLTQxNGYtOTBjZS0xMGQ1MDhjNGFlZWEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMxOCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMThUMDIxNzI0WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZTUxNjg2MTQyNmE4ODk4MTc2ZDJjZmYyYzIxMWRiM2U3ZWJiODUzMTc1NzIwZGVjZGI5YTFjY2Y5MGE2ZDUyOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.FgSSpgKX_lfn8x_epEN5vHIL3xvlctq_T3eV92RltN4)Features
--------

[](#features)

- **ClassName Remapping**: Reads `$legacy_classnames` from DataObjects and injects into `DatabaseAdmin.classname_value_remapping`
- **Table Renames**: Reads `$legacy_table_names` from DataObjects and renames tables before schema updates
- **Column Renames**: Rename columns via config (useful for fixing name collisions)
- **Conflict Handling**: Automatically handles cases where both old and new tables exist

Usage
-----

[](#usage)

### DataObject Migrations

[](#dataobject-migrations)

Add legacy mappings directly to your DataObjects:

```
class MyModel extends DataObject
{
    private static $table_name = 'MyModel';

    // Old class names that should map to this class
    private static $legacy_classnames = [
        'Old\Namespace\MyModel',
        'Another\Old\MyModel',
    ];

    // Old table names that should be renamed to this class's table
    private static $legacy_table_names = [
        'OldTableName',
    ];
}
```

### Config-based Migrations

[](#config-based-migrations)

For join tables, versioned tables, or other non-DataObject tables:

```
Restruct\SilverStripe\Migrations\DatabaseMigrationExtension:
  # Table renames
  table_mappings:
    OldJoinTable: NewJoinTable
    OldModel_Versions: NewModel_Versions

  # Classname remappings (see explanation below)
  classname_mappings:
    'Old\Namespace\SomeClass': 'New\Namespace\SomeClass'

  # Column renames: [table => [old_column => new_column]]
  column_renames:
    MyTable:
      old_column_name: new_column_name

  # Table merges: merge deprecated tables into their replacements
  table_merges:
    OldBlockType:
      target: NewBlockType             # Target table to merge into
      columns:                         # Column mapping (optional)
        OldImageID: NewImageID
      marker:                          # Set a value on migrated records (optional)
        table: Element                 # Table containing the marker column
        column: Style                  # Column name
        value: 'old-style'             # Value to set
      versioned: true                  # Auto-handle _Live and _Versions tables
```

### Running Migrations

[](#running-migrations)

```
vendor/bin/sake dev/build flush=1
```

Migrations run automatically before SilverStripe processes any schema updates.

How ClassName Remapping Works
-----------------------------

[](#how-classname-remapping-works)

SilverStripe stores the fully-qualified class name in a `ClassName` column for polymorphic queries. When you rename or move a class, existing database records still reference the old class name:

```
| ID | ClassName                  | Title    |
|----|----------------------------|----------|
| 1  | Old\Namespace\MyModel      | Record 1 |
| 2  | Old\Namespace\MyModel      | Record 2 |

```

Without remapping, SilverStripe cannot instantiate these records because the old class no longer exists.

**What happens during dev/build:**

1. The extension collects mappings from:

    - `$legacy_classnames` on each DataObject
    - `classname_mappings` config (for cases where you can't modify the class)
2. Injects them into SilverStripe's built-in `DatabaseAdmin.classname_value_remapping`
3. SilverStripe runs UPDATE queries to fix the values:

    ```
    UPDATE MyModel SET ClassName = 'New\Namespace\MyModel'
    WHERE ClassName = 'Old\Namespace\MyModel'
    ```

**After remapping:**

```
| ID | ClassName                  | Title    |
|----|----------------------------|----------|
| 1  | New\Namespace\MyModel      | Record 1 |
| 2  | New\Namespace\MyModel      | Record 2 |

```

### When to use `$legacy_classnames` vs `classname_mappings` config

[](#when-to-use-legacy_classnames-vs-classname_mappings-config)

Use `$legacy_classnames` on your DataObject when:

- You control the class and can add the config to it

Use `classname_mappings` in YAML config when:

- The old class has been **deleted** from the codebase (you can't add config to a non-existent class)
- The class is from a **vendor/third-party module** you can't modify
- You prefer **centralised configuration** in one YAML file

**Common use cases:**

- Namespace changes (SS3→SS4/5 upgrades)
- Refactoring/renaming classes
- Merging multiple classes into one
- Moving classes between modules

How Table Migrations Work
-------------------------

[](#how-table-migrations-work)

The extension hooks into `DatabaseAdmin::onBeforeBuild()` and renames tables before SilverStripe processes schema updates.

**Conflict handling:** If both old and new tables exist:

- If the new table is empty: moves it aside as `_obsolete_NewTable` and renames old→new
- If both have data: logs a warning for manual resolution

How Table Merges Work
---------------------

[](#how-table-merges-work)

Table merges handle the case where you're **consolidating two block types** (or similar DataObjects) into one. This is different from a simple rename because the target table already has its own data.

**Use case:** Merging `BlockBanner` into `BlockHero` with a "banner" style variation:

```
Restruct\SilverStripe\Migrations\DatabaseMigrationExtension:
  # First: remap ClassName values so records point to the new class
  classname_mappings:
    'App\Blocks\BlockBanner': 'App\Blocks\BlockHero'

  # Second: rename columns if needed (runs before merge)
  column_renames:
    BlockBanner:
      BannerImageID: HeroImageID

  # Third: merge table data (runs after schema build)
  table_merges:
    BlockBanner:
      target: BlockHero
      columns:
        HeroImageID: HeroImageID        # Source → target column mapping
      marker:
        table: Element                  # BaseElement stores Style in Element table
        column: Style
        value: 'banner-style'           # Mark migrated records
      versioned: true                   # Handle _Live and _Versions tables
```

**What happens during dev/build:**

1. **onBeforeBuild** (before schema):

    - ClassName remapping → `BlockBanner` records now have `ClassName = 'BlockHero'`
    - Column renames → `BannerImageID` becomes `HeroImageID`
2. **Schema build** (SilverStripe):

    - Creates/updates `BlockHero` table structure
3. **onAfterBuild** (after schema):

    - Table merge:
        - Inserts `BlockBanner` records that don't exist in `BlockHero`
        - Updates existing `BlockHero` records where columns are empty
        - Sets `Element.Style = 'banner-style'` on migrated records
        - Handles `_Live` and `_Versions` tables if `versioned: true`
        - Moves `BlockBanner` tables to `_obsolete_BlockBanner` (with counter suffix if already exists)

**Marker field:** The `marker` config is useful for:

- Distinguishing migrated records from original ones
- Setting a style/variant value so the correct template is rendered
- Audit trail of which records came from the deprecated type

After Migrations: Cleanup Options
---------------------------------

[](#after-migrations-cleanup-options)

Once migrations have successfully run on all environments (dev, staging, production), you have two options:

### Option 1: Remove migration config (recommended for one-time migrations)

[](#option-1-remove-migration-config-recommended-for-one-time-migrations)

After deployment, you can safely remove:

- `$legacy_classnames` and `$legacy_table_names` from your DataObjects
- `table_mappings`, `classname_mappings`, and `column_renames` from YAML config

The migrations are idempotent (they check if old tables/values exist before acting), so keeping them has no functional impact. However, removing them:

- Keeps your codebase clean
- Slightly improves `dev/build` performance (fewer checks)
- Makes it clear which migrations are historical vs active

### Option 2: Keep migration config (recommended for distributed systems)

[](#option-2-keep-migration-config-recommended-for-distributed-systems)

You may want to keep migration config in place when:

- **Multiple databases** need migrating at different times (e.g., client installations)
- **Database restores** from old backups might reintroduce legacy data
- **Future-proofing** against re-running migrations on cloned/restored environments

**Performance impact:** Minimal. The extension iterates through DataObject classes once during `dev/build` to collect mappings. Table/column checks only run if mappings are found, and bail out early if old tables/columns don't exist.

### Hybrid approach

[](#hybrid-approach)

Keep migration config during a transition period, then remove it in a future release once all environments are confirmed migrated.

License
-------

[](#license)

MIT

###  Health Score

32

—

LowBetter than 72% of packages

Maintenance79

Regular maintenance activity

Popularity14

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity25

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~15 days

Total

2

Last Release

114d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4d3680d6353e5f171543435b89965ba2588186ad7ec0ec97cbf572704fec2a4f?d=identicon)[micschk](/maintainers/micschk)

---

Top Contributors

[![micschk](https://avatars.githubusercontent.com/u/1005986?v=4)](https://github.com/micschk "micschk (4 commits)")

### Embed Badge

![Health badge](/badges/restruct-silverstripe-database-migrations/health.svg)

```
[![Health](https://phpackages.com/badges/restruct-silverstripe-database-migrations/health.svg)](https://phpackages.com/packages/restruct-silverstripe-database-migrations)
```

###  Alternatives

[silverstripe/postgresql

SilverStripe now has tentative support for PostgreSQL ('Postgres')

16258.1k2](/packages/silverstripe-postgresql)[brettt89/silverstripe-garbage-collector

SilverStripe Garbage Collector module

109.6k](/packages/brettt89-silverstripe-garbage-collector)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
