Skip to content

Commit

Permalink
API Reverse config extra statics control flow
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Hamish Friedlander committed Aug 22, 2012
1 parent 4916b36 commit fa37c44
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 73 deletions.
19 changes: 19 additions & 0 deletions core/ClassInfo.php
Expand Up @@ -231,6 +231,25 @@ static function classes_for_folder($folderPath) {

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

}

19 changes: 8 additions & 11 deletions core/Config.php
Expand Up @@ -173,12 +173,6 @@ public function pushConfigManifest(SS_ConfigManifest $manifest) {
$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();

Expand Down Expand Up @@ -371,14 +365,17 @@ function get($class, $name, $sourceOptions = 0, &$result = null, $suppress = nul

// 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);
Expand Down
5 changes: 1 addition & 4 deletions core/Extension.php
Expand Up @@ -46,14 +46,11 @@ function __construct() {
/**
* 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
}

/**
Expand Down
80 changes: 55 additions & 25 deletions core/Object.php
Expand Up @@ -463,6 +463,7 @@ public static function add_extension($class, $extension) {
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));
Expand Down Expand Up @@ -505,6 +506,7 @@ public static function remove_extension($class, $extension) {
if($subclasses) foreach($subclasses as $subclass) {
unset(self::$classes_constructed[$subclass]);
unset(self::$extra_methods[$subclass]);
unset(self::$extension_sources[$subclass]);
}
}

Expand All @@ -531,38 +533,66 @@ public static function get_extensions($class, $includeArgumentString = false) {

// -----------------------------------------------------------------------------------------------------------------

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

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

$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;
Expand Down
25 changes: 6 additions & 19 deletions model/DataExtension.php
Expand Up @@ -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');
}
Expand Down
7 changes: 4 additions & 3 deletions model/Hierarchy.php
Expand Up @@ -25,9 +25,10 @@ function augmentDatabase() {
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)
);
}

/**
Expand Down
7 changes: 4 additions & 3 deletions model/Versioned.php
Expand Up @@ -107,9 +107,10 @@ function __construct($stages=array('Stage','Live')) {
'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)
);
}

/**
Expand Down
18 changes: 10 additions & 8 deletions search/FulltextSearchable.php
Expand Up @@ -75,14 +75,16 @@ function __construct($searchFields = array()) {
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]
)
)
);
}

/**
Expand Down

0 comments on commit fa37c44

Please sign in to comment.