Versions:

This document contains information about a future release and not the current stable version (3). Be aware that information on this page may change and API's may not be stable for production use.

4.0.0 (unreleased)

Introduction

This version introduces many breaking changes, which in most projects can be managed through a combination of automatic upgrade processes as well as manual code review. This document reviews these changes and will guide developers in preparing existing 3.x code for compatibility with 4.0

Highlights of major changes

  • Minimum version dependencies have increased; PHP 5.5 and Internet Explorer 11 (or other modern browser) is required.
  • All code earlier marked as deprecated for 4.0 has now been removed. See deprecation documentation for information on code deprecation.
  • All code has been migrated to follow the PSR-2 coding standard. Most significantly, all SilverStripe classes are now namespaced, and some have been renamed. This has major implications for arrangement of templates, as well as other references to classes via string literals or configuration. Automatic upgrading tools have been developed to cope with the bulk of these changes (see upgrading notes). Some classes have been rearranged so that each file contains only one class definition. It is recommended that user code follow the same convention. For example, page types and their controllers should longer be collated in a single file.
  • Object class has been removed.
  • Asset storage has been abstracted, and a new concept of DBFile references via database column references now exists in addition to references via the existing File dataobject. File security and protected files are now a core feature.
  • A new front-end development process has been developed for the construction of javascript based components, prominently featuring ReactJS to develop highly functional CMS content areas. A new standard form schema API has been developed to allow back-end PHP constructed forms to scaffold themselves within ReactJS powered sections.
  • CMS CSS has been re-developed using bootstrap 4 as a base (blog post)
  • Asset admin has been replaced with a purely ReactJS powered upgrade, and split out module called asset-admin. You'll need to add this to your composer.json to retain file management capabilities for your CMS authors.
  • Versioning is now a much more powerful feature, with the addition of campaigns to allow batches of related or inter-dependent objects to be published as a single act. Dependencies between versioned objects can be declared using the new ownership API, so that developers can ensure that relational consistency is maintained during publishing. This new system can be managed via the new "Campaigns" CMS section (blog post)
  • Template variable casting (e.g. <h1>$Title</h1>) is enforced by default, which will ensure safe HTML encode unless explicitly opted out.
  • Themes are now configured to cascade, where you can specify a list of themes, and have the template engine search programatically through a prioritised list when resolving template and CSS file paths.
  • Removed module path constants (e.g. FRAMWORK_PATH) and support for hardcoded file paths (e.g. mysite/css/styles.css)
  • i18n Updated to use symfony/translation over zend Framework 1. Zend_Translate has been removed.
  • Replaced Zend_Cache and the Cache API with a PSR-16 implementation (symfony/cache)
  • _ss_environment.php files have been removed in favour of .env and "real" environment variables.
  • admin has been moved to a new module silverstripe/admin.
  • Behat support updated to behat 3. See the behat extension for more information.
  • The GDBackend and ImagickBackend classes have been replaced by a unified InterventionBackend which uses the intervention/image library to power manipualations.
  • Dependencies can managed via recipe-plugin. See recipe-core and recipe-cms as examples.
  • Authentication has been upgraded to a modular approach using re-usable interfaces and easier to hook in to LoginHandlers.
  • Core modules are installed in the vendor/ folder by default (other modules can opt-in, see guide)
  • Renamed constant for temp folder from TEMP_FOLDER to TEMP_PATH for naming consistency with other path variables and constants

Upgrading

The below sections describe how to go about updating an existing site to be prepared for upgrade to 4.0. Most of these upgrading tasks will involve manual code review, although in some cases there are some automated processes that users can run to

Standard Upgrade

This section describes the processes which every project upgrading to 4.0 should follow. This should be followed as a standard first point of upgrade.

Upgrade project index.php and .htaccess rewrites

The location of SilverStripe's "entry file" has changed. Your project and server environment will need to adjust the path to this file from framework/main.php to index.php. If you are running Apache, adjust your .htaccess file. For other webservers, please consult the installation guides. Since 4.0, URL rewrite capabilities are required, unless you PHP's built-in webserver through silverstripe/serve.

The upgrader tool has a task runner which automates this process for you.

composer global require silverstripe/upgrader
cd ~/Project/Root
~/.composer/vendor/bin/upgrade-code doctor

This will ensure that your .htaccess and index.php are set to a reasonable default value for a clean installation. If you have applied customisations to your .htaccess file (e.g. a custom main.php, HTTP header configuration, deny file access), you'll need to manually reapply these to the copied default file.

Upgrade references to renamed and namespaced classes

Nearly all core PHP classes have been namespaced. For example, DataObject is now called SilverStripe\ORM\DataObject. The below tasks describe how to upgrade an existing site to remain compatible with the newly upgraded classes.

Using the upgrader tool to automatically upgrade

This task should be run on every upgraded project.

We have developed an upgrader tool to (semi-)automatically update your 3.x code to the new naming. Here's an example how to upgrade your mysite folder:

composer global require silverstripe/upgrader
cd ~/Project/Root
~/.composer/vendor/bin/upgrade-code upgrade ./mysite --write

If you want to do a dry-run, omit the --write option to see a preview of a diff of all changed project files.

This will resolve the majority of upgrading work, but we strongly recommend reviewing the diff running some regression testing on your functionality. SilverStripe core classes can be referenced in your PHP files, but also in YAML configuration and SilverStripe templates. For a full list of renamed classes, check the .upgrade.yml definitions in each module.

The rename won't affect class-based permission codes or database table names.

Upgrade references to literal or class table names

In 3.x the class name of any DataObject matched the table name, but in 4.x all classes are namespaced, and it is necessary to map between table and class for querying the database.

In order to ensure you are using the correct table for any class a new DataObjectSchema service is available to manage these mappings (see versioned documentation).

For example, the below shows how you would update a query with a hard-coded table name:

public function countDuplicates($model, $fieldToCheck)
{
    $query = new SilverStripe\ORM\Queries\SQLSelect();
+    $table = SilverStripe\ORM\DataObject::getSchema()->tableForField($model, $field);
-    $query->setFrom("\"{$model}\"");
+    $query->setFrom("\"{$table}\"");
-    $query->setWhere(["\"{$model}\".\"{$field}\"" => $model->$fieldToCheck]);
+    $query->setWhere(["\"{$table}\".\"{$field}\"" => $model->$fieldToCheck]);
    return $query->count();
}
Replacing literal strings with fully qualified class name, or ::class constant

You'll need to update any strings that represent class names and make sure they're fully qualified. In particular, relationship definitions such as has_one and has_many will need to be updated to refer to fully qualified class names.

In configs and with literal PHP strings it is recommended to use the php ::class constant, as demonstrated below.

<?php
+ use SilverStripe\ORM\DataObject;
+ use SilverStripe\Security\Member;
class MyClass extends DataObject 
{
    private static $has_one = [
    -  'Author' => 'Member',
    +  'Author' => Member::class,
    ];
}

In the context of YAML, the magic constant ::class does not apply. Fully qualified class names must be hard coded.

-MyObject:
+My\Project\MyObject:
  property: value
Update references to literal classes to use Injector

In many places, classes namespaced in SilverStripe 4.x will still have a non-namespaced service name that can be accessed via Injector. You should upgrade direct object constructors with the Injector API.

E.g.

- $field = new Varchar();
+ $field = Injector::inst()->create('Varchar');

Migrate your controllers to their own files

The convention for naming controllers is now [MyPageType]Controller, where it used to be [MyPageType]_Controller. This change was made to be more compatible with the PSR-2 standards.

You can still use, for example, Page_Controller, but you will get a deprecation notice. It is best to change it to PageController during your upgrade process. Keep in mind any modules or other thirdparty code that extend PageController are likely to assume that class exists.

By default, a controller for a page type must reside in the same namespace as its page. To use different logic, override SiteTree::getControllerName().

Upgrade template locations and references

Templates are now much more strict about their locations. You can no longer put a template in an arbitrary folder and have it be found. Case is now also checked on case-sensitive filesystems.

Either include the folder in the template name (renderWith('MyEmail.ss') => renderWith('emails/MyEmail.ss')), move the template into the correct directory, or both.

Core template locations have moved - if you're including or overriding these (e.g. for FormField templates) please adjust to the new paths. The forms folder no longer exists, and instead template locations will be placed in paths that match the SilverStripe\Forms namespace.

Upgrade <% include %>

When using <% include %> template tag you can continue to leave out the Includes folder, but this now will also search templates in the base folder if no Include can be found.

<% include Sidebar %> will match Includes/Sidebar.ss, but will also match Sidebar.ss if the former is not present.

Please refer to our template syntax for details.

Upgrade static config settings to private static

If you have some class configuration statics defined and they aren't private, you may find that they don't register anymore. For example, this code, taken from the silverstripe/tagfield module will no longer work in SilverStripe 4.0.

public static $allowed_actions = [
    'suggest'
];

Changing the visibility to private (as per RequestHandler::$allowed_actions visibility) will make it 4.0 compatible.

