2014-10-22 12:47:56 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
<div class="info" markdown="1">
|
|
|
|
Convention is for extension class names to end in `Extension`. This isn't a requirement but makes it clearer
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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](../configuration).
|
|
|
|
|
|
|
|
**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 output `Hi <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
|
|
|
|
<p>$CurrentMember.SayHi</p>
|
|
|
|
|
|
|
|
// "Hi Sam"
|
|
|
|
|
|
|
|
**mysite/code/Page.php**
|
|
|
|
:::php
|
|
|
|
$member = Member::currentUser();
|
|
|
|
echo $member->SayHi;
|
|
|
|
|
|
|
|
// "Hi Sam"
|
|
|
|
|
|
|
|
|
|
|
|
## Modifying Existing Methods
|
|
|
|
|
2015-04-02 04:51:51 +02:00
|
|
|
If the `Extension` needs to modify an existing method it's a little trickier. It requires that the method you want to
|
2014-10-22 12:47:56 +02:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
<div class="info" markdown="1">
|
|
|
|
The `$validator` parameter is passed by reference, as it is an object.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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(new UploadField('Image', 'Profile Image'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
<div class="notice" markdown="1">
|
|
|
|
If you're providing a module or working on code that may need to be extended by other code, it should provide a *hook*
|
|
|
|
which allows an Extension to modify the results.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
:::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.
|
|
|
|
|
|
|
|
<div class="notice" markdown='1'>
|
|
|
|
Please note that each callback is only ever called once, and then cleared, so multiple extensions to the same function
|
|
|
|
require that a callback is registered each time, if necessary.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
<div class="notice" markdown="1">
|
|
|
|
This method is preferred to disabling, enabling, and calling field extensions manually.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
:::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](injector/)
|
|
|
|
* [api:Object::useCustomClass]
|
|
|
|
|
|
|
|
## API Documentation
|
|
|
|
|
|
|
|
* [api:Extension]
|
|
|
|
* [api:DataExtension]
|