Clarify usage of extend within DataExtensions

This commit is contained in:
Will Rossiter 2013-12-18 19:59:58 +13:00
parent b54661fd9f
commit 763aa2fbf6

View File

@ -2,90 +2,151 @@
## Introduction
Extensions allow for adding additional functionality to a `[api:DataObject]`.
In some cases, it can be easier to completely replace the used class throughout the core with your custom
implementation. Have a look at `[api:Object->useCustomClass()]`.
Extensions allow for adding additional functionality to a `[api:DataObject]` or
modifying existing functionality without the hassle of creating a subclass.
## Usage
Your extension will need to be a subclass of `[api:DataExtension]` or the `[api:Extension]` class.
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 Controllers)
:::php
<?php
// mysite/code/MyMemberExtension.php
class MyMemberExtension extends DataExtension {}
This defines your own extension where you can add your own functions, database fields or other properties you want.
After you create this extension however it does not yet apply it to your object. Next you need to tell SilverStripe what
class MyMemberExtension extends DataExtension {
}
This defines your own extension where we can add our own functions, database
fields or other properties. After this class has been created, it
does not yet apply it to your object. Next you need to tell SilverStripe what
class you want to extend.
### Adding a extension to a built-in class
Sometimes you will want to add extension to classes that you can't cleanly subclass.
For example, you might want to add a `MyMemberExtension` class to the `[api:Member]` object.
For example, you may might want to add a `MyMemberExtension` class to the
`[api:Member]` object to provide a custom method.
In order to active this extension, you'd add the following to your [config.yml](/topics/configuration).
In order to active this extension, you need to add the following to your
[config.yml](/topics/configuration).
:::yml
Member:
extensions:
- MyMemberExtension
Alternatively, you can add extensions through PHP code as well (in your `config.php` file),
which means they can be used in conditional configuration.
Alternatively, you can add extensions through PHP code (in your `_config.php`
file).
:::php
Member::add_extension('MyMemberExtension');
### Extending code to allow for extensions
If you're providing a module or working on code that may need to be extended by
other code, it can provide a *hook* which allows an Extension to modify the
results. This is done through the `[api:Object->extend()]` method.
:::php
public function myFunc() {
$foo = // ..
$this->extend('alterFoo', $foo);
return $foo;
}
In this example, the myFunc() method adds a hook to allow `DataExtension`
subclasses added to the instance to define an `alterFoo($foo)` method to modify
the result of the method.
The `$foo` parameter is passed by reference, as it is an object.
### Accessing the original Object from an Extension
In your extension class you can refer to the source object through the `owner`
property on the class.
:::php
<?php
class CustomMember extends Member {
public function alterFoo($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, you can use
`[api:Object->getExtensionInstances()]` and `[api:Object->hasExtension($extension)]`.
## Implementation
### Adding extra 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 static variables on load.
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 static variables on load.
The function should return a map where the keys are the names of the static variables to update:
The function should return a map where the keys are the names of the static
variables to update:
:::php
<?php
class CustomMember extends DataExtension {
private static $db = array(
'AvatarURL' => 'Varchar',
'Position' => 'Varchar',
);
private static $has_one = array(
'RelatedMember' => 'Member',
'Image' => 'Image',
);
}
### Modifying CMS Fields
The member class demonstrates an extension that allows you to update the default CMS fields for an
object in an extension:
The member class demonstrates an extension that allows you to update the default
CMS fields for an object in an extension:
:::php
public function getCMSFields() {
// ...
$this->extend('updateCMSFields', $fields);
return $fields;
}
<?php
class CustomMember extends DataExtension {
The `$`fields parameter is passed by reference, as it is an object.
private static $db = array(
'Position' => 'Varchar',
);
private static $has_one = array(
'Image' => 'Image',
);
:::php
public function updateCMSFields(FieldList $fields) {
$fields->push(new TextField('Position', 'Position Title'));
$fields->push(new TextField('Position'));
$fields->push(new UploadField('Image', 'Profile Image'));
}
}
### Adding/modifying fields prior to extensions
User code can intervene in the process of extending cms fields by using `beforeUpdateCMSFields`
in its implementation of `getCMSFields`. This can be useful in cases where user code will add
fields to a dataobject that should be present in the `$fields` parameter when passed to
`updateCMSFields` in extensions.
User code can intervene in the process of extending cms fields by using
`beforeUpdateCMSFields` in its implementation of `getCMSFields`. This can be
useful in cases where user code will add fields to a dataobject that should be
present in the `$fields` parameter when passed to `updateCMSFields` in
extensions.
This method is preferred to disabling, enabling, and calling cms field extensions manually.
This method is preferred to disabling, enabling, and calling cms field
extensions manually.
:::php
function getCMSFields() {
@ -101,81 +162,100 @@ This method is preferred to disabling, enabling, and calling cms field extension
### Object extension injection points
`Object` now 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.
`Object` now 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.
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.
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 initialisation. The code
needs to assign a value if not specified in self::$defaults, but before extensions have been 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() uses ($self) {
if(empty($self->MyField)) {
$self->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
}
});
parent::__construct();
}
### Custom database generation
Some extensions are designed to transparently add more sophisticated data-collection capabilities to your data object.
For example, `[api:Versioned]` adds version tracking and staging to any data object that it is applied to. To do this,
you need to be able to create additional database tables and fields to keep your state stored in.
Some extensions are designed to transparently add more sophisticated
data-collection capabilities to your `DataObject`. For example, `[api:Versioned]`
adds version tracking and staging to any `DataObject` that it is applied to.
To do this, define an **augmentDatabase()** method on your extension. This will be called when db/build is visited.
To do this, define an **augmentDatabase()** method on your extension. This will
be called when the database is rebuilt.
* You can query ``$this->owner`` for information about the data object, such as the fields it has
* You can use **DB::requireTable($tableName, $fieldList, $indexList)** to set up your new tables. This function takes
care of creating, modifying, or leaving tables as required, based on your desired schema.
* You can query `$this->owner` for information about the data object, such as
the fields it has
* You can use **DB::requireTable($tableName, $fieldList, $indexList)** to set
up your new tables. This function takes care of creating, modifying, or leaving
tables as required, based on your desired schema.
### Custom write queries
If you have customised the generated database, then you probably want to change the way that writes happen. This is
used by `[api:Versioned]` to get an entry written in ClassName_versions whenever an insert/update happens.
If you have customised the generated database, then you probably want to change
the way that writes happen. This isused by `[api:Versioned]` to get an entry
written in ClassName_versions whenever an insert/update happens.
To do this, define the **augmentWrite(&$manipulation)** method. This method is passed a manipulation array representing
the write about to happen, and is able to amend this as desired, since it is passed by reference.
To do this, define the **augmentWrite(&$manipulation)** method. This method is
passed a manipulation array representing the write about to happen, and is able
to amend this as desired, since it is passed by reference.
### Custom relation queries
The other queries that you will want to customise are the selection queries, called by get & get_one. For example, the
Versioned object has code to redirect every request to ClassName_live, if you are browsing the live site.
The other queries that you will want to customise are the selection queries,
called by get & get_one. For example, the Versioned object has code to redirect
every request to ClassName_live, if you are browsing the live site.
To do this, define the **augmentSQL(SQLQuery &$query)** method. Again, the $query object is passed by reference and can
be modified as needed by your method. Instead of a manipulation array, we have a `[api:SQLQuery]` object.
To do this, define the **augmentSQL(SQLQuery &$query)** method. Again, the
`$query` object is passed by reference and can be modified as needed by your
method. Instead of a manipulation array, we have a `[api:SQLQuery]` object.
### Additional methods
The other thing you may want to do with a extension is provide a method that can be called on the `[api:DataObject]` that is
being extended. For instance, you may add a publish() method to every `[api:DataObject]` that is extended with `[api:Versioned]`.
The other thing you may want to do with a extension is provide a method that can
be called on the `[api:DataObject]` that is being extended. For instance, you
may add a publish() method to every `[api:DataObject]` that is extended with
`[api:Versioned]`.
This is as simple as defining a method called publish() on your extension. Bear in mind, however, that instead of
$this, you should be referring to $this->owner.
This is as simple as defining a method called publish() on your extension. Bear
in mind, however, that instead of $this, you should be referring to
`$this->owner`.
* $this = The `[api:DataExtension]` object.
* $this->owner = The related `[api:DataObject]` object.
If you want to add your own internal properties, you can add this to the `[api:DataExtension]`, and these will be referred
to as `$this->propertyName`. Every `[api:DataObject]` has an associated `[api:DataExtension]` instance for each class that it is
extended by.
If you want to add your own internal properties, you can add this to the
`[api:DataExtension]`, and these will be referred to as `$this->propertyName`.
Every `[api:DataObject]` has an associated `[api:DataExtension]` instance for
each class that it is extended by.
:::php
<?php
class Customer extends DataObject {
private static $has_one = array('Account'=>'Account');
private static $has_one = array(
'Account' => 'Account'
);
private static $extensions = array(
'CustomerWorkflow'
@ -189,8 +269,9 @@ extended by.
'IsMarkedForDeletion'=>'Boolean'
);
private static $has_many = array('Customers'=>'Customer');
private static $has_many = array(
'Customers' => 'Customer'
);
}
class CustomerWorkflow extends DataExtension {
@ -198,9 +279,15 @@ extended by.
public function IsMarkedForDeletion() {
return ($this->owner->Account()->IsMarkedForDeletion == 1) ? true : false;
}
}
## API Documentation
`[api:DataExtension]`
* `[api:Extension]`
* `[api:DataExtension]`
## See Also
* [Injector](injector/)
* `[api:Object::useCustomClass]`