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.
This commit is contained in:
Hamish Friedlander 2012-08-23 09:29:13 +12:00
parent 4916b361f1
commit fa37c448a5
8 changed files with 107 additions and 73 deletions

View File

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

View File

@ -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);

View File

@ -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
}
/**

View File

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

View File

@ -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');
}

View File

@ -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)
);
}
/**

View File

@ -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)
);
}
/**

View File

@ -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]
)
)
);
}
/**