-public static $allowed_actions = [
+private static $allowed_actions = [
    'suggest'
];

Upgrade module paths in file references

You should no longer rely on modules being placed in a deterministic folder (e.g. /framework), and use getters on the Module object instead. They expect a module composer name, followed by the module relative path, separated by a double colon. This prepares SilverStripe for moving modules out of the webroot at a later point.

Usage in templates:

<!-- before -->
<img src="framework/images/image.png" />
<% require css("framework/css/styles.css") %>

<!-- after -->
<img src="$ModulePath(silverstripe/framework)/image.png" />
<% require css("silverstripe/framework: css/styles.css") %>

Usage in Requirements:

// before
Requirements::css('framework/css/styles.css');

// after
Requirements::css('silverstripe/framework: css/styles.css');

Usage with custom logic:

// before
$moduleFilePath = FRAMEWORK_DIR . '/MyFile.php';
$baseFilePath = BASE_PATH . '/composer.json'; 
$mysiteFilePath = 'mysite/css/styles.css';
$themesFilePath = SSViewer::get_theme_folder() . '/css/styles.css';
$themeFolderPath = THEMES_DIR . '/simple';

// after
use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\View\ThemeResourceLoader;
$moduleFilePath = ModuleLoader::getModule('silverstripe/framework')->getRelativeResourcePath('MyFile.php');
$baseFilePath = Director::baseFolder() . '/composer.json';
$mysiteFilePath = ModuleLoader::getModule('mysite')->getRelativeResourcePath('css/styles.css');
$themesFilePath = ThemeResourceLoader::inst()->findThemedResource('css/styles.css');
$themeFolderPath = ThemeResourceLoader::inst()->getPath('simple');

To ensure consistency, we've also deprecated support for path constants:

  • FRAMEWORK_DIR and FRAMEWORK_PATH
  • FRAMEWORK_ADMIN_DIR and FRAMEWORK_ADMIN_PATH
  • FRAMEWORK_ADMIN_THIRDPARTY_DIR and FRAMEWORK_ADMIN_THIRDPARTY_PATH
  • THIRDPARTY_DIR and THIRDPARTY_PATH
  • CMS_DIR and CMS_PATH
  • THEMES_DIR and THEMES_PATH

    Adapt tooling to modules in vendor folder

SilverStripe modules can now be installed like any other composer package: In the vendor/ folder instead of the webroot. Modules need to opt in to this behaviour after they've ensured that no hardcoded path references exist (see "Upgrade module paths in file references").

All core modules have been moved over already:

-framework/
-cms/
...
+vendor/silverstripe/framework
+vendor/silverstripe/cms

Since the vendor/ folder isn't publicly accessible, modules need to declare which files need to be exposed via the new vendor-plugin (e.g. images or CSS/JS files). These files will be either symlinked or copied into a new resources/ folder automatically on composer install.

If your deployment process relies on composer install on the production environment, and this environment supports symlinks, you don't need to change anything. If you deploy release archives, either ensure those archives can correctly extract symlinks, or explicitly switch to the "copy" mode to avoid symlinks.

API Specific Upgrades

The below sections deal with upgrades to specific parts of various API. Projects which rely on certain API should be upgraded as appropriate using any of the relevant processes documented below.

Upgrade references of SS_Log to use PSR-3 logging

One of the great changes that comes with SilverStripe 4 is the introduction of PSR-3 compatible logger interfaces. This means we can use thirdparty services like Monolog. SS_Log has been replaced with a logger which can be accessed using the LoggerInterface::class service.

For instance, code which logs errors should be upgraded as below:

-SS_Log::log('My error message', SS_Log::ERR);
+use Psr\Log\LoggerInterface;
+Injector::inst()->get(LoggerInterface::class)->error('My error message');
How to customise the default PSR-3 logger

If necessary, you may need to customise either the default logging handler, or one of the error formatters. For example, if running unit tests you may want to suppress errors. You can temporarily disable logging by setting a NullHandler

---
Name: custom-dev-logging
After: dev-logging
Only:
  environment: dev
---
# Replace default handler with null {#replace-default-handler-with-null}
SilverStripe\Core\Injector\Injector:
  Monolog\Handler\HandlerInterface: Monolog\Handler\NullHandler

Alternatively you can customise one or both of the below services:

  • Monolog\Formatter\FormatterInterface.detailed service, which is used for displaying detailed error messages useful for developers.
  • Monolog\Formatter\FormatterInterface.friendly service, which is used to display "error" page content to visitors of the site who encounter errors.

For example, a custom error page generator could be added as below:

---
Name: custom-errorpage
After:
  - '#loggingformatters'
---
SilverStripe\Core\Injector\Injector:
  Monolog\Formatter\FormatterInterface.friendly:
    class: WebDesignGroup\ShopSite\Logging\ErrorPageFormatter

WebDesignGroup\ShopSite\Logging\ErrorPageFormatter should be a class that implements the Monolog\Formatter\FormatterInterface interface.

Upgrade mysite/_config.php

The globals $database and $databaseConfig are deprecated. You should upgrade your site _config.php files to use the .env configuration (below).

conf/ConfigureFromEnv.php is also no longer used, and references to this file should be deleted.

If you need to configure database details in PHP you should configure these details via .env file, or alternatively (but less recommended) use the new DB::setConfig() api.

The global $project is deprecated in favour of the configuration setting SilverStripe\Core\Manifest\ModuleManifest.project.

Changes to mysite/_config.php:

<?php
-global $project;
-$project = 'mysite';
-include 'conf/ConfigureFromEnv.php';

And also add to mysite/_config/mysite.yml:

SilverStripe\Core\Manifest\ModuleManifest:
  project: mysite

Upgrade of _ss_environment.php to .env configuration

The php configuration _ss_environment.php file has been replaced in favour of a non-executable .env file, which follows a syntax similar to an .ini file for key/value pair assignment. Like the old php file, .env may be placed in either the web root, or one level above.

For example, if you have the below _ss_environment.php file, your .env would be rewritten as follows:

_ss_environment.php:

// Environment
define('SS_ENVIRONMENT_TYPE', 'dev');
define('SS_DEFAULT_ADMIN_USERNAME', 'admin');
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
$_FILE_TO_URL_MAPPING[__DIR__] = 'http://localhost';

// Database
define('SS_DATABASE_CHOOSE_NAME', true);
define('SS_DATABASE_CLASS', 'MySQLDatabase');
define('SS_DATABASE_USERNAME', 'root');
define('SS_DATABASE_PASSWORD', '');
define('SS_DATABASE_SERVER', '127.0.0.1');

.env:

## Environment {#environment}
SS_ENVIRONMENT_TYPE="dev"
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
SS_BASE_URL="http://localhost/"

### Database {#database}
SS_DATABASE_CHOOSE_NAME="true"
SS_DATABASE_CLASS="MySQLDatabase"
SS_DATABASE_USERNAME="root"
SS_DATABASE_PASSWORD=""
SS_DATABASE_SERVER="127.0.0.1"

The removal of the _ss_environment.php file means that conditional logic is no longer available in the environment variable set-up process. This generally encouraged bad practice and should be avoided. If you still require conditional logic early in the bootstrap, this is best placed in the _config.php files.

Note also that $_FILE_TO_URL_MAPPING has been removed and replaced with SS_BASE_URL env var. This url must be an absolute url with an optional protocol. The following are valid, for example:

SS_BASE_URL="http://localhost/"
SS_BASE_URL="https://localhost/"
SS_BASE_URL="//localhost/"

The global values $database and $databaseConfig have been deprecated, as has ConfigureFromEnv.php which is no longer necessary.

See Environment Management docs for full details.

Replace usages of Object class

Object has been superseded by a trio of traits which replace components of this legacy class:

  • Injectable: Provides MyClass::create() and MyClass::singleton()
  • Configurable: Provides MyClass::config()
  • Extensible: Provides all methods related to extensions (E.g. add_extension()). All custom method and extension instances are constructed lazily. In particular specific Object class usages should be replaced as below:

Upgrade subclasses

-class MyClass extends Object
-{
-}
+use SilverStripe\Core\Extensible;
+use SilverStripe\Core\Injector\Injectable;
+use SilverStripe\Core\Config\Configurable;
+class MyClass
+{
+    use Extensible;
+    use Injectable;
+    use Configurable;
+}

References to $this->class

-$obj->class
+get_class($obj);

-$this->class;
+static::class;

Upgrade parse_class_spec()

-$spec = Object::parse_class_spec($spec);
+$spec = ClassInfo::parse_class_spec($spec);

Upgrade create_from_string()

-$obj = Object::create_from_string('Varchar(100)');
+$obj = Injector::inst()->create('Varchar(100)');

Extensions


-Object::add_extension('File', 'Versioned');
+File::add_extension(Versioned::class);
+DataObject::add_extension(File::class, Versioned::class); // alternate

-$has = Object::has_extension('File', 'Versioned');
+$has = File::has_extension(Versioned::class);
+$has = DataObject::has_extension(File::class, Versioned::class); // alternate

-$extensions = Object::get_extensions('File');
+$extensions = File::get_extensions();
+$extensions = DataObject::get_extensions(File::class); // alternate

Upgrade references to Session object

Session object is no longer statically accessible via Session::inst(). Instead, Session is a member of the current request.

-public function httpSubmission($data, $form, $request)
-{
-    Session::set('loggedIn', null);
-}
+public function httpSubmission($data, $form, $request)
+{
+    $request->getSession()->set('loggedIn', null);
+}

In some places it may still be necessary to access the session object where no request is available. In rare cases it is still possible to access the request of the current controller via Controller::curr()->getRequest() to gain access to the current session.

Upgrade extensions to work as singletons

In SilverStripe 4.0 extensions are now all singletons, meaning that state stored as protected vars within extensions are now shared across all object instances that use this extension.

As a result, you must take care that all protected vars are either refactored to be stored against the owner object, or are keyed in a fashion distinct to the parent.

E.g.

class MyExtension extends Extension {
    public function getContent() {
-        if (!$this->contentCache) {
-            $this->contentCache = $this->generateContent();
-        }
-        return $this->contentCache;
+        $contentCache = $this->owner->getField('contentCache');
+        if (!$contentCache) {
+            $contentCache = $this->generateContent();
+            $this->owner->setField('contentCache', $contentCache);
+        }
+        return $contentCache;
    }
}

In addition when using extensions with distinct constructor arguments it's advisable to use yml to register those constructor arguments and use a service name or alias in private static $extensions.

E.g. This is the service definition for the various versioned extension variants

---
Name: versionedextension
---
SilverStripe\Core\Injector\Injector:
  # Versioning only
  SilverStripe\Versioned\Versioned.versioned:
    class: SilverStripe\Versioned\Versioned
    constructor:
      mode: Versioned
  # Staging and Versioning
  SilverStripe\Versioned\Versioned.stagedversioned:
    class: SilverStripe\Versioned\Versioned
    constructor:
      mode: StagedVersioned
  # Default is alias for .stagedversioned
  SilverStripe\Versioned\Versioned: '%$SilverStripe\Versioned\Versioned.stagedversioned'

Upgrade your extension references as below:

class MyClass extends DataObject {
    private static $extensions = [
-        Versioned::class . '(Versioned)',
+        Versioned::class . '.versioned',
    ];
}

Compatibility with the new front-end building tools

If you are using Requirements from 3.x then your scripts should continue to work as they did before. The new front-end tooling is intended only for those wishing to customise the CMS look and feel, or behaviour.

Those wishing to customise the CMS should read about how to customise the admin interface.

Upgrade static references to asset paths

All static files (images, javascript, stylesheets, fonts) used for the CMS and forms interfaces in framework and cms have moved locations. These assets are now placed in a client/ subfolder, to account for a structural change where both javascript and styles are co-located in component-specific folders. This will affect you if you have used Requirements::block() on files in the framework/ or cms/ folder. Care should also be taken when referencing images in these folders from your own stylesheets (url()), or via SilverStripe templates (<img> tags).

Requirements now throws an exception then a file is not found, rather than failing silently, so check your Requirements are pointing to files that exist.

framework/javascript       => silverstripe-admin/client/dist/
framework/javascript/lang  => silverstripe-admin/client/lang/
framework/images           => silverstripe-admin/client/dist/images/
framework/css              => silverstripe-admin/client/dist/css/
framework/scss             => silverstripe-admin/client/src/styles/
admin/javascript/          => silverstripe-admin/client/src/
admin/javascript/src/      => silverstripe-admin/client/src/legacy/ (mostly)
admin/javascript/lang/     => silverstripe-admin/client/lang/
admin/scss/                => silverstripe-admin/client/styles/legacy/
admin/css/                 => silverstripe-admin/client/dist/css/
admin/css/screen.css       => silverstripe-admin/client/dist/css/bundle.css
admin/images/              => silverstripe-admin/client/dist/images/
admin/images/sprites/src/  => silverstripe-admin/client/src/sprites/
admin/images/sprites/dist/ => silverstripe-admin/client/dist/sprites/
admin/font/                => silverstripe-admin/client/dist/font/

Most JavaScript files in framework/javascript have been removed, and are bundled through Webpack into a combined file instead. If you have referenced these files elsewhere, please consider running the ES6 source files in admin/client/src/legacy through your own transpiling and bundle process.

This also includes JavaScript i18n support, and the removal of the i18n::js_i18n configuration option used in Requirements::add_i18n_javascript().

SilverStripe core is moving away from Requirements::combine_files in favour of Webpack as of 4.0. Requirements::combine_files is being considered for deprecation in future versions.

All JavaScript thirdparty dependencies have either been moved to NPM (see package.json), or moved into the framework/admin/thirdparty folder. If you are hotlinking to any of these files, please consider packaging your own versions in your projects and modules. For CMS modules, you can also use many library globals which the core bundles already expose (see Build Tooling).

One commonly linked thirdparty dependency is jquery.js bundled with SilverStripe:

framework/thirdparty/jquery/jquery.js => framework/admin/thirdparty/jquery/jquery.js

Explicit text casting is now enforced on all template variables

Now whenever a $Variable is used in a template, regardless of whether any casts or methods are suffixed to the reference, it will be cast to either an explicit DBField for that field, or the value declared by the default_cast on the parent object.

The default value of default_cast is Text, meaning that now many cases where a field was left un-uncoded, this will now be safely encoded via Convert::raw2xml. In cases where un-cast fields were used to place raw HTML into templates, this will now encode this until explicitly cast for that field.

You can resolve this in your model by adding an explicit cast to HTML for those fields.

Before:

use SilverStripe\View\ViewableData;
use SilverStripe\Core\Convert;

class MyObject extends ViewableData
{
    public function getSomeHTML
    {
        $title = Convert::raw2xml($this->Title);
        return "<h1>{$title}</h1>";
    }
}

After:

class MyObject extends SilverStripe\View\ViewableData
{
    private static $casting = [
        'SomeHTML' => 'HTMLText'
    ];

    public function getSomeHTML
    {
        $title = SilverStripe\Core\Convert::raw2xml($this->Title);
        return "<h1>{$title}</h1>";
    }
}

If you need to encode a field (such as HTMLText) for use in html attributes, use .ATT instead, or if used in an actual XML file use .CDATA (see template casting).

Upgrade code that uses SQLQuery

Where your code once used SQLQuery you should now use SQLSelect in all cases, as this has been removed (check the 3.2.0 upgrading notes).

Upgrade code that uses UploadField

This field has been superceded by a new class provided by the asset-admin module, which provides a more streamlined simpler mechanism for uploading File dataobjects.

A helper service FileHandleField is provided to assist with dependency injection. Where the asset-admin module is not installed this service will fall back to the FileField class instead. Usages of UploadField will need to be upgraded as below.

Before:

use SilverStripe\Forms\FieldList;
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\ORM\DataObject;

class MyClass extends DataObject
{
    public function getCMSFields()
    {
        return new FieldList(
            new UploadField('Files')
        );
    }
}

After:

use SilverStripe\ORM\DataObject;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FileHandleField;

class MyClass extends DataObject
{
    public function getCMSFields()
    {
        return FieldList::create(
            Injector::inst()->create(FileHandleField::class, 'Files')
        );
    }
}

Upgrade code that uses i18n

In many cases, localisation strings which worked in 3.x will continue to work in 4.0, however certain patterns have been deprecated and will be removed in 5.0. These include:

  • _t calls with sprintf-style placeholders (%s). Replace with named placeholders instead.
  • _t calls with non-associative injection arguments. Please use an associative array for all arguments.
  • _t calls which do not include a default value will now raise a warning. This can be disabled by setting the i18n.missing_default_warning config to false. Note: If you attempt to use non-associative injection arguments with named placeholders, the result will now trigger an exception.

Implementors of i18nEntityProvider should note that the return type for provideI18nEntities() has changed as well. The non-associative array return type is deprecated. If returning a default string for a module other than itself, it should return an array with the default and module keys respectively.

Full locale-rule respecting localisation for plural forms is now supported. The default key for an object plural form is <Namespaced\ClassName>.PLURALS, and follows CLDR array form for each pluralisation. See the CLDR chart for reference.

The below demonstrates how you can provide new localisation strings for an object, including both plurals and cross-module localisations.

use SilverStripe\ORM\DataObject;

class MyObject extends DataObject, implements i18nEntityProvider
{
    public function provideI18nEntities()
    {
        return [
            'MyObject.SINGULAR_NAME' => 'object',
            'MyObject.PLURAL_NAME' => 'objects',
            'MyObject.PLURALS' => [
                'one' => 'An object',
                'other' => '{count} objects',
            ],
            'AnotherSection.DESCRIPTION' => [
                'default' => 'This is the description for this section',
                'module' => 'extendedmodule',
            ],
        ];
    }
}

In YML format this will be expressed as the below:

mymodule/lang/en.yml:

en:
  MyObject:
    SINGULAR_NAME: 'object'
    PLURAL_NAME: 'objects'
    PLURALS:
      one: 'An object',
      other: '{count} objects'

extendedmodule/lang/en.yml:

en:
  AnotherSection:
    DESCRIPTION: 'This is the description for this section'

Usage of these pluralised strings is through the existing _t() method, and require a | pipe-delimeter with a {count} argument.

public function pluralise($count)
{
    return _t('MyObject.PLURALS', 'An object|{count} objects', [ 'count' => $count ]);
}

In templates this can also be invoked as below:

<%t MyObject.PLURALS 'An item|{count} items' count=$Count %>

Removed Member.DateFormat and Member.TimeFormat database settings

We're using native HTML5 date and time pickers in DateField and TimeField now (discussion), where the browser localises the output based on the browser/system preferences. In this context it no longer makes sense to give users control over their own date and time formats in their CMS profile. Consequently, we've also removed MemberDatetimeOptionsetField. Member->getDateFormat() and Member->getTimeFormat() still exist, and default to the IntlDateFormatter defaults for the selected locale.

New asset storage mechanism

File system has been abstracted into an abstract interface. By default, the out of the box filesystem uses Flysystem with a local storage mechanism (under the assets directory).

Because the filesystem now uses the sha1 of file contents in order to version multiple versions under the same filename, the default storage paths in 4.0 will not be the same as in 3.

In order to retain existing file paths in line with framework version 3 you should set the \SilverStripe\Filesystem\Flysystem\FlysystemAssetStore.legacy_paths config to true. Note that this will not allow you to utilise certain file versioning features in 4.0.

SilverStripe\Filesystem\Flysystem\FlysystemAssetStore:
  legacy_paths: true

See our "File Management" guide for more information.

Depending on your server configuration, it may also be necessary to adjust your assets folder permissions. Please see the common installation problems guide for configuration instruction.

Migrating File DataObject from 3.x to 4.0

Since the structure of File dataobjects has changed between 3.0 and 4.0, a new task MigrateFileTask has been added to assist in migration of legacy files.

You can run this task on the command line:

$ ./framework/sake dev/tasks/MigrateFileTask

For more information on this task please consult the file migration documentation.

Note that any File dataobject which is not in the File.allowed_extensions config will be deleted from the database during migration. Any invalid file on the filesystem will not be deleted, but will no longer be attached to a dataobject anymore, and should be cleaned up manually.

To disable this, set the following config:

SilverStripe\Assets\FileMigrationHelper:
  delete_invalid_files: false

Upgrade code which acts on Image

As all image-specific manipulations has been refactored from Image into an ImageManipulations trait, which is applied to both File and DBFile. These both implement a common interface AssetContainer, which has the getIsImage() method. In some cases, it may be preferable to invoke this method to detect if the asset is an image or not, rather than checking the subclass, as the asset may also be a DBFile with an image filter applied, rather than an instance of the Image dataobject.

In addition, a new file category image/supported has been added, which is a subset of the image category. This is the subset of all image types which may be assigned to the [Image](api:SilverStripe\Assets\Image) dataobject, and may have manipulations applied to it. This should be used as the file type restriction on any [UploadField](api:SilverStripe\AssetAdmin\Forms\UploadField) which is intended to upload images for manipulation.

-if($file instanceof Image) {
-    $upload = new UploadField();
-    $upload->setAllowedFileCategories('image');
-}
+if ($file->getIsImage()) {
+    $upload = new UploadField();
+    $upload->setAllowedFileCategories('image/supported');
+}

In cases where image-only assets may be assigned to relationships then your datamodel should specify explicitly an Image datatype, or refer to DBFile('image/supported').

use SilverStripe\Assets\Image;
class MyObject extends SilverStripe\ORM\DataObject
{
    private static $has_one = [
        "ImageObject" => Image::class
    ];
    private static $db = [
        "ImageField" => "DBFile('image/supported')"
    ];
}

Upgrading code that writes to File dataobjects, or writes files to the 'assets' folder

In the past all that was necessary to write a File DataObject to the database was to ensure a physical file existed in the assets folder, and that the Filename of the DataObject was set to the same location.

Since the storage of physical files is no longer a standard location, it's necessary to delegate the writing of such files to the asset persistence layer. As a wrapper for an individual file, you can use any of the setFrom* methods to assign content from a local (e.g. temporary) file, a stream, or a string of content. You would need to upgrade your code as below.

-function importTempFile($tmp)
-{
-    copy($tmp, ASSETS_PATH . '/imported/' . basename($tmp));
-    $file = new File();
-    $file->setFilename('assets/imported/'.basename($tmp));
-    $file->write();
-}
+public function importTempFile($tmp)
+{
+   Versioned::reading_stage('Stage');
+   $file = new File();
+   $file->setFromLocalFile($tmp, 'imported/' . basename($tmp));
+    $file->write();
+    $file->doPublish();
+}

Note that 'assets' is no longer present in the new code, and the path beneath what was once assets is now used to generate the 'filename' value. This is because there is no longer an assumption that files are stored in the assets folder.

There are other important considerations in working with File dataobjects which differ from legacy:

  • File synchronisation is no longer automatic. This is due to the fact that there is no longer a 1-to-1 relationship between physical files and File DataObjects.
  • Folder DataObjects are now purely logical DataObjects, and perform no actual filesystem folder creation on write.
  • All Files are versioned, which means that by default, new File records will not be visibile to the public site. You will need to make sure to invoke ->doPublish() on any File DataObject you wish visitors to be able to see.

You can disable File versioning by adding the following to your _config.php

SilverStripe\Assets\File::remove_extension('Versioned');

Upgrading code performs custom image manipulations

As file storage and handling has been refactored into the abstract interface, many other components which were once specific to Image.php have now been moved into a shared ImageManipulation trait. Manipulations of file content, which are used to generate what are now called "variants" of assets, is now a generic api available to both File and DBFile classes through this trait.

Custom manipulations, applied via extensions, must be modified to use the new API. For instance, code which sizes images to a fixed width should be updated as below:

Before:

// in MyImageExtension.php
class MyImageExtension extends SilverStripe\ORM\DataExtension
{
    public function GalleryThumbnail($height)
    {
        return $this->getFormattedImage('GalleryThumbnail', $height);
    }

    public function generateGalleryThumbnail(Image_Backend $backend, $height)
    {
        return $backend->paddedResize(300, $height);
    }
}

// in _config.php
SilverStripe\Assets\Image::add_extension('MyImageExtension');

Now image manipulations are implemented with a single method via a callback generator:

use SilverStripe\Assets\File;

// in MyImageExtension.php
class MyImageExtension extends SilverStripe\Core\Extension
{
    public function GalleryThumbnail($height)
    {
        // Generates the manipulation key
        $variant = $this->owner->variantName(__FUNCTION__, $height);

        // Instruct the backend to search for an existing variant with this key,
        // and include a callback used to generate this image if it doesn't exist
        return $this->owner->manipulateImage($variant, function (Image_Backend $backend) use ($height) {
            return $backend->paddedResize(300, $height);
        });
    }
}

// in _config.php
File::add_extension('MyImageExtension');
\SilverStripe\Filesystem\Storage\DBFile::add_extension('MyImageExtension');

There are a few differences in this new API:

  • The extension is no longer specific to DataObjects, so it uses the generic Extension class instead of DataExtension
  • This extension is added to both DBFile and File, or order to make this manipulation available to non-dataobject file references as well, but it could be applied to either independently.
  • A helper method variantName is invoked in order to help generate a unique variant key. Custom code may use another generation mechanism.
  • Non-image files may also have manipulations, however the specific manipulateImage should not be used in this case. A generic manipulate method may be used, although the callback for this method both is given, and should return, an AssetStore instance and file tuple (Filename, Hash, and Variant) rather than an Image_Backend.

Upgrading code that uses File or Image shortcode handler

The handle_shortcode methods have been removed from the core File and Image classes and moved to separate classes in their own respective namespace. Image and File do not implement the ShortcodeHandler interface anymore.

The shortcode handler for File has been moved to SilverStripe\Assets\ShortcodesFileShortcodeProvider and the Image handler has been moved to SilverStripe\Assets\Shortcodes\ImageShortcodeProvider

Before this change, to use the handle_shortcode method, you would do something like this:

<?php

class MyShortcodeUser extends Object 

{

    private $content;

    public function Content($arguments, $parser, $shortcode) 
    {
        return File::handle_shortcode($arguments, $this->content, $parser, $shortcode);
    }
}

In the new situation, this would look like this:

<?php 

use SilverStripe\Assets\Shortcodes\FileShortcodeProvider;

class MyShortcodeUser extends Object 

{

    private $content;

    public function Content($arguments, $parser, $shortcode) 
    {
        return FileShortcodeProvider::handle_shortcode($arguments, $this->content, $parser, $shortcode);
    }
}

Upgrading code that uses composite db fields.

The CompositeDBField interface has been replaced with an abstract class, DBComposite. In many cases, custom code that handled saving of content into composite fields can be removed, as it is now handled by the base class.

The below describes the minimum amount of effort required to implement a composite DB field.

use SilverStripe\ORM\FieldType\DBComposite;

class MyAddressField extends DBComposite
{
    private static $composite_db = [
        'Street' => 'Varchar(200)',
        'Suburb' => 'Varchar(100)',
        'City' => 'Varchar(100)',
        'Country' => 'Varchar(100)'
    ];

    public function scaffoldFormField($title = null, $params = null)
    {
        new SilverStripe\Forms\TextField($this->getName(), $title);
    }
}

Upgrading code that references DataObject::database_fields or DataObject::db

These methods have been updated to include base fields (such as ID, ClassName, Created, and LastEdited), as well as composite DB fields.

DataObject::database_fields does not have a second parameter anymore, and can be called directly on an object or class. E.g. Member::database_fields(). If user code requires the list of fields excluding base fields, then use custom_database_fields instead, or make sure to call unset($fields['ID']); if this field should be excluded.

DataObject:db() will return all logical fields, including foreign key ids and composite DB Fields, alongside any child fields of these composites. This method can now take a second parameter $includesTable, which when set to true (with a field name as the first parameter), will also include the table prefix in Table.ClassName(args) format.

Upgrade code that uses SQLQuery

SQLQuery is still implemented, but now extends the new SQLSelect class and has some methods deprecated. Previously this class was used for both selecting and deleting, but these have been superceded by the specialised SQLSelect and SQLDelete classes.

Take care for any code or functions which expect an object of type SQLQuery, as these references should be replaced with SQLSelect. Legacy code which generates SQLQuery can still communicate with new code that expects SQLSelect as it is a subclass of SQLSelect, but the inverse is not true.

Upgrade BuildTask classes

Similarly to the $table_name configuration property for DataObjects, you should define a private static $segment for BuildTask instances to ensure that you can still run your task via sake dev/tasks/MyTask. Without defining it, the default will be a fully-qualified class name like sake dev/tasks/Me-MyModule-Tasks-MyTask. This can also be configured in YAML.

use SilverStripe\Dev\BuildTask;

class MyTask extends BuildTask
{
    private static $segment = 'MyTask';
}

Upgrade implementations of augmentSQL

Since this method now takes a SQLSelect as a first parameter, existing code referencing the deprecated SQLQuery type will raise a PHP error.

Before:

function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null)
{
    $locale = Translatable::get_current_locale();
    if(!preg_match('/("|\'|`)Locale("|\'|`)/', implode(' ', $query->getWhere()))) {
        $qry = sprintf('"Locale" = \'%s\'', Convert::raw2sql($locale));
        $query->addWhere($qry);
    }
}

