Aug 26, 2014

Report about migration of SilverStripe from 2.4 to 3.1

These days I've been working on a migration of a project from SilverStripe 2.4.x to a 3.1.x version. There are already some decent reports on this issue:

So the following text will be more about my personal migration experience and may overlap in some points with those articles.

Convert Models, public static becomes private static

All static model properties for the Database Mapper must be now private instead of public, so for instance:

<?php class MyPage extends Page {

  public static $db = [];




class MyPage extends Page {

  private static $db = [];


The same is for summary_fields, indexes, has_one, has_many etc …

Templates: from control to loop

All control sections are replaced by loop. So

<% control Children %><li>$Title</li>



This can be easily replaced with a regex on all *.ss template files:

replace with

and replace with.

Replace obsolete form field classes and fix tab's naming in getCMSFields

The tabs will be renamed from Root.Content.Main to Root.Main and so on (Content is not existing anymore).

From DataObjectManager to GridField

Since the SS versions below version 3 didn't provide a feature to manage 1:n and n:m relations in the CMS, most of the time the popular module DataObjectManager helped out. It provided a sortable-feature, bulk upload and 1:n and n:m (editable) relations. Both features can now be managed by the GridField with these optional addons (all are manageable via Composer):

From ImageDataObjectManager to SortableGridField

Lets say we have a sortable GalleryImage:

<?php class GalleryImage extends DataObject {

  private static $db = [
    'Title' => 'Varchar(255)',
    'SortOrder' =&gt; 'Int', // we have to attach this field for the sortable grid field explicitly

  private static $has_one = [
    'Image' =&gt; 'Image',


First we have to increase the SortOrder (DataObjectManager started at 0, GridField starts at 1):

  UPDATE GalleryImage SET SortOrder=SortOrder+1 WHERE 1;

Finnaly we have to replace in getCMSFields:

<?php function getCMSFields() {
  $fields = parent::getCMSFields();
  $fields->addFieldToTab('Root.Content.Main', new ImageDataObjectManager($this, 'Images', 'GalleryImage', 'Image'));
  return $fields;  


<?php function getCMSFields() {
  $fields = parent::getCMSFields();
  $config = GridFieldConfig_RelationEditor::create();
  $config->addComponent(new GridFieldSortableRows('SortOrder'));
  $gridField = new GridField("GalleryImage", "Gallery Images", $this-&gt;GalleryImages(), $config);
  // optional if you want specific rows
    "Title" =&gt; "MyTitle",
    "Image.CMSThumbnail" =&gt; "Images",
  $fields-&gt;addFieldToTab('Root.Main', $gridField);
  return $fields;  

I recommend to define a method on your page model to get preconfigured GridFields (saves a lot of repeating code). For instance:

<?php protected function fieldForSortableManyCMSFields($title, $model, $fieldMapping = null) {
    $relation = array_keys($model)[0];
    $modelClass = $model[array_keys($model)[0]];
    $config = GridFieldConfig_RelationEditor::create();
    // optional: set a pagination number
    $config-&gt;addComponent(new GridFieldSortableRows('SortOrder'));
    $gridField = new GridField($relation, $title, $this-&gt;{$relation}(), $config);
    if (is_array($fieldMapping)) {
    return $gridField;

So that you can easily call in getCMSFields later:

<?php function getCMSFields() {
  $fields = parent::getCMSFields();
  $fields->addFieldToTab('Root.Images', $this-&gt;fieldForSortableManyCMSFields("Gallery Images", [ "Images" =&gt; "GalleryImage" ], ["Title" =&gt; "Title", "Image.CMSThumbnail" =&gt; "Image"]));
  return $fields;