From 876f4c5299ec50c4d53d904f79d187dc215dc2e6 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Thu, 22 Dec 2011 15:29:16 +1300 Subject: [PATCH] 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 --- core/Extension.php | 17 +++++++++- core/Object.php | 71 +++++++++++++++++------------------------ model/DataExtension.php | 61 ++++++++++++----------------------- 3 files changed, 66 insertions(+), 83 deletions(-) diff --git a/core/Extension.php b/core/Extension.php index 2cbd4f264..f4fa0d79d 100644 --- a/core/Extension.php +++ b/core/Extension.php @@ -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; } - + + + } diff --git a/core/Object.php b/core/Object.php index 240f23ca0..3ddfd07a8 100755 --- a/core/Object.php +++ b/core/Object.php @@ -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; diff --git a/model/DataExtension.php b/model/DataExtension.php index e4fa3bb23..362d8a6f4 100644 --- a/model/DataExtension.php +++ b/model/DataExtension.php @@ -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'); } /**