After:

public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
{
    $locale = Translatable::get_current_locale();
    if (!preg_match('/("|\'|`)Locale("|\'|`)/', implode(' ', $query->getWhereParameterised($parameters))))  {
        $query->addWhere([
            '"Locale"' => $locale
        ]);
    }
}

Moved ErrorPage into a new module

ErrorPage has been moved to a separate silverstripe/errorpage module to allow for alternative approaches to managing error responses. The module is installed by default on new projects, but needs to be added to existing projects to preserve functionality on the existing "Page not found" and "Server error" pages in the CMS.

composer require silverstripe/errorpage

Alternatively you can implement your own onBeforeHTTPError() handling to present custom errors. By default, SilverStripe will display a plaintext "not found" message when the module isn't installed. Check the module upgrading guide for more configuration API changes on the ErrorPage class.

Upgrading asset web.config, .htaccess, or other server configuration

Server configuration files for /assets are no longer static, and are regenerated via a set of standard SilverStripe templates on flush. These templates include:

  • Assets_HTAccess.ss: Template for public permissions on the Apache server.
  • Assets_WebConfig.ss: Template for public permissions on the IIS server.
  • Protected_HTAccess.ss: Template for the protected store on the Apache server (should deny all requests).
  • Protected_WebConfig.ss: Template for the protected store on the IIS server (should deny all requests).

