Merge pull request #3982 from dhensby/pulls/safe-unnesting

Safe unnesting of Config and Injector
This commit is contained in:
Damian Mooyman 2015-06-26 10:08:29 +12:00
commit 8e1da86bf1
2 changed files with 212 additions and 194 deletions

View File

@ -13,61 +13,61 @@ use SilverStripe\Framework\Injector\Factory;
* A simple injection manager that manages creating objects and injecting * A simple injection manager that manages creating objects and injecting
* dependencies between them. It borrows quite a lot from ideas taken from * dependencies between them. It borrows quite a lot from ideas taken from
* Spring's configuration, but is adapted to the stateless PHP way of doing * Spring's configuration, but is adapted to the stateless PHP way of doing
* things. * things.
* *
* In its simplest form, the dependency injector can be used as a mechanism to * In its simplest form, the dependency injector can be used as a mechanism to
* instantiate objects. Simply call * instantiate objects. Simply call
* *
* Injector::inst()->get('ClassName') * Injector::inst()->get('ClassName')
* *
* and a new instance of ClassName will be created and returned to you. * and a new instance of ClassName will be created and returned to you.
* *
* Classes can have specific configuration defined for them to * Classes can have specific configuration defined for them to
* indicate dependencies that should be injected. This takes the form of * indicate dependencies that should be injected. This takes the form of
* a static variable $dependencies defined in the class (or configuration), * a static variable $dependencies defined in the class (or configuration),
* which indicates the name of a property that should be set. * which indicates the name of a property that should be set.
* *
* eg * eg
* *
* <code> * <code>
* class MyController extends Controller { * class MyController extends Controller {
* *
* public $permissions; * public $permissions;
* public $defaultText; * public $defaultText;
* *
* static $dependencies = array( * static $dependencies = array(
* 'defaultText' => 'Override in configuration', * 'defaultText' => 'Override in configuration',
* 'permissions' => '%$PermissionService', * 'permissions' => '%$PermissionService',
* ); * );
* } * }
* </code> * </code>
* *
* will result in an object of type MyController having the defaultText property * will result in an object of type MyController having the defaultText property
* set to 'Override in configuration', and an object identified * set to 'Override in configuration', and an object identified
* as PermissionService set into the property called 'permissions'. The %$ * as PermissionService set into the property called 'permissions'. The %$
* syntax tells the injector to look the provided name up as an item to be created * syntax tells the injector to look the provided name up as an item to be created
* by the Injector itself. * by the Injector itself.
* *
* A key concept of the injector is whether to manage the object as * A key concept of the injector is whether to manage the object as
* *
* * A pseudo-singleton, in that only one item will be created for a particular * * A pseudo-singleton, in that only one item will be created for a particular
* identifier (but the same class could be used for multiple identifiers) * identifier (but the same class could be used for multiple identifiers)
* * A prototype, where the same configuration is used, but a new object is * * A prototype, where the same configuration is used, but a new object is
* created each time * created each time
* * unmanaged, in which case a new object is created and injected, but no * * unmanaged, in which case a new object is created and injected, but no
* information about its state is managed. * information about its state is managed.
* *
* Additional configuration of items managed by the injector can be done by * Additional configuration of items managed by the injector can be done by
* providing configuration for the types, either by manually loading in an * providing configuration for the types, either by manually loading in an
* array describing the configuration, or by specifying the configuration * array describing the configuration, or by specifying the configuration
* for a type via SilverStripe's configuration mechanism. * for a type via SilverStripe's configuration mechanism.
* *
* Specify a configuration array of the format * Specify a configuration array of the format
* *
* array( * array(
* array( * array(
* 'id' => 'BeanId', // the name to be used if diff from the filename * 'id' => 'BeanId', // the name to be used if diff from the filename
* 'priority' => 1, // priority. If another bean is defined with the same ID, * 'priority' => 1, // priority. If another bean is defined with the same ID,
* // but has a lower priority, it is NOT overridden * // but has a lower priority, it is NOT overridden
* 'class' => 'ClassName', // the name of the PHP class * 'class' => 'ClassName', // the name of the PHP class
* 'src' => '/path/to/file' // the location of the class * 'src' => '/path/to/file' // the location of the class
@ -77,7 +77,7 @@ use SilverStripe\Framework\Injector\Factory;
* *
* 'factory' => 'FactoryService' // A factory service to use to create instances. * 'factory' => 'FactoryService' // A factory service to use to create instances.
* 'construct' => array( // properties to set at construction * 'construct' => array( // properties to set at construction
* 'scalar', * 'scalar',
* '%$BeanId', * '%$BeanId',
* ) * )
* 'properties' => array( * 'properties' => array(
@ -145,9 +145,9 @@ class Injector {
* @var array * @var array
*/ */
private $specs; private $specs;
/** /**
* A map of all the properties that should be automagically set on all * A map of all the properties that should be automagically set on all
* objects instantiated by the injector * objects instantiated by the injector
*/ */
private $autoProperties; private $autoProperties;
@ -158,11 +158,11 @@ class Injector {
* @var Injector * @var Injector
*/ */
private static $instance; private static $instance;
/** /**
* Indicates whether or not to automatically scan properties in injected objects to auto inject * Indicates whether or not to automatically scan properties in injected objects to auto inject
* stuff, similar to the way grails does things. * stuff, similar to the way grails does things.
* *
* @var boolean * @var boolean
*/ */
private $autoScanProperties = false; private $autoScanProperties = false;
@ -180,16 +180,16 @@ class Injector {
* @var Factory * @var Factory
*/ */
protected $objectCreator; protected $objectCreator;
/** /**
* Locator for determining Config properties for services * Locator for determining Config properties for services
* *
* @var ServiceConfigurationLocator * @var ServiceConfigurationLocator
*/ */
protected $configLocator; protected $configLocator;
/** /**
* Create a new injector. * Create a new injector.
* *
* @param array $config * @param array $config
* Service configuration * Service configuration
@ -202,16 +202,16 @@ class Injector {
$this->specs = array( $this->specs = array(
'Injector' => array('class' => 'Injector') 'Injector' => array('class' => 'Injector')
); );
$this->autoProperties = array(); $this->autoProperties = array();
$creatorClass = isset($config['creator']) ? $config['creator'] : 'InjectionCreator'; $creatorClass = isset($config['creator']) ? $config['creator'] : 'InjectionCreator';
$locatorClass = isset($config['locator']) ? $config['locator'] : 'ServiceConfigurationLocator'; $locatorClass = isset($config['locator']) ? $config['locator'] : 'ServiceConfigurationLocator';
$this->objectCreator = new $creatorClass; $this->objectCreator = new $creatorClass;
$this->configLocator = new $locatorClass; $this->configLocator = new $locatorClass;
if ($config) { if ($config) {
$this->load($config); $this->load($config);
} }
@ -219,7 +219,7 @@ class Injector {
/** /**
* The injector instance this one was copied from when Injector::nest() was called. * The injector instance this one was copied from when Injector::nest() was called.
* *
* @var Injector * @var Injector
*/ */
protected $nestedFrom = null; protected $nestedFrom = null;
@ -246,15 +246,15 @@ class Injector {
public static function set_inst(Injector $instance) { public static function set_inst(Injector $instance) {
return self::$instance = $instance; return self::$instance = $instance;
} }
/** /**
* Make the newly active {@link Injector} be a copy of the current active * Make the newly active {@link Injector} be a copy of the current active
* {@link Injector} instance. * {@link Injector} instance.
* *
* You can then make changes to the injector with methods such as * You can then make changes to the injector with methods such as
* {@link Injector::inst()->registerService()} which will be discarded * {@link Injector::inst()->registerService()} which will be discarded
* upon a subsequent call to {@link Injector::unnest()} * upon a subsequent call to {@link Injector::unnest()}
* *
* @return Injector Reference to new active Injector instance * @return Injector Reference to new active Injector instance
*/ */
public static function nest() { public static function nest() {
@ -266,24 +266,33 @@ class Injector {
} }
/** /**
* Change the active Injector back to the Injector instance the current active * Change the active Injector back to the Injector instance the current active
* Injector object was copied from. * Injector object was copied from.
* *
* @return Injector Reference to restored active Injector instance * @return Injector Reference to restored active Injector instance
*/ */
public static function unnest() { public static function unnest() {
return self::set_inst(self::$instance->nestedFrom); if (self::inst()->nestedFrom) {
self::set_inst(self::inst()->nestedFrom);
}
else {
user_error(
"Unable to unnest root Injector, please make sure you don't have mis-matched nest/unnest",
E_USER_WARNING
);
}
return self::inst();
} }
/** /**
* Indicate whether we auto scan injected objects for properties to set. * Indicate whether we auto scan injected objects for properties to set.
* *
* @param boolean $val * @param boolean $val
*/ */
public function setAutoScanProperties($val) { public function setAutoScanProperties($val) {
$this->autoScanProperties = $val; $this->autoScanProperties = $val;
} }
/** /**
* Sets the default factory to use for creating new objects. * Sets the default factory to use for creating new objects.
* *
@ -292,32 +301,32 @@ class Injector {
public function setObjectCreator(Factory $obj) { public function setObjectCreator(Factory $obj) {
$this->objectCreator = $obj; $this->objectCreator = $obj;
} }
/** /**
* @return Factory * @return Factory
*/ */
public function getObjectCreator() { public function getObjectCreator() {
return $this->objectCreator; return $this->objectCreator;
} }
/** /**
* Set the configuration locator * Set the configuration locator
* @param ServiceConfigurationLocator $configLocator * @param ServiceConfigurationLocator $configLocator
*/ */
public function setConfigLocator($configLocator) { public function setConfigLocator($configLocator) {
$this->configLocator = $configLocator; $this->configLocator = $configLocator;
} }
/** /**
* Retrieve the configuration locator * Retrieve the configuration locator
* @return ServiceConfigurationLocator * @return ServiceConfigurationLocator
*/ */
public function getConfigLocator() { public function getConfigLocator() {
return $this->configLocator; return $this->configLocator;
} }
/** /**
* Add in a specific mapping that should be catered for on a type. * Add in a specific mapping that should be catered for on a type.
* This allows configuration of what should occur when an object * This allows configuration of what should occur when an object
* of a particular type is injected, and what items should be injected * of a particular type is injected, and what items should be injected
* for those properties / methods. * for those properties / methods.
@ -326,19 +335,19 @@ class Injector {
* The class to set a mapping for * The class to set a mapping for
* @param type $property * @param type $property
* The property to set the mapping for * The property to set the mapping for
* @param type $injectType * @param type $injectType
* The registered type that will be injected * The registered type that will be injected
* @param string $injectVia * @param string $injectVia
* Whether to inject by setting a property or calling a setter * Whether to inject by setting a property or calling a setter
*/ */
public function setInjectMapping($class, $property, $toInject, $injectVia = 'property') { public function setInjectMapping($class, $property, $toInject, $injectVia = 'property') {
$mapping = isset($this->injectMap[$class]) ? $this->injectMap[$class] : array(); $mapping = isset($this->injectMap[$class]) ? $this->injectMap[$class] : array();
$mapping[$property] = array('name' => $toInject, 'type' => $injectVia); $mapping[$property] = array('name' => $toInject, 'type' => $injectVia);
$this->injectMap[$class] = $mapping; $this->injectMap[$class] = $mapping;
} }
/** /**
* Add an object that should be automatically set on managed objects * Add an object that should be automatically set on managed objects
* *
@ -370,7 +379,7 @@ class Injector {
$spec = array('class' => $spec); $spec = array('class' => $spec);
} }
$file = isset($spec['src']) ? $spec['src'] : null; $file = isset($spec['src']) ? $spec['src'] : null;
$name = null; $name = null;
if (file_exists($file)) { if (file_exists($file)) {
@ -378,21 +387,21 @@ class Injector {
$name = substr($filename, 0, strrpos($filename, '.')); $name = substr($filename, 0, strrpos($filename, '.'));
} }
// class is whatever's explicitly set, // class is whatever's explicitly set,
$class = isset($spec['class']) ? $spec['class'] : $name; $class = isset($spec['class']) ? $spec['class'] : $name;
// or the specid if nothing else available. // or the specid if nothing else available.
if (!$class && is_string($specId)) { if (!$class && is_string($specId)) {
$class = $specId; $class = $specId;
} }
// make sure the class is set... // make sure the class is set...
$spec['class'] = $class; $spec['class'] = $class;
$id = is_string($specId) ? $specId : (isset($spec['id']) ? $spec['id'] : $class); $id = is_string($specId) ? $specId : (isset($spec['id']) ? $spec['id'] : $class);
$priority = isset($spec['priority']) ? $spec['priority'] : 1; $priority = isset($spec['priority']) ? $spec['priority'] : 1;
// see if we already have this defined. If so, check priority weighting // see if we already have this defined. If so, check priority weighting
if (isset($this->specs[$id]) && isset($this->specs[$id]['priority'])) { if (isset($this->specs[$id]) && isset($this->specs[$id]['priority'])) {
if ($this->specs[$id]['priority'] > $priority) { if ($this->specs[$id]['priority'] > $priority) {
@ -412,12 +421,12 @@ class Injector {
// We've removed this check because new functionality means that the 'class' field doesn't need to refer // We've removed this check because new functionality means that the 'class' field doesn't need to refer
// specifically to a class anymore - it could be a compound statement, ala SilverStripe's old Object::create // specifically to a class anymore - it could be a compound statement, ala SilverStripe's old Object::create
// functionality // functionality
// //
// if (!class_exists($class)) { // if (!class_exists($class)) {
// throw new Exception("Failed to load '$class' from $file"); // throw new Exception("Failed to load '$class' from $file");
// } // }
// store the specs for now - we lazy load on demand later on. // store the specs for now - we lazy load on demand later on.
$this->specs[$id] = $spec; $this->specs[$id] = $spec;
// EXCEPT when there's already an existing instance at this id. // EXCEPT when there's already an existing instance at this id.
@ -430,20 +439,20 @@ class Injector {
return $this; return $this;
} }
/** /**
* Update the configuration of an already defined service * Update the configuration of an already defined service
* *
* Use this if you don't want to register a complete new config, just append * Use this if you don't want to register a complete new config, just append
* to an existing configuration. Helpful to avoid overwriting someone else's changes * to an existing configuration. Helpful to avoid overwriting someone else's changes
* *
* updateSpec('RequestProcessor', 'filters', '%$MyFilter') * updateSpec('RequestProcessor', 'filters', '%$MyFilter')
* *
* @param string $id * @param string $id
* The name of the service to update the definition for * The name of the service to update the definition for
* @param string $property * @param string $property
* The name of the property to update. * The name of the property to update.
* @param mixed $value * @param mixed $value
* The value to set * The value to set
* @param boolean $append * @param boolean $append
* Whether to append (the default) when the property is an array * Whether to append (the default) when the property is an array
@ -457,20 +466,20 @@ class Injector {
} else { } else {
$this->specs[$id]['properties'][$property] = $value; $this->specs[$id]['properties'][$property] = $value;
} }
// and reload the object; existing bindings don't get // and reload the object; existing bindings don't get
// updated though! (for now...) // updated though! (for now...)
if (isset($this->serviceCache[$id])) { if (isset($this->serviceCache[$id])) {
$this->instantiate(array('class'=>$id), $id); $this->instantiate(array('class'=>$id), $id);
} }
} }
} }
/** /**
* Update a class specification to convert constructor configuration information if needed * Update a class specification to convert constructor configuration information if needed
* *
* We do this as a separate process to avoid unneeded calls to convertServiceProperty * We do this as a separate process to avoid unneeded calls to convertServiceProperty
* *
* @param array $spec * @param array $spec
* The class specification to update * The class specification to update
*/ */
@ -484,7 +493,7 @@ class Injector {
* Recursively convert a value into its proper representation with service references * Recursively convert a value into its proper representation with service references
* resolved to actual objects * resolved to actual objects
* *
* @param string $value * @param string $value
*/ */
public function convertServiceProperty($value) { public function convertServiceProperty($value) {
if (is_array($value)) { if (is_array($value)) {
@ -494,7 +503,7 @@ class Injector {
} }
return $newVal; return $newVal;
} }
if (is_string($value) && strpos($value, '%$') === 0) { if (is_string($value) && strpos($value, '%$') === 0) {
$id = substr($value, 2); $id = substr($value, 2);
return $this->get($id); return $this->get($id);
@ -518,10 +527,10 @@ class Injector {
* set any relevant properties * set any relevant properties
* *
* Optionally, you can pass a class name directly for creation * Optionally, you can pass a class name directly for creation
* *
* To access this from the outside, you should call ->get('Name') to ensure * To access this from the outside, you should call ->get('Name') to ensure
* the appropriate checks are made on the specific type. * the appropriate checks are made on the specific type.
* *
* *
* @param array $spec * @param array $spec
* The specification of the class to instantiate * The specification of the class to instantiate
@ -556,9 +565,9 @@ class Injector {
// now set the service in place if needbe. This is NOT done for prototype beans, as they're // now set the service in place if needbe. This is NOT done for prototype beans, as they're
// created anew each time // created anew each time
if (!$type) { if (!$type) {
$type = isset($spec['type']) ? $spec['type'] : null; $type = isset($spec['type']) ? $spec['type'] : null;
} }
if ($id && (!$type || $type != 'prototype')) { if ($id && (!$type || $type != 'prototype')) {
// this ABSOLUTELY must be set before the object is injected. // this ABSOLUTELY must be set before the object is injected.
// This prevents circular reference errors down the line // This prevents circular reference errors down the line
@ -573,7 +582,7 @@ class Injector {
/** /**
* Inject $object with available objects from the service cache * Inject $object with available objects from the service cache
* *
* @todo Track all the existing objects that have had a service bound * @todo Track all the existing objects that have had a service bound
* into them, so we can update that binding at a later point if needbe (ie * into them, so we can update that binding at a later point if needbe (ie
* if the managed service changes) * if the managed service changes)
@ -583,12 +592,12 @@ class Injector {
* @param string $asType * @param string $asType
* The ID this item was loaded as. This is so that the property configuration * The ID this item was loaded as. This is so that the property configuration
* for a type is referenced correctly in case $object is no longer the same * for a type is referenced correctly in case $object is no longer the same
* type as the loaded config specification had it as. * type as the loaded config specification had it as.
*/ */
public function inject($object, $asType=null) { public function inject($object, $asType=null) {
$objtype = $asType ? $asType : get_class($object); $objtype = $asType ? $asType : get_class($object);
$mapping = isset($this->injectMap[$objtype]) ? $this->injectMap[$objtype] : null; $mapping = isset($this->injectMap[$objtype]) ? $this->injectMap[$objtype] : null;
// first off, set any properties defined in the service specification for this // first off, set any properties defined in the service specification for this
// object type // object type
if (isset($this->specs[$objtype]) && isset($this->specs[$objtype]['properties'])) { if (isset($this->specs[$objtype]) && isset($this->specs[$objtype]['properties'])) {
@ -662,8 +671,8 @@ class Injector {
if ($injections && count($injections)) { if ($injections && count($injections)) {
foreach ($injections as $property => $value) { foreach ($injections as $property => $value) {
// we're checking empty in case it already has a property at this name // we're checking empty in case it already has a property at this name
// this doesn't catch privately set things, but they will only be set by a setter method, // this doesn't catch privately set things, but they will only be set by a setter method,
// which should be responsible for preventing further setting if it doesn't want it. // which should be responsible for preventing further setting if it doesn't want it.
if (empty($object->$property)) { if (empty($object->$property)) {
$value = $this->convertServiceProperty($value); $value = $this->convertServiceProperty($value);
$this->setObjectProperty($object, $property, $value); $this->setObjectProperty($object, $property, $value);
@ -691,7 +700,7 @@ class Injector {
* Set an object's property to a specific value * Set an object's property to a specific value
* @param string $name * @param string $name
* The name of the property to set * The name of the property to set
* @param mixed $value * @param mixed $value
* The value to set * The value to set
*/ */
protected function setObjectProperty($object, $name, $value) { protected function setObjectProperty($object, $name, $value) {
@ -704,14 +713,14 @@ class Injector {
/** /**
* Does the given service exist, and if so, what's the stored name for it? * Does the given service exist, and if so, what's the stored name for it?
* *
* We do a special check here for services that are using compound names. For example, * We do a special check here for services that are using compound names. For example,
* we might want to say that a property should be injected with Log.File or Log.Memory, * we might want to say that a property should be injected with Log.File or Log.Memory,
* but have only registered a 'Log' service, we'll instead return that. * but have only registered a 'Log' service, we'll instead return that.
* *
* Will recursively call hasService for each depth of dotting * Will recursively call hasService for each depth of dotting
* *
* @return string * @return string
* The name of the service (as it might be different from the one passed in) * The name of the service (as it might be different from the one passed in)
*/ */
public function hasService($name) { public function hasService($name) {
@ -719,52 +728,52 @@ class Injector {
if (isset($this->specs[$name])) { if (isset($this->specs[$name])) {
return $name; return $name;
} }
// okay, check whether we've got a compound name - don't worry about 0 index, cause that's an // okay, check whether we've got a compound name - don't worry about 0 index, cause that's an
// invalid name // invalid name
if (!strpos($name, '.')) { if (!strpos($name, '.')) {
return null; return null;
} }
return $this->hasService(substr($name, 0, strrpos($name, '.'))); return $this->hasService(substr($name, 0, strrpos($name, '.')));
} }
/** /**
* Register a service object with an optional name to register it as the * Register a service object with an optional name to register it as the
* service for * service for
* *
* @param stdClass $service * @param stdClass $service
* The object to register * The object to register
* @param string $replace * @param string $replace
* The name of the object to replace (if different to the * The name of the object to replace (if different to the
* class name of the object to register) * class name of the object to register)
* *
*/ */
public function registerService($service, $replace = null) { public function registerService($service, $replace = null) {
$registerAt = get_class($service); $registerAt = get_class($service);
if ($replace != null) { if ($replace != null) {
$registerAt = $replace; $registerAt = $replace;
} }
$this->specs[$registerAt] = array('class' => get_class($service)); $this->specs[$registerAt] = array('class' => get_class($service));
$this->serviceCache[$registerAt] = $service; $this->serviceCache[$registerAt] = $service;
$this->inject($service); $this->inject($service);
} }
/** /**
* Register a service with an explicit name * Register a service with an explicit name
* *
* @deprecated since 3.1.1 * @deprecated since 3.1.1
*/ */
public function registerNamedService($name, $service) { public function registerNamedService($name, $service) {
return $this->registerService($service, $name); return $this->registerService($service, $name);
} }
/** /**
* Removes a named object from the cached list of objects managed * Removes a named object from the cached list of objects managed
* by the inject * by the inject
* *
* @param type $name * @param type $name
* The name to unregister * The name to unregister
*/ */
public function unregisterNamedObject($name) { public function unregisterNamedObject($name) {
@ -772,35 +781,35 @@ class Injector {
} }
/** /**
* Clear out all objects that are managed by the injetor. * Clear out all objects that are managed by the injetor.
*/ */
public function unregisterAllObjects() { public function unregisterAllObjects() {
$this->serviceCache = array('Injector' => $this); $this->serviceCache = array('Injector' => $this);
} }
/** /**
* Get a named managed object * Get a named managed object
* *
* Will first check to see if the item has been registered as a configured service/bean * Will first check to see if the item has been registered as a configured service/bean
* and return that if so. * and return that if so.
* *
* Next, will check to see if there's any registered configuration for the given type * Next, will check to see if there's any registered configuration for the given type
* and will then try and load that * and will then try and load that
* *
* Failing all of that, will just return a new instance of the * Failing all of that, will just return a new instance of the
* specificied object. * specificied object.
* *
* @param string $name * @param string $name
* the name of the service to retrieve. If not a registered * the name of the service to retrieve. If not a registered
* service, then a class of the given name is instantiated * service, then a class of the given name is instantiated
* @param boolean $asSingleton * @param boolean $asSingleton
* Whether to register the created object as a singleton * Whether to register the created object as a singleton
* if no other configuration is found * if no other configuration is found
* @param array $constructorArgs * @param array $constructorArgs
* Optional set of arguments to pass as constructor arguments * Optional set of arguments to pass as constructor arguments
* if this object is to be created from scratch * if this object is to be created from scratch
* (ie asSingleton = false) * (ie asSingleton = false)
* *
*/ */
public function get($name, $asSingleton = true, $constructorArgs = null) { public function get($name, $asSingleton = true, $constructorArgs = null) {
// reassign the name as it might actually be a compound name // reassign the name as it might actually be a compound name
@ -809,14 +818,14 @@ class Injector {
// we don't want to return the singleton version of it. // we don't want to return the singleton version of it.
$spec = $this->specs[$serviceName]; $spec = $this->specs[$serviceName];
$type = isset($spec['type']) ? $spec['type'] : null; $type = isset($spec['type']) ? $spec['type'] : null;
// if we're explicitly a prototype OR we're not wanting a singleton // if we're explicitly a prototype OR we're not wanting a singleton
if (($type && $type == 'prototype') || !$asSingleton) { if (($type && $type == 'prototype') || !$asSingleton) {
if ($spec && $constructorArgs) { if ($spec && $constructorArgs) {
$spec['constructor'] = $constructorArgs; $spec['constructor'] = $constructorArgs;
} else { } else {
// convert any _configured_ constructor args. // convert any _configured_ constructor args.
// we don't call this for get() calls where someone passes in // we don't call this for get() calls where someone passes in
// constructor args, otherwise we end up calling convertServiceParams // constructor args, otherwise we end up calling convertServiceParams
// way too often // way too often
$this->updateSpecConstructor($spec); $this->updateSpecConstructor($spec);
@ -830,7 +839,7 @@ class Injector {
return $this->serviceCache[$serviceName]; return $this->serviceCache[$serviceName];
} }
} }
$config = $this->configLocator->locateConfigFor($name); $config = $this->configLocator->locateConfigFor($name);
if ($config) { if ($config) {
$this->load(array($name => $config)); $this->load(array($name => $config));
@ -844,7 +853,7 @@ class Injector {
} }
} }
// If we've got this far, we're dealing with a case of a user wanting // If we've got this far, we're dealing with a case of a user wanting
// to create an object based on its name. So, we need to fake its config // to create an object based on its name. So, we need to fake its config
// if the user wants it managed as a singleton service style object // if the user wants it managed as a singleton service style object
$spec = array('class' => $name, 'constructor' => $constructorArgs); $spec = array('class' => $name, 'constructor' => $constructorArgs);
@ -856,10 +865,10 @@ class Injector {
return $this->instantiate($spec); return $this->instantiate($spec);
} }
/** /**
* Magic method to return an item directly * Magic method to return an item directly
* *
* @param string $name * @param string $name
* The named object to retrieve * The named object to retrieve
* @return mixed * @return mixed
@ -870,20 +879,20 @@ class Injector {
/** /**
* Similar to get() but always returns a new object of the given type * Similar to get() but always returns a new object of the given type
* *
* Additional parameters are passed through as * Additional parameters are passed through as
* *
* @param type $name * @param type $name
*/ */
public function create($name) { public function create($name) {
$constructorArgs = func_get_args(); $constructorArgs = func_get_args();
array_shift($constructorArgs); array_shift($constructorArgs);
return $this->get($name, false, count($constructorArgs) ? $constructorArgs : null); return $this->get($name, false, count($constructorArgs) ? $constructorArgs : null);
} }
/** /**
* Creates an object with the supplied argument array * Creates an object with the supplied argument array
* *
* @param string $name * @param string $name
* Name of the class to create an object of * Name of the class to create an object of
* @param array $args * @param array $args
@ -893,4 +902,4 @@ class Injector {
public function createWithArgs($name, $constructorArgs) { public function createWithArgs($name, $constructorArgs) {
return $this->get($name, false, $constructorArgs); return $this->get($name, false, $constructorArgs);
} }
} }

View File

@ -10,62 +10,62 @@
* - An array * - An array
* - A non-array value * - A non-array value
* *
* If the value is an array, each value in the array may also be one of those * If the value is an array, each value in the array may also be one of those
* three types. * three types.
* *
* A property can have a value specified in multiple locations, each of which * A property can have a value specified in multiple locations, each of which
* have a hard coded or explicit priority. We combine all these values together * have a hard coded or explicit priority. We combine all these values together
* into a "composite" value using rules that depend on the priority order of * into a "composite" value using rules that depend on the priority order of
* the locations to give the final value, using these rules: * the locations to give the final value, using these rules:
* *
* - If the value is an array, each array is added to the _beginning_ of the * - If the value is an array, each array is added to the _beginning_ of the
* composite array in ascending priority order. If a higher priority item has * composite array in ascending priority order. If a higher priority item has
* a non-integer key which is the same as a lower priority item, the value of * a non-integer key which is the same as a lower priority item, the value of
* those items is merged using these same rules, and the result of the merge * those items is merged using these same rules, and the result of the merge
* is located in the same location the higher priority item would be if there * is located in the same location the higher priority item would be if there
* was no key clash. Other than in this key-clash situation, within the * was no key clash. Other than in this key-clash situation, within the
* particular array, order is preserved. * particular array, order is preserved.
* *
* - If the value is not an array, the highest priority value is used without * - If the value is not an array, the highest priority value is used without
* any attempt to merge. * any attempt to merge.
* *
* It is an error to have mixed types of the same named property in different * It is an error to have mixed types of the same named property in different
* locations (but an error will not necessarily be raised due to optimizations * locations (but an error will not necessarily be raised due to optimizations
* in the lookup code). * in the lookup code).
* *
* The exception to this is "false-ish" values - empty arrays, empty strings, * The exception to this is "false-ish" values - empty arrays, empty strings,
* etc. When merging a non-false-ish value with a false-ish value, the result * etc. When merging a non-false-ish value with a false-ish value, the result
* will be the non-false-ish value regardless of priority. When merging two * will be the non-false-ish value regardless of priority. When merging two
* false-ish values the result will be the higher priority false-ish value. * false-ish values the result will be the higher priority false-ish value.
* *
* The locations that configuration values are taken from in highest -> lowest * The locations that configuration values are taken from in highest -> lowest
* priority order. * priority order.
* *
* - Any values set via a call to Config#update. * - Any values set via a call to Config#update.
* *
* - The configuration values taken from the YAML files in _config directories * - The configuration values taken from the YAML files in _config directories
* (internally sorted in before / after order, where the item that is latest * (internally sorted in before / after order, where the item that is latest
* is highest priority). * is highest priority).
* *
* - Any static set on an "additional static source" class (such as an * - Any static set on an "additional static source" class (such as an
* extension) named the same as the name of the property. * extension) named the same as the name of the property.
* *
* - Any static set on the class named the same as the name of the property. * - Any static set on the class named the same as the name of the property.
* *
* - The composite configuration value of the parent class of this class. * - The composite configuration value of the parent class of this class.
* *
* At some of these levels you can also set masks. These remove values from the * At some of these levels you can also set masks. These remove values from the
* composite value at their priority point rather than add. They are much * composite value at their priority point rather than add. They are much
* simpler. They consist of a list of key / value pairs. When applied against * simpler. They consist of a list of key / value pairs. When applied against
* the current composite value: * the current composite value:
* *
* - If the composite value is a sequential array, any member of that array * - If the composite value is a sequential array, any member of that array
* that matches any value in the mask is removed. * that matches any value in the mask is removed.
* *
* - If the composite value is an associative array, any member of that array * - If the composite value is an associative array, any member of that array
* that matches both the key and value of any pair in the mask is removed. * that matches both the key and value of any pair in the mask is removed.
* *
* - If the composite value is not an array, if that value matches any value * - If the composite value is not an array, if that value matches any value
* in the mask it is removed. * in the mask it is removed.
* *
* @package framework * @package framework
@ -74,7 +74,7 @@
class Config { class Config {
/** /**
* A marker instance for the "anything" singleton value. Don't access * A marker instance for the "anything" singleton value. Don't access
* directly, even in-class, always use self::anything() * directly, even in-class, always use self::anything()
* *
* @var Object * @var Object
@ -82,9 +82,9 @@ class Config {
private static $_anything = null; private static $_anything = null;
/** /**
* Get a marker class instance that is used to do a "remove anything with * Get a marker class instance that is used to do a "remove anything with
* this key" by adding $key => Config::anything() to the suppress array * this key" by adding $key => Config::anything() to the suppress array
* *
* @return Object * @return Object
*/ */
public static function anything() { public static function anything() {
@ -98,7 +98,7 @@ class Config {
// -- Source options bitmask -- // -- Source options bitmask --
/** /**
* source options bitmask value - merge all parent configuration in as * source options bitmask value - merge all parent configuration in as
* lowest priority. * lowest priority.
* *
* @const * @const
@ -106,7 +106,7 @@ class Config {
const INHERITED = 0; const INHERITED = 0;
/** /**
* source options bitmask value - only get configuration set for this * source options bitmask value - only get configuration set for this
* specific class, not any of it's parents. * specific class, not any of it's parents.
* *
* @const * @const
@ -114,23 +114,23 @@ class Config {
const UNINHERITED = 1; const UNINHERITED = 1;
/** /**
* source options bitmask value - inherit, but stop on the first class * source options bitmask value - inherit, but stop on the first class
* that actually provides a value (event an empty value). * that actually provides a value (event an empty value).
* *
* @const * @const
*/ */
const FIRST_SET = 2; const FIRST_SET = 2;
/** /**
* @const source options bitmask value - do not use additional statics * @const source options bitmask value - do not use additional statics
* sources (such as extension) * sources (such as extension)
*/ */
const EXCLUDE_EXTRA_SOURCES = 4; const EXCLUDE_EXTRA_SOURCES = 4;
// -- get_value_type response enum -- // -- get_value_type response enum --
/** /**
* Return flag for get_value_type indicating value is a scalar (or really * Return flag for get_value_type indicating value is a scalar (or really
* just not-an-array, at least ATM) * just not-an-array, at least ATM)
* *
* @const * @const
@ -144,7 +144,7 @@ class Config {
const IS_ARRAY = 2; const IS_ARRAY = 2;
/** /**
* Get whether the value is an array or not. Used to be more complicated, * Get whether the value is an array or not. Used to be more complicated,
* but still nice sugar to have an enum to compare and not just a true / * but still nice sugar to have an enum to compare and not just a true /
* false value. * false value.
* *
@ -171,7 +171,7 @@ class Config {
} }
/** /**
* @todo If we can, replace next static & static methods with DI once that's in * @todo If we can, replace next static & static methods with DI once that's in
*/ */
protected static $instance; protected static $instance;
@ -180,7 +180,7 @@ class Config {
* *
* Configs should not normally be manually created. * Configs should not normally be manually created.
* *
* In general use you will use this method to obtain the current Config * In general use you will use this method to obtain the current Config
* instance. * instance.
* *
* @return Config * @return Config
@ -198,7 +198,7 @@ class Config {
* *
* {@link Config} objects should not normally be manually created. * {@link Config} objects should not normally be manually created.
* *
* A use case for replacing the active configuration set would be for * A use case for replacing the active configuration set would be for
* creating an isolated environment for unit tests. * creating an isolated environment for unit tests.
* *
* @param Config $instance New instance of Config to assign * @param Config $instance New instance of Config to assign
@ -213,13 +213,13 @@ class Config {
} }
/** /**
* Make the newly active {@link Config} be a copy of the current active * Make the newly active {@link Config} be a copy of the current active
* {@link Config} instance. * {@link Config} instance.
* *
* You can then make changes to the configuration by calling update and * You can then make changes to the configuration by calling update and
* remove on the new value returned by {@link Config::inst()}, and then discard * remove on the new value returned by {@link Config::inst()}, and then discard
* those changes later by calling unnest. * those changes later by calling unnest.
* *
* @return Config Reference to new active Config instance * @return Config Reference to new active Config instance
*/ */
public static function nest() { public static function nest() {
@ -231,13 +231,22 @@ class Config {
} }
/** /**
* Change the active Config back to the Config instance the current active * Change the active Config back to the Config instance the current active
* Config object was copied from. * Config object was copied from.
* *
* @return Config Reference to new active Config instance * @return Config Reference to new active Config instance
*/ */
public static function unnest() { public static function unnest() {
return self::set_instance(self::$instance->nestedFrom); if (self::inst()->nestedFrom) {
self::set_instance(self::inst()->nestedFrom);
}
else {
user_error(
"Unable to unnest root Config, please make sure you don't have mis-matched nest/unnest",
E_USER_WARNING
);
}
return self::inst();
} }
/** /**
@ -246,7 +255,7 @@ class Config {
protected $cache; protected $cache;
/** /**
* Each copy of the Config object need's it's own cache, so changes don't * Each copy of the Config object need's it's own cache, so changes don't
* leak through to other instances. * leak through to other instances.
*/ */
public function __construct() { public function __construct() {
@ -257,22 +266,22 @@ class Config {
$this->cache = clone $this->cache; $this->cache = clone $this->cache;
} }
/** /**
* @var Config - The config instance this one was copied from when * @var Config - The config instance this one was copied from when
* Config::nest() was called. * Config::nest() was called.
*/ */
protected $nestedFrom = null; protected $nestedFrom = null;
/** /**
* @var array - Array of arrays. Each member is an nested array keyed as * @var array - Array of arrays. Each member is an nested array keyed as
* $class => $name => $value, where value is a config value to treat as * $class => $name => $value, where value is a config value to treat as
* the highest priority item. * the highest priority item.
*/ */
protected $overrides = array(); protected $overrides = array();
/** /**
* @var array $suppresses Array of arrays. Each member is an nested array * @var array $suppresses Array of arrays. Each member is an nested array
* keyed as $class => $name => $value, where value is a config value suppress * keyed as $class => $name => $value, where value is a config value suppress
* from any lower priority item. * from any lower priority item.
*/ */
protected $suppresses = array(); protected $suppresses = array();
@ -287,7 +296,7 @@ class Config {
*/ */
public function pushConfigStaticManifest(SS_ConfigStaticManifest $manifest) { public function pushConfigStaticManifest(SS_ConfigStaticManifest $manifest) {
array_unshift($this->staticManifests, $manifest); array_unshift($this->staticManifests, $manifest);
$this->cache->clean(); $this->cache->clean();
} }
@ -483,8 +492,8 @@ class Config {
} }
if (isset($this->suppresses[$k][$class][$name])) { if (isset($this->suppresses[$k][$class][$name])) {
$suppress = $suppress $suppress = $suppress
? array_merge($suppress, $this->suppresses[$k][$class][$name]) ? array_merge($suppress, $this->suppresses[$k][$class][$name])
: $this->suppresses[$k][$class][$name]; : $this->suppresses[$k][$class][$name];
} }
} }
@ -730,7 +739,7 @@ class Config_LRU {
// Target count - not always the lowest, but guaranteed to exist (or hit an empty item) // Target count - not always the lowest, but guaranteed to exist (or hit an empty item)
$target = $this->c - self::SIZE + 1; $target = $this->c - self::SIZE + 1;
$i = $stop = $this->i; $i = $stop = $this->i;
do { do {
if (!($i--)) $i = self::SIZE-1; if (!($i--)) $i = self::SIZE-1;
$item = $this->cache[$i]; $item = $this->cache[$i];