Conflicts: control/Director.php filesystem/File.php filesystem/GD.php filesystem/ImagickBackend.php forms/HtmlEditorField.php javascript/UploadField_uploadtemplate.js model/Image.php model/Image_Backend.php model/fieldtypes/Enum.php templates/AssetUploadField.ss tests/model/ImageTest.php tests/search/FulltextFilterTest.php
8.1 KiB
title: Extensions summary: Extensions and DataExtensions let you modify and augment objects transparently.
Extensions and DataExtensions
An [api:Extension] allows for adding additional functionality to a [api:Object] or modifying existing functionality without the hassle of creating a subclass. Developers can add Extensions to any [api:Object] subclass within core, modules or even their own code to make it more reusable.
Extensions are defined as subclasses of either [api:DataExtension] for extending a [api:DataObject] subclass or the [api:Extension] class for non DataObject subclasses (such as [api:Controllers])
mysite/code/extensions/MyMemberExtension.php
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'DateOfBirth' => 'SS_Datetime'
);
public function SayHi() {
// $this->owner refers to the original instance. In this case a `Member`.
return "Hi " . $this->owner->Name;
}
}
After this class has been created, it does not yet apply it to any object. We need to tell SilverStripe what classes
we want to add the MyMemberExtension
too. To activate this extension, add the following via the Configuration API.
mysite/_config/app.yml
:::yml
Member:
extensions:
- MyMemberExtension
Alternatively, we can add extensions through PHP code (in the _config.php
file).
:::php
Member::add_extension('MyMemberExtension');
This class now defines a MyMemberExtension
that applies to all Member
instances on the website. It will have
transformed the original Member
class in two ways:
- Added a new [api:SS_Datetime] for the users date of birth, and;
- Added a
SayHi
method to outputHi <User>
From within the extension we can add more functions, database fields, relations or other properties and have them added
to the underlying DataObject
just as if they were added to the original Member
class but without the need to edit
that file directly.
Adding Database Fields
Extra database fields can be added with a extension in the same manner as if they were placed on the DataObject
class
they're applied to. These will be added to the table of the base object - the extension will actually edit the $db,
$has_one etc.
mysite/code/extensions/MyMemberExtension.php
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'Position' => 'Varchar',
);
private static $has_one = array(
'Image' => 'Image',
);
public function SayHi() {
// $this->owner refers to the original instance. In this case a `Member`.
return "Hi " . $this->owner->Name;
}
}
mysite/templates/Page.ss
:::ss
$CurrentMember.Position
$CurrentMember.Image
Adding Methods
Methods that have a unique name will be called as part of the __call
method on [api:Object]. In the previous example
we added a SayHi
method which is unique to our extension.
mysite/templates/Page.ss :::ss
$CurrentMember.SayHi
// "Hi Sam"
mysite/code/Page.php :::php $member = Member::currentUser(); echo $member->SayHi;
// "Hi Sam"
Modifying Existing Methods
If the Extension
needs to modify an existing method it's a little trickier. It requires that the method you want to
customize has provided an Extension Hook in the place where you want to modify the data. An Extension Hook is done
through the [api:Object->extend]
method.
framework/security/Member.php
:::php
public function getValidator() {
// ..
$this->extend('updateValidator', $validator);
// ..
}
Extension Hooks can be located anywhere in the method and provide a point for any Extension
instances to modify the
variables at that given point. In this case, the core function getValidator
on the Member
class provides an
updateValidator
hook for developers to modify the core method. The MyMemberExtension
would modify the core member's
validator by defining the updateValidator
method.
mysite/code/extensions/MyMemberExtension.php
:::php
<?php
class MyMemberExtension extends DataExtension {
// ..
public function updateValidator($validator) {
// we want to make date of birth required for each member
$validator->addRequiredField('DateOfBirth');
}
}
Another common example of when you will want to modify a method is to update the default CMS fields for an object in an
extension. The CMS
provides a updateCMSFields
Extension Hook to tie into.
:::php
<?php
class MyMemberExtension extends DataExtension {
private static $db = array(
'Position' => 'Varchar',
);
private static $has_one = array(
'Image' => 'Image',
);
public function updateCMSFields(FieldList $fields) {
$fields->push(new TextField('Position'));
$fields->push($upload = new UploadField('Image', 'Profile Image'));
$upload->setAllowedFileCategories('image/supported');
}
}
:::php
public function Foo() {
$foo = // ..
$this->extend('updateFoo', $foo);
return $foo;
}
The convention for extension hooks is to provide an update{$Function}
hook at the end before you return the result. If
you need to provide extension hooks at the beginning of the method use before{..}
.
Owner
In your [api:Extension] class you can only refer to the source object through the owner
property on the class as
$this
will refer to your Extension
instance.
:::php
<?php
class MyMemberExtension extends DataExtension {
public function updateFoo($foo) {
// outputs the original class
var_dump($this->owner);
}
}
Checking to see if an Object has an Extension
To see what extensions are currently enabled on an object, use [api:Object->getExtensionInstances] and [api:Object->hasExtension]
:::php
$member = Member::currentUser();
print_r($member->getExtensionInstances());
if($member->hasExtension('MyCustomMemberExtension')) {
// ..
}
Object extension injection points
Object
has two additional methods, beforeExtending
and afterExtending
, each of which takes a method name and a
callback to be executed immediately before and after Object::extend()
is called on extensions.
This is useful in many cases where working with modules such as Translatable
which operate on DataObject
fields
that must exist in the FieldList
at the time that $this->extend('UpdateCMSFields')
is called.
Example: A class that wants to control default values during object initialization. The code needs to assign a value
if not specified in self::$defaults
, but before extensions have been called:
:::php
function __construct() {
$self = $this;
$this->beforeExtending('populateDefaults', function() use ($self) {
if(empty($self->MyField)) {
$self->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
}
});
parent::__construct();
}
Example 2: User code can intervene in the process of extending cms fields.
:::php
public function getCMSFields() {
$this->beforeUpdateCMSFields(function($fields) {
// Include field which must be present when updateCMSFields is called on extensions
$fields->addFieldToTab("Root.Main", new TextField('Detail', 'Details', null, 255));
});
$fields = parent::getCMSFields();
// ... additional fields here
return $fields;
}
Related Documentaion
- Injector
- [api:Object::useCustomClass]
API Documentation
- [api:Extension]
- [api:DataExtension]