You will need to make sure that these files are writable via the web server, and that any necessary configuration customisation is done via overriding these templates.

Depending on your server configuration, it may also be necessary to adjust your assets folder permissions. Please see the common installation problems guide for configuration instruction.

If upgrading from an existing installation, make sure to invoke ?flush=all at least once.

See our "File Security" guide for more information.

ListboxField is now multiple-only

Previously, this field would operate as either a single select (default) or multi-select by setting setMultiple to either true or false.

Now this field should only be used for multi-selection. Single-selection should be done using a regular DropdownField.

GroupedDropdownField::setDisabled now only accepts a list of values.

Where previously you could specify a list of grouped values in the same way as setSource, this method now only accepts either a non-associative array of values (not titles) or an SS_List of items to disable.

Upgrading TinyMCE to 4.0

Please see the tinymce upgrading guide to assist with upgrades to customisations to tinymce 3.

In Framework 4.0 the user interface for TinyMCE has been trimmed down considerably, with certain toolbar buttons removed from the default cms configuration. These include:

  • Strikethrough
  • Styles dropdown
  • Block quotes
  • Horizontal Rule
  • Undo / Redo
  • Cut / Paste as word
  • Select all
  • Fullscreen

However, these function may be enabled on a case by case basis through modifification of the default tinymce config, or by creating custom configurations.

The optional ss_macron plugin for inserting Māori diacritical marks has been removed from core. You can configure the built-in charmap plugin instead:

$editor = SilverStripe\Forms\HTMLEditor\HtmlEditorConfig::get('cms');
$editor->enablePlugins('charmap');
$editor->addButtonsToLine(1, 'charmap');
$editor->setOption('charmap_append', [
    ['256','A - macron'],
    ['274','E - macron'],
    ['298','I - macron'],
    ['332','O - macron'],
    ['362','U - macron'],
    ['257','a - macron'],
    ['275','e - macron'],
    ['299','i - macron'],
    ['333','o - macron'],
    ['363','u - macron']
]);

For more information on available options and plugins please refer to the TinyMCE documentation

Upgrading DataObjects with the Versioned extension

In most cases, versioned models with the default versioning parameters will not need to be changed. However, there are now additional restrictions on the use of custom stage names.

Rather than declaring the list of stages a model has, the constructor for Versioned will take a single mode parameter, which declares whether or not the model is versioned and has a draft and live stage, or alternatively if it only has versioning without staging.

Each form of this extension is registered under the appropriate service identifier, which you should use in your model as below:

use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Versioning\Versioned;

/**
 * This model has staging and versioning. Stages will be "Stage" and "Live"
 */
class MyStagedModel extends SilverStripe\ORM\DataObject
{
    private static $extensions = [
        Versioned::class . '.stagedversioned',
    ];
}

/**
 * This model has versioning only, and will not has a draft or live stage, nor be affected by the current stage.
 */
class MyVersionedModel extends DataObject
{
    private static $extensions = [
        Versioned::class . '.versioned',
    ];
}

Additionally, the following api methods have been added:

  • Versioned::publishRecursive Publishes this object, and all owned objects
  • Versioned::publishSingle Publishes this object, but not owned objects
  • Versioned::copyVersionToStage Replaces the old publish method.

These methods are deprecated:

  • Versioned::publish Replaced by Versioned::copyVersionToStage
  • Versioned::doPublish Replaced by Versioned::publishRecursive

Implementation of ownership API

In order to support the recursive publishing of dataobjects, a new API has been developed to allow developers to declare dependencies between objects. This is done to ensure that the published state of linked components are consistent with their "owner." Without the concept of ownership, these linked components could be implicitly exposed on the frontend, which may not align with the intent of the content author.

For instance, on a products page which has a list of products, the products should not be published unless the products page is, too. The ownership API solves this by allowing you to declare a two-way relationship between objects, typically, but not necessarily, linked by a database relationship (has_many, many_many, etc.).

use SilverStripe\Versioned\Versioned;
use Page;
use SilverStripe\ORM\DataObject;

class ProductPage extends Page 
{
    private static $has_many = [
        'Products' => Product::class
    ];

    private static $owns = [
        'Products'
    ];
}

class Product extends DataObject 
{
    private static $extensions = [
        Versioned::class
    ];

    private static $has_one = [
        'Parent' => ProductPage::class
    ];
}

If your objects are linked by something other than a database relationship, for instance, a custom getter that is computed at runtime, the same rules can be applied, as long as you provide an $owned_by setting on the child object.

For more information, see the DataObject ownership documentation and the versioning documentation

ChangeSet batch publishing

ChangeSet objects have been added, which allow groups of objects to be published in a single atomic transaction. This API will utilise the ownership API to ensure that changes to any object include all necessary changes to owners or owned entities within the same changeset.

New [image] shortcode in HTMLText fields

The new Ownership API relies on relationships between objects. Many of these relationships are already made explicit through has_one, has_many and many_many. Images inserted into HTMLText fields (through a WYSIWYG editor) need to be tracked as well. Instead of <img> tags, the field will insert [image] shortcodes which point to the database identifier of the Image record rather than its path on the filesystem. The shortcode will be automatically replaced when the field is rendered. Newly inserted images will automatically receive the shortcode and ownership tracking, and existing <img> will continue to work.

Upgrading references to DBField and subclasses

A major change in 4.0 is the introduction of namespaced DBField subclasses. Now as a standard, all DBField subclasses have a DB prefix, are namespaced, and have an associative alias which omits the DB prefix.

This means that for the most part, code that worked in 3.0 won't need to be changed, although if you have any hard class literals which reference the old classes, they will need to be updated to point to the new namespaced classes.

An exception to this is any classes which once had the SS_ prefix, which will now be instead prefixed with DB, and have an un-aliased prefix. For example SS_Datetime is now DBDateTime, and has the alias DateTime which may be used in config.

Before:

use SilverStripe\ORM\DataObject;

class MyObject extends DataObject
{
    private static $db = [
        'Number' => 'Int',
        'Time' => 'SS_Datetime'
    ];

    /**
     * @param Int $val
     * @return Varchar
     */
    public function TextNumber()
    {
        return new Varchar('TextNumber', 'Number is ' . $this->Number);
    }
}

After:

use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBVarchar;

class MyObject extends DataObject
{
    private static $db = [
        'Number' => 'Int',
        'Time' => 'Datetime'
    ];

    /**
     * @param Int $val
     * @return Varchar
     */
    public function TextNumber()
    {
        return new DBVarchar('TextNumber', 'Number is ' . $this->Number);
    }
}

Note that string references to SS_Datetime passed to injector, or used in config values, will still work, and will refer to the updated class names.

Upgrading from deprecated RestfulService

Install Guzzle to get an API consuming library. composer require guzzlehttp/guzzle or add guzzlehttp/guzzle: "^6.0" to your composer.json.

For information on how to use Guzzle, please see the extensive Guzzle documentation

In case you want to keep using RestfulService, you can use Firesphere/silverstripe-restfulservice, but it is unmaintained and deprecated.

Upgrading from deprecated Oembed

Instead of Oembed, the framework now relies on oscarotero/Embed to handle getting the shortcode-data for embedding. If you have custom embedding-code relying on Oembed, please refer to the documentation provided by oscarotero.

Admin URL can now be configured via custom Director routing rule

The default admin/ URL to access the CMS interface can now be changed via a custom Director routing rule for AdminRootController. If your website or module has hard coded admin URLs in PHP, templates or JavaScript, make sure to update those with the appropriate function or config call. See CMS architecture for language specific functions.

Upgrading custom Authenticators

The methods register and unregister on Authenticator are deprecated in favor of the Config system. This means that any custom Authenticator needs to be registered through the yml config. For further information on how to create a custom authentication method, see the Authentication documentation.

SilverStripe\Security\Authenticator;
  authenticators:
    - MyVendor\MyModule\MyAuthenticator

If there is no authenticator registered, Authenticator will try to fall back on the default_authenticator, which can be changed using the following config, replacing the MemberAuthenticator with your authenticator:

SilverStripe\Security\Authenticator:
  default_authenticator: SilverStripe\Security\MemberAuthenticator

As soon as a custom authenticator is registered, the default authenticator will not be available anymore, unless enabled specifically in the config.

