API Standardise Relation interface
21 KiB
4.0.0 (unreleased)
Overview
Framework
- Minimum PHP version raised to 5.5.0
- Deprecate
SQLQuery
in favourSQLSelect
DataList::filter
by null now internally generates "IS NULL" or "IS NOT NULL" conditions appropriately on queriesDataObject::database_fields
now returns all fields on that table.DataObject::db
now returns composite fields.DataObject::ClassName
field has been refactored into aDBClassName
type field.- Image manipulations have been moved into a new
[api:ImageManipulation]
trait. CMSFileAddController
removed.- UploadField::setAllowedFileCategories('image') now excludes non-resizeable images. 'unresizeable_image' is can be used to validate these types.
Image_Backend
API now loads and saves 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.
New API
- 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.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
formfield 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.
Deprecated classes/methods
ORM
DataList::getRelation
is removed, as it was mutable. UseDataList::applyRelation
instead, which is immutable.
ErrorPage
ErrorPage.static_filepath
config has been removed.ErrorPage::get_filepath_for_errorcode
has been removedErrorPage::alternateFilepathForErrorcode
extension point has been removed
See notes below on upgrading extensions to the ErrorPage class
Assets and Filesystem
The following image manipulations previously deprecated has been removed:
Image::SetRatioSize
superceded byFit
Image::SetWidth
superceded byScaleWidth
Image::SetHeight
superceded byScaleHeight
Image::SetSize
superceded byPad
Image::PaddedImage
superceded byPad
Image::CroppedImage
superceded byFill
Image::AssetLibraryPreview
superceded byPreviewThumbnail
Image::AssetLibraryThumbnail
superceded byCMSThumbnail
The following File
methods have been removed. Since there is no longer any assumed local path for any file,
methods which dealt with such paths may no longer be relied on.
File::deletedatabaseOnly
File::link_shortcode_handler
renamed tohandle_shortcode
File::setParentID
File::getFullPath
File::getRelativePath
File::Content
database field is removed
Image manipulations have been moved out of Image.php and now available to any File or DBFile which has the appropriate mime types. The following file manipulations classes and methods have been removed:
CleanImageManipulationCache
classImage_Cached
classImage::regenerateFormattedImages
Image::getGeneratedImages
Image::deleteFormattedImages
AssetAdmin::deleteunusedthumbnails
AssetAdmin::getUnusedThumbnails
Many Folder
methods have also been removed:
Folder::syncChildren
Folder::constructChild
Folder::addUploadToFolder
The following filesystem synchronisation methods are also removed
Filesystem::sync
AssetAdmin::doSync
The Spyc YAML library has been removed from /thirdparty. Please load it yourself, or use the Symfony YAML component that’s automatically installed by composer.
Requirements
The following methods and properties on Requirements_Backend
have been renamed:
Requirements_Backund::$combine_files
made protected and renamed$combinedFiles
Requirements_Backend::$combine_js_with_min
made protected and renamed$minifyCombinedFiles
Requirements_Backend::$write_header_comments
made protected and renamed$writeHeaderComment
Requirements_Backend::$write_js_to_body
made protected and renamed to$writeJavascriptToBody
Requirements_Backend::$force_js_to_bottom
renamed to$forceJSToBottom
get_combined_files_enabled
renamed togetCombinedFilesEnabled
set_combined_files_enabled
renamed tosetCombinedFilesEnabled
get_suffix_requirements
renamed togetSuffixRequirements
set_suffix_requirements
renamed tosetSuffixRequirements
get_custom_scripts
renamed togetCustomScripts
unblock_all
renamed tounblockAll
include_in_response
renamed toincludeInResponse
combine_files
renamed tocombineFiles
get_combine_files
renamed togetCombinedFiles
clear_combined_files
renamed toclearCombinedFiles
process_combined_files
renamed toprocessCombinedFiles
set_write_js_to_body
renamed tosetWriteJavascriptToBody
set_force_js_to_bottom
renamed tosetForceJSToBottom
New methods on Requirements
are added to access these:
get_minify_combined_js_files
set_minify_combined_js_files
get_force_js_to_bottom
get_write_js_to_body
And some methods on Requirements
and Requirements_Backend
have been removed as they are obsolete.
delete_combined_files
(both classes)
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.
In addition, a new API for javascript files has been added to support front-end tools
such as browserify that pre-combine scripts. You can specify
a 'provides' option to the second parameter of Requirements::javascript
to declare
included files.
E.g.
:::php
Requirements::javascript('mysite/js/dist/bundle.js', ['provides' => [
'mysite/js/jquery.js'
'mysite/js/src/main.js',
'mysite/js/src/functions.js'
]]);
Upgrading
Update code that uses SQLQuery
Where your code once used SQLQuery you should now use SQLSelect in all cases, as this has been removed.
See the 3.2.0 upgrading notes for details on how existing code should be upgraded.
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.
:::yaml
\SilverStripe\Filesystem\Flysystem\FlysystemAssetStore:
legacy_paths: true
See [/developer_guides/files/file_management] for more information on how the new system works.
Migrating File DataObject from 3.x to 4.0
Since the structure of File
dataobjects has changed, a new task MigrateFileTask
has been added to assist
in migration of legacy files. Migration can be invoked by either this task, or can be configured to automatically
run during dev build by setting the File.migrate_legacy_file
config to true. However, it's recommended that
this task is run manually during an explicit migration process, as this process could potentially consume
large amounts of memory and run for an extended time.
:::yaml
File:
migrate_legacy_file: true
Upgrade code which acts on Image
As all image-specific manipulations has been refactored from Image
into an ImageManipulations
trait, which
is applied to both File
and DBFile
. These both implement a common interface AssetContainer
, which
has the getIsImage()
method. In some cases, it may be preferable to invoke this method to detect
if the asset is an image or not, rather than checking the subclass, as the asset may also be a DBFile
with
an image filter applied, rather than an instance of the Image
dataobject.
In addition, a new file category image/supported
has been added, which is a subset of the image
category.
This is the subset of all image types which may be assigned to the [api:Image]
dataobject, and may have
manipulations applied to it. This should be used as the file type restriction on any [api:UploadField]
which
is intended to upload images for manipulation.
Before:
:::php
if($file instanceof Image) {
$upload = new UploadField();
$upload->setAllowedFileCategories('image');
}
After:
:::php
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')
.
E.g.
:::php
class MyObject extends DataObject {
private static $has_one = array(
"ImageObject" => "Image"
);
private static $db = array(
"ImageField" => "DBFile('image/supported')"
);
}
Upgrading code that writes to File
dataobjects, or writes files to the 'assets' folder
In the past all that was necessary to write a File
dataobject to the database was to ensure a physical file
existed in the assets folder, and that the Filename of the dataobject was set to the same location.
Since the storage of physical files is no longer a standard location, it's necessary to delegate the writing of such
files to the asset persistence layer. As a wrapper for an individual file, you can use any of the setFrom*
methods to assign content from a local (e.g. temporary) file, a stream, or a string of content.
You would need to upgrade your code as below.
Before:
:::php
function importTempFile($tmp) {
copy($tmp, ASSETS_PATH . '/imported/' . basename($tmp));
$file = new File();
$file->setFilename('assets/imported/'.basename($tmp));
$file->write();
}
After:
:::php
function importTempFile($tmp) {
$file = new File();
$file->setFromLocalFile($tmp, 'imported/'.basename($tmp));
$file->write();
}
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:
- Deleting File dataobjects no longer removes the physical file directly. This is because any file could be referenced from DBFile fields, and deleting these could be a potentially unsafe operation.
- 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.
- Moving files now performs a file copy rather than moving the underlying file, although only a single DataObject will exist, and will reference the destination path.
- Folder dataobjects are now purely logical dataobjects, and perform no actual filesystem folder creation on write.
Upgrading code performs custom image manipulations
As file storage and handling has been refactored into the abstract interface, many other components which were
once specific to Image.php have now been moved into a shared ImageManipulation
trait. Manipulations of file content,
which are used to generate what are now called "variants" of assets, is now a generic api available to both File
and DBFile
classes through this trait.
Custom manipulations, applied via extensions, must be modified to use the new API.
For instance, code which sizes images to a fixed width should be updated as below:
Before:
:::php
// in MyImageExtension.php
class MyImageExtension extends 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
Image::add_extension('MyImageExtension');
Now image manipulations are implemented with a single method via a callback generator.
After:
:::php
// in MyImageExtension.php
class MyImageExtension extends 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');
DBFile::add_extension('MyImageExtension');
There are a few differences in this new API:
- The extension is no longer specific to dataobjects, so it uses the generic 'Extension' class instead of 'DataExtension'
- This extension is added to both
DBFile
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.
Upgrading code that uses composite db fields.
CompositeDBField
is now an abstract class, not an interface. 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.
:::php
<?
class MyAddressField extends CompositeDBField {
private static $composite_db = array(
'Street' => 'Varchar(200)',
'Suburb' => 'Varchar(100)',
'City' => 'Varchar(100)',
'Country' => 'Varchar(100)'
);
public function scaffoldFormField($title = null) {
new AddressFormField($this->getName(), $title);
}
}
Upgrading code that references DataObject::database_fields
or DataObject::db
These methods have been updated to include base fields (such as ID, ClassName, Created, and LastEdited), as well as composite DB fields.
DataObject::database_fields
does not have a second parameter anymore, and can be called directly on an object
or class. E.g. Member::database_fields()
If user code requires the list of fields excluding base fields, then use custom_database_fields instead, or
make sure to call unset($fields['ID']);
if this field should be excluded.
DataObject:db()
will return all logical fields, including foreign key ids and composite DB Fields, alongside
any child fields of these composites. This method can now take a second parameter $includesTable, which
when set to true (with a field name as the first parameter), will also include the table prefix in
Table.ClassName(args)
format.
Update code that uses SQLQuery
SQLQuery is still implemented, but now extends the new SQLSelect class and has some methods deprecated. Previously this class was used for both selecting and deleting, but these have been superceded by the specialised SQLSelect and SQLDelete classes.
Take care for any code or functions which expect an object of type SQLQuery
, as
these references should be replaced with SQLSelect
. Legacy code which generates
SQLQuery
can still communicate with new code that expects SQLSelect
as it is a
subclass of SQLSelect
, but the inverse is not true.
Update implementations of augmentSQL
Since this method now takes a SQLSelect
as a first parameter, existing code referencing the deprecated SQLQuery
type will raise a PHP error.
E.g.
Before:
:::php
function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) {
$locale = Translatable::get_current_locale();
if(!preg_match('/("|\'|`)Locale("|\'|`)/', implode(' ', $query->getWhere()))) {
$qry = sprintf('"Locale" = \'%s\'', Convert::raw2sql($locale));
$query->addWhere($qry);
}
}
After:
:::php
function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) {
$locale = Translatable::get_current_locale();
if(!preg_match('/("|\'|`)Locale("|\'|`)/', implode(' ', $query->getWhereParameterised($parameters)))) {
$query->addWhere(array(
'"Locale"' => $locale
));
}
}
Update code that modifies the behaviour of ErrorPage
ErrorPage has been updated to use a configurable asset backend, similar to the AssetStore
described above.
This replaces the ErrorPage.static_filepath
config that was used to write local files.
As a result, error pages may be cached either to a local filesystem, or an external Flysystem store (which is configured via setting a new Flysystem backend with YAML).
ErrorPage::get_filepath_for_errorcode()
has been removed, because the local path for a specific code is
no longer assumed. Instead you should use ErrorPage::get_content_for_errorcode
which retrieves the
appropriate content for that error using one of the methods above.
In order to retrieve the actual filename (which is used to identify an error page regardless of base
path), you can use ErrorPage::get_error_filename()
instead. Unlike the old get_filepath_for_errorcode
method, there is no $locale parameter.
In case that user code must customise this filename, such as for extensions which provide a locale value
for any error page, the extension point updateErrorFilename
can be used. This extension point should
also be used to replace any alternateFilepathForErrorcode
used.
E.g.
:::php
class MyErrorPageExtension extends SiteTreeExtension {
public function updateErrorFilename(&$name, &$statuscode) {
if($this->owner->exists()) {
$locale = $this->Locale;
} else {
$locale = Translatable::get_current_locale();
}
$name = "error-{$statusCode}-{$locale}.html";
}
}
:::yaml
ErrorPage:
extensions:
- MyErrorPageExtension
Upgrading asset web.config, .htaccess, or other server configuration
Server configuration files for /assets are no longer static, and are regenerated via a set of standard silverstripe templates on flush. These templates include:
Assets_HTAccess.ss
: Template for public permissions on the Apache server.Assets_WebConfig.ss
: Template for public permissions on the IIS server.Protected_HTAccess.ss
: Template for the protected store on the Apache server (should deny all requests).Protected_WebConfig.ss
: Template for the protected store on the IIS server (should deny all requests).
You will need to make sure that these files are writable via the web server, and that any necessary configuration customisation is done via overriding these templates.
If upgrading from an existing installation, make sure to invoke ?flush=all at least once.
See [/developer_guides/files/file_security] for more information.
ListboxField
is now multiple-only
Previously, this field would operate as either a single select (default) or multi-select by setting
setMultiple
to either true or false.
Now this field should only be used for multi-selection. Single-selection should be done using
a regular DropdownField
.
GroupedDropdownField::setDisabled
now only accepts a list of values.
Where previously you could specify a list of grouped values in the same way as setSource
, this
method now only accepts either a non-associative array of values (not titles) or an SS_List
of items to disable.