Merge pull request #734 from silverstripe-rebelalliance/trac/7799

API Reverse config extra statics control flow
This commit is contained in:
Sean Harvey 2012-08-26 14:24:05 -07:00
commit 674b52b4f7
8 changed files with 107 additions and 73 deletions

View File

@ -231,6 +231,25 @@ class ClassInfo {
return $matchedClasses; 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;
}
} }

View File

@ -173,12 +173,6 @@ class Config {
$this->collectConfigPHPSettings = false; $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 */ /** @var [Config_ForClass] - The list of Config_ForClass instances, keyed off class */
static protected $for_class_instances = array(); static protected $for_class_instances = array();
@ -371,14 +365,17 @@ class Config {
// Then look at the static variables // Then look at the static variables
$nothing = new stdClass(); $nothing = new stdClass();
$classes = array($class);
$sources = array($class);
// Include extensions only if not flagged not to, and some have been set // 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])) { if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) {
$classes = array_merge($classes, self::$extra_static_sources[$class]); $extraSources = Object::get_extra_config_sources($class);
if ($extraSources) $sources = array_merge($sources, $extraSources);
} }
foreach ($classes as $staticSource) { foreach ($sources as $staticSource) {
$value = Object::static_lookup($staticSource, $name, $nothing); if (is_array($staticSource)) $value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
else $value = Object::static_lookup($staticSource, $name, $nothing);
if ($value !== $nothing) { if ($value !== $nothing) {
self::merge_low_into_high($result, $value, $suppress); self::merge_low_into_high($result, $value, $suppress);

View File

@ -46,14 +46,11 @@ abstract class Extension {
/** /**
* Called when this extension is added to a particular class * 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 * @static
* @param $class * @param $class
*/ */
static function add_to_class($class, $extensionClass, $args = null) { static function add_to_class($class, $extensionClass, $args = null) {
Config::add_static_source($class, $extensionClass); // NOP
} }
/** /**

View File

@ -463,6 +463,7 @@ abstract class Object {
if($subclasses) foreach($subclasses as $subclass) { if($subclasses) foreach($subclasses as $subclass) {
unset(self::$classes_constructed[$subclass]); unset(self::$classes_constructed[$subclass]);
unset(self::$extra_methods[$subclass]); unset(self::$extra_methods[$subclass]);
unset(self::$extension_sources[$subclass]);
} }
Config::inst()->update($class, 'extensions', array($extension)); Config::inst()->update($class, 'extensions', array($extension));
@ -505,6 +506,7 @@ abstract class Object {
if($subclasses) foreach($subclasses as $subclass) { if($subclasses) foreach($subclasses as $subclass) {
unset(self::$classes_constructed[$subclass]); unset(self::$classes_constructed[$subclass]);
unset(self::$extra_methods[$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() { public function __construct() {
$this->class = get_class($this); $this->class = get_class($this);
// Don't bother checking some classes that should never be extended foreach(ClassInfo::ancestry(get_called_class()) as $class) {
static $notExtendable = array('Object', 'ViewableData', 'RequestHandler'); if(in_array($class, self::$unextendable_classes)) continue;
$extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES);
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);
// If we haven't told that extension it's attached to this class yet, do that now if($extensions) foreach($extensions as $extension) {
if (!isset(self::$_added_extensions[$extensionClass][$class])) { $instance = self::create_from_string($extension);
// First call the add_to_class method - this will inherit down & is defined on Extension, so if not defined, no worries $instance->setOwner(null, $class);
call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass, $extensionArgs); $this->extension_instances[$instance->class] = $instance;
// 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(!isset(self::$classes_constructed[$this->class])) { if(!isset(self::$classes_constructed[$this->class])) {
$this->defineMethods(); $this->defineMethods();
self::$classes_constructed[$this->class] = true; self::$classes_constructed[$this->class] = true;

View File

@ -31,34 +31,21 @@ abstract class DataExtension extends Extension {
'api_access' => false, 'api_access' => false,
); );
static function add_to_class($class, $extensionClass, $args = null) { static function get_extra_config($class, $extension, $args) {
if(method_exists($class, 'extraDBFields')) { if(method_exists($extension, 'extraDBFields')) {
$extraStaticsMethod = 'extraDBFields'; $extraStaticsMethod = 'extraDBFields';
} else { } else {
$extraStaticsMethod = 'extraStatics'; $extraStaticsMethod = 'extraStatics';
} }
$statics = Injector::inst()->get($extensionClass, true, $args)->$extraStaticsMethod($class, $extensionClass); $statics = Injector::inst()->get($extension, true, $args)->$extraStaticsMethod();
if ($statics) { if ($statics) {
Deprecation::notice('3.1.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use add_to_class", Deprecation::SCOPE_GLOBAL); Deprecation::notice('3.1.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use get_extra_config", Deprecation::SCOPE_GLOBAL);
return $statics;
// 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, $args);
} }
public static function unload_extra_statics($class, $extension) { public static function unload_extra_statics($class, $extension) {
throw new Exception('unload_extra_statics gone'); throw new Exception('unload_extra_statics gone');
} }

View File

@ -25,9 +25,10 @@ class Hierarchy extends DataExtension {
function augmentWrite(&$manipulation) { function augmentWrite(&$manipulation) {
} }
static function add_to_class($class, $extensionClass, $args = null) { static function get_extra_config($class, $extension, $args) {
Config::inst()->update($class, 'has_one', array('Parent' => $class)); return array(
parent::add_to_class($class, $extensionClass, $args); 'has_one' => array('Parent' => $class)
);
} }
/** /**

View File

@ -107,9 +107,10 @@ class Versioned extends DataExtension {
'Version' => 'Int' 'Version' => 'Int'
); );
static function add_to_class($class, $extensionClass, $args = null) { static function get_extra_config($class, $extension, $args) {
Config::inst()->update($class, 'has_many', array('Versions' => $class)); array(
parent::add_to_class($class, $extensionClass, $args); 'has_many' => array('Versions' => $class)
);
} }
/** /**

View File

@ -75,14 +75,16 @@ class FulltextSearchable extends DataExtension {
parent::__construct(); parent::__construct();
} }
static function add_to_class($class, $extensionClass, $args = null) { static function get_extra_config($class, $extensionClass, $args) {
Config::inst()->update($class, 'indexes', array('SearchFields' => array( return array(
'type' => 'fulltext', 'indexes' => array(
'name' => 'SearchFields', 'SearchFields' => array(
'value' => $args[0] 'type' => 'fulltext',
))); 'name' => 'SearchFields',
'value' => $args[0]
parent::add_to_class($class, $extensionClass, $args); )
)
);
} }
/** /**