From fa37c448a5ce22330be8985acf1a71769e472a2c Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Thu, 23 Aug 2012 09:29:13 +1200 Subject: [PATCH] API Reverse config extra statics control flow Config system used to provide an add_static_source method, which was intended for use by Extensions to add statics. But extensions for a class arent initialised until at least one instance of that class is created, so before that the Config system didnt include values from extensions This patch reverses the control flow, so that the Config system explictly asks each Object for its additional config sources via the new method get_extra_config_sources. This method returns an array that can contain string names of classes and also raw associative arrays. The developer visible change is that Extension#add_to_class has been deprecated. Instead there is a new method, get_extra_config, which has the same method signature but needs to guarantee that it doesnt cause side effects. Additionally there is no need to call parent::get_extra_config - this is handled automatically. --- core/ClassInfo.php | 19 +++++++++ core/Config.php | 19 ++++----- core/Extension.php | 5 +-- core/Object.php | 80 ++++++++++++++++++++++++----------- model/DataExtension.php | 25 +++-------- model/Hierarchy.php | 7 +-- model/Versioned.php | 7 +-- search/FulltextSearchable.php | 18 ++++---- 8 files changed, 107 insertions(+), 73 deletions(-) diff --git a/core/ClassInfo.php b/core/ClassInfo.php index 39baa1765..7aa17fa56 100644 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -231,6 +231,25 @@ class ClassInfo { return $matchedClasses; } + + private static $method_from_cache = array(); + + static function has_method_from($class, $method, $compclass) { + if (!isset(self::$method_from_cache[$class])) self::$method_from_cache[$class] = array(); + + if (!array_key_exists($method, self::$method_from_cache[$class])) { + self::$method_from_cache[$class][$method] = false; + + $classRef = new ReflectionClass($class); + + if ($classRef->hasMethod($method)) { + $methodRef = $classRef->getMethod($method); + self::$method_from_cache[$class][$method] = $methodRef->getDeclaringClass()->getName(); + } + } + + return self::$method_from_cache[$class][$method] == $compclass; + } } diff --git a/core/Config.php b/core/Config.php index 6bc413be3..4cf55611b 100644 --- a/core/Config.php +++ b/core/Config.php @@ -173,12 +173,6 @@ class Config { $this->collectConfigPHPSettings = false; } - static $extra_static_sources = array(); - - static function add_static_source($forclass, $donorclass) { - self::$extra_static_sources[$forclass][] = $donorclass; - } - /** @var [Config_ForClass] - The list of Config_ForClass instances, keyed off class */ static protected $for_class_instances = array(); @@ -371,14 +365,17 @@ class Config { // Then look at the static variables $nothing = new stdClass(); - $classes = array($class); + + $sources = array($class); // Include extensions only if not flagged not to, and some have been set - if ((($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) && isset(self::$extra_static_sources[$class])) { - $classes = array_merge($classes, self::$extra_static_sources[$class]); + if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) { + $extraSources = Object::get_extra_config_sources($class); + if ($extraSources) $sources = array_merge($sources, $extraSources); } - foreach ($classes as $staticSource) { - $value = Object::static_lookup($staticSource, $name, $nothing); + foreach ($sources as $staticSource) { + if (is_array($staticSource)) $value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing; + else $value = Object::static_lookup($staticSource, $name, $nothing); if ($value !== $nothing) { self::merge_low_into_high($result, $value, $suppress); diff --git a/core/Extension.php b/core/Extension.php index 7eb2a8ce7..70072616f 100644 --- a/core/Extension.php +++ b/core/Extension.php @@ -46,14 +46,11 @@ abstract class Extension { /** * 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, $args = null) { - Config::add_static_source($class, $extensionClass); + // NOP } /** diff --git a/core/Object.php b/core/Object.php index b37423762..1b7f98a07 100755 --- a/core/Object.php +++ b/core/Object.php @@ -463,6 +463,7 @@ abstract class Object { if($subclasses) foreach($subclasses as $subclass) { unset(self::$classes_constructed[$subclass]); unset(self::$extra_methods[$subclass]); + unset(self::$extension_sources[$subclass]); } Config::inst()->update($class, 'extensions', array($extension)); @@ -505,6 +506,7 @@ abstract class Object { if($subclasses) foreach($subclasses as $subclass) { unset(self::$classes_constructed[$subclass]); unset(self::$extra_methods[$subclass]); + unset(self::$extension_sources[$subclass]); } } @@ -531,38 +533,66 @@ abstract class Object { // ----------------------------------------------------------------------------------------------------------------- - private static $_added_extensions = array(); + private static $extension_sources = array(); + + // Don't bother checking some classes that should never be extended + private static $unextendable_classes = array('Object', 'ViewableData', 'RequestHandler'); + + static public function get_extra_config_sources($class = null) { + if($class === null) $class = get_called_class(); + + // If this class is unextendable, NOP + if(in_array($class, self::$unextendable_classes)) return; + + // If we have a pre-cached version, use that + if(array_key_exists($class, self::$extension_sources)) return self::$extension_sources[$class]; + + // Variable to hold sources in + $sources = null; + + // Get a list of extensions + $extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES); + + if($extensions) { + // Build a list of all sources; + $sources = array(); + + foreach($extensions as $extension) { + list($extensionClass, $extensionArgs) = self::parse_class_spec($extension); + $sources[] = $extensionClass; + + if(!ClassInfo::has_method_from($extensionClass, 'add_to_class', 'Extension')) { + Deprecation::notice('3.1.0', "add_to_class deprecated on $extensionClass. Use get_extra_config instead"); + } + + call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass, $extensionArgs); + + foreach(array_reverse(ClassInfo::ancestry($extensionClass)) as $extensionClassParent) { + if (ClassInfo::has_method_from($extensionClassParent, 'get_extra_config', $extensionClassParent)) { + $extras = $extensionClassParent::get_extra_config($class, $extensionClass, $extensionArgs); + if ($extras) $sources[] = $extras; + } + } + } + } + + return self::$extension_sources[$class] = $sources; + } public function __construct() { $this->class = get_class($this); - // Don't bother checking some classes that should never be extended - static $notExtendable = array('Object', 'ViewableData', 'RequestHandler'); - - if($extensionClasses = ClassInfo::ancestry($this->class)) foreach($extensionClasses as $class) { - if(in_array($class, $notExtendable)) continue; - if($extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED)) { - foreach($extensions as $extension) { - // Get the extension class for this extension - list($extensionClass, $extensionArgs) = self::parse_class_spec($extension); + foreach(ClassInfo::ancestry(get_called_class()) as $class) { + if(in_array($class, self::$unextendable_classes)) continue; + $extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES); - // 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, $extensionArgs); - - // 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; - } + if($extensions) foreach($extensions as $extension) { + $instance = self::create_from_string($extension); + $instance->setOwner(null, $class); + $this->extension_instances[$instance->class] = $instance; } } - + if(!isset(self::$classes_constructed[$this->class])) { $this->defineMethods(); self::$classes_constructed[$this->class] = true; diff --git a/model/DataExtension.php b/model/DataExtension.php index b2dae0611..d975f7db2 100644 --- a/model/DataExtension.php +++ b/model/DataExtension.php @@ -31,34 +31,21 @@ abstract class DataExtension extends Extension { 'api_access' => false, ); - static function add_to_class($class, $extensionClass, $args = null) { - if(method_exists($class, 'extraDBFields')) { + static function get_extra_config($class, $extension, $args) { + if(method_exists($extension, 'extraDBFields')) { $extraStaticsMethod = 'extraDBFields'; } else { $extraStaticsMethod = 'extraStatics'; } - $statics = Injector::inst()->get($extensionClass, true, $args)->$extraStaticsMethod($class, $extensionClass); + $statics = Injector::inst()->get($extension, true, $args)->$extraStaticsMethod(); if ($statics) { - Deprecation::notice('3.1.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use add_to_class", Deprecation::SCOPE_GLOBAL); - - // 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; + Deprecation::notice('3.1.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use get_extra_config", Deprecation::SCOPE_GLOBAL); + return $statics; } - - parent::add_to_class($class, $extensionClass, $args); } - + public static function unload_extra_statics($class, $extension) { throw new Exception('unload_extra_statics gone'); } diff --git a/model/Hierarchy.php b/model/Hierarchy.php index d6100fa29..3bcc25a20 100644 --- a/model/Hierarchy.php +++ b/model/Hierarchy.php @@ -25,9 +25,10 @@ class Hierarchy extends DataExtension { function augmentWrite(&$manipulation) { } - static function add_to_class($class, $extensionClass, $args = null) { - Config::inst()->update($class, 'has_one', array('Parent' => $class)); - parent::add_to_class($class, $extensionClass, $args); + static function get_extra_config($class, $extension, $args) { + return array( + 'has_one' => array('Parent' => $class) + ); } /** diff --git a/model/Versioned.php b/model/Versioned.php index 67dedb5bd..01e583ad5 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -107,9 +107,10 @@ class Versioned extends DataExtension { 'Version' => 'Int' ); - static function add_to_class($class, $extensionClass, $args = null) { - Config::inst()->update($class, 'has_many', array('Versions' => $class)); - parent::add_to_class($class, $extensionClass, $args); + static function get_extra_config($class, $extension, $args) { + array( + 'has_many' => array('Versions' => $class) + ); } /** diff --git a/search/FulltextSearchable.php b/search/FulltextSearchable.php index 77f660fb1..006ac028b 100644 --- a/search/FulltextSearchable.php +++ b/search/FulltextSearchable.php @@ -75,14 +75,16 @@ class FulltextSearchable extends DataExtension { parent::__construct(); } - static function add_to_class($class, $extensionClass, $args = null) { - Config::inst()->update($class, 'indexes', array('SearchFields' => array( - 'type' => 'fulltext', - 'name' => 'SearchFields', - 'value' => $args[0] - ))); - - parent::add_to_class($class, $extensionClass, $args); + static function get_extra_config($class, $extensionClass, $args) { + return array( + 'indexes' => array( + 'SearchFields' => array( + 'type' => 'fulltext', + 'name' => 'SearchFields', + 'value' => $args[0] + ) + ) + ); } /**