API CHANGE: Modify extensions system to support new config system. Statics are now declared directly on extensions, and there is an add_to_class method extensions can hook into to modify class configuration

This commit is contained in:
Hamish Friedlander 2011-12-22 15:29:16 +13:00
parent 0ab171d7c0
commit 876f4c5299
3 changed files with 66 additions and 83 deletions

View File

@ -43,6 +43,19 @@ abstract class Extension {
$this->class = get_class($this);
}
/**
* Called when this extension is added to a particular class
*
* TODO: This is likely to be replaced by event sytem before 3.0 final, so be aware
* this API is fairly unstable.
*
* @static
* @param $class
*/
static function add_to_class($class, $extensionClass) {
Config::add_static_source($class, $extensionClass);
}
/**
* Set the owner of this extension.
* @param Object $owner The owner object,
@ -85,6 +98,8 @@ abstract class Extension {
public static function get_classname_without_arguments($extensionStr) {
return (($p = strpos($extensionStr, '(')) !== false) ? substr($extensionStr, 0, $p) : $extensionStr;
}
}

View File

@ -422,7 +422,9 @@ abstract class Object {
*/
public static function has_extension($class, $requiredExtension) {
$requiredExtension = strtolower($requiredExtension);
if($extensions = self::combined_static($class, 'extensions')) foreach($extensions as $extension) {
$extensions = Config::inst()->get($class, 'extensions');
if($extensions) foreach($extensions as $extension) {
$left = strtolower(Extension::get_classname_without_arguments($extension));
$right = strtolower(Extension::get_classname_without_arguments($requiredExtension));
if($left == $right) return true;
@ -457,30 +459,19 @@ abstract class Object {
}
// unset some caches
self::$cached_statics[$class]['extensions'] = null;
$subclasses = ClassInfo::subclassesFor($class);
$subclasses[] = $class;
if($subclasses) foreach($subclasses as $subclass) {
unset(self::$classes_constructed[$subclass]);
unset(self::$extra_methods[$subclass]);
}
// merge with existing static vars
$extensions = self::uninherited_static($class, 'extensions');
// We use unshift rather than push so that module extensions are added before built-in ones.
// in particular, this ensures that the Versioned rewriting is done last.
if($extensions) array_unshift($extensions, $extension);
else $extensions = array($extension);
self::set_static($class, 'extensions', $extensions);
Config::inst()->update($class, 'extensions', array($extension));
// load statics now for DataObject classes
if(is_subclass_of($class, 'DataObject')) {
if(is_subclass_of($extensionClass, 'DataExtension')) {
DataExtension::load_extra_statics($class, $extension);
}
else {
if(!is_subclass_of($extensionClass, 'DataExtension')) {
user_error("$extensionClass cannot be applied to $class without being a DataExtension", E_USER_ERROR);
}
}
@ -504,37 +495,19 @@ abstract class Object {
* @param string $extension Classname of an {@link Extension} subclass, without parameters
*/
public static function remove_extension($class, $extension) {
// unload statics now for DataObject classes
if(is_subclass_of($class, 'DataObject')) {
if(!preg_match('/^([^(]*)/', $extension, $matches)) {
user_error("Bad extension '$extension'", E_USER_WARNING);
} else {
$extensionClass = $matches[1];
DataExtension::unload_extra_statics($class, $extensionClass);
}
}
if(self::has_extension($class, $extension)) {
self::set_static(
$class,
'extensions',
array_diff(self::uninherited_static($class, 'extensions'), array($extension))
);
}
Config::inst()->remove($class, 'extensions', Config::anything(), $extension);
// unset singletons to avoid side-effects
global $_SINGLETONS;
$_SINGLETONS = array();
// unset some caches
self::$cached_statics[$class]['extensions'] = null;
$subclasses = ClassInfo::subclassesFor($class);
$subclasses[] = $class;
if($subclasses) foreach($subclasses as $subclass) {
unset(self::$classes_constructed[$subclass]);
unset(self::$extra_methods[$subclass]);
}
}
/**
@ -545,7 +518,8 @@ abstract class Object {
* or eval'ed classname strings with constructor arguments.
*/
function get_extensions($class, $includeArgumentString = false) {
$extensions = self::get_static($class, 'extensions');
$extensions = Config::inst()->get($class, 'extensions');
if($includeArgumentString) {
return $extensions;
} else {
@ -558,7 +532,9 @@ abstract class Object {
}
// -----------------------------------------------------------------------------------------------------------------
private static $_added_extensions = array();
public function __construct() {
$this->class = get_class($this);
@ -567,9 +543,22 @@ abstract class Object {
if($extensionClasses = ClassInfo::ancestry($this->class)) foreach($extensionClasses as $class) {
if(in_array($class, $notExtendable)) continue;
if($extensions = self::uninherited_static($class, 'extensions')) {
if($extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED)) {
foreach($extensions as $extension) {
// Get the extension class for this extension
$extensionClass = Extension::get_classname_without_arguments($extension);
// If we haven't told that extension it's attached to this class yet, do that now
if (!isset(self::$_added_extensions[$extensionClass][$class])) {
// First call the add_to_class method - this will inherit down & is defined on Extension, so if not defined, no worries
call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass);
// Then register it as having been told about us
if (!isset(self::$_added_extensions[$extensionClass])) self::$_added_extensions[$extensionClass] = array($class => true);
else self::$_added_extensions[$extensionClass][$class] = true;
}
$instance = self::create_from_string($extension);
$instance->setOwner(null, $class);
$this->extension_instances[$instance->class] = $instance;

View File

@ -30,59 +30,38 @@ abstract class DataExtension extends Extension {
'searchable_fields' => true,
'api_access' => false,
);
private static $extra_statics_loaded = array();
/**
* Load the extra static definitions for the given extension
* class name, called by {@link Object::add_extension()}
*
* @param string $class Class name of the owner class (or owner base class)
* @param string $extension Class name of the extension class
*/
public static function load_extra_statics($class, $extension) {
if(!empty(self::$extra_statics_loaded[$class][$extension])) return;
self::$extra_statics_loaded[$class][$extension] = true;
if(preg_match('/^([^(]*)/', $extension, $matches)) {
$extensionClass = $matches[1];
static function add_to_class($class, $extensionClass) {
if(method_exists($class, 'extraDBFields')) {
$extraStaticsMethod = 'extraDBFields';
} else {
user_error("Bad extension '$extension' - can't find classname", E_USER_WARNING);
return;
$extraStaticsMethod = 'extraStatics';
}
// If the extension has been manually applied to a subclass, we should ignore that.
if(Object::has_extension(get_parent_class($class), $extensionClass)) return;
// If there aren't any extraStatics we shouldn't try to load them.
if(!method_exists($extensionClass, 'extraStatics')) return;
$statics = singleton($extensionClass)->$extraStaticsMethod($class, $extensionClass);
$statics = call_user_func(array(singleton($extensionClass), 'extraStatics'), $class, $extension);
if($statics) {
foreach($statics as $name => $newVal) {
if(isset(self::$extendable_statics[$name])) {
// Array to be merged
if(self::$extendable_statics[$name]) {
$origVal = Object::uninherited_static($class, $name);
// Can't use add_static_var() here as it would merge the array rather than replacing
Object::set_static($class, $name, array_merge((array)$origVal, $newVal));
// Value to be overwritten
} else {
Object::set_static($class, $name, $newVal);
}
if ($statics) {
Deprecation::notice('3.0.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use add_to_class");
// TODO: This currently makes extraStatics the MOST IMPORTANT config layer, not the least
foreach (self::$extendable_statics as $key => $merge) {
if (isset($statics[$key])) {
if (!$merge) Config::inst()->remove($class, $key);
Config::inst()->update($class, $key, $statics[$key]);
}
}
// TODO - remove this
DataObject::$cache_has_own_table[$class] = null;
DataObject::$cache_has_own_table_field[$class] = null;
}
parent::add_to_class($class, $extensionClass);
}
public static function unload_extra_statics($class, $extension) {
self::$extra_statics_loaded[$class][$extension] = false;
throw new Exception('unload_extra_statics gone');
}
/**