111 KiB
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
Overview
- 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 (check our deprecation process)
- 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).
- Object class has been replaced with traits (details).
- Asset storage has been abstracted, and a new concept of
DBFile
references via database column references now exists in addition to references via the existingFile
dataobject. File security and protected files are now a core feature (details) - 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 v4 as a base (blog post)
- Asset admin has been replaced with a purely ReactJS powered upgrade, and split out module called asset-admin.
- 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 "changeset" (details).
- Dependencies between versioned objects can be declared using the new ownership API, so that developers can ensure that relational consistency is maintained during publishing (details) 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 (details) - 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.
FRAMEWORK_PATH
) and support for hardcoded file paths (e.g.mysite/css/styles.css
) (details) - Replaced Zend_Translate with symfony/translation (details)
- Replaced
Zend_Cache
and theCache
API with a PSR-16 implementation (symfony/cache) (details) _ss_environment.php
files have been removed in favour of.env
and "real" environment variables (details).- Behat support updated to v3 ( details)
- The
GDBackend
andImagickBackend
classes have been replaced by a unifiedInterventionBackend
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 (details).
- 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
toTEMP_PATH
for naming consistency with other path variables and constants
Upgrading Guide
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.
Composer dependency update
As a first step, you need to update your composer dependencies.
The easiest way is to start with a new composer.json
file
and gradually move over settings from your old one.
This way you don't get dependency conflicts with potentially incompatible modules.
Backup your existing composer.json
and overwrite it with the following content:
{
"name": "myvendor/myproject",
"require": {
"silverstripe/recipe-cms": "^1"
},
"prefer-stable": true,
"minimum-stability": "dev"
}
This composer file uses the new recipe approach
which bundles all core dependencies in a meta package. If you want more granular control over what gets installed,
check the composer.json
files in recipe-core and recipe-cms.
Since this is a pre-release, you need to allow composer to install unstable dependencies via minimum-stability: dev
.
Now run a composer update
. This will remove all existing modules from your local codebase
since we replaced your project's composer.json
. Now you can move back your modules
one by one, checking for compatible versions on packagist.org.
Note: If you have issues with a pre-existing composer install you can force a clean re-install with the below commands:
rm -rf vendor/composer
rm composer.lock
composer update
For modules with stable releases, simply set your composer constraints to the new version (with a next significant release operator).
{
"require": {
- "myvendor/compatible-module": "~2.0",
+ "myvendor/compatible-module": "~3.0",
}
}
For modules with a compatible pre-release, use an explicit stability constraints. This can be changed to a next significant release operator once the module is stable.
{
"require": {
- "myvendor/prerelease-module": "~2.0",
+ "myvendor/prerelease-module": "~3.0@dev",
}
}
For modules that don't have a pre-release branch started, you should raise an issue on the repository asking for 4.0 compatibility. For now, you should attempt to continue the upgrade without the module and temporarily disable its functionality.
Install the upgrader tool
A lot of upgrade work can be automated, and we've written an upgrader tool for this purpose. Install it via composer:
composer global require silverstripe/upgrader
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.
Install the upgrader:
export PATH=$PATH:~/.composer/vendor/bin/
composer global require silverstripe/upgrader
Run the upgrader:
cd ~/my-project-root
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.
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.
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:
cd ~/my-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.
_ss_environment.php
changed to.env
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
SS_ENVIRONMENT_TYPE="dev"
SS_DEFAULT_ADMIN_USERNAME="admin"
SS_DEFAULT_ADMIN_PASSWORD="password"
SS_BASE_URL="http://localhost/"
### 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.
To access environment variables you can use the SilverStripe\Core\Environment::getEnv()
method.
See Environment Management docs for full details.
Migrate File DataObject
Since the structure of File
dataobjects has changed, a new task MigrateFileTask
has been added to assist in migration of legacy files (see file migration documentation).
$ ./vendor/bin/sake dev/tasks/MigrateFileTask
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, and should be cleaned up manually.
To disable this, set the following config:
SilverStripe\Assets\FileMigrationHelper:
delete_invalid_files: false
Get upgrade tips on your code
While there's some code we can automatically rewrite, other uses of changed SilverStripe APIs aren't that obvious.
You can use our heuristics to get some hints on where you need to review code manually.
Hints will generally point to more detail about a specific upgrade in this guide.
This task should be run after upgrade-code upgrade
.
~/.composer/vendor/bin/upgrade-code inspect ./mysite
These hints only cover a part of the upgrade work, but can serve as a good indicator for where to start.
Rewrite literal 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();
}
Rewrite literal class names
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
Move 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()
.
Template locations and references
Templates are now more strict about their locations. 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.
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.
Config settings should be set to private static
Class configuration defined as static
properties need to be marked as private
to take effect:
-public static $allowed_actions = [
+private static $allowed_actions = [
'suggest'
];
Module paths can't be hardcoded
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:
-<img src="framework/images/image.png" />
+<img src="$ModulePath(silverstripe/framework)/image.png" />
-<% require css("framework/css/styles.css") %>
+<% require css("silverstripe/framework: css/styles.css") %>
Usage in Requirements:
-Requirements::css('framework/css/styles.css');
+Requirements::css('silverstripe/framework: css/styles.css');
Usage with custom logic:
+use SilverStripe\Core\Manifest\ModuleLoader;
+use SilverStripe\View\ThemeResourceLoader;
-$moduleFilePath = FRAMEWORK_DIR . '/MyFile.php';
+$moduleFilePath = ModuleLoader::getModule('silverstripe/framework')->getRelativeResourcePath('MyFile.php');
-$baseFilePath = BASE_PATH . '/composer.json';
+$baseFilePath = Director::baseFolder() . '/composer.json';
-$mysiteFilePath = 'mysite/css/styles.css';
+$mysiteFilePath = ModuleLoader::getModule('mysite')->getRelativeResourcePath('css/styles.css');
-$themesFilePath = SSViewer::get_theme_folder() . '/css/styles.css';
+$themesFilePath = ThemeResourceLoader::inst()->findThemedResource('css/styles.css');
-$themeFolderPath = THEMES_DIR . '/simple';
+$themeFolderPath = ThemeResourceLoader::inst()->getPath('simple');
Usage for Page and ModelAdmin:
class ListingPage extends \Page {
private static $icon = 'mycompany/silverstripe-mymodule: client/images/sitetree_icon.png';
}
class MyCustomModelAdmin extends \SilverStripe\Admin\ModelAdmin {
private static $menu_icon = 'mycompany/silverstripe-mymodule: client/images/modeladmin_icon.png';
}
To ensure consistency, we've also deprecated support for path constants:
- Deprecated
FRAMEWORK_DIR
andFRAMEWORK_PATH
- Deprecated
FRAMEWORK_ADMIN_DIR
andFRAMEWORK_ADMIN_PATH
- Deprecated
FRAMEWORK_ADMIN_THIRDPARTY_DIR
andFRAMEWORK_ADMIN_THIRDPARTY_PATH
- Deprecated
THIRDPARTY_DIR
andTHIRDPARTY_PATH
- Deprecated
CMS_DIR
andCMS_PATH
- Deprecated
THEMES_DIR
andTHEMES_PATH
- Deprecated
MODULES_PATH
andMODULES_DIR
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/
+vendor/silverstripe/framework/
-cms/
+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.
SS_Log replaced with 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');
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
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
conf/ConfigureFromEnv.php
is 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
Object class replaced by traits
Object has been superseded by traits.
Injectable
: ProvidesMyClass::create()
andMyClass::singleton()
Configurable
: ProvidesMyClass::config()
Extensible
: Provides all methods related to extensions (E.g. add_extension()).
Upgrade subclass use
-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;
+}
Upgrade 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)');
Upgrade extension use
-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
Session object removes static methods
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);
+ $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.
Extensions are now singletons
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.
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;
}
}
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
. Please review the default service definitions below:
---
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:
class MyClass extends DataObject {
private static $extensions = [
- Versioned::class . '(Versioned)',
+ Versioned::class . '.versioned',
];
}
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,
which are also sub-folders of the modules which now reside in vendor
.
This will affect you if you have used Requirements::block()
on files in the framework/
or cms/
folder.
In order to identify resources it is preferred to use the new module-root prefixed string form when adding requirements.
Usage in Requirements:
-Requirements::css('framework/admin/css/styles.css');
+Requirements::css('silverstripe/admin: client/dist/styles/bundle.css');
The location of these resources is determined from the name
field in the composer.json
for each module. silverstripe/admin:
will be mapped to the folder vendor/silverstripe/admin
where this module is installed, based on the vendor/silverstripe/admin/composer.json
name
matching the prefix in Requirements::css()
.
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()
.
The CMS UI is moving away from Requirements::combine_files()
in favour of Webpack.
This method 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
If you have customised the CMS UI (via JavaScript or CSS), please read our guide to customise the admin interface.
Explicit text casting on 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.
use SilverStripe\View\ViewableData;
use SilverStripe\Core\Convert;
class MyObject extends ViewableData
{
+ private static $casting = [
+ 'SomeHTML' => 'HTMLText'
+ ];
public function getSomeHTML
{
- $title = Convert::raw2xml($this->Title);
+ $title = 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).
Replace UploadField with injected service
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.
use SilverStripe\Forms\FieldList;
-use SilverStripe\AssetAdmin\Forms\UploadField;
+use SilverStripe\Forms\FileHandleField;
use SilverStripe\ORM\DataObject;
class MyClass extends DataObject
{
public function getCMSFields()
{
return new FieldList(
- new UploadField('Files')
+ Injector::inst()->create(FileHandleField::class, 'Files')
);
}
}
i18n placeholders, plurals and i18nEntityProvider
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 thei18n.missing_default_warning
config to false.
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.
Image handling
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')"
];
}
Writing to File
dataobjects or 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');
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 ofDataExtension
- This extension is added to both
DBFile
andFile
, 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 genericmanipulate
method may be used, although the callback for this method both is given, and should return, anAssetStore
instance and file tuple (Filename, Hash, and Variant) rather than an Image_Backend.
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
Example of changed shortcode handling:
+use SilverStripe\Assets\Shortcodes\FileShortcodeProvider;
class MyShortcodeUser extends Object
{
private $content;
public function Content($arguments, $parser, $shortcode)
{
- return File::handle_shortcode($arguments, $this->content, $parser, $shortcode);
+ return FileShortcodeProvider::handle_shortcode($arguments, $this->content, $parser, $shortcode);
}
}
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
{
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);
}
}
Removed DataObject::database_fields
or DataObject::db
The methods DataObject::database_fields()
, DataObject::custom_database_fields()
and DataObject::db()
have
been removed.
Instead, to get all database fields for a dataobject, including base fields (such as ID, ClassName, Created, and LastEdited), use DataObject::getSchema()->databaseFields($className, $aggregate = true)
.
To omit the base fields, pass a value of false
as the $aggregate
parameter, e.g. DataObject::getSchema()->databaseFields(Member::class, false)
.
Composite database fields are omitted from the databaseFields()
method. To get those, use DataObject::getSchema()->compositeFields($className, $aggregate = true)
.
Rewrite SQLQuery to more specific classes
Instead of SQLQuery
, you should now use SQLSelect
, SQLUpdate
, SQLInsert
or SQLDelete
- check the 3.2.0 upgrading notes for details.
Example:
-function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null)
+public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
{
if(!preg_match('/MyField/', implode(' ', $query->getWhere()))) {
- $query->addWhere('"MyField" = 'foo');
+ $query->addWhere(['"MyField"' => 'foo']);
}
}
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';
}
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.
Server configuration files for assets
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.
TinyMCE v4
Please see the tinymce upgrading guide to assist with upgrades to customisations to TinyMCE v3.
In Framework 4.0 the user interface for TinyMCE has been trimmed down considerably, with certain toolbar buttons removed from the default cms configuration:
- 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 (check TinyMCE documentation).
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']
]);
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 objectsVersioned::publishSingle
Publishes this object, but not owned objectsVersioned::copyVersionToStage
Replaces the oldpublish
method.
These methods are deprecated:
Versioned::publish
Replaced byVersioned::copyVersionToStage
Versioned::doPublish
Replaced byVersioned::publishRecursive
New 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.
Renamed DBField and subclasses
All DBField
subclasses are namespaced, have a DB
prefix, and drop any existing SS_
prefix.
For example, Text
becomes SilverStripe\ORM\FieldType\DBText
,
and SS_Datetime
becomes SilverStripe\ORM\FieldType\DBDatetime
.
Since they are aliased to their old name, you likely won't need to change your DataObject::$db
definitions.
If you are instanciating or otherwise referencing those classes directly (not through strings),
they'll likely get rewritten automatically through the
upgrader tool.
Example:
use SilverStripe\ORM\DataObject;
+use SilverStripe\ORM\FieldType\DBVarchar;
class MyObject extends DataObject
{
private static $db = [
'Number' => 'Int',
- 'Time' => 'SS_Datetime'
+ 'Time' => 'Datetime'
];
public function TextNumber()
{
- return new Varchar('TextNumber', 'Number is ' . $this->Number);
+ return new DBVarchar('TextNumber', 'Number is ' . $this->Number);
}
}
Removed RestfulService
The RestfulService
API was a (poor) attempt at a built-in HTTP client.
We've removed it, and recommend using Guzzle instead.
Removed 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 this package.
Configurable Admin URL
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.
Custom Authenticators
The methods register()
and unregister()
on Authenticator
are deprecated in favour
of the Config
system. This means that any custom Authenticator
needs to be registered
through the YAML config. Check the Authentication docs
for details how to setup a custom handler.
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.
Every request is now authenticated against an IdentityStore
interface.
By default that's 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 to null
.
IdentityStores are responsible for logging members in and out (e.g. destroy cookies and sessions, or instantiate them).
Config is now immutable
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.
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
.
Replace Zend_Cache with symfony/cache
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'
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
PSR-2 Coding Standard compliance
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.
PSR-4 autoloading for project code
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
- 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. inFeatureContext
definitions of the behat-extension module). Runcomposer 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](php documentation) for more information.- Admin URL can now be configured via custom Director routing rule
Controller::init
visibility changed to protected. UseController::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-usecustomclass}Object::invokeWithExtensions
now has the same method signature asObject::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 methodsInjectable
Provides Injector API helper methodsExtensible
Allows extensions to be applied
Convert
class has extra methods for formatting file sizes in php_ini compatible formatConvert::memstring2bytes()
will parse a php_ini memory size.Convert::bytes2memstring()
will format the memory size with the appropriate scale.
SiteTree.alternatePreviewLink
is deprecated. UseupdatePreviewLink
instead.Injector
dependencies no longer automatically inherit from parent classes.$action
parameter toController::Link()
method is standardised.- Moved test database cleanup task from
sake dev/tests/cleanupdb
tosake dev/tasks/CleanupTestDatabasesTask
Injector::load
given asrc
parameter will no longer guess the service name from the filename. Now the service name will either by the array key, or theclass
parameter value.- Uniqueness checks for
File.Name
is performed on write only (not insetName()
) - 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
andZend_Locale
with thephp-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 aModule
object instance.- Moved
Object::parse_class_spec()
toClassInfo
- Removed
create_from_spec()
. Supportede byInjector
natively. - Moved
Controller::Link()
to parent class (RequestHandler
) - Moved
Controller::redirect()
to parent class (RequestHandler
) - Moved
Controller::redirectBack()
to parent class (RequestHandler
) RequestHandler::Link()
now relies on theurl_segment
handler being provided for the class. If left unset, this will raise an error.RequestHandler::getBackURL()
andgetReturnReferer()
have been added to safely inspect the current request to see if there is a url to redirect back to.
- Renamed
LeftAndMain_TreeNode
toCMSMain_TreeNode
- Removed
LeftAndMain::SiteTreeAsUL()
(override left inCMSMain
) - Moved
LeftAndMain::getSiteTreeFor()
toCMSMain
- Moved
LeftAndMain::getsubtree()
toCMSMain
- Moved
LeftAndMain::updatetreenodes()
toCMSMain
- Moved
LeftAndMain::savetreenodes()
toCMSMain
- Renamed
LeftAndMain::EditorToolbar()
toModals()
. Returns aModalController
handler instance rather than aHTMLEditorField_Toolbar
- Removed
Director::$dev_servers
andDirector::$test_servers
- Removed
Director::$urlParams
andDirector::setUrlParams()
- Removed
Director.alternate_host
. UseDirector.alternate_base_url
instead. - Removed
Director.alternate_protocol
. UseDirector.alternate_base_url
instead. - '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
, andSS_TRUSTED_PROXY_IP_HEADER
are no longer supported. These settings should go into the Injector service configuration for TrustedProxyMiddleware instead.- Removed
SS_HOST
environment constant. UseSS_BASE_URL
instead. Member::canLogIn()
now returns boolean. UseMember::validateCanLogin()
to get aValidationResult
- Moved
Security::has_default_admin
toDefaultAdminService::hasDefaultAdmin()
- Moved
Security::check_default_admin
toDefaultAdminService::isDefaultAdminCredentials()
- Moved
Security::default_admin_torname
toDefaultAdminService::getDefaultAdminUsername()
- Moved
Security::default_admin_password
toDefaultAdminService::getDefaultAdminPassword()
- Moved
Security::setDefaultAdmin
toDefaultAdminService::setDefaultAdmin()
- Moved
Security::clearDefaultAdmin
toDefaultAdminService::clearDefaultAdmin()
- Moved
Security::findAnAdministrator
toDefaultAdminService::findOrCreateDefaultAdmin()
- Deprecated
Member::checkPassword()
. UseAuthenticator::checkPassword()
instead - Deprecated
RequestFilter
. Use HTTPMiddleware instead. - Changed
RequestFilter
: The$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.- Removed
ConfigureFromEnv.php
- Changed
Session
object to avoid static access (Session::inst()
). Use it from the current request via$request->getSession()
instead. All static methods have been removed, and theinst_
prefix removed from all instance members. Director.rules
config no longer supportredirect:<url>
directly via config.- Removed
Director::get_environment_type()
andDirector::set_environment_type()
. Get theKernel
instance via injector and querygetEnvironment()
instead. (e.g.$type = Injector::inst()->get(Kernel::class)->getEnvironment()
) - Removed
Director.environment_type
to configure the environment via YAML. Use a .env file to manage environment settings. - Removed
increase_xdebug_nesting_level_to()
(functionality has been inlined intoAppKernel
) - Moved
set_increase_time_limit_max()
toEnvironment::setTimeLimitMax()
- Moved
get_increase_time_limit_max()
toEnvironment::getTimeLimitMax()
- Moved
set_increase_memory_limit_max()
toEnvironment::setMemoryLimitMax()
- Moved
get_increase_memory_limit_max()
toEnvironment::getMemoryLimitMax()
- Moved
increase_time_limit_to()
toEnvironment::increaseTimeLimitTo()
- Moved
increase_memory_limit_to()
toEnvironment::increaseMemoryLimitTo()
- Moved
translate_memstring()
toConvert::memstring2bytes
. - Moved
getTempFolder()
toTempFolder::getTempFolder()
- Removed
getTempParentFolder()
- Removed
getTempFolderUsername()
- Removed
CMSMain::buildbrokenlinks()
- Removed
Injector::unregisterAllObjects()
. UseunregisterObjects
to unregister groups of objects limited by type instead. - Removed
SS_Log
. UseInjector::inst()->get(LoggerInterface::class)
instead. - Removed
CMSBatchAction_Delete
- Removed
CMSBatchAction_DeleteFromLive
- Removed
CMSMain.enabled_legacy_actions
config. CMSmain.getCMSTreeTitle
is now ignored on extensions. UseupdateCMSTreeTitle
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 - Removed
TestRunner
andJSTestRunner
- Removed
PhpUnitWrapper
,PhpUnitWrapper_3_4
,PhpUnitWrapper_3_5
,PhpUnitWrapper_Generic
,SapphireTestSuite
APIs - Removed
SapphireTestReporter
andCliTestReporter
- Removed
SapphireTest::skipTest()
, usemarkTestSkipped()
in asetUp()
method instead 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 fromthemes/<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
- Removed the
Spyc
YAML library. Please load it yourself, or use the included Symfony YAML component. - Removed
RestfulService
. Use Guzzle instead (details) - Removed
Oembed
in favour of a oscarotero/Embed - Removed
TextParser
andBBCodeParser
. These are available in an archived module, silverstripe-archive/bbcodeparser - Removed
ViewableData::ThemeDir
. UseThemeResourceLoader::findThemedResource
in conjunction withSSViewer::get_themes
instead. - Removed
Config::FIRST_SET
andConfig::INHERITED
- Removed
RequestHandler.require_allowed_actions
. This is now fixed to on and cannot be disabled. - Removed
ClassManifest::getModules()
. UseModuleLoader
instead - Removed
ClassManifest::getConfigDirs()
. UseModuleLoader
instead - Removed
ClassManifest::getConfigs()
. UseModuleLoader
instead - Removed
Session::set_config()
andSession::get_config()
. Use theSession.timeout
config setting instead - Removed
Security::set_login_recording()
andSecurity::get_login_recording()
. Use theSecurity.login_recording
config setting instead. - Removed
ModelAsController::find_old_page()
. UseOldPageRedirector::find_old_page()
instead - Removed
RootURLController:set_default_homepage_link()
andRootURLController::get_default_homepage_link()
. Use theRootURLController.default_homepage_link
config setting instead. - Removed
CreditCardField
,CountryDropdownField
,PhoneNumberField
,MemberDatetimeOptionsetField
,InlineFormAction
. Use custom code instead - Removed
ResetFormAction
, useFormAction::create()->setAttribute('type', 'reset')
instead Injector
now complies with PSR-11. Accordingly,hasService()
has been renamed tohas()
, andget()
will throwSilverStripe\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 ofExtension::setOwner()
has also been removed. - Deprecated
ClassInfo::baseDataClass()
. UseDataObject::getSchema()->baseDataClass()
instead. - Deprecated
ClassInfo::table_for_object_field()
. UseDataObject::getSchema()->tableForField()
instead - Deprecated
Config::inst()->update()
. UseConfig::modify()->set()
orConfig::modify()->merge()
instead.
ORM
- Deprecated
SQLQuery
in favourSQLSelect
(details) - Added
DataObject.many_many
'through' relationships now support join dataobjects in place of automatically generated join tables. See the [/developer_guides/relations](datamodel relationship docs) for more info. - Added
DataList::filter()
by null now internally generates "IS NULL" or "IS NOT NULL" conditions appropriately on queries. - Changed
DataObject
constructor to require an additional parameter, which must be included in subclasses. DataObject::db
now returns composite fields.DataObject::ClassName
field has been refactored into aDBClassName
type field (instead of a string).DataObject::can()
has new method signature with$context
parameter.DataObject::duplicate()
now requires explicit flag to duplicatebelongs_many_many
(off by default), but now works with unsaved relations. By default onlymany_many
are duplicated.HTMLText
no longer enables shortcodes by default. Two injector aliases have been created for this class which can be used to select the correct behaviour. UseHTMLText
for shortcodes enabled, andHTMLFragment
without shortcodes enabled (the new default).- Renamed
String::NoHTML()
toPlain()
- Removed
String::LimitWordCountXML()
. UseLimitWordCount()
instead. - Removed
String::BigSummary()
. UseSummary()
instead. - Changed
HTMLText
limit methods to operate on plain text rather than attempt to manipulate the underlying HTML. FormField::Title()
andFormField::RightTitle()
are now cast as plain text by default (but can be overridden).- Renamed
FormField#createTag()
toFormField::create_tag()
Hierarchy
class has had much of it's functionality refactored out intoMarkedSet
:isMarked()
isTreeOpened()
isExpanded()
markByID()
markPartialTree()
markExpanded()
markUnexpanded()
markToExpose()
markClosed()
markOpened()
markedNodeIDs()
getChildrenAsUL()
replaced withrenderChildren()
, which now takes a template name.markingFilterMatches()
(and made protected)markChildren()
(and made protected)
- Search filter classes (e.g.
ExactMatchFilter
) are now registered withInjector
via a newDataListFilter.
prefix convention. see search filter documentation for more information. Permission::flush_permission_cache()
renamed toreset()
and added toResettable
interface.- Changed
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. - Renamed
Versioned::reading_stage()
toset_stage()
(throws an error if setting an invalid stage) - Renamed
Versioned::current_stage()
toget_stage()
- Removed
Versioned::getVersionedStages()
- Removed
Versioned::get_live_stage()
. Use theVersioned::LIVE
constant instead. - Removed
Versioned::getDefaultStage()
. Use theVersioned::DRAFT
constant instead. - Changed
Versioned::$versionableExtensions
fromprivate static
toprotected static
- Added
Versioned::hasStages()
to check if an object has a given stage. - Added
Versioned::stageTable()
to get the table for a given class and stage. - Any extension declared via
versionableExtensions
config on Versioned dataobject must nowVersionableExtension
interface at a minimum.Translatable
has been removed from defaultversionableExtensions
- 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
intoVersioned
extension. - All versioned DataObjects have
canPublish()
,canArchive()
,canUnpublish()
permission checks - All versioned Dataobjects have
doPublish()
,doArchive()
,doPublish()
, anddoUnpublish()
actions. However,do*()
methods will no longer automatically checkcan*()
permissions, and must be done by usercode before invocation. - Moved
SiteTree::getIsAddedToStage()
toVersioned::isOnDraftOnly()
- Moved
SiteTree::getIsModifiedOnStage()
toVersioned::isModifiedOnDraft()
- Moved
SiteTree::isPublished()
toVersioned
. - Renamed
SiteTree::getExistsOnLive()
toisPublished()
- Added
Versioned::isOnDraft()
- Added
Versioned::isArchived()
- Added
Versioned::isOnLiveOnly()
- Added
ChangeSet
andChangeSetItem
for batch publishing of versioned dataobjects. - Added
DataObject.table_name
config to customise the database table for any record. - Added
DataObjectSchema
class to assist with mapping between classes and tables. - Changed
DataObject.indexes
to usecolumns
instead ofvalue
to define index contents. - Changed
Money
to treat values as empty only ifAmount
field is null. If anAmount
value is provided without aCurrency
specified, it will be formatted as per the current locale. - Removed
DatabaseAdmin#clearAllData()
. UseDB::get_conn()->clearAllData()
instead - Moved
SapphireTest
temp DB methods into a newTempDatabase
class. This allows applications to create temp databases when not running tests. - Moved
SapphireTest::using_temp_db()
toTempDatabase->isUsed()
- Moved
SapphireTest::kill_temp_db()
toTempDatabase->kill()
- Moved
SapphireTest::empty_temp_db()
toTempDatabase->clearAllData()
- Moved
SapphireTest::create_temp_db()
toTempDatabase->build()
- Moved
SapphireTest::delete_all_temp_dbs()
toTempDatabase->deleteAll()
- Moved
SapphireTest::resetDBSchema()
toTempDatabase->resetSchema()
DBDate
,DBTime
andDBDatetime
have changed methods:- Added
getTimestamp()
to get the respective date / time as unix timestamp (seconds since 1970-01-01) - Changed
Format()
method to use CLDR format strings, rather than PHP format string. E.g.d/m/Y H:i:s
(php format) should be replaced with todd/MM/y HH:mm:ss
(CLDR format). - Added
getISOFormat()
to return the standard date/time ISO 8601 pattern in CLDR format. - Changed
setValue()
method to expect 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 toy-MM-dd
internally.- 2-digit values for year will now raise errors.
- Changed
FormatFromSettings()
to default toNice()
format if no member is logged in. - Changed
Nice()
,Long()
andFull()
methods to follow standard formatting rules for the current locale, rather than pre-defined formats. - Added
Short()
to format concise date/time values, as a shorter version thanNice
- Added
getFormatter()
to return a locale-specific date/time formatter. - Added
DBTime::FormatFromSettings()
- Added
- Deprecated globals
$database
and$databaseConfig
. UseDB::setConfig()
instead. - Removed
DataModel
- Changed
DataObject::can*()
methods to no longer accept a member ID. These must now be passed aMember
object or left null - Moved
DataObject::db()
toDataObjectSchema::fieldSpec()
andDataObjectSchema::fieldSpecs()
- Moved
DataObject::manyManyComponent()
toDataObjectSchema
(access throughDataObject->getSchema()
) - Moved
DataObject::belongsToComponent()
toDataObjectSchema
(access throughDataObject->getSchema()
) - Moved
DataObject::hasOneComponent()
toDataObjectSchema
(access throughDataObject->getSchema()
) - Moved
DataObject::hasManyComponent()
toDataObjectSchema
(access throughDataObject->getSchema()
) - Moved
DataObject::getRemoteJoinField()
toDataObjectSchema
(access throughDataObject->getSchema()
) - Moved
DataObject::database_fields()
toDataObjectSchema::databaseFields()
- Moved
DataObject::has_own_table()
toDataObjectSchema::classHasTable()
- Moved
DataObject::composite_fields()
toDataObjectSchema::compositeFields()
- Moved
DataObject::manyManyExtraFieldsForComponent()
toDataObjectSchema
- Deprecated
DataObject::$destroyed
- Removed
DataObject::validateModelDefinitions
. Relations are now validated withinDataObjectSchema
- Removed
DataObject
methodshasOwnTableDatabaseField
,has_own_table_database_field
and {#dataobject-has-own}hasDatabaseFields
are superceded byDataObjectSchema::fieldSpec
. Use$schema->fieldSpec($class, $field, DataObjectSchema::DB_ONLY | DataObjectSchema::UNINHERITED )
. Excludeuninherited
option to search all tables in the class hierarchy. - Renamed
DataObject::is_composite_field()
toDataObjectSchema::compositeField()
- Renamed
DataObject::custom_database_fields()
toDataObjectSchema::databaseFields()
orDataObjectSchema::fieldSpecs()
instead. - Removed
DataList::getRelation
, as it was mutable. UseDataList::applyRelation
instead, which is immutable. Member
Field 'RememberLoginToken' removed, replaced with 'RememberLoginHashes' has_many relationship- Removed
UpgradeSiteTreePermissionSchemaTask
- Removed
EncryptAllPasswordsTask
- Removed
DBString::LimitWordCountXML()
method. UseLimitWordCount()
for XML safe version. - Removed
SiteTree::getExistsOnLive()
. UseisPublished()
instead. - Removed
SiteTree::getIsDeletedFromStage()
. UseisOnDraft()
instead (inverse case). - Changed
DataObject.many_many
to remove triangular resolution. Both themany_many
andbelongs_many_many
must point directly to the specific class on the opposing side, not a subclass or parent. - Removed
DataObject::validateModelDefinitions()
. Validation and parsing of config is now handled withinDataObjectSchema
. CMSBatchAction_Delete
removed. UseCMSBatchAction_Archive
instead.- Removed
Date::past_date()
- Removed
Date::prior_monday()
- Removed
Date::weekday()
- Removed
Date::next_day()
- Removed
Date::day_before()
- Removed
Date::days_between()
- Removed
Date::nice_format()
. Use locale-specific formatting forNice()
- Removed
Time::nice_format()
. Use locale-specific formatting forNice()
- Removed
Datetime::nice_format()
. Use locale-specific formatting forNice()
- Removed
Time::TwelveHour()
- Removed
Time::Nice24()
- Removed
Money::NiceWithShortname()
- Removed
Money::NiceWithName()
- Removed
Money::getShortName()
- Removed
Money::getCurrencyName()
- Removed additional arguments from
Money::getSymbol()
. The result of this value is now localised based on the currency code assigned to theMoney
instance - Removed
Money::getAllowedCurrencies
. Apply validation toMoneyField
instead. - Removed
Hierarchy::parentStack()
removed. UsegetAncestors()
instead - Removed
Hierarchy::doAllChildrenIncludingDeleted()
. UseAllChildrenIncludingDeleted()
instead - Removed
Hierarchy::naturalNext()
- Removed
Hierarchy::naturalPrev()
- Removed
Hierarchy::markingFinished()
Filesystem
- Image manipulations have been moved into a new
[ImageManipulation](api:SilverStripe\Assets\ImageManipulation)
trait. - Removed
CMSFileAddController
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 fromAssetContainer
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 theDataObject::ID
in adata-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 toImage::regenerateImageHTML()
Upload::load()
now stores assets directly without saving into aFile
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.File::handle_shortcode()
andImage::handle_shortcode()
have moved to their own classes inSilverStripe\Assets\Shortcodes
, and are namedFileShortcodeProvider
andImageShortcodeProvider
respectively.AssetNameGenerator
interface, including aDefaultAssetNameGenerator
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 theJSMinifier
class, but user code can substitute their own.AssetField
form field to provide anUploadField
style uploader for the newDBFile
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.AssetAdaptor
has a new configdefault_server
which helps tell the code which server type to use if no matching type was found by scanning the server software - defaults toapache
The following image manipulations have been removed:
- Renamed
Image::SetRatioSize()
toFit()
- Renamed
Image::SetWidth()
toScaleWidth()
- Renamed
Image::SetHeight()
toScaleHeight()
- Renamed
Image::SetSize()
toPad()
- Renamed
Image::PaddedImage()
toPad()
- Renamed
Image::CroppedImage()
toFill()
- Renamed
Image::AssetLibraryPreview()
toPreviewThumbnail()
- Renamed
Image::AssetLibraryThumbnail()
toCMSThumbnail()
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.
- Removed
File::deletedatabaseOnly()
- Renamed
File::link_shortcode_handler()
tohandle_shortcode()
- Removed
File::setParentID()
- Removed
File::getFullPath()
- Removed
File::getRelativePath()
- Removed
File::Content
database field (wasn't used by core)
Image manipulations have been moved out of Image
and now available to any File
or DBFile
which has the
appropriate mime types. The following file manipulations classes and methods have been removed:
- Removed
Image_Cached
- Removed
Image::regenerateFormattedImages()
- Removed
Image::getGeneratedImages()
- Removed
Image::deleteFormattedImages()
- Removed
Image::handle_shortcode()
moved toSilverStripe\Assets\Shortcodes\ImageShortcodeProvider::handle_shortcode()
- Removed
AssetAdmin::deleteunusedthumbnails()
- Removed
AssetAdmin::getUnusedThumbnails()
- Removed
Folder_UnusedAssetsField
- Removed
Folder::syncChildren()
- Removed
Folder::constructChild()
- Removed
Folder::addUploadToFolder()
- Removed
RegenerateCachedImagesTask
- Removed
CleanImageManipulationCache
- Removed
Filesystem::sync()
- Removed
AssetAdmin::doSync()
Templates and Form
- Upgrade to TinyMCE 4.x
- 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 bySSViewer::chooseTemplate
HtmlEditorConfig
is now an abstract class, with a default implementationTinyMCEConfig
for the built in TinyMCE editor.HtmlEditorField::setEditorConfig()
may now take an instance of aHtmlEditorConfig
class, as well as a standard config identifier name.HeaderField
requires a$name
constructor argument (new HeaderField('MyName', 'My Title')
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 inthemedCSS()
andthemedJavascript()
removed.- Ability to select a theme through
admin/settings
has been removed fromSiteConfig
. Please useSSViewer.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 ofUploadField
. This may also be used in normal entwine forms for managing files in a similar way toUploadField
. However, this does not support inline editing of files. - Added method
FormField::setSubmittedValue($value, $data)
to process input submitted from form submission, in contrast toFormField::setValue($value, $data)
which is intended to load its value from the ORM. The second argument tosetValue()
has been added. FormField::create_tag()
moved toSilverStripe\View\HTML->createTag()
.- Changed
ListboxField
to multiple only. Previously, this field would operate as either a single select (default) or multi-select throughsetMultiple()
. Now this field should only be used for multi-selection. Single-selection should be done using a regularDropdownField
. GroupedDropdownField::setDisabled()
now only accepts a list of values instead of a list of grouped values. The method now expectes a non-associative array of values (not titles) or anSS_List
.
The following methods and properties on Requirements_Backend
have been renamed: {#requirements}
- Renamed
$combine_files
to$combinedFiles
- Renamed
$combine_js_with_min
to$minifyCombinedFiles
- Renamed
$write_header_comments
to$writeHeaderComment
- Renamed
$write_js_to_body
to$writeJavascriptToBody
- Renamed
$force_js_to_bottom
to$forceJSToBottom
- Renamed
get_combined_files_enabled()
togetCombinedFilesEnabled()
- Renamed
set_combined_files_enabled()
tosetCombinedFilesEnabled()
- Renamed
get_suffix_requirements()
togetSuffixRequirements()
- Renamed
set_suffix_requirements()
tosetSuffixRequirements()
- Renamed
get_custom_scripts()
togetCustomScripts()
- Renamed
unblock_all()
tounblockAll()
- Renamed
include_in_response()
toincludeInResponse()
- Renamed
combine_files()
tocombineFiles()
- Renamed
get_combine_files()
togetCombinedFiles()
- Renamed
clear_combined_files()
toclearCombinedFiles()
- Renamed
process_combined_files()
toprocessCombinedFiles()
- Renamed
set_write_js_to_body()
tosetWriteJavascriptToBody()
- Renamed
set_force_js_to_bottom()
tosetForceJSToBottom()
- Added
get_minify_combined_js_files()
andset_minify_combined_js_files()
- Added
get_force_js_to_bottom()
- Added
get_write_js_to_body()
- Changed
includeInHTML()
to remove the first parameter ($template
)
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 {#form-validation}
handle FormField
and Form
messages. This trait has a newsetMessage()
API to assign a message, type, and cast.
Use getMessage()
, getMessageType()
, getMessageCast()
and getMessageCastingHelper()
to retrieve them.
Form
behaviour methods have been changed:
__construct()
now allows aRequestHandler
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 withvalidationResult
instead, which returns aValidationResult
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 invokingsaveFormState
inside a custom validation response handler.- Renamed
setupFormErrors()
torestoreFormState()
- Renamed
resetValidation()
toclearFormState()
- Added
loadMessagesFrom()
to load aValidationResult
into a form. - Changed
setMessage()
to accept$cast
as a third parameter (instead of a$escapeHtml
boolean) - Removed
messageForForm()
. UsesetMessage()
orsessionMessage()
instead. - Added
getSessionValidationResult()
/setSessionValidationResult()
to get / set session errors - Added
getSessionData()
/setSessionData()
to get / set field values cached in the session - Removed
addErrorMessage()
. UsesessionMessage()
orsessionError()
to add a form level message, throw aValidationException
during submission, or add a custom validator. Form
is no longer aRequestHandler
, but implements theHasRequestHandler
interface and returns aFormRequestHandler
instance fromgetRequestHandler()
. theForm
constructor no longer has any mandatory parameters, and the first parameter allows a non-Controller
RequestHandler
to be passed.- Moved
buttonClicked()
toFormRequestHandler
- Moved
checkAccessAction()
toFormRequestHandler
- Moved
handleField()
toFormRequestHandler
- Moved
httpSubmission()
toFormRequestHandler
- Moved
Link()
toFormRequestHandler
Validator
methods have changed:
- Changed
validate()
to return aValidationResult
instance. - Removed
requireField()
. UseRequiredFields
subclass instead.
ValidationResult
now has these methods:
- Added
serialize()
/unserialize()
for saving within session state - Renamed
messageList()
togetMessages()
- Changed
error()
toaddMessage()
/addError()
/addFieldMessage()
/addFieldError()
- Renamed
valid()
toisValid()
ValidationException
has these changes:
- Changed constructor to remove second argument (
$message
). It now only accepts$result
, which may be a string, and optional$code
New DatetimeField
methods replace getConfig()
/ setConfig()
: {#datetimefield}
- Added
getTimezone()
/setTimezone()
- Added
getDateTimeOrder()
/setDateTimeOrder()
- Added
getLocale()
/setLocale()
- Removed
datavaluefield
config 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
andTimeField
Consequently,getDateField()
andgetTimeField()
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 usesetHTML5(false)
to set your custom format, or usedataValue()
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()
: {#datefield}
- Added
getDateFormat()
/setDateFormat()
- Added
getMinDate()
/setMinDate()
- Added
getMaxDate()
/setMaxDate()
- Added
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 byDateField->locale
and browser settings)
New TimeField
methods replace getConfig()
/ setConfig()
{#timefield}
- Added
getTimeFormat()
/setTimeFormat()
- Added
getLocale()
/setLocale()
Further API changes:
- Removed
TabularStyle
- Removed
NestedForm
- Removed
FieldList->getTabPathRewrites()
- Removed
FieldList->setTabPathRewrites()
- Removed
FieldList->rewriteTabPath()
- Removed
Form->transformTo()
- Removed
Form->callfieldmethod()
- Removed
Form->single_field_required()
- Removed
Form->current_action()
- Removed
Form->set_current_action()
- Removed
Form->testSubmission()
- Removed
Form->testAjaxSubmission()
- Removed
ValidationResult->messageList()
- Removed
ValidationResult->codeList()
- Removed
ValidationResult->message()
- Removed
ValidationResult->starredList()
- Removed
ValidationResult->error()
- Removed
ValidationResult->valid()
- Removed
ReportAdminForm.ss
template - Removed
FormField::dontEscape()
. Escaping is now managed on a class by class basis. - Removed
PermissionCheckboxSetField::getAssignedPermissionCodes()
- Removed
Requirements::delete_combined_files()
- Removed
NumericField_Readonly
. UsesetReadonly(true)
instead. - Removed
SSViewer->set_source_file_comments()
- Removed
SSViewer->get_source_file_comments()
- Removed
SSViewer->getOption()
- Removed
SSViewer->setOption()
- Removed
MemberDatetimeOptionsetField
(no replacement) - Removed
DateField_View_JQuery
(replaced with native HTML5 support inDateField
) - Moved
HTMLEditorField_Toolbar
toSilverStripe\Admin\ModalController
- Moved
HTMLEditorField_Embed
toSilverStripe\AssetAdmin\EmbedResource
- Removed
HTMLEditorField_File
- Removed
HTMLEditorField_Flash
- Removed
HTMLEditorField_Image
i18n
- 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.
- Moved
i18n.all_locales
config setting toSilverStripe\i18n\Data\Locales.locales
- Moved
i18n.common_languages
config setting toSilverStripe\i18n\Data\Locales.languages
- Moved
i18n.likely_subtags
config setting toSilverStripe\i18n\Data\Locales.likely_subtags
- Moved
i18n.tinymce_lang
config setting toSilverStripe\Forms\HTMLEditor\TinyMCEConfig.tinymce_lang
- Moved
i18n::get_tinymce_lang()
toSilverStripe\Forms\HTMLEditor\TinyMCEConfig::get_tinymce_lang()
- Moved
i18n::get_locale_from_lang()
toSilverStripe\i18n\Data\Locales::localeFromLang()
- Moved
i18n::get_lange_from_locale()
toSilverStripe\i18n\Data\Locales::langFromLocale()
- Moved
i18n::validate_locale()
toSilverStripe\i18n\Data\Locales::validate()
- Moved
i18n::get_common_languages()
toSilverStripe\i18n\Data\Locales::getLanguages()
- Moved
i18n::get_locale_name()
toSilverStripe\i18n\Data\Locales::localeName()
- Moved
i18n::get_language_name()
toSilverStripe\i18n\Data\Locales::languageName()
- Moved
i18n.module_priority
config setting toSilverStripe\i18n\Data\Sources.module_priority
- Moved
i18n::get_owner_module()
toSilverStripe\Core\Manifest\ClassManifest::getOwnerModule()
This now returns aModule
object instance instead of a string. - Moved
i18n::get_existing_translations()
toSilverStripe\i18n\Data\Sources::getKnownLocales()
- Removed
Zend_Translate
- Changed
i18n::_t()
to remove support for sprintf-style%s
arguments - Changed
i18n::_t()
to remove support for non-associative injection with named parameters - Removed
i18n::get_language_name()
- Removed
i18n::get_language_code()
- Removed
i18n::get_common_locales()
- Removed
i18n.common_locales
- Changed
Mailer
to an interface Email
re-written to be powered by SwiftMailer- Default template body variable renamed from
$Body
to$EmailContent
- Renamed
Email->setTemplate()
toEmail->setHTMLTemplate()
- Added
Email->setPlainTemplate()
for rendering plain versions of email - Renamed
Email->populateTemplate()
toEmail->setData()
SapphireTest
is_running_tests()
is no longer public and user code should not rely on this. Test-specific behaviour should be implemented insetUp()
andtearDown()
- Removed
setUpOnce()
. Please usesetUpBeforeClass()
- Removed
tearDownOnce()
. Please usetearDownAfterClass()
- Removed
TestListener
- Renamed
$requiredExtensions
to$required_extensions
(and made static) - Renamed
$extraDataObjects
to$extra_dataobjects
(and made static) - Renamed
$extraControllers
to$extra_controllers
(and made static)
Security
LoginForm
now has an abstract methodgetAuthenticatorName()
. 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 byMemberAuthenticator
and won't affect normal use.Authenticator
methodsregister()
andunregister()
are deprecated in favour of usingConfig
- Unused
SetPassword
property removed fromMember
. UseMember::changePassword
or setPassword
directly.