By default, the SilverStripe\Security\MemberAuthenticator is seen as the default authenticator until it's explicitly set in the config.

IdentityStore and RequestFilters

As of SilverStripe 4, every request is authenticated against an IdentityStore, by default a CookieAuthenticationHandler and a SessionAuthenticationHandler, which are called from the AuthenticationHandler. If there is a valid Member, it is set on Security::setCurrentUser(), which defaults no null.

IdentityStores are responsible for logging members in and out (e.g. destroy cookies and sessions, or instantiate them).

Upgrading Config API usages

Performance optimisations have been made to Config which, under certain circumstances, require developer care when modifying or caching config values. The top level config object is now immutable on application bootstrap, and requires a developer to invoke Config::modify() to make mutable prior to modification. This will immediately have a slight performance hit, so should be done sparingly, and avoided at all if possible in performance intensive applications.

In addition, the Config::inst()->update() method is deprecated, and replaced with Config::modify()->set() and Config::modify()->merge() to respectively replace and merge config.

When config is merged (either via modification or merged between yml blocks) falsey-values (including nulls) now replace any prior values (even arrays).

One removed feature is the Config::FIRST_SET option. Either use uninherited config directly on the class directly, or use the inherited config lookup. As falsey values now overwrite all parent class values, it is now generally safer to use the default inherited config, where in the past you would need to use FIRST_SET.

Upgrading Cache API

We have replaced the unsupported Zend_Cache library with symfony/cache. This also allowed us to remove SilverStripe's Cache API and use dependency injection with a standard PSR-16 cache interface instead.

Caches should be retrieved through Injector instead of Cache::factory(), and have a slightly different API (e.g. set() instead of save()).

-$cache = Cache::factory('myCache');
+use Psr\SimpleCache\CacheInterface;
+$cache = Injector::inst()->get(CacheInterface::class . '.myCache');

// create a new item by trying to get it from the cache
-$myValue = $cache->load('myCacheKey');
+$myValue = $cache->get('myCacheKey');

// set a value and save it via the adapter
-$cache->save(1234, 'myCacheKey');
+$cache->set('myCacheKey', 1234);

// retrieve the cache item
-if (!$cache->load('myCacheKey')) {
-    // ... item does not exists in the cache
-}
+if (!$cache->has('myCacheKey')) {
+    // ... item does not exists in the cache
+}

// Remove a cache key
-$cache->remove('myCacheKey');
+$cache->delete('myCacheKey');

With the necessary minimal config in _config/mycache.yml

---
Name: mycache
---
SilverStripe\Core\Injector\Injector:
  Psr\SimpleCache\CacheInterface.myCache:
    factory: SilverStripe\Core\Cache\CacheFactory
    constructor:
      namespace: 'mycache'
Configuration Changes

Caches are now configured through dependency injection services instead of PHP. See our "Caching" docs for more details. Before (mysite/_config.php):

Cache::add_backend(
    'primary_memcached', 
    'Memcached',
    [
        'servers' => [
            'host' => 'localhost', 
            'port' => 11211, 
        ]
    ]
);
Cache::pick_backend('primary_memcached', 'any', 10);

After (mysite/_config/config.yml):

---
After:
  - '#corecache'
---
SilverStripe\Core\Injector\Injector:
  MemcachedClient:
    class: 'Memcached'
    calls:
      - [ addServer, [ 'localhost', 11211 ] ]
  SilverStripe\Core\Cache\CacheFactory:
    class: 'SilverStripe\Core\Cache\MemcachedCacheFactory'
    constructor:
      client: '%$MemcachedClient

User-code style upgrades

Although it is not mandatory to upgrade project code to follow SilverStripe and PSR-2 standard it is highly recommended to ensure that code is consistent. The below sections will assist you with bringing yourself up to speed.

Please note that before upgrading user code style it is necessary to run the standard upgrade path to fix references and usages of framework API.

Upgrading user-code to use namespaces

Upgrading code to use namespaces is quite a complex process, and as such we have provided several development tools and processes to help make the upgrade user friendly and as automated as possible.

Using the upgrader tool to automatically apply namespaces

The upgrader tool provides a feature to not only automatically namespace code, but will provide automatic upgrade of other code references to those classes.

Use the below to setup upgrader, and apply a namespace to a given code folder.

composer global require silverstripe/upgrader
cd ~/Project/Root
~/.composer/vendor/bin/upgrade-code add-namespace "WebDesignGroup\ShopSite" ./mysite/code --recursive --write

If you want to do a dry-run, omit the --write option to see a preview of a diff of all changed project files.

This task will do the following:

  • Add the given namespace to all files in the code class, and subdirectories.
  • Sub-namespaces will be applied based on directory structure
  • All references to classes in any namespaced files will be safely retained with additional use directives added as necessary.
  • Register all namespaced classese in a mysite/.upgrade.yml file for migration of other code

This task will not do the following, and must be done manually:

  • Adding table_name to any namespaced classes
  • Upgrade other references to namespaced classes outside of this folder
  • Migrate any database table records Please see the following steps for more information.
Using the upgrader tool to update references to namespaced user classes

Once a project has been namespaced all newly renamed classes will have a mapping included in the mysite/.upgrade.yml file. If you have any user-code that references these, you may need to run the upgrader again (as you did did to upgrade your project to namespaced framework classe).

composer global require silverstripe/upgrader
cd ~/Project/Root
~/.composer/vendor/bin/upgrade-code upgrade ./othercode --write
Updating custom dataobjects to use existing table names

Once you have namespaced your user code it will be necessary to customise the table_name config for your dataobjects, in order to ensure the correct table is used after upgrade. It is recommended to point this to the base name of the class, excluding namespace, as in 3.x.

namespace WebDesignGroup\ShopSite;
use SilverStripe\ORM\DataObject;
use Page;
class GalleryPage extends Page
{
+    private static $table_name = 'GalleryPage';
}
Class name remapping

If you've namespaced one of your custom page types, you may notice a message in the CMS telling you it's obsolete. This is likely because the ClassName field in the SiteTree table still contains the singular model name, e.g. GalleryPage and that when you change it to WebDesignGroup\ShopSite\GalleryPage then everything works again.

The dev/build task is configured to look for a legacy class name mapping configuration setting and will update this for you automatically. You can use this to add DB upgrading rules for your own classes.

For example, you could upgrade references to the newly namespaced Banner class by adding this to your mysite/_config/upgrade.yml file:

SilverStripe\ORM\DatabaseAdmin:
  classname_value_remapping:
    GalleryPage: WebDesignGroup\ShopSite\GalleryPage 

The next time you run a dev/build the class name for all GalleryPage pages will be automatically updated to the new WebDesignGroup\ShopSite\GalleryPage

Using php code checker to automatically update to PSR-2

You can use the php codesniffer tool to not only detect and lint PSR-2 coding errors, but also do some minimal automatic code style migration.

  • Install the necessary library: composer require squizlabs/php_codesniffer
  • Copy silverstripe standards config file from framework/phpcs.xml to your project root: cp ./framework/phpcs.xml ./phpcs.xml
  • Run the automatic upgrade tool on your code folder vendor/bin/phpcbf ./mysite/code
  • Run the automatic linting tool to detect and manually fix other errors: vendor/bin/phpcs ./mysite/code

Repeat the final step and manually repair suggested changes, as necessary, until you no longer have any linting issues.

Upgrade user-code to use PSR-4 autoloading

While not critical to an upgrade, SilverStripe 4.0 has adopted the PS-4 autoloading standard for the core modules, so it's probably a good idea to be consistent.

You can implement this in your composer configuration like so:

{
    "autoload": {
        "psr-4": {
            "WebDesignGroup\\ShopSite\\": "mysite/src/"
        }
    }
}

Now you just need to ensure that each class site in the correct folder location (including case sensitivity) to match its namespace. For example, WebDesignGroup\ShopSite\Model\GalleryItem.php should live at mysite/src/Model/GalleryItem.php.

Note that you don’t have to use src/ as the root folder name. You can continue to use code/ if you want to. SilverStripe has adopted the PSR-4 approach and have also started to use src/ as a default folder location instead of code/. If you’re going to change, it's probably a good time to do it while you're upgrading.

For examples, take a look at the file/folder structure and to the composer.json configuration in either the framework or cms modules.

Please note that there are changes to template structure which in some cases require templates to be in a folder location that matches the namespace of the class that it belongs to, e.g. themes/mytheme/templates/MyVendor/Foobar/Model/MyModel.ss.

API Changes

General and Core API

