Skip to content

Commit

Permalink
API CHANGE: Modify extensions system to support new config system. St…
Browse files Browse the repository at this point in the history
…atics are now declared directly on extensions, and there is an add_to_class method extensions can hook into to modify class configuration
  • Loading branch information
Hamish Friedlander committed Mar 9, 2012
1 parent 0ab171d commit 876f4c5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 83 deletions.
17 changes: 16 additions & 1 deletion core/Extension.php
Expand Up @@ -43,6 +43,19 @@ function __construct() {
$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,
Expand Down Expand Up @@ -85,6 +98,8 @@ public function getOwner() {
public static function get_classname_without_arguments($extensionStr) {
return (($p = strpos($extensionStr, '(')) !== false) ? substr($extensionStr, 0, $p) : $extensionStr;
}




}

71 changes: 30 additions & 41 deletions core/Object.php
Expand Up @@ -422,7 +422,9 @@ public static function add_static_var($class, $name, $value, $replace = false) {
*/
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;
Expand Down Expand Up @@ -457,30 +459,19 @@ public static function add_extension($class, $extension) {
}

// 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);
}
}
Expand All @@ -504,37 +495,19 @@ public static function add_extension($class, $extension) {
* @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]);
}

}

/**
Expand All @@ -545,7 +518,8 @@ public static function remove_extension($class, $extension) {
* 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 {
Expand All @@ -558,7 +532,9 @@ function get_extensions($class, $includeArgumentString = false) {
}

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


private static $_added_extensions = array();

public function __construct() {
$this->class = get_class($this);

Expand All @@ -567,9 +543,22 @@ public function __construct() {

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;
Expand Down
61 changes: 20 additions & 41 deletions model/DataExtension.php
Expand Up @@ -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);

if ($statics) {
Deprecation::notice('3.0.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use add_to_class");

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

/**
Expand Down

0 comments on commit 876f4c5

Please sign in to comment.