Skip to content
This repository
Browse code

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.
  • Loading branch information...
commit fa37c448a5ce22330be8985acf1a71769e472a2c 1 parent 4916b36
Hamish Friedlander authored August 23, 2012
19  core/ClassInfo.php
@@ -231,6 +231,25 @@ static function classes_for_folder($folderPath) {
231 231
 
232 232
 		return $matchedClasses;
233 233
 	}
  234
+
  235
+	private static $method_from_cache = array();
  236
+
  237
+	static function has_method_from($class, $method, $compclass) {
  238
+		if (!isset(self::$method_from_cache[$class])) self::$method_from_cache[$class] = array();
  239
+
  240
+		if (!array_key_exists($method, self::$method_from_cache[$class])) {
  241
+			self::$method_from_cache[$class][$method] = false;
  242
+
  243
+			$classRef = new ReflectionClass($class);
  244
+
  245
+			if ($classRef->hasMethod($method)) {
  246
+				$methodRef = $classRef->getMethod($method);
  247
+				self::$method_from_cache[$class][$method] = $methodRef->getDeclaringClass()->getName();
  248
+			}
  249
+		}
  250
+
  251
+		return self::$method_from_cache[$class][$method] == $compclass;
  252
+	}
234 253
 	
235 254
 }
236 255
 
19  core/Config.php
@@ -173,12 +173,6 @@ public function pushConfigManifest(SS_ConfigManifest $manifest) {
173 173
 		$this->collectConfigPHPSettings = false;
174 174
 	}
175 175
 
176  
-	static $extra_static_sources = array();
177  
-
178  
-	static function add_static_source($forclass, $donorclass) {
179  
-		self::$extra_static_sources[$forclass][] = $donorclass;
180  
-	}
181  
-
182 176
 	/** @var [Config_ForClass] - The list of Config_ForClass instances, keyed off class */
183 177
 	static protected $for_class_instances = array();
184 178
 
@@ -371,14 +365,17 @@ function get($class, $name, $sourceOptions = 0, &$result = null, $suppress = nul
371 365
 
372 366
 		// Then look at the static variables
373 367
 		$nothing = new stdClass();
374  
-		$classes = array($class);
  368
+
  369
+		$sources = array($class);
375 370
 		// Include extensions only if not flagged not to, and some have been set
376  
-		if ((($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) && isset(self::$extra_static_sources[$class])) {
377  
-			$classes =  array_merge($classes, self::$extra_static_sources[$class]);
  371
+		if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) {
  372
+			$extraSources = Object::get_extra_config_sources($class);
  373
+			if ($extraSources) $sources = array_merge($sources, $extraSources);
378 374
 		}
379 375
 
380  
-		foreach ($classes as $staticSource) {
381  
-			$value = Object::static_lookup($staticSource, $name, $nothing);
  376
+		foreach ($sources as $staticSource) {
  377
+			if (is_array($staticSource)) $value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
  378
+			else $value = Object::static_lookup($staticSource, $name, $nothing);
382 379
 
383 380
 			if ($value !== $nothing) {
384 381
 				self::merge_low_into_high($result, $value, $suppress);
5  core/Extension.php
@@ -46,14 +46,11 @@ function __construct() {
46 46
 	/**
47 47
 	 * Called when this extension is added to a particular class
48 48
 	 *
49  
-	 * TODO: This is likely to be replaced by event sytem before 3.0 final, so be aware
50  
-	 * this API is fairly unstable.
51  
-	 *
52 49
 	 * @static
53 50
 	 * @param $class
54 51
 	 */
55 52
 	static function add_to_class($class, $extensionClass, $args = null) {
56  
-		Config::add_static_source($class, $extensionClass);
  53
+		// NOP
57 54
 	}
58 55
 
59 56
 	/**
80  core/Object.php
@@ -463,6 +463,7 @@ public static function add_extension($class, $extension) {
463 463
 		if($subclasses) foreach($subclasses as $subclass) {
464 464
 			unset(self::$classes_constructed[$subclass]);
465 465
 			unset(self::$extra_methods[$subclass]);
  466
+			unset(self::$extension_sources[$subclass]);
466 467
 		}
467 468
 
468 469
 		Config::inst()->update($class, 'extensions', array($extension));
@@ -505,6 +506,7 @@ public static function remove_extension($class, $extension) {
505 506
 		if($subclasses) foreach($subclasses as $subclass) {
506 507
 			unset(self::$classes_constructed[$subclass]);
507 508
 			unset(self::$extra_methods[$subclass]);
  509
+			unset(self::$extension_sources[$subclass]);
508 510
 		}
509 511
 	}
510 512
 	
@@ -531,38 +533,66 @@ public static function get_extensions($class, $includeArgumentString = false) {
531 533
 	
532 534
 	// -----------------------------------------------------------------------------------------------------------------
533 535
 
534  
-	private static $_added_extensions = array();
  536
+	private static $extension_sources = array();
  537
+
  538
+	// Don't bother checking some classes that should never be extended
  539
+	private static $unextendable_classes = array('Object', 'ViewableData', 'RequestHandler');
  540
+
  541
+	static public function get_extra_config_sources($class = null) {
  542
+		if($class === null) $class = get_called_class();
  543
+
  544
+		// If this class is unextendable, NOP
  545
+		if(in_array($class, self::$unextendable_classes)) return;
  546
+
  547
+		// If we have a pre-cached version, use that
  548
+		if(array_key_exists($class, self::$extension_sources)) return self::$extension_sources[$class];
  549
+
  550
+		// Variable to hold sources in
  551
+		$sources = null;
  552
+
  553
+		// Get a list of extensions
  554
+		$extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES);
  555
+
  556
+		if($extensions) {
  557
+			// Build a list of all sources;
  558
+			$sources = array();
  559
+
  560
+			foreach($extensions as $extension) {
  561
+				list($extensionClass, $extensionArgs) = self::parse_class_spec($extension);
  562
+				$sources[] = $extensionClass;
  563
+
  564
+				if(!ClassInfo::has_method_from($extensionClass, 'add_to_class', 'Extension')) {
  565
+					Deprecation::notice('3.1.0', "add_to_class deprecated on $extensionClass. Use get_extra_config instead");
  566
+				}
  567
+
  568
+				call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass, $extensionArgs);
  569
+
  570
+				foreach(array_reverse(ClassInfo::ancestry($extensionClass)) as $extensionClassParent) {
  571
+					if (ClassInfo::has_method_from($extensionClassParent, 'get_extra_config', $extensionClassParent)) {
  572
+						$extras = $extensionClassParent::get_extra_config($class, $extensionClass, $extensionArgs);
  573
+						if ($extras) $sources[] = $extras;
  574
+					}
  575
+				}
  576
+			}
  577
+		}
  578
+
  579
+		return self::$extension_sources[$class] = $sources;
  580
+	}
535 581
 
536 582
 	public function __construct() {
537 583
 		$this->class = get_class($this);
538 584
 
539  
-		// Don't bother checking some classes that should never be extended
540  
-		static $notExtendable = array('Object', 'ViewableData', 'RequestHandler');
541  
-		
542  
-		if($extensionClasses = ClassInfo::ancestry($this->class)) foreach($extensionClasses as $class) {
543  
-			if(in_array($class, $notExtendable)) continue;
544  
-			if($extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED)) {
545  
-				foreach($extensions as $extension) {
546  
-					// Get the extension class for this extension
547  
-					list($extensionClass, $extensionArgs) = self::parse_class_spec($extension);
548  
-
549  
-					// If we haven't told that extension it's attached to this class yet, do that now
550  
-					if (!isset(self::$_added_extensions[$extensionClass][$class])) {
551  
-						// First call the add_to_class method - this will inherit down & is defined on Extension, so if not defined, no worries
552  
-						call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass, $extensionArgs);
553  
-
554  
-						// Then register it as having been told about us
555  
-						if (!isset(self::$_added_extensions[$extensionClass])) self::$_added_extensions[$extensionClass] = array($class => true);
556  
-						else self::$_added_extensions[$extensionClass][$class] = true;
557  
-					}
  585
+		foreach(ClassInfo::ancestry(get_called_class()) as $class) {
  586
+			if(in_array($class, self::$unextendable_classes)) continue;
  587
+			$extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES);
558 588
 
559  
-					$instance = self::create_from_string($extension);
560  
-					$instance->setOwner(null, $class);
561  
-					$this->extension_instances[$instance->class] = $instance;
562  
-				}
  589
+			if($extensions) foreach($extensions as $extension) {
  590
+				$instance = self::create_from_string($extension);
  591
+				$instance->setOwner(null, $class);
  592
+				$this->extension_instances[$instance->class] = $instance;
563 593
 			}
564 594
 		}
565  
-		
  595
+
566 596
 		if(!isset(self::$classes_constructed[$this->class])) {
567 597
 			$this->defineMethods();
568 598
 			self::$classes_constructed[$this->class] = true;
25  model/DataExtension.php
@@ -31,34 +31,21 @@
31 31
 		'api_access' => false,
32 32
 	);
33 33
 
34  
-	static function add_to_class($class, $extensionClass, $args = null) {
35  
-		if(method_exists($class, 'extraDBFields')) {
  34
+	static function get_extra_config($class, $extension, $args) {
  35
+		if(method_exists($extension, 'extraDBFields')) {
36 36
 			$extraStaticsMethod = 'extraDBFields';
37 37
 		} else {
38 38
 			$extraStaticsMethod = 'extraStatics';
39 39
 		}
40 40
 
41  
-		$statics = Injector::inst()->get($extensionClass, true, $args)->$extraStaticsMethod($class, $extensionClass);
  41
+		$statics = Injector::inst()->get($extension, true, $args)->$extraStaticsMethod();
42 42
 
43 43
 		if ($statics) {
44  
-			Deprecation::notice('3.1.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use add_to_class", Deprecation::SCOPE_GLOBAL);
45  
-
46  
-			// TODO: This currently makes extraStatics the MOST IMPORTANT config layer, not the least
47  
-			foreach (self::$extendable_statics as $key => $merge) {
48  
-				if (isset($statics[$key])) {
49  
-					if (!$merge) Config::inst()->remove($class, $key);
50  
-					Config::inst()->update($class, $key, $statics[$key]);
51  
-				}
52  
-			}
53  
-
54  
-			// TODO - remove this
55  
-			DataObject::$cache_has_own_table[$class]       = null;
56  
-			DataObject::$cache_has_own_table_field[$class] = null;
  44
+			Deprecation::notice('3.1.0', "$extraStaticsMethod deprecated. Just define statics on your extension, or use get_extra_config", Deprecation::SCOPE_GLOBAL);
  45
+			return $statics;
57 46
 		}
58  
-
59  
-		parent::add_to_class($class, $extensionClass, $args);
60 47
 	}
61  
-	
  48
+
62 49
 	public static function unload_extra_statics($class, $extension) {
63 50
 		throw new Exception('unload_extra_statics gone');
64 51
 	}
7  model/Hierarchy.php
@@ -25,9 +25,10 @@ function augmentDatabase() {
25 25
 	function augmentWrite(&$manipulation) {
26 26
 	}
27 27
 
28  
-	static function add_to_class($class, $extensionClass, $args = null) {
29  
-		Config::inst()->update($class, 'has_one', array('Parent' => $class));
30  
-		parent::add_to_class($class, $extensionClass, $args);
  28
+	static function get_extra_config($class, $extension, $args) {
  29
+		return array(
  30
+			'has_one' => array('Parent' => $class)
  31
+		);
31 32
 	}
32 33
 
33 34
 	/**
7  model/Versioned.php
@@ -107,9 +107,10 @@ function __construct($stages=array('Stage','Live')) {
107 107
 		'Version' => 'Int'
108 108
 	);
109 109
 
110  
-	static function add_to_class($class, $extensionClass, $args = null) {
111  
-		Config::inst()->update($class, 'has_many', array('Versions' => $class));
112  
-		parent::add_to_class($class, $extensionClass, $args);
  110
+	static function get_extra_config($class, $extension, $args) {
  111
+		array(
  112
+			'has_many' => array('Versions' => $class)
  113
+		);
113 114
 	}
114 115
 	
115 116
 	/**
18  search/FulltextSearchable.php
@@ -75,14 +75,16 @@ function __construct($searchFields = array()) {
75 75
 		parent::__construct();
76 76
 	}
77 77
 
78  
-	static function add_to_class($class, $extensionClass, $args = null) {
79  
-		Config::inst()->update($class, 'indexes', array('SearchFields' => array(
80  
-			'type' => 'fulltext',
81  
-			'name' => 'SearchFields',
82  
-			'value' => $args[0]
83  
-		)));
84  
-
85  
-		parent::add_to_class($class, $extensionClass, $args);
  78
+	static function get_extra_config($class, $extensionClass, $args) {
  79
+		return array(
  80
+			'indexes' => array(
  81
+				'SearchFields' => array(
  82
+					'type' => 'fulltext',
  83
+					'name' => 'SearchFields',
  84
+					'value' => $args[0]
  85
+				)
  86
+			)
  87
+		);
86 88
 	}
87 89
 
88 90
 	/**

0 notes on commit fa37c44

Please sign in to comment.
Something went wrong with that request. Please try again.