General and Core API Additions / Changes

  • Minimum PHP version raised to 5.6 (with support for PHP 7.x)
  • Dropped support for PHP safe mode (removed php 5.4).
  • Once PHP versions become unsupported by the PHP Project), we drop support for those versions in the [next minor release](/contributing/release-process This means PHP 5.6 and PHP 7.0 support will become unsupported in Dec 2018.
  • Minimum CMS browser requirement raised from Internet Explorer 8 to Internet Explorer 11
  • Updated PHPUnit from 3.7 to 4.8 (upgrade notes). Please remove any PHPUnit related require_once() calls (e.g. in FeatureContext definitions of the behat-extension module). Run composer require --dev 'phpunit/phpunit:~4.8' on existing projects to pull in the new dependency.
  • always_populate_raw_post_data will now raise a deprecation warning in install.php when running in php 5.x, unless set to -1. This is due to $HTTP_RAW_POST_DATA being removed in php 7. See the http://php.net/manual/en/reserved.variables.httprawpostdata.php for more information.
  • Admin URL can now be configured via custom Director routing rule
  • Controller::init visibility changed to protected. Use Controller::doInit() instead.
  • Controller::join_links supports an array of link sections.
  • Object::useCustomClass has been removed. You should use the config API with Injector instead.
  • Object::invokeWithExtensions now has the same method signature as Object::extend and behaves the same way.
  • ServiceConfigurationLocator is now an interface not a class.
  • i18nTextCollectorTask merge is now true by default.
  • Object has been broken up into various traits, each of which can be added to other objects independently:
    • Configurable Provides Config API helper methods
    • Injectable Provides Injector API helper methods
    • Extensible Allows extensions to be applied
  • Convert class has extra methods for formatting file sizes in php_ini compatible format
    • Convert::memstring2bytes() will parse a php_ini memory size.
    • Convert::bytes2memstring() will format the memory size with the appropriate scale.
  • SiteTree.alternatePreviewLink is deprecated. Use updatePreviewLink instead.
  • Injector dependencies no longer automatically inherit from parent classes.
  • $action parameter to Controller::Link() method is standardised.
  • Moved test database cleanup task from sake dev/tests/cleanupdb to sake dev/tasks/CleanupTestDatabasesTask
  • Injector::load given a src parameter will no longer guess the service name from the filename. Now the service name will either by the array key, or the class parameter value.
  • Uniqueness checks for File.Name is performed on write only (not in setName())
  • Created Resettable interface to better declare objects which should be reset between tests.
  • Added a server requirement for the php-intl extension (shipped by default with most PHP distributions)
  • Replaced Zend_Date and Zend_Locale with the php-intl extension.
  • Consistently use CLDR date formats (rather than a mix of CLDR and date() formats)
  • Moved config into a new module: silverstripe/config. See upgrading notes below.
  • Falsey config values (null, 0, false, etc) can now replace non-falsey values.
  • Introduced new ModuleLoader manifest, which allows modules to be found via composer name. E.g. $cms = ModuleLoader::inst()->getManifest()->getModule('silverstripe/cms')
  • ClassManifest::getOwnerModule() now returns a Module object instance.
  • Object class has been removed.
    • parse_class_spec moved to ClassInfo
    • create_from_spec functionality removed, but now supportede by Injector natively.
    • Injectable, Extensible and Configurable traits added to support other methods.
  • Certain methods have been moved from Controller to RequestHandler:
    • Link
    • redirect
    • redirectBack
  • RequestHandler link and redirection behaviour has been enhanced slightly:
    • Link now relies on the url_segment handler being provided for the class. If left unset, this will raise an error.
    • getBackURL and getReturnReferer have been added to safely inspect the current request to see if there is a url to redirect back to.
  • LeftAndMain class has had all tree manipulation code moved into CMS module class CMSMain. This includes:
    • LeftAndMain_TreeNode class -> renamed to CMSMain_TreeNode
    • SiteTreeAsUL removed (override left in CMSMain)
    • getSiteTreeFor() -> moved to CMSMain
    • getsubtree() -> moved to CMSMain
    • updatetreenodes() -> moved to CMSMain
    • savetreenodes() -> moved to CMSMain
    • EditorToolbar() method renamed to Modals() and now returns a ModalController handler instance rather than a HTMLEditorField_Toolbar
  • Some Director API have been removed.
    • $dev_servers
    • $test_servers
    • $urlParams and setUrlParams()
    • Director.alternate_host removed. Use Director.alternate_base_url instead.
    • Director.alternate_protocol removed. Use Director.alternate_base_url instead.
  • Global enviromnent variables changed:
    • 'BlockUntrustedIPS' env setting has been removed. All IPs are untrusted unless SS_TRUSTED_PROXY_IPS is set to '*' See Environment Management docs for full details.
    • SS_TRUSTED_PROXY_HOST_HEADER, SS_TRUSTED_PROXY_PROTOCOL_HEADER, and SS_TRUSTED_PROXY_IP_HEADER are no longer supported. These settings should go into the Injector service configuration for TrustedProxyMiddleware instead.
    • MODULES_PATH removed
    • MODULES_DIR removed
    • SS_HOST removed. Use SS_BASE_URL instead.
    • Member::canLogIn() now returns boolean. Use Member::validateCanLogin() to get a ValidationResult
  • Security methods deprecated:
    • has_default_admin use DefaultAdminService::hasDefaultAdmin() instead
    • check_default_admin use DefaultAdminService::isDefaultAdminCredentials() instead
    • default_admin_username use DefaultAdminService::getDefaultAdminUsername() instead
    • default_admin_password use DefaultAdminService::getDefaultAdminPassword() instead
    • setDefaultAdmin use DefaultAdminService::setDefaultAdmin() instead
    • clearDefaultAdmin use DefaultAdminService::clearDefaultAdmin() instead
    • findAnAdministrator use DefaultAdminService::findOrCreateDefaultAdmin() instead
  • Member methods deprecated:
    • checkPassword. Use Authenticator::checkPassword() instead
  • RequestFilter has been deprecated in favour of HTTPMiddleware. Also the legacy RequestFilter API has changed: $session and $dataModel variables removed from preRequest / postRequest.
  • Extension instances are now singletons and no longer are constructed once per extended object. See the 'Upgrade extensions to work as singletons' section on this page for more information.

General and Core Removed API

  • Removed ConfigureFromEnv.php as it's no longer necessary.
  • Session object has had significant refactoring. This object no longer is accessed via Session::inst(), but instead should be queried from the current request via $request->getSession(). All static methods have been removed, and the inst_ prefix removed from all instance members. Please see the upgrading section on Session object for more details.
  • Director.rules config no longer support redirect:<url> directly via config.
  • Director::get_environment_type() and Director::set_environment_type() are removed. Get the Kernel instance via injector and query getEnvironment() instead. E.g. $type = Injector::inst()->get(Kernel::class)->getEnvironment();.
  • The environment can no longer be configured via YAML (Director.environment_type). Use a .env file to manage environment settings.
  • Many global methods have been refactored into Environment or Convert class.
    • increase_xdebug_nesting_level_to removed (functionality has been inlined into AppKernel)
    • set_increase_time_limit_max moved to Environment::setTimeLimitMax()
    • get_increase_time_limit_max moved to Environment::getTimeLimitMax()
    • set_increase_memory_limit_max moved to Environment::setMemoryLimitMax()
    • get_increase_memory_limit_max moved to Environment::getMemoryLimitMax()
    • increase_time_limit_to moved to Environment::increaseTimeLimitTo()
    • increase_memory_limit_to moved to Environment::increaseMemoryLimitTo()
    • translate_memstring moved to Convert::memstring2bytes.
    • getTempFolder moved to TempFolder::getTempFolder()
    • getTempParentFolder removed.
    • getTempFolderUsername removed.
  • CMSMain::buildbrokenlinks() action is removed.
  • Injector::unregisterAllObjects() has been removed. Use unregisterObjects to unregister groups of objects limited by type instead.
  • SS_Log class has been removed. Use Injector::inst()->get(LoggerInterface::class) instead.
  • Removed CMSBatchAction_Delete
  • Removed CMSBatchAction_DeleteFromLive
  • Removed CMSMain.enabled_legacy_actions config.
  • CMSmain.getCMSTreeTitle is now ignored on extensions. Use updateCMSTreeTitle in extensions instead.
  • Removed ability to run tests via web requests (http://mydomain.com/dev/tests), use the standard CLI command instead (vendor/bin/phpunit).
  • Removed dev/jstests/ controller (no replacement)
  • Removed TestRunner and JSTestRunner APIs
  • Removed PhpUnitWrapper, PhpUnitWrapper_3_4, PhpUnitWrapper_3_5, PhpUnitWrapper_Generic, SapphireTestSuite APIs
  • Removed SapphireTestReporter and CliTestReporter
  • Removed SapphireTest::skipTest(), use markTestSkipped() in a setUp() method instead
  • Removed the History.js javascript library.
  • debugmethods querystring argument has been removed from debugging.
  • Moved ErrorPage into a new module: silverstripe/errorpage. See upgrading notes in that module.
  • Removed VirtualPage_Controller. Virtual pages will now share whichever controller the “target” page uses
  • Removed Config_LRU
  • Removed SilverStripeInjectionCreator
  • Removed i18n::get_translatable_modules method.
  • Removed i18nTextCollector_Writer_Php
  • i18nTextCollector no longer collects from themes/<theme> root dir. Modules which provide themes via <moduleName>/themes/<theme> are now preferred.
  • Removed i18nSSLegacyAdapter
  • Removed FunctionalTest::stat
  • Removed LeftAndMainMarkingFilter
  • Removed Controller::getFormOwner
  • Removed TeamCityListener
  • The Spyc YAML library has been removed from /thirdparty. Please load it yourself, or use the Symfony YAML component that’s automatically installed by composer.
  • RestfulService has been removed. Use Guzzle instead. See Upgrading notes.
  • Our self-maintained Oembed implementation has been removed, in favour of introducing oscarotero/Embed as a dependency.
  • Removed TextParser and BBCodeParser. These are available in an archived module, silverstripe-archive/bbcodeparser
  • Removed ViewableData::ThemeDir. Use ThemeResourceLoader::findThemedResource in conjunction with SSViewer::get_themes instead.
  • Removed Config::FIRST_SET and Config::INHERITED
  • Removed RequestHandler.require_allowed_actions. This is now fixed to on and cannot be disabled.
  • Config or module searching methods have been removed from ClassManifest. Use ModuleLoader to get this information instead:
    • getModules
    • getConfigDirs
    • getConfigs
  • Removed Session::set_config() and Session::get_config(). Use the Session.timeout config setting instead
  • Removed Security::set_login_recording() and Security::get_login_recording(). Use the Security.login_recording config setting instead.
  • Removed ModelAsController::find_old_page(). Use OldPageRedirector::find_old_page() instead
  • Removed RootURLController:set_default_homepage_link() and RootURLController::get_default_homepage_link(). Use the RootURLController.default_homepage_link config setting instead.
  • Removed CreditCardField, CountryDropdownField, PhoneNumberField, MemberDatetimeOptionsetField, InlineFormAction. Use custom code instead
  • Removed ResetFormAction, use FormAction::create()->setAttribute('type', 'reset') instead
  • Injector now complies with PSR-11. Accordingly, hasService() has been renamed to has(), and get() will throw SilverStripe\Core\Injector\InjectorNotFoundException when the service can't be found.
  • Removed CustomMethods::createMethod(). Use closures instead.
  • Removed Extension::$ownerBaseClass property. You should use $this->owner->baseClass() instead. The second argument of Extension::setOwner() has also been removed.

General and Core Deprecated API

A very small number of methods were chosen for deprecation, and will be removed in 5.0 rather than 4.0

  • ClassInfo::baseDataClass - Use DataObject::getSchema()->baseDataClass() instead.
  • ClassInfo::table_for_object_field - Use DataObject::getSchema()->tableForField() instead
  • Config::inst()->update() is deprecated. Use Config::modify()->set() or Config::modify()->merge() instead.

ORM API

ORM API Additions / Changes

  • Deprecate SQLQuery in favour SQLSelect
  • DataObject.many_many 'through' relationships now support join dataobjects in place of automatically generated join tables. See the /developer_guides/relations for more info.
  • DataList::filter by null now internally generates "IS NULL" or "IS NOT NULL" conditions appropriately on queries.
  • DataList::createDataObject is now public.
  • DataObject constructor now has an additional parameter, which must be included in subclasses.
  • DataObject::db now returns composite fields.
  • DataObject::ClassName field has been refactored into a DBClassName type field.
  • DataObject::can has new method signature with $context parameter.
  • DataObject::duplicate Now requires explicit flag to duplicate belongs_many_many (off by default), but now works with unsaved relations. By default only many_many are duplicated.
  • DBHTMLText no longer enables shortcodes by default. Two injector aliases have been created for this class which can be used to select the correct behaviour:
    • HTMLText: DBHTMLText with shortcodes enabled
    • HTMLFragment: DBHTMLText without shortcodes enabled (as default)
  • CMSPreviewable has been moved from the SilverStripe\Admin namespace to SilverStripe\ORM
  • Changes to DBString formatting:
    • NoHTML is renamed to Plain
    • LimitWordCountXML is removed. Use LimitWordCount instead.
    • BigSummary is removed. Use Summary instead.
    • Most limit methods on DBHTMLText now plain text rather than attempt to manipulate the underlying HTML.
  • FormField::Title and FormField::RightTitle are now cast as plain text by default (but can be overridden).
  • FormField#createTag() has been renamed to FormField::create_tag()
  • Hierarchy class has had much of it's functionality refactored out into MarkedSet:
    • isMarked
    • isTreeOpened
    • isExpanded
    • markByID
    • markPartialTree
    • markExpanded
    • markUnexpanded
    • markToExpose
    • markClosed
    • markOpened
    • markedNodeIDs
    • getChildrenAsUL replaced with renderChildren, which now takes a template name.
    • markingFilterMatches (and made protected)
    • markChildren (and made protected)
  • Removed DataList::applyFilterContext private method
  • Search filter classes (e.g. ExactMatchFilter) are now registered with Injector via a new DataListFilter. prefix convention. see search filter documentation for more information.
  • Permission::flush_permission_cache() renamed to reset() and added to Resettable interface.
  • Versioned API has some breaking changes:
    • Versioned constructor now only allows a single string to declare whether staging is enabled or not. The number of names of stages are no longer able to be specified. See below for upgrading notes for models with custom stages.
    • reading_stage is now set_stage and throws an error if setting an invalid stage.
    • current_stage is now get_stage
    • getVersionedStages is gone.
    • get_live_stage is removed. Use the Versioned::LIVE constant instead.
    • getDefaultStage is removed. Use the Versioned::DRAFT constant instead.
    • $versionableExtensions is now private static instead of protected static
    • hasStages is addded to check if an object has a given stage.
    • stageTable is added to get the table for a given class and stage.
    • Any extension declared via versionableExtensions config on Versioned dataobject must now VersionableExtension interface at a minimum. Translatable has been removed from default versionableExtensions
    • The default CMS delete behaviour for versioned dataobjects is now to delete from both draft and live stages, and to save to the archive. There is no longer a separate "Archive" action.
    • Any writes to versioned dataobjects (in either Draft or Live) will always write to the draft (main) table as a source of truth. This means that records created in Live mode will always be available to the CMS and in draft mode.
    • _versions suffixed tables are now renamed to _Versions. This fix will be automatically applied during dev/build.
  • A lot of standard versioned API has been refactored from SiteTree into Versioned extension.
    • All versioned DataObjects have canPublish(), canArchive(), canUnpublish() permission checks
    • All versioned Dataobjects have doPublish(), doArchive(), doPublish(), and doUnpublish() actions. However, do*() methods will no longer automatically check can*() permissions, and must be done by usercode before invocation.
    • SiteTree::getIsAddedToStage() moved to Versioned and renamed to isOnDraftOnly()
    • SiteTre::getIsModifiedOnStage() moved to Versioned and renamed to isModifiedOnDraft()
    • SiteTree::isPublished() moved to Versioned.
    • SiteTree::getExistsOnLive() removed in favour of isPublished()
    • isOnDraft() added to Versioned.
    • isArchived() added to Versioned.
    • isOnLiveOnly() added to Versioned.
  • ChangeSet and ChangeSetItem have been added for batch publishing of versioned dataobjects.
  • DataObject.table_name config can now be used to customise the database table for any record.
  • DataObjectSchema class added to assist with mapping between classes and tables.
  • DataObject.indexes now uses columns instead of value to define index contents.
  • DBMoney values are now treated as empty only if Amount field is null. If an Amount value is provided without a Currency specified, it will be formatted as per the current locale.
  • Removed DatabaseAdmin#clearAllData(). Use DB::get_conn()->clearAllData() instead
  • SapphireTest temp DB methods have been removed and put into a new TempDatabase class. This allows applications to create temp databases when not running tests. This class now takes a connection name as a constructor class, and no longer has static methods. The following methods have been moved to this new class and renamed as non-static:
    • using_temp_db -> isUsed()
    • kill_temp_db -> kill()
    • empty_temp_db _> clearAllData()
    • create_temp_db -> build()
    • delete_all_temp_dbs -> deleteAll()
    • resetDBSchema -> resetSchema()

The below methods have been added or had their functionality updated to DBDate, DBTime and DBDatetime

  • getTimestamp() added to get the respective date / time as unix timestamp (seconds since 1970-01-01)
  • Format() method now use CLDR format strings, rather than PHP format string. E.g. d/m/Y H:i:s (php format) should be replaced with to dd/MM/y HH:mm:ss (CLDR format).
  • getISOFormat() added which returns the standard date/time ISO 8601 pattern in CLDR format.
  • setValue method is now a lot more restrictive, and expects dates and times to be passed in ISO 8601 format (y-MM-dd) or (HH:mm:ss). Certain date formats will attempt to parse with the below restrictions:
    • /, . or - are supported date separators, but will be replaced with - internally.
    • US date formats (m-d-y / y-d-m) will not be supported and may be parsed incorrectly. (Note: Date form fields will still support localised date formats).
    • dd-MM-y will be converted to y-MM-dd internally.
    • 2-digit values for year will now raise errors.
  • FormatFromSettings will default to Nice() format if no member is logged in.
  • Nice, Long and Full methods will now follow standard formatting rules for the current locale, rather than pre-defined formats.
  • Short added to format concise date/time values, as a shorter version than Nice
  • getFormatter method added, which returns a locale-specific date/time formatter.

DBTime specific changes:

  • Added DBTime::FormatFromSettings

ORM Removed API

  • Deprecated globals $database and $databaseConfig. Please use DB::setConfig() instead.
  • DataModel removed
  • DataObject::can* methods no longer accept a member ID. These must now be passed a Member object or left null
  • DataObject::db removed and replaced with DataObjectSchema::fieldSpec and DataObjectSchema::fieldSpecs
  • DataObject::manyManyComponent moved to DataObjectSchema
  • DataObject::belongsToComponent moved to DataObjectSchema
  • DataObject::hasOneComponent moved to DataObjectSchema
  • DataObject::hasManyComponent moved to DataObjectSchema
  • DataObject::getRemoteJoinField moved to DataObjectSchema
  • DataObject::database_fields renamed and moved to DataObjectSchema::databaseFields
  • DataObject::has_own_table renamed and moved to DataObjectSchema::classHasTable
  • DataObject::composite_fields renamed and moved to `DataObjectSchema::compositeFields``
  • DataObject::manyManyExtraFieldsForComponent moved to DataObjectSchema
  • DataObject::$destroyed is now deprecated
  • Removed DataObject::validateModelDefinitions. Relations are now validated within DataObjectSchema
  • Removed DataObject methods hasOwnTableDatabaseField, has_own_table_database_field and hasDatabaseFields are superceded by DataObjectSchema::fieldSpec. Use $schema->fieldSpec($class, $field, DataObjectSchema::DB_ONLY | DataObjectSchema::UNINHERITED ). Exclude uninherited option to search all tables in the class hierarchy.
  • Removed DataObject::is_composite_field. Use DataObjectSchema::compositeField instead.
  • Removed DataObject::custom_database_fields. Use DataObjectSchema::databaseFields or DataObjectSchema::fieldSpecs instead.
  • Removed DataList::getRelation, as it was mutable. Use DataList::applyRelation instead, which is immutable.
  • Removed DataList::applyFilterContext private method
  • Member Field 'RememberLoginToken' removed, replaced with 'RememberLoginHashes' has_many relationship
  • Removed UpgradeSiteTreePermissionSchemaTask
  • Removed EncryptAllPasswordsTask
  • Removed DBString::LimitWordCountXML() method. Use LimitWordCount for XML safe version.
  • Removed SiteTree::getExistsOnLive(). Use isPublished() instead.
  • Removed SiteTree::getIsDeletedFromStage(). Use isOnDraft() instead (inverse case).
  • DataObject.many_many no longer supports triangular resolution. Both the many_many and belongs_many_many must point directly to the specific class on the opposing side, not a subclass or parent.
  • DataObject::validateModelDefinitions() has been removed. Validation and parsing of config is now handled within DataObjectSchema.
  • CMSBatchAction_Delete removed. Use CMSBatchAction_Archive instead.
  • Removed several DBDate methods:
    • past_date
    • prior_monday
    • weekday
    • next_day
    • day_before
    • days_between
  • nice_format has been removed from DBDate / DBTime / DBDatetime has been removed in favour of locale-specific formatting for Nice()
  • Removed several DBTime methods:
    • TwelveHour
    • Nice24
  • Removed some DBMoney methods due to lack of support in php-intl.
    • NiceWithShortname
    • NiceWithName
    • getShortName
    • getCurrencyName
  • Removed additional arguments from DBMoney::getSymbol. The result of this value is now localised based on the currency code assigned to the DBMoney instance
  • Removed DBMoney::getAllowedCurrencies. Apply validation to MoneyField instead.
  • Hierarchy has lots of removed api:
    • parentStack() removed. Use getAncestors() instead
    • doAllChildrenIncludingDeleted() removed. Use AllChildrenIncludingDeleted() instead.
    • naturalNext removed.
    • naturalPrev removed.
    • markingFinished removed.

Filesystem API

Filesystem API Additions / Changes

  • Image manipulations have been moved into a new [ImageManipulation](api:SilverStripe\Assets\ImageManipulation) trait.
  • CMSFileAddController removed.
  • UploadField::setAllowedFileCategories('image') now excludes non-resizeable images. 'unresizeable_image' is can be used to validate these types.
  • Image_Backend API now loads and saves from AssetContainer instances rather than local files.
  • The following File categories have been renamed: 'zip' to 'archive', 'doc' to 'document', and 'mov' to 'video'
  • File::updateLinks no longer takes urls as parameters. All file links are now identified either by the DataObject::ID in a data-fileid property, or via shortcodes. This is necessary because file urls are no longer able to identify assets.
  • Extension point HtmlEditorField::processImage has been removed, and moved to Image::regenerateImageHTML
  • Upload::load now stores assets directly without saving into a File dataobject.
  • Protected file storage is now a core Framework API. See [/developer_guides/files/file_security] for more information.
  • File is now versioned, and should be published before they can be used on the frontend. See section on Migrating File DataObject from 3.x to 4.0 below for upgrade notes.
  • New filesystem abstraction including new DBFile database field to hold file references.
  • ShortcodeHandler interface to help generate standard handlers for HTML shortcodes in the editor.
  • handle_shortcode methods are removed from File and Image and moved to their own classes in SilverStripe\Assets\Shortcodes and are named FileShortcodeProvider and ImageShortcodeProvider respectively.
  • AssetNameGenerator interface, including a DefaultAssetNameGenerator implementation, which is used to generate renaming suggestions based on an original given filename in order to resolve file duplication issues.
  • GeneratedAssetHandler API now used to store and manage generated files (such as those used for error page cache or combined files).
  • Requirements_Minifier API can be used to declare any new mechanism for minifying combined required files. By default this api is provided by the JSMinifier class, but user code can substitute their own.
  • AssetField formfield to provide an UploadField style uploader for the new DBFile database field.
  • AssetControlExtension is applied by default to all DataObjects, in order to support the management of linked assets and file protection.
  • ProtectedFileController class is used to serve up protected assets.

Filesystem removed API

The following image manipulations previously deprecated has been removed:

  • Image::SetRatioSize superceded by Fit
  • Image::SetWidth superceded by ScaleWidth
  • Image::SetHeight superceded by ScaleHeight
  • Image::SetSize superceded by Pad
  • Image::PaddedImage superceded by Pad
  • Image::CroppedImage superceded by Fill
  • Image::AssetLibraryPreview superceded by PreviewThumbnail
  • Image::AssetLibraryThumbnail superceded by CMSThumbnail

The following File methods have been removed. Since there is no longer any assumed local path for any file, methods which dealt with such paths may no longer be relied on.

  • File::deletedatabaseOnly
  • File::link_shortcode_handler renamed to handle_shortcode
  • File::setParentID
  • File::getFullPath
  • File::getRelativePath
  • File::Content database field is removed

Image manipulations have been moved out of Image.php and now available to any File or DBFile which has the appropriate mime types. The following file manipulations classes and methods have been removed:

  • Image_Cached class
  • Image::regenerateFormattedImages method
  • Image::getGeneratedImages method
  • Image::deleteFormattedImages method
  • Image::handle_shortcode moved to SilverStripe\Assets\Shortcodes\ImageShortcodeProvider::handle_shortcode
  • AssetAdmin::deleteunusedthumbnails method
  • AssetAdmin::getUnusedThumbnails method

Many Folder api have also been removed:

  • Folder_UnusedAssetsField class
  • Folder::syncChildren method
  • Folder::constructChild method
  • Folder::addUploadToFolder method

The following filesystem synchronisation methods and tasks are also removed

  • RegenerateCachedImagesTask class
  • CleanImageManipulationCache class
  • Filesystem::sync method
  • AssetAdmin::doSync method

Template and Form API

Template and Form API Additions / Changes

  • Upgrade of TinyMCE to version 4.
  • Templates now use a standard template lookup system via SSViewer::get_templates_by_class which builds a candidate list for a given class. Actual resolution of existing templates for any list of candidates is actually performed by SSViewer::chooseTemplate
  • HtmlEditorConfig is now an abstract class, with a default implementation TinyMCEConfig for the built in TinyMCE editor.
  • HtmlEditorField::setEditorConfig may now take an instance of a HtmlEditorConfig class, as well as a standard config identifier name.
  • HeaderField requires a $name constructor argument (new HeaderField('MyName', 'My Title')
  • default_cast is now enforced on all template variables. See upgrading notes below.
  • FormField templates no longer look in the 'forms' folder for templates. As all form fields are now namespaced, the path for these templates will now match the namespace of the given class instead.
  • $module parameter in themedCSS and themedJavascript removed.
  • Theme selector has been removed from SiteConfig. Please use SSViewer.themes config instead.
  • FormAction::setValidationExempt can be used to turn on or off form validation for individual actions
  • GridField edit form now has improved support for versioned DataObjects, with basic publishing actions available when editing records.
  • PopoverField added to provide popup-menu behaviour in react forms (currently not available for non-react forms).
  • Introduction of experimental FormFactory API as a substitute for DataObject classes being responsible for building their own form fields. This builds a form based on a given controller and model, and can be customised on a case by case basis. This has been introduced initially for the asset-admin module.
  • Introduced AssetAdmin\Forms\UploadField as a react-friendly version of UploadField. This may also be used in normal entwine forms for managing files in a similar way to UploadField. However, this does not support inline editing of files.
  • Added method FormField::setSubmittedValue($value, $data) to process input submitted from form submission, in contrast to FormField::setValue($value, $data) which is intended to load its value from the ORM. The second argument to setValue() has been added.
  • HTMLValue service name is now fully qualified SilverStripe\View\Parsers\HTMLValue.
  • FormField::create_tag() has been moved to HTML class and renamed to createTag. Invoke with HTML::createTag().

The following methods and properties on Requirements_Backend have been renamed:

  • Requirements_Backund::$combine_files made protected and renamed $combinedFiles
  • Requirements_Backend::$combine_js_with_min made protected and renamed $minifyCombinedFiles
  • Requirements_Backend::$write_header_comments made protected and renamed $writeHeaderComment
  • Requirements_Backend::$write_js_to_body made protected and renamed to $writeJavascriptToBody
  • Requirements_Backend::$force_js_to_bottom renamed to $forceJSToBottom
  • get_combined_files_enabled renamed to getCombinedFilesEnabled
  • set_combined_files_enabled renamed to setCombinedFilesEnabled
  • get_suffix_requirements renamed to getSuffixRequirements
  • set_suffix_requirements renamed to setSuffixRequirements
  • get_custom_scripts renamed to getCustomScripts
  • unblock_all renamed to unblockAll
  • include_in_response renamed to includeInResponse
  • combine_files renamed to combineFiles
  • get_combine_files renamed to getCombinedFiles
  • clear_combined_files renamed to clearCombinedFiles
  • process_combined_files renamed to processCombinedFiles
  • set_write_js_to_body renamed to setWriteJavascriptToBody
  • set_force_js_to_bottom renamed to setForceJSToBottom

New methods on Requirements are added to access these:

  • get_minify_combined_js_files
  • set_minify_combined_js_files
  • get_force_js_to_bottom
  • get_write_js_to_body

Some methods on Requirements have had their method signatures changed:

  • includeInHTML has had the first parameter $template removed as it was previously deprecated.

A new config Requirements_Backend.combine_in_dev has been added in order to allow combined files to be forced on during development. If this is off, combined files is only enabled in live environments.

Form validation has been refactored significantly. A new FormMessage trait has been created to handle field-level and form-level messages. This has the following properties:

  • setMessage to assign a message, type, and cast
  • getMessage retrieves the message string
  • getMessageType retrieves the message type (E.g. error, good, info)
  • getMessageCast retrieves the cast type
  • getMessageCastingHelper retrieves the DBField cast to use for the appropriate message cast
  • getSchemaMessage encodes this message for form schema use in ReactJS.

Form behaviour methods have been changed:

  • __construct() now allows a RequestHandler to be passed as a first argument, rather than a Controller. in addition this argument is now optional. This allows forms to be constructed as a model only.
  • validate is replaced with validationResult instead, which returns a ValidationResult instance. This is no longer automatically persisted in the state by default, unless a redirection occurs. You can also save any response in the state by manually invoking saveFormState inside a custom validation response handler.
  • setupFormErrors renamed to restoreFormState
  • resetValidation renamed to clearFormState
  • loadMessagesFrom method created to load a ValidationResult into a form.
  • setMessage. third parameter is now $cast type
  • messageForForm removed. Use setMessage or sessionMessage instead.
  • getSessionValidationResult / setSessionValidationResult used to get / set session errors
  • getSessionData / setSessionData used to get / set field values cached in the session
  • getAjaxErrorResponse and getRedirectReferer created to simplify getValidationErrorResponse
  • addErrorMessage removed. Users can either use sessionMessage or sessionError to add a form level message, throw a ValidationException during submission, or add a custom validator.
  • Form is no longer a RequestHandler, but implements the HasRequestHandler interface and returns a FormRequestHandler instance from getRequestHandler(). the Form constructor no longer has any mandatory parameters, and the first parameter allows a non-Controller RequestHandler to be passed. Certain methods have been moved from Form to FormRequestHandler:
    • buttonClicked
    • checkAccessAction
    • handleField
    • httpSubmission
    • Link

Validator methods have changed:

  • validate method now returns a ValidationResult instance.
  • requireField method removed. Use RequiredFields subclass instead.

ValidationResult now has these methods:

  • serialize / unserialize for saving within session state
  • messageList renamed to getMessages
  • error method replaced with addMessage / addError / addFieldMessage / addFieldError
  • valid renamed to isValid

ValidationException has these changes:

  • $message second constructor parameter is removed. Constructor only accepts $result, which may be a string, and optional $code

New DatetimeField methods replace getConfig() / setConfig():

  • getTimezone() / setTimezone()
  • getDateTimeOrder() / setDateTimeOrder()
  • getLocale() / setLocale()
  • datavaluefield config is removed as internal data value is now fixed to ISO 8601 format

The DatetimeField has changed behaviour:

  • It uses a combined input instead of a composite from DateField and TimeField Consequently, getDateField() and getTimeField() have been removed.
  • It returns ISO 8601 normalised dates by default in Value(), which include a "T" separator between date and time. This is required to allow HTML5 input. Either use setHTML5(false) to set your custom format, or use dataValue() to retrieve a whitespace separated representation.
  • It no longer accepts setValue() as an array with 'date' and 'time' keys
  • Added getHTML5() / setHTML5()

New DateField methods replace getConfig() / setConfig():

  • getDateFormat() / setDateFormat()
  • getMinDate() / setMinDate()
  • getMaxDate() / setMaxDate()
  • getLocale() / setLocale()

The DateField has changed behavior:

  • DateField no longer provides a jQuery UI date picker (showcalendar option), and uses HTML5 date pickers by default instead.
  • DateField provides an optional polyfill for browsers without HTML5 date picker support
  • The dmyfields option has been replced with native HTML5 behaviour (as one single <input type=date>).
  • getClientLocale / setClientLocale have been removed (handled by DateField->locale and browser settings)

New TimeField methods replace getConfig() / setConfig()

  • getTimeFormat() / setTimeFormat()
  • getLocale() / setLocale()
  • getClientConfig() has been removed (in favour of setHTML5())

Template and Form Removed API

  • Removed TabularStyle
  • Removed NestedForm
  • Removed FieldList methods:
    • getTabPathRewrites
    • setTabPathRewrites
    • rewriteTabPath
  • Removed Form methods (see above for replacements):
    • transformTo
    • callfieldmethod
    • single_field_required
    • current_action
    • set_current_action
    • setupFormErrors
    • resetValidation
    • messageForForm
    • addErrorMessage
    • testSubmission
    • testAjaxSubmission
  • Removed Validator::requireField() method.
  • Removed ValidationResult (see above for replacements):
    • messageList
    • codeList
    • message
    • starredList
    • error
    • valid
  • Removed ReportAdminForm.ss template
  • FormField::dontEscape() has been removed. Escaping is now managed on a class by class basis.
  • Removed PermissionCheckboxSetField::getAssignedPermissionCodes() (never implemented)
  • Requirements::delete_combined_files() and Requirements::delete_combined_files() methods have been removed as they are obsolete.
  • Removed DatetimeField, DateField and TimeField methods getConfig and setConfig. Individual getters and setters for individual options are provided instead. See above for list of new methods.
  • Removed NumericField_Readonly. Use setReadonly(true) instead.
  • SSViewer deprecated methods removed:
    • set_source_file_comments()
    • get_source_file_comments()
    • getOption
    • setOption
  • Removed MemberDatetimeOptionsetField (no replacement)
  • Removed DateField_View_JQuery (replaced with native HTML5 support in DateField)
  • The following HTMLEditorField_* classes have been removed:
    • HTMLEditorField_Toolbar (replaced With ModalController in admin module)
    • HTMLEditorField_Embed (replaced with EmbedResource in asset-admin module)
    • HTMLEditorField_File
    • HTMLEditorField_Flash
    • HTMLEditorField_Image

i18n API

i18n API Additions / Changes

  • Upgrade of i18n to symfony/translation
  • Localisation based on language-only (without any specific locale) is now supported
  • i18nEntityProvider::provideI18nEntities() Now is expected to return only a single array map of key to default values.
  • i18n keys for '.PLURAL_NAME' and '.SINGULAR_NAME' have been changed back to use the namespaced class names for all DataObject subclasses, rather than just the basename without namespace.
  • i18n key for locale-respective pluralisation rules added as '.PLURALS'. These can be configured within yaml in array format as per ruby i18n pluralization rules.
  • i18n.all_locales config moved to SilverStripe\i18n\Data\Locales.locales
  • i18n.common_languages config moved to SilverStripe\i18n\Data\Locales.languages
  • i18n.likely_subtags config moved to SilverStripe\i18n\Data\Locales.likely_subtags
  • i18n.tinymce_lang config moved to SilverStripe\Forms\HTMLEditor\TinyMCEConfig.tinymce_lang
  • i18n::get_tinymce_lang() moved to SilverStripe\Forms\HTMLEditor\TinyMCEConfig::get_tinymce_lang()
  • i18n::get_locale_from_lang() moved to SilverStripe\i18n\Data\Locales::localeFromLang()
  • i18n::get_lange_from_locale() moved to SilverStripe\i18n\Data\Locales::langFromLocale()
  • i18n::validate_locale() moved to SilverStripe\i18n\Data\Locales::validate()
  • i18n::get_common_languages() moved to SilverStripe\i18n\Data\Locales::getLanguages()
  • i18n::get_locale_name() moved to SilverStripe\i18n\Data\Locales::localeName()
  • i18n::get_language_name() moved to SilverStripe\i18n\Data\Locales::languageName()
  • i18n.module_priority config moved to SilverStripe\i18n\Data\Sources.module_priority
  • i18n::get_owner_module() moved to SilverStripe\Core\Manifest\ClassManifest::getOwnerModule() This now returns a Module object instance instead of a string.
  • i18n::get_existing_translations() moved to SilverStripe\i18n\Data\Sources::getKnownLocales()

i18n API Removed API

  • Zend_Translate removed
  • i18n::_t() Support for sprintf-style %s arguments deprecated
  • i18n::_t() Using non-associative injection with named parameters is now an error
  • i18n::get_language_name() removed.
  • i18n::get_language_code() removed.
  • i18n::get_common_locales() removed.
  • i18n.common_locales config removed

Email Additions / Changes

  • Mailer converted to an interface
  • SwfitMailer added as new default mailer
  • Email re-written to be powered by SwiftMailer
  • Default template body variable renamed from $Body to $EmailContent
  • $email->setTemplate() renamed to $email->setHTMLTemplate()
  • Added $email->setPlainTemplate for rendering plain versions of email
  • $email->populateTemplate() has been replaced with $email->setData()

SapphireTest

  • is_running_tests() is no longer public and user code should not rely on this. Test-specific behaviour should be implemented in setUp() and tearDown()
  • setUpOnce removed. Please use setUpBeforeClass
  • tearDownOnce removed. Please use tearDownAfterClass
  • TestListener class removed
  • SapphireTest::$requiredExtensions renamed to SapphireTest::$required_extensions and made static
  • SapphireTest::$extraDataObjects renamed to SapphireTest::$extra_dataobjects and made static
  • SapphireTest::$extraControllers renamed to SapphireTest::$extra_controllers and made static

Security

  • LoginForm now has an abstract method getAuthenticatorName(). If you have made subclasses of this, you will need to define this method and return a short name describing the login method.
  • MemberLoginForm has a new constructor argument for the authenticator class, although this is usually constructed by MemberAuthenticator and won't affect normal use.
  • Authenticator methods register and unregister are deprecated in favour of using Config
  • Unused SetPassword property removed from Member. Use Member::changePassword or set Password directly.

Was this article helpful?