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:
Sam Minnee 2007-08-16 06:32:49 +00:00
parent c3f124dd95
commit 015a7dec45
4 changed files with 78 additions and 106 deletions

24
core/Extension.php Normal file
View 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;
}
}
?>

View File

@ -39,11 +39,6 @@ class DataObject extends Controller {
*/
protected $components;
/**
* This DataObjects extensions, eg Versioned.
* @var array
*/
protected $extension_instances = array();
/**
* True if this DataObject has been destroyed.
@ -95,14 +90,6 @@ class DataObject extends Controller {
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();
// Must be called after parent constructor
@ -126,15 +113,16 @@ class DataObject extends Controller {
/**
* 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.
*/
function duplicate() {
function duplicate($doWrite = true) {
$className = $this->class;
$clone = new $className( $this->record );
$clone->ID = 0;
$clone->write();
if($doWrite) $clone->write();
return $clone;
}
@ -176,10 +164,10 @@ class DataObject extends Controller {
function defineMethods() {
if($this->class == 'DataObject') return;
if($this->extension_instances) foreach($this->extension_instances as $i => $instance) {
$this->addMethodsFrom('extension_instances', $i);
parent::defineMethods();
// Define the extra db fields
if($this->extension_instances) foreach($this->extension_instances as $i => $instance) {
$instance->loadExtraDBFields();
}
@ -201,8 +189,6 @@ class DataObject extends Controller {
$this->addWrapperMethod($relationship, 'getComponent');
}
}
parent::defineMethods();
}
/**
@ -312,6 +298,9 @@ class DataObject extends Controller {
*/
protected function onBeforeWrite() {
$this->brokenOnWrite = false;
$dummy = null;
$this->extend('augmentBeforeWrite', $dummy);
}
/**
@ -1304,56 +1293,6 @@ class DataObject extends Controller {
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>
*
@ -1470,6 +1409,11 @@ class DataObject extends Controller {
* Flush the cached results for get_one()
*/
public function flushCache() {
if($this->class == 'DataObject') {
DataObject::$cache_get_one = array();
return;
}
$classes = ClassInfo::ancestry($this->class);
foreach($classes as $class) {
// 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);
$baseClass = array_shift($tableClasses);
return DataObject::get_one($callerClass,"`$baseClass`.ID = $id");
return DataObject::get_one($callerClass,"`$baseClass`.`ID` = $id");
} else {
user_error("DataObject::get_by_id passed a non-numeric ID #$id", E_USER_WARNING);
}
@ -1696,12 +1640,33 @@ class DataObject extends Controller {
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.
* 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
*/
public static $db = null;
@ -1788,18 +1753,5 @@ class DataObject extends Controller {
* @var string
*/
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;
}
?>

View File

@ -9,20 +9,7 @@
* Plug-ins for additional functionality in your DataObjects.
* DataObject decorators add extra functionality to your data objects.
*/
abstract class DataObjectDecorator extends Object {
/**
* 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;
}
abstract class DataObjectDecorator extends Extension {
/**
* 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.
* @param SQLQuery $query Query to augment.
*/
abstract function augmentSQL(SQLQuery &$query);
function augmentSQL(SQLQuery &$query) {
}
/**
* Update the database schema as required by this extension.
*/
abstract function augmentDatabase();
function augmentDatabase() {
}
/**
* Define extra database fields.

View File

@ -100,8 +100,8 @@ class SiteTree extends DataObject {
* if you need such behaviour.
* @return SiteTree The duplicated object.
*/
public function duplicate() {
$page = parent::duplicate();
public function duplicate($doWrite = true) {
$page = parent::duplicate($doWrite);
$page->CheckedPublicationDifferences = $page->AddedToStage = true;
return $page;
}
@ -459,6 +459,8 @@ class SiteTree extends DataObject {
$this->URLSegment = $segment;
}
DataObject::set_context_obj($this);
// Ensure URLSegment is unique
$idFilter = $this->ID ? " AND `SiteTree`.ID <> '$this->ID'" : '';
$count = 1;
@ -467,6 +469,8 @@ class SiteTree extends DataObject {
$this->URLSegment = ereg_replace('-[0-9]+$','', $this->URLSegment) . "-$count";
}
DataObject::set_context_obj(null);
// If the URLSegment has been changed, rewrite links
if(isset($this->changed['URLSegment']) && $this->changed['URLSegment']) {
if($this->hasMethod('BackLinkTracking')) {
@ -483,6 +487,7 @@ class SiteTree extends DataObject {
parent::onBeforeWrite();
}
/**
* Generate a URL segment based on the title provided.
* @param string $title Page title.