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; 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;
} }
?> ?>

View File

@ -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.

View File

@ -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.