mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Clarify usage of extend within DataExtensions
This commit is contained in:
parent
b54661fd9f
commit
763aa2fbf6
@ -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 {}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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',
|
||||
);
|
||||
|
||||
:::php
|
||||
public function updateCMSFields(FieldList $fields) {
|
||||
$fields->push(new TextField('Position', 'Position Title'));
|
||||
$fields->push(new UploadField('Image', 'Profile Image'));
|
||||
private static $has_one = array(
|
||||
'Image' => 'Image',
|
||||
);
|
||||
|
||||
public function updateCMSFields(FieldList $fields) {
|
||||
$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,106 +162,132 @@ 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'
|
||||
);
|
||||
private static $extensions = array(
|
||||
'CustomerWorkflow'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
class Account extends DataObject {
|
||||
|
||||
private static $db = array(
|
||||
'IsMarkedForDeletion'=>'Boolean'
|
||||
);
|
||||
|
||||
private static $has_many = array('Customers'=>'Customer');
|
||||
private static $db = array(
|
||||
'IsMarkedForDeletion'=>'Boolean'
|
||||
);
|
||||
|
||||
private static $has_many = array(
|
||||
'Customers' => 'Customer'
|
||||
);
|
||||
}
|
||||
|
||||
class CustomerWorkflow extends DataExtension {
|
||||
|
||||
public function IsMarkedForDeletion() {
|
||||
return ($this->owner->Account()->IsMarkedForDeletion == 1) ? true : false;
|
||||
}
|
||||
|
||||
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]`
|
||||
|
Loading…
x
Reference in New Issue
Block a user