mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
NEW Added beforeExtending, afterExtending, and beforeUpdateCMSFields to allow user code better control over interaction with extending methods
This commit is contained in:
parent
9f532fe976
commit
6e0e3564e1
@ -66,6 +66,52 @@ abstract class Object {
|
|||||||
*/
|
*/
|
||||||
protected $extension_instances = array();
|
protected $extension_instances = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of callbacks to call prior to extensions having extend called on them,
|
||||||
|
* each grouped by methodName.
|
||||||
|
*
|
||||||
|
* @var array[callable]
|
||||||
|
*/
|
||||||
|
protected $beforeExtendCallbacks = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows user code to hook into Object::extend prior to control
|
||||||
|
* being delegated to extensions. Each callback will be reset
|
||||||
|
* once called.
|
||||||
|
*
|
||||||
|
* @param string $method The name of the method to hook into
|
||||||
|
* @param callable $callback The callback to execute
|
||||||
|
*/
|
||||||
|
protected function beforeExtending($method, $callback) {
|
||||||
|
if(empty($this->beforeExtendCallbacks[$method])) {
|
||||||
|
$this->beforeExtendCallbacks[$method] = array();
|
||||||
|
}
|
||||||
|
$this->beforeExtendCallbacks[$method][] = $callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of callbacks to call after extensions having extend called on them,
|
||||||
|
* each grouped by methodName.
|
||||||
|
*
|
||||||
|
* @var array[callable]
|
||||||
|
*/
|
||||||
|
protected $afterExtendCallbacks = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows user code to hook into Object::extend after control
|
||||||
|
* being delegated to extensions. Each callback will be reset
|
||||||
|
* once called.
|
||||||
|
*
|
||||||
|
* @param string $method The name of the method to hook into
|
||||||
|
* @param callable $callback The callback to execute
|
||||||
|
*/
|
||||||
|
protected function afterExtending($method, $callback) {
|
||||||
|
if(empty($this->afterExtendCallbacks[$method])) {
|
||||||
|
$this->afterExtendCallbacks[$method] = array();
|
||||||
|
}
|
||||||
|
$this->afterExtendCallbacks[$method][] = $callback;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of the factory method, allows you to create an instance of a class
|
* An implementation of the factory method, allows you to create an instance of a class
|
||||||
*
|
*
|
||||||
@ -929,6 +975,14 @@ abstract class Object {
|
|||||||
public function extend($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
|
public function extend($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
|
||||||
$values = array();
|
$values = array();
|
||||||
|
|
||||||
|
if(!empty($this->beforeExtendCallbacks[$method])) {
|
||||||
|
foreach(array_reverse($this->beforeExtendCallbacks[$method]) as $callback) {
|
||||||
|
$value = call_user_func($callback, $a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||||
|
if($value !== null) $values[] = $value;
|
||||||
|
}
|
||||||
|
$this->beforeExtendCallbacks[$method] = array();
|
||||||
|
}
|
||||||
|
|
||||||
if($this->extension_instances) foreach($this->extension_instances as $instance) {
|
if($this->extension_instances) foreach($this->extension_instances as $instance) {
|
||||||
if(method_exists($instance, $method)) {
|
if(method_exists($instance, $method)) {
|
||||||
$instance->setOwner($this);
|
$instance->setOwner($this);
|
||||||
@ -938,6 +992,14 @@ abstract class Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!empty($this->afterExtendCallbacks[$method])) {
|
||||||
|
foreach(array_reverse($this->afterExtendCallbacks[$method]) as $callback) {
|
||||||
|
$value = call_user_func($callback, $a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||||
|
if($value !== null) $values[] = $value;
|
||||||
|
}
|
||||||
|
$this->afterExtendCallbacks[$method] = array();
|
||||||
|
}
|
||||||
|
|
||||||
return $values;
|
return $values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,3 +455,6 @@ you can enable those warnings and future-proof your code already.
|
|||||||
* Hard limit displayed pages in the CMS tree to `500`, and the number of direct children to `250`,
|
* Hard limit displayed pages in the CMS tree to `500`, and the number of direct children to `250`,
|
||||||
to avoid excessive resource usage. Configure through `Hierarchy.node_threshold_total` and `
|
to avoid excessive resource usage. Configure through `Hierarchy.node_threshold_total` and `
|
||||||
Hierarchy.node_threshold_leaf`. Set to `0` to show tree unrestricted.
|
Hierarchy.node_threshold_leaf`. Set to `0` to show tree unrestricted.
|
||||||
|
* `Object` now has `beforeExtending` and `afterExtending` to inject behaviour around method extension.
|
||||||
|
`DataObject` also has `beforeUpdateCMSFields` to insert fields between automatic scaffolding and extension
|
||||||
|
by `updateCMSFields`. See the [DataExtension Reference](/reference/dataextension) for more information.
|
||||||
|
@ -78,6 +78,57 @@ The `$`fields parameter is passed by reference, as it is an object.
|
|||||||
$fields->push(new UploadField('Image', 'Profile Image'));
|
$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.
|
||||||
|
|
||||||
|
This method is preferred to disabling, enabling, and calling cms field extensions manually.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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 initialisation. 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
|
### Custom database generation
|
||||||
|
|
||||||
Some extensions are designed to transparently add more sophisticated data-collection capabilities to your data object.
|
Some extensions are designed to transparently add more sophisticated data-collection capabilities to your data object.
|
||||||
|
@ -63,6 +63,8 @@ data management interfaces with very little custom coding.
|
|||||||
|
|
||||||
You can also alter the fields of built-in and module `DataObject` classes through
|
You can also alter the fields of built-in and module `DataObject` classes through
|
||||||
your own `[DataExtension](/reference/dataextension)`, and a call to `[api:DataExtension->updateCMSFields()]`.
|
your own `[DataExtension](/reference/dataextension)`, and a call to `[api:DataExtension->updateCMSFields()]`.
|
||||||
|
`[api::DataObject->beforeUpdateCMSFields()]` can also be used to interact with and add to automatically
|
||||||
|
scaffolded fields prior to being passed to extensions (See `[DataExtension](/reference/dataextension)`).
|
||||||
|
|
||||||
### Searchable Fields
|
### Searchable Fields
|
||||||
|
|
||||||
|
@ -1999,6 +1999,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
return $fs->getFieldList();
|
return $fs->getFieldList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows user code to hook into DataObject::getCMSFields prior to updateCMSFields
|
||||||
|
* being called on extensions
|
||||||
|
*
|
||||||
|
* @param callable $callback The callback to execute
|
||||||
|
*/
|
||||||
|
protected function beforeUpdateCMSFields($callback) {
|
||||||
|
$this->beforeExtending('updateCMSFields', $callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centerpiece of every data administration interface in Silverstripe,
|
* Centerpiece of every data administration interface in Silverstripe,
|
||||||
* which returns a {@link FieldList} suitable for a {@link Form} object.
|
* which returns a {@link FieldList} suitable for a {@link Form} object.
|
||||||
|
@ -8,6 +8,9 @@ class DataExtensionTest extends SapphireTest {
|
|||||||
'DataExtensionTest_Player',
|
'DataExtensionTest_Player',
|
||||||
'DataExtensionTest_RelatedObject',
|
'DataExtensionTest_RelatedObject',
|
||||||
'DataExtensionTest_MyObject',
|
'DataExtensionTest_MyObject',
|
||||||
|
'DataExtensionTest_CMSFieldsBase',
|
||||||
|
'DataExtensionTest_CMSFieldsChild',
|
||||||
|
'DataExtensionTest_CMSFieldsGrandchild'
|
||||||
);
|
);
|
||||||
|
|
||||||
protected $requiredExtensions = array(
|
protected $requiredExtensions = array(
|
||||||
@ -156,6 +159,61 @@ class DataExtensionTest extends SapphireTest {
|
|||||||
$this->assertEquals("hello world", $mo->testMethodApplied());
|
$this->assertEquals("hello world", $mo->testMethodApplied());
|
||||||
$this->assertEquals("hello world", $do->testMethodApplied());
|
$this->assertEquals("hello world", $do->testMethodApplied());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPageFieldGeneration() {
|
||||||
|
$page = new DataExtensionTest_CMSFieldsBase();
|
||||||
|
$fields = $page->getCMSFields();
|
||||||
|
$this->assertNotEmpty($fields);
|
||||||
|
|
||||||
|
// Check basic field exists
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('PageField'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPageExtensionsFieldGeneration() {
|
||||||
|
$page = new DataExtensionTest_CMSFieldsBase();
|
||||||
|
$fields = $page->getCMSFields();
|
||||||
|
$this->assertNotEmpty($fields);
|
||||||
|
|
||||||
|
// Check extending fields exist
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldRemove')); // Not removed yet!
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldKeep'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubpageFieldGeneration() {
|
||||||
|
$page = new DataExtensionTest_CMSFieldsChild();
|
||||||
|
$fields = $page->getCMSFields();
|
||||||
|
$this->assertNotEmpty($fields);
|
||||||
|
|
||||||
|
// Check extending fields exist
|
||||||
|
$this->assertEmpty($fields->dataFieldByName('ExtendedFieldRemove')); // Removed by child class
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldKeep'));
|
||||||
|
$this->assertNotEmpty($preExtendedField = $fields->dataFieldByName('ChildFieldBeforeExtension'));
|
||||||
|
$this->assertEquals($preExtendedField->Title(), 'ChildFieldBeforeExtension: Modified Title');
|
||||||
|
|
||||||
|
// Post-extension fields
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('ChildField'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubSubpageFieldGeneration() {
|
||||||
|
$page = new DataExtensionTest_CMSFieldsGrandchild();
|
||||||
|
$fields = $page->getCMSFields();
|
||||||
|
$this->assertNotEmpty($fields);
|
||||||
|
|
||||||
|
// Check extending fields exist
|
||||||
|
$this->assertEmpty($fields->dataFieldByName('ExtendedFieldRemove')); // Removed by child class
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldKeep'));
|
||||||
|
|
||||||
|
// Check child fields removed by grandchild in beforeUpdateCMSFields
|
||||||
|
$this->assertEmpty($fields->dataFieldByName('ChildFieldBeforeExtension')); // Removed by grandchild class
|
||||||
|
|
||||||
|
// Check grandchild field modified by extension
|
||||||
|
$this->assertNotEmpty($preExtendedField = $fields->dataFieldByName('GrandchildFieldBeforeExtension'));
|
||||||
|
$this->assertEquals($preExtendedField->Title(), 'GrandchildFieldBeforeExtension: Modified Title');
|
||||||
|
|
||||||
|
// Post-extension fields
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('ChildField'));
|
||||||
|
$this->assertNotEmpty($fields->dataFieldByName('GrandchildField'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DataExtensionTest_Member extends DataObject implements TestOnly {
|
class DataExtensionTest_Member extends DataObject implements TestOnly {
|
||||||
@ -313,3 +371,93 @@ DataExtensionTest_MyObject::add_extension('DataExtensionTest_Ext1');
|
|||||||
DataExtensionTest_MyObject::add_extension('DataExtensionTest_Ext2');
|
DataExtensionTest_MyObject::add_extension('DataExtensionTest_Ext2');
|
||||||
DataExtensionTest_MyObject::add_extension('DataExtensionTest_Faves');
|
DataExtensionTest_MyObject::add_extension('DataExtensionTest_Faves');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for CMS fields
|
||||||
|
*/
|
||||||
|
class DataExtensionTest_CMSFieldsBase extends DataObject implements TestOnly {
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'PageField' => 'Varchar(255)'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $extensions = array(
|
||||||
|
'DataExtensionTest_CMSFieldsBaseExtension'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
$fields->addFieldToTab('Root.Test', new TextField('PageField'));
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension to top level test class, tests that updateCMSFields work
|
||||||
|
*/
|
||||||
|
class DataExtensionTest_CMSFieldsBaseExtension extends DataExtension implements TestOnly {
|
||||||
|
private static $db = array(
|
||||||
|
'ExtendedFieldKeep' => 'Varchar(255)',
|
||||||
|
'ExtendedFieldRemove' => 'Varchar(255)'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function updateCMSFields(FieldList $fields) {
|
||||||
|
$fields->addFieldToTab('Root.Test', new TextField('ExtendedFieldRemove'));
|
||||||
|
$fields->addFieldToTab('Root.Test', new TextField('ExtendedFieldKeep'));
|
||||||
|
|
||||||
|
if($childField = $fields->dataFieldByName('ChildFieldBeforeExtension')) {
|
||||||
|
$childField->setTitle('ChildFieldBeforeExtension: Modified Title');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($grandchildField = $fields->dataFieldByName('GrandchildFieldBeforeExtension')) {
|
||||||
|
$grandchildField->setTitle('GrandchildFieldBeforeExtension: Modified Title');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Second level test class.
|
||||||
|
* Tests usage of beforeExtendingCMSFields
|
||||||
|
*/
|
||||||
|
class DataExtensionTest_CMSFieldsChild extends DataExtensionTest_CMSFieldsBase implements TestOnly {
|
||||||
|
private static $db = array(
|
||||||
|
'ChildField' => 'Varchar(255)',
|
||||||
|
'ChildFieldBeforeExtension' => 'Varchar(255)'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$this->beforeExtending('updateCMSFields', function(FieldList $fields) {
|
||||||
|
$fields->addFieldToTab('Root.Test', new TextField('ChildFieldBeforeExtension'));
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->afterExtending('updateCMSFields', function(FieldList $fields){
|
||||||
|
$fields->removeByName('ExtendedFieldRemove', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
$fields->addFieldToTab('Root.Test', new TextField('ChildField'));
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Third level test class, testing that beforeExtendingCMSFields can be nested
|
||||||
|
*/
|
||||||
|
class DataExtensionTest_CMSFieldsGrandchild extends DataExtensionTest_CMSFieldsChild implements TestOnly {
|
||||||
|
private static $db = array(
|
||||||
|
'GrandchildField' => 'Varchar(255)'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$this->beforeUpdateCMSFields(function(FieldList $fields) {
|
||||||
|
// Remove field from parent's beforeExtendingCMSFields
|
||||||
|
$fields->removeByName('ChildFieldBeforeExtension', true);
|
||||||
|
|
||||||
|
// Adds own pre-extension field
|
||||||
|
$fields->addFieldToTab('Root.Test', new TextField('GrandchildFieldBeforeExtension'));
|
||||||
|
});
|
||||||
|
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
$fields->addFieldToTab('Root.Test', new TextField('GrandchildField'));
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user