mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Moved extension/decorator system from DataObject to Object, so that any object in the system can be extended
Added doWrite option to duplicate, so that writing can be disabled on a duplicated node Added augmentBeforeWrite, so that extensions can do more onBeforeWrite stuff Added set_context_obj() so that objects can give some 'context' to the decorators during particular operations Created a super-class of DataObjectDecorator, Extension that isn't specifically targetted at DataObjects git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@40226 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
c3f124dd95
commit
015a7dec45
24
core/Extension.php
Normal file
24
core/Extension.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add extension that can be added to an object with Object::add_extension().
|
||||||
|
* For DataObject extensions, use DataObjectDecorator
|
||||||
|
*/
|
||||||
|
|
||||||
|
abstract class Extension extends Object {
|
||||||
|
/**
|
||||||
|
* The DataObject that owns this decorator.
|
||||||
|
* @var DataObject
|
||||||
|
*/
|
||||||
|
protected $owner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the owner of this decorator.
|
||||||
|
* @param DataObject $owner
|
||||||
|
*/
|
||||||
|
function setOwner(Object $owner) {
|
||||||
|
$this->owner = $owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -39,11 +39,6 @@ class DataObject extends Controller {
|
|||||||
*/
|
*/
|
||||||
protected $components;
|
protected $components;
|
||||||
|
|
||||||
/**
|
|
||||||
* This DataObjects extensions, eg Versioned.
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $extension_instances = array();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if this DataObject has been destroyed.
|
* True if this DataObject has been destroyed.
|
||||||
@ -95,14 +90,6 @@ class DataObject extends Controller {
|
|||||||
HTTP::register_modification_date($record['LastEdited']);
|
HTTP::register_modification_date($record['LastEdited']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the extensions
|
|
||||||
if($extensions = $this->stat('extensions')) {
|
|
||||||
foreach($extensions as $extension) {
|
|
||||||
$instance = eval("return new $extension;");
|
|
||||||
$instance->setOwner($this);
|
|
||||||
$this->extension_instances[$instance->class] = $instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
// Must be called after parent constructor
|
// Must be called after parent constructor
|
||||||
@ -126,15 +113,16 @@ class DataObject extends Controller {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a duplicate of this node.
|
* Create a duplicate of this node.
|
||||||
* It will create the duplicate in the database.
|
*
|
||||||
|
* @param $doWrite Perform a write() operation before returning the object. If this is true, it will create the duplicate in the database.
|
||||||
*
|
*
|
||||||
* @return DataObject A duplicate of this node. The exact type will be the type of this node.
|
* @return DataObject A duplicate of this node. The exact type will be the type of this node.
|
||||||
*/
|
*/
|
||||||
function duplicate() {
|
function duplicate($doWrite = true) {
|
||||||
$className = $this->class;
|
$className = $this->class;
|
||||||
$clone = new $className( $this->record );
|
$clone = new $className( $this->record );
|
||||||
$clone->ID = 0;
|
$clone->ID = 0;
|
||||||
$clone->write();
|
if($doWrite) $clone->write();
|
||||||
return $clone;
|
return $clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,14 +163,14 @@ class DataObject extends Controller {
|
|||||||
*/
|
*/
|
||||||
function defineMethods() {
|
function defineMethods() {
|
||||||
if($this->class == 'DataObject') return;
|
if($this->class == 'DataObject') return;
|
||||||
|
|
||||||
if($this->extension_instances) foreach($this->extension_instances as $i => $instance) {
|
|
||||||
$this->addMethodsFrom('extension_instances', $i);
|
|
||||||
|
|
||||||
// Define the extra db fields
|
parent::defineMethods();
|
||||||
|
|
||||||
|
// Define the extra db fields
|
||||||
|
if($this->extension_instances) foreach($this->extension_instances as $i => $instance) {
|
||||||
$instance->loadExtraDBFields();
|
$instance->loadExtraDBFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up accessors for joined items
|
// Set up accessors for joined items
|
||||||
if($manyMany = $this->many_many()) {
|
if($manyMany = $this->many_many()) {
|
||||||
foreach($manyMany as $relationship => $class) {
|
foreach($manyMany as $relationship => $class) {
|
||||||
@ -201,8 +189,6 @@ class DataObject extends Controller {
|
|||||||
$this->addWrapperMethod($relationship, 'getComponent');
|
$this->addWrapperMethod($relationship, 'getComponent');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::defineMethods();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -312,6 +298,9 @@ class DataObject extends Controller {
|
|||||||
*/
|
*/
|
||||||
protected function onBeforeWrite() {
|
protected function onBeforeWrite() {
|
||||||
$this->brokenOnWrite = false;
|
$this->brokenOnWrite = false;
|
||||||
|
|
||||||
|
$dummy = null;
|
||||||
|
$this->extend('augmentBeforeWrite', $dummy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1304,56 +1293,6 @@ class DataObject extends Controller {
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the given function on all of this object's extensions
|
|
||||||
*
|
|
||||||
* @param string $funcName The name of the function.
|
|
||||||
* @param mixed $arg An Argument to be passed to each of the extension functions.
|
|
||||||
*/
|
|
||||||
public function extend($funcName, &$arg) {
|
|
||||||
if($this->extension_instances) {
|
|
||||||
foreach($this->extension_instances as $extension) {
|
|
||||||
if($extension->hasMethod($funcName)) {
|
|
||||||
$extension->$funcName($arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an extension on this DataObject
|
|
||||||
*
|
|
||||||
* @param string $name Classname of the Extension (e.g. 'Versioned')
|
|
||||||
*
|
|
||||||
* @return DataObjectDecorator The instance of the extension
|
|
||||||
*/
|
|
||||||
public function getExtension($name) {
|
|
||||||
return $this->extension_instances[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given extension class is attached to this object
|
|
||||||
*
|
|
||||||
* @param string $requiredExtension Classname of the extension
|
|
||||||
*
|
|
||||||
* @return boolean True if the given extension class is attached to this object
|
|
||||||
*/
|
|
||||||
public function hasExtension($requiredExtension) {
|
|
||||||
return isset($this->extension_instances[$requiredExtension]) ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an extension to the given object.
|
|
||||||
* This can be used to add extensions to built-in objects, such as role decorators on Member
|
|
||||||
*/
|
|
||||||
public static function add_extension($className, $extensionName) {
|
|
||||||
Object::addStaticVars($className, array(
|
|
||||||
'extensions' => array(
|
|
||||||
$extensionName,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a bunch of fields in a list - a <ul> of <li><b>name:</b> value</li>
|
* Get a bunch of fields in a list - a <ul> of <li><b>name:</b> value</li>
|
||||||
*
|
*
|
||||||
@ -1470,6 +1409,11 @@ class DataObject extends Controller {
|
|||||||
* Flush the cached results for get_one()
|
* Flush the cached results for get_one()
|
||||||
*/
|
*/
|
||||||
public function flushCache() {
|
public function flushCache() {
|
||||||
|
if($this->class == 'DataObject') {
|
||||||
|
DataObject::$cache_get_one = array();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$classes = ClassInfo::ancestry($this->class);
|
$classes = ClassInfo::ancestry($this->class);
|
||||||
foreach($classes as $class) {
|
foreach($classes as $class) {
|
||||||
// If someone else has called get_one and flushCache() is called, then that object will be destroyed.
|
// If someone else has called get_one and flushCache() is called, then that object will be destroyed.
|
||||||
@ -1540,7 +1484,7 @@ class DataObject extends Controller {
|
|||||||
$tableClasses = ClassInfo::dataClassesFor($callerClass);
|
$tableClasses = ClassInfo::dataClassesFor($callerClass);
|
||||||
$baseClass = array_shift($tableClasses);
|
$baseClass = array_shift($tableClasses);
|
||||||
|
|
||||||
return DataObject::get_one($callerClass,"`$baseClass`.ID = $id");
|
return DataObject::get_one($callerClass,"`$baseClass`.`ID` = $id");
|
||||||
} else {
|
} else {
|
||||||
user_error("DataObject::get_by_id passed a non-numeric ID #$id", E_USER_WARNING);
|
user_error("DataObject::get_by_id passed a non-numeric ID #$id", E_USER_WARNING);
|
||||||
}
|
}
|
||||||
@ -1695,13 +1639,34 @@ class DataObject extends Controller {
|
|||||||
public function isInDB() {
|
public function isInDB() {
|
||||||
return is_numeric( $this->ID ) && $this->ID > 0;
|
return is_numeric( $this->ID ) && $this->ID > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a 'context object' that can be used to provide hints about how to process a particular get / get_one request.
|
||||||
|
* In particular, DataObjectDecorators can use this to amend queries more effectively.
|
||||||
|
* Care must be taken to unset the context object after you're done with it, otherwise you will have a stale context,
|
||||||
|
* which could cause horrible bugs.
|
||||||
|
*/
|
||||||
|
public static function set_context_obj($obj) {
|
||||||
|
if($obj && self::$context_obj) user_error("Dataobject::set_context_obj called when there is already a context.", E_USER_WARNING);
|
||||||
|
self::$context_obj = $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current context object.
|
||||||
|
*/
|
||||||
|
public static function context_obj() {
|
||||||
|
return self::$context_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static $context_obj = null;
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------------//
|
//-------------------------------------------------------------------------------------------//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database field definitions.
|
* Database field definitions.
|
||||||
* This is a map from field names to field type. The field
|
* This is a map from field names to field type. The field
|
||||||
* type should be a class that extends DBField.
|
* type should be a class that extends .
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static $db = null;
|
public static $db = null;
|
||||||
@ -1788,18 +1753,5 @@ class DataObject extends Controller {
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public static $default_sort = null;
|
public static $default_sort = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Extensions to be used on this DataObject. An array of extension names
|
|
||||||
* and parameters eg:
|
|
||||||
*
|
|
||||||
* static $extensions = array(
|
|
||||||
* "Hierarchy",
|
|
||||||
* "Versioned('Stage', 'Live')",
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
public static $extensions = null;
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
@ -9,20 +9,7 @@
|
|||||||
* Plug-ins for additional functionality in your DataObjects.
|
* Plug-ins for additional functionality in your DataObjects.
|
||||||
* DataObject decorators add extra functionality to your data objects.
|
* DataObject decorators add extra functionality to your data objects.
|
||||||
*/
|
*/
|
||||||
abstract class DataObjectDecorator extends Object {
|
abstract class DataObjectDecorator extends Extension {
|
||||||
/**
|
|
||||||
* The DataObject that owns this decorator.
|
|
||||||
* @var DataObject
|
|
||||||
*/
|
|
||||||
protected $owner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the owner of this decorator.
|
|
||||||
* @param DataObject $owner
|
|
||||||
*/
|
|
||||||
function setOwner(DataObject $owner) {
|
|
||||||
$this->owner = $owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the extra database fields defined in extraDBFields.
|
* Load the extra database fields defined in extraDBFields.
|
||||||
@ -43,12 +30,16 @@ abstract class DataObjectDecorator extends Object {
|
|||||||
* Edit the given query object to support queries for this extension.
|
* Edit the given query object to support queries for this extension.
|
||||||
* @param SQLQuery $query Query to augment.
|
* @param SQLQuery $query Query to augment.
|
||||||
*/
|
*/
|
||||||
abstract function augmentSQL(SQLQuery &$query);
|
function augmentSQL(SQLQuery &$query) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the database schema as required by this extension.
|
* Update the database schema as required by this extension.
|
||||||
*/
|
*/
|
||||||
abstract function augmentDatabase();
|
function augmentDatabase() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define extra database fields.
|
* Define extra database fields.
|
||||||
|
@ -100,8 +100,8 @@ class SiteTree extends DataObject {
|
|||||||
* if you need such behaviour.
|
* if you need such behaviour.
|
||||||
* @return SiteTree The duplicated object.
|
* @return SiteTree The duplicated object.
|
||||||
*/
|
*/
|
||||||
public function duplicate() {
|
public function duplicate($doWrite = true) {
|
||||||
$page = parent::duplicate();
|
$page = parent::duplicate($doWrite);
|
||||||
$page->CheckedPublicationDifferences = $page->AddedToStage = true;
|
$page->CheckedPublicationDifferences = $page->AddedToStage = true;
|
||||||
return $page;
|
return $page;
|
||||||
}
|
}
|
||||||
@ -459,6 +459,8 @@ class SiteTree extends DataObject {
|
|||||||
$this->URLSegment = $segment;
|
$this->URLSegment = $segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DataObject::set_context_obj($this);
|
||||||
|
|
||||||
// Ensure URLSegment is unique
|
// Ensure URLSegment is unique
|
||||||
$idFilter = $this->ID ? " AND `SiteTree`.ID <> '$this->ID'" : '';
|
$idFilter = $this->ID ? " AND `SiteTree`.ID <> '$this->ID'" : '';
|
||||||
$count = 1;
|
$count = 1;
|
||||||
@ -466,6 +468,8 @@ class SiteTree extends DataObject {
|
|||||||
$count++;
|
$count++;
|
||||||
$this->URLSegment = ereg_replace('-[0-9]+$','', $this->URLSegment) . "-$count";
|
$this->URLSegment = ereg_replace('-[0-9]+$','', $this->URLSegment) . "-$count";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DataObject::set_context_obj(null);
|
||||||
|
|
||||||
// If the URLSegment has been changed, rewrite links
|
// If the URLSegment has been changed, rewrite links
|
||||||
if(isset($this->changed['URLSegment']) && $this->changed['URLSegment']) {
|
if(isset($this->changed['URLSegment']) && $this->changed['URLSegment']) {
|
||||||
@ -483,6 +487,7 @@ class SiteTree extends DataObject {
|
|||||||
parent::onBeforeWrite();
|
parent::onBeforeWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a URL segment based on the title provided.
|
* Generate a URL segment based on the title provided.
|
||||||
* @param string $title Page title.
|
* @param string $title Page title.
|
||||||
|
Loading…
Reference in New Issue
Block a user