From 9ebbb5d917b3435581f55c94b144a0b80a5b46b9 Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Wed, 3 Feb 2016 22:55:18 +0100 Subject: [PATCH] Add ability to register a type while resolving it Closes #5 --- CHANGES.md | 170 +++++++------- TUTORIAL.md | 417 +++++++++++++++++----------------- source/poodinis/container.d | 41 +++- test/poodinis/containertest.d | 7 + 4 files changed, 336 insertions(+), 299 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8b2fc16..480262f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,84 +1,86 @@ -Poodinis Changelog -================== -**Version NEXT** -* ADD setting persistent registration options -* DEPRECATE DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION, use doNotAddConcreteTypeRegistration instead - -**Version 6.0.0** -* CHANGE registration scopes are replaced by a single factory implementation. If you were not doing anything with the internal scope mechanism, you -should not be affected by this change. -* ADD application contexts. You can register dependencies within an application context which allow you to fine-tune the creation of dependency instances. -* CHANGE all public poodinis imports to private. This should not affect you if you use the package import "poodinis" instead of individual modules. -* REMOVE deprecated ADD_CONCRETE_TYPE_REGISTRATION registration option. -* REMOVE deprecated RegistrationOptions alias. - -**Version 5.0.0** -* DEPRECATE ADD_CONCRETE_TYPE_REGISTRATION registration option. It basically does nothing anymore. See next point. -* CHANGE adding registrations by super type always registers them by concrete type as well now. (Previously done with ADD_CONCRETE_TYPE_REGISTRATION). See DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION for the reverse behaviour. -* CHANGE RegistrationOptions enum name to RegistrationOption -* DEPRECATE Usage of RegistrationOptions, please use RegistrationOption instead. - -**Version 4.0.0** -* REMOVE deprecated module "dependency.d" - -**Version 3.0.0** -This version is only compatible with DMD 2.068.0 or higher! -* ADD UDA which always resolved a new instance to an autowired member, regardless of registration scope. - -**Version 2.2.0** -* ADD canonical package module "package.d". Use "import poodinis;" to import the project. -* DEPRECATE module "dependency.d". Please use the canonical package module. See previous point. -* ADD autowiring of dynamic arrays. All registered instances of the element type of the array will be assigned to it. - -**Version 2.1.0** -* ADD option for registering a class by concrete type when registering that class by supertype. - -**Version 2.0.0** -This version introduces changes which might be incompatible with your current codebase -* CHANGE dependency container to be synchronized. Sharing a dependency container between threads is now possible. -The implication is that all dependency container instances must be shared now. -You don't have to change anything if you were only using the singleton dependency container. - -**Version 1.0.0** -This version introduces changes which are incompatible with previous versions -* REMOVE deprecated autowire constructor -* REMOVE deprecated container alias -* ADD documentation for public API -* REMOVE @Autowired UDA. Use @Autowire instead. -* ADD quickstart from readme to compilable example project. -* ADD example project for the use of qualifiers - -**Version 0.3.1** -* FIX issue where autowiring members which are declared by interface or supertype would get autowired incorrectly. - -**Version 0.3.0** -* ADD alternative workaround to readme for autowire limitation -* CHANGE returning of resolved instances by returning them by qualifier type instead -* ADD debug specifier to reduce verbosity of debug output - -**Version 0.2.0** -* ADD ability to register type with multiple concrete types. They can be correctly resolved using qualifiers. -* DEPRECATE template for autowiring in constructor. This workaround is buggy. Use qualifiers instead. - -**Version 0.1.4** -* Make Poodinis compatible with D 2.066.0 and DUB 0.9.22 -* FIX incorrect clearing of registrations -This release should be backwards compatible with the previous versions of D and DUB, but please note that there are no more separate -configurations for release and debug builds. You have to specify a build type in DUB. - -**Version 0.1.3** -* ADD global autowire function for convenience -* CHANGE workaround to be more simple -* FIX autowiring classes which contain non-symbolic declarations such as aliases. As a result, only variables are attempted to be autowired. - -**Version 0.1.2** -* ADD workaround for failing to autowire types registered by supertype or interface - -**Version 0.1.1** -* FIX: Also auto-wire members from base classes - -**Version 0.1.0** -* Initial open-source release -* ADD support for registering and resolving -* ADD registration scopes -* ADD autowiring +Poodinis Changelog +================== +**Version NEXT** +* ADD setting persistent registration options +* DEPRECATE DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION, use doNotAddConcreteTypeRegistration instead +* ADD resolve options to container resolve() +* ADD ability to register a type while resolving it. Use resolve option registerBeforeResolving + +**Version 6.0.0** +* CHANGE registration scopes are replaced by a single factory implementation. If you were not doing anything with the internal scope mechanism, you +should not be affected by this change. +* ADD application contexts. You can register dependencies within an application context which allow you to fine-tune the creation of dependency instances. +* CHANGE all public poodinis imports to private. This should not affect you if you use the package import "poodinis" instead of individual modules. +* REMOVE deprecated ADD_CONCRETE_TYPE_REGISTRATION registration option. +* REMOVE deprecated RegistrationOptions alias. + +**Version 5.0.0** +* DEPRECATE ADD_CONCRETE_TYPE_REGISTRATION registration option. It basically does nothing anymore. See next point. +* CHANGE adding registrations by super type always registers them by concrete type as well now. (Previously done with ADD_CONCRETE_TYPE_REGISTRATION). See DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION for the reverse behaviour. +* CHANGE RegistrationOptions enum name to RegistrationOption +* DEPRECATE Usage of RegistrationOptions, please use RegistrationOption instead. + +**Version 4.0.0** +* REMOVE deprecated module "dependency.d" + +**Version 3.0.0** +This version is only compatible with DMD 2.068.0 or higher! +* ADD UDA which always resolved a new instance to an autowired member, regardless of registration scope. + +**Version 2.2.0** +* ADD canonical package module "package.d". Use "import poodinis;" to import the project. +* DEPRECATE module "dependency.d". Please use the canonical package module. See previous point. +* ADD autowiring of dynamic arrays. All registered instances of the element type of the array will be assigned to it. + +**Version 2.1.0** +* ADD option for registering a class by concrete type when registering that class by supertype. + +**Version 2.0.0** +This version introduces changes which might be incompatible with your current codebase +* CHANGE dependency container to be synchronized. Sharing a dependency container between threads is now possible. +The implication is that all dependency container instances must be shared now. +You don't have to change anything if you were only using the singleton dependency container. + +**Version 1.0.0** +This version introduces changes which are incompatible with previous versions +* REMOVE deprecated autowire constructor +* REMOVE deprecated container alias +* ADD documentation for public API +* REMOVE @Autowired UDA. Use @Autowire instead. +* ADD quickstart from readme to compilable example project. +* ADD example project for the use of qualifiers + +**Version 0.3.1** +* FIX issue where autowiring members which are declared by interface or supertype would get autowired incorrectly. + +**Version 0.3.0** +* ADD alternative workaround to readme for autowire limitation +* CHANGE returning of resolved instances by returning them by qualifier type instead +* ADD debug specifier to reduce verbosity of debug output + +**Version 0.2.0** +* ADD ability to register type with multiple concrete types. They can be correctly resolved using qualifiers. +* DEPRECATE template for autowiring in constructor. This workaround is buggy. Use qualifiers instead. + +**Version 0.1.4** +* Make Poodinis compatible with D 2.066.0 and DUB 0.9.22 +* FIX incorrect clearing of registrations +This release should be backwards compatible with the previous versions of D and DUB, but please note that there are no more separate +configurations for release and debug builds. You have to specify a build type in DUB. + +**Version 0.1.3** +* ADD global autowire function for convenience +* CHANGE workaround to be more simple +* FIX autowiring classes which contain non-symbolic declarations such as aliases. As a result, only variables are attempted to be autowired. + +**Version 0.1.2** +* ADD workaround for failing to autowire types registered by supertype or interface + +**Version 0.1.1** +* FIX: Also auto-wire members from base classes + +**Version 0.1.0** +* Initial open-source release +* ADD support for registering and resolving +* ADD registration scopes +* ADD autowiring diff --git a/TUTORIAL.md b/TUTORIAL.md index 246c3eb..4fa9dee 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1,207 +1,212 @@ -Poodinis Tutorial -================= -This tutorial will give you an overview of all functionality offered by Poodinis and how to use them. - -The container -------------- -To register a class, a new dependency container must be instantiated: -```d -// Register a private container -auto dependencies = new DependencyContainer(); -// Or use the singleton container -dependencies = DependencyContainer.getInstance(); -``` -###Registering dependencies -To make dependencies available, they have to be registered: -```d -// Register concrete class -dependencies.register!ExampleClass; -// Register by super type -dependencies.register!(ExampleInterface, ExampleClass); -``` -In the above example, dependencies on the concrete class and interface will resolve an instance of class ExampleClass. A dependency registered by super type will automatically be registered by concrete type. - -Resolving dependencies ----------------------- -To manually resolve a dependency, all you have to do is resolve the dependency's type using the container in which it is registered: -```d -auto exampleClassInstance = dependencies.resolve!ExampleClass; -``` -If the class is registered by interface and not by concrete type, you can still resolve the class by concrete type: - -```d -auto exampleClassInstance = dependencies.resolve!ExampleInterface; -auto exampleClassInstance2 = dependencies.resolve!ExampleClass; -assert(exampleClassInstance is exampleClassInstance2); -``` -If you want to prevent registrations from being both registered by interface and concrete type, use the DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION option when registering: -```d -dependencies.register!(ExampleInterface, ExampleClass)(RegistrationOptions.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); -auto exampleClassInstance = dependencies.resolve!ExampleInterface; -auto exampleClassInstance2 = dependencies.resolve!ExampleClass; // A ResolveException is thrown -``` - -Dependency creation behaviour ------------------ -You can control how a dependency is resolved by specifying a creation scope during registration. The scope determines which instance is returned, be it the same each time or a new one. The following scopes are available: - -* Resolve a dependency using a single instance (default): - -```d -dependencies.register!(ExampleClass).singleInstance(); -``` -* Resolve a dependency with a new instance each time it is resolved: - -```d -dependencies.register!(ExampleClass).newInstance(); -``` -* Resolve a dependency using a pre-existing instance - -```d -auto preExistingInstance = new ExampleClass(); -dependencies.register!(ExampleClass).existingInstance(preExistingInstance); -``` - -Autowiring ----------- -The real value of any dependency injection framework comes from its ability to autowire dependencies. Poodinis supports autowiring by simply applying the **@Autowire** UDA to a member of a class: -```d -class ExampleClassA {} - -class ExampleClassB { - @Autowire - public ExampleClassA dependency; -} - -dependencies.register!ExampleClassA; -auto exampleInstance = new ExampleClassB(); -dependencies.autowire(exampleInstance); -assert(exampleInstance.dependency !is null); -``` -At the moment, it is only possible to autowire public members or properties. - -Dependencies are automatically autowired when a class is resolved. So when you register ExampleClassB, its member, *dependency*, is automatically autowired: -```d -dependencies.register!ExampleClassA; -dependencies.register!ExampleClassB; -auto instance = dependencies.resolve!ExampleClassB; -assert(instance.dependency !is null); -``` -If an interface is to be autowired, you must register a concrete class by interface. Any class registered by concrete type can only be injected when a dependency on a concrete type is autowired. - -Circular dependencies ---------------------- -Poodinis can autowire circular dependencies when they are registered with singleInstance or existingInstance registration scopes. Circular dependencies in registrations with newInstance scopes will not be autowired, as this would cause an endless loop. - -Registering and resolving using qualifiers ------------------------------------------- -You can register multiple concrete types to a super type. When doing so, you will need to specify a qualifier when resolving that type: -```d -// Color is an interface, Blue and Red are classes implementing that interface -dependencies.register!(Color, Blue); -dependencies.register!(Color, Red); -auto blueInstance = dependencies.resolve!(Color, Blue); -``` -If you want to autowire a type registered to multiple concrete types, specify a qualified type as template argument: -```d -class BluePaint { - @Autowire!Blue - public Color color; -} -``` -If you registered multiple concrete types to the same supertype and you do not resolve using a qualifier, a ResolveException is thrown stating that there are multiple candidates for the type to be resolved. - -Autowiring all registered instances to an array ------------------------------------------------ -If you have registered multiple concrete types to a super type, you can autowire them all to an array, in which case you can easily operate on them all: -```d -// Color is an interface, Blue and Red are classes implementing that interface - -class ColorMixer { - @Autowire - public Color[] colors; -} - -dependencies.register!(Color, Blue); -dependencies.register!(Color, Red); -auto mixer = dependencies.resolve!ColorMixer; -``` -Member mixer.colors will now contain instances of Blue and Red. The order of the instances is not guarenteed to be that of the order in which they were registered. - -Application Contexts --------------------- -You can fine-tune dependency configuration using application contexts. Application contexts allow you to centralize all dependency configuration as well as define -how instances of certain classes should be constructed using factory methods. -###Defining and using application contexts -An application context is defined as follows: -```d -class Context : ApplicationContext { - public override void registerDependencies(shared(DependencyContainer) container) { - container.register!SomeClass; - container.register!(SomeInterface, SomeOtherClass).newInstance(); - } - - @Component - public SomeLibraryClass libraryClass() { - return new SomeLibraryClass("This class needs constructor parameters so I have to register it through an application context"); - } -} -``` -In the override *registerDependencies()* you can register all dependencies which do not need complex set-up, just like you would do when directly using the dependency container. -This override is optional. You can still register simple dependencies outside of the context (or in another context). -Complex dependencies are registered through member methods of the context. These member methods serve as factory methods which will be called when a dependency is resolved. -They are annotated with the *@Component* UDA to let the container know that these methods should be registered as dependencies. The type of the registration is the same as the return type of the method. -Factory methods are useful when you have to deal with dependencies which require constructor arguments or elaborate set-up after instantiation. -Application contexts have to be registered with a dependency container. They are registered as follows: -```d -container.registerContext!Context; -``` -All registered dependencies can now be resolved by the same dependency container. Registering a context will also register it as a dependency, meaning you can autowire the application context in other classes. -You can register as many types of application contexts as you like. - -###Autowiring application contexts -Application contexts can make use of autowired dependencies like any other dependency. When registering an application context, all its components are registered first after which the application context is autowired. -This means that after the registration of an application context some dependencies will already be resolved and instantiated. The following example illustrates how autowired members can be used in a context: -```d -class Context : ApplicationContext { - - @Autowire - public SomeClass someClass; - - @Autowire - public SomeOtherClass someOtherClass; - - public override void registerDependencies(shared(DependencyContainer) container) { - container.register!SomeClass; - } - - @Component - public SomeLibraryClass libraryClass() { - return new SomeLibraryClass(someClass, someOtherClass); - } -} -``` -As you can see, autowired dependencies can be used within factory methods. When *SomeLibraryClass* is resolved, it will be created with a resolved instance of *SomeClass* and *SomeOtherClass*. As shown, autowired dependencies can be registered within the same -application context, but don't neccesarily have to be. You can even autowire dependencies which are created within a factory method within the same application context. -Application contexts are directly autowired after they have been registered. This means that all autowired dependencies which are not registered in the application context itself need to be registered before registering the application context. - -###Controlling component registration -You can further influence how components are registered and created with additional UDAs: -```d -class Context : ApplicationContext { - @Component - @Prototype // Will create a new instance every time the dependency is resolved. - @RegisterByType!SomeInterface // Registers the dependency by the specified super type instead of the return type - public SomeClass someClass() { - return new SomeClass(); - } -} -``` - -Persistent Registration Options -------------------------------- -If you want registration options to be persistent (applicable for every call to register()), you can use the container method setPersistentRegistrationOptions(): -```d -dependencies.setPersistentRegistrationOptions(RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); // Sets the option -dependencies.unsetPersistentRegistrationOptions(); // Clears the persistentent options +Poodinis Tutorial +================= +This tutorial will give you an overview of all functionality offered by Poodinis and how to use them. + +The container +------------- +To register a class, a new dependency container must be instantiated: +```d +// Register a private container +auto dependencies = new DependencyContainer(); +// Or use the singleton container +dependencies = DependencyContainer.getInstance(); +``` +###Registering dependencies +To make dependencies available, they have to be registered: +```d +// Register concrete class +dependencies.register!ExampleClass; +// Register by super type +dependencies.register!(ExampleInterface, ExampleClass); +``` +In the above example, dependencies on the concrete class and interface will resolve an instance of class ExampleClass. A dependency registered by super type will automatically be registered by concrete type. + +Resolving dependencies +---------------------- +To manually resolve a dependency, all you have to do is resolve the dependency's type using the container in which it is registered: +```d +auto exampleClassInstance = dependencies.resolve!ExampleClass; +``` +If the class is registered by interface and not by concrete type, you can still resolve the class by concrete type: + +```d +auto exampleClassInstance = dependencies.resolve!ExampleInterface; +auto exampleClassInstance2 = dependencies.resolve!ExampleClass; +assert(exampleClassInstance is exampleClassInstance2); +``` +If you want to prevent registrations from being both registered by interface and concrete type, use the DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION option when registering: +```d +dependencies.register!(ExampleInterface, ExampleClass)(RegistrationOptions.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); +auto exampleClassInstance = dependencies.resolve!ExampleInterface; +auto exampleClassInstance2 = dependencies.resolve!ExampleClass; // A ResolveException is thrown +``` +It is also possible to register a type while resolving it. Doing so means you don't need to explicitly register it beforehand. To do this, use the resolve option "registerBeforeResolving": +```d +dependencies.resolve!ExampleClass([ResolveOption.registerBeforeResolving]); +``` +Naturally this can only be done when you are resolving a concrete type or an interface type by qualifier. + +Dependency creation behaviour +----------------- +You can control how a dependency is resolved by specifying a creation scope during registration. The scope determines which instance is returned, be it the same each time or a new one. The following scopes are available: + +* Resolve a dependency using a single instance (default): + +```d +dependencies.register!(ExampleClass).singleInstance(); +``` +* Resolve a dependency with a new instance each time it is resolved: + +```d +dependencies.register!(ExampleClass).newInstance(); +``` +* Resolve a dependency using a pre-existing instance + +```d +auto preExistingInstance = new ExampleClass(); +dependencies.register!(ExampleClass).existingInstance(preExistingInstance); +``` + +Autowiring +---------- +The real value of any dependency injection framework comes from its ability to autowire dependencies. Poodinis supports autowiring by simply applying the **@Autowire** UDA to a member of a class: +```d +class ExampleClassA {} + +class ExampleClassB { + @Autowire + public ExampleClassA dependency; +} + +dependencies.register!ExampleClassA; +auto exampleInstance = new ExampleClassB(); +dependencies.autowire(exampleInstance); +assert(exampleInstance.dependency !is null); +``` +At the moment, it is only possible to autowire public members or properties. + +Dependencies are automatically autowired when a class is resolved. So when you register ExampleClassB, its member, *dependency*, is automatically autowired: +```d +dependencies.register!ExampleClassA; +dependencies.register!ExampleClassB; +auto instance = dependencies.resolve!ExampleClassB; +assert(instance.dependency !is null); +``` +If an interface is to be autowired, you must register a concrete class by interface. Any class registered by concrete type can only be injected when a dependency on a concrete type is autowired. + +Circular dependencies +--------------------- +Poodinis can autowire circular dependencies when they are registered with singleInstance or existingInstance registration scopes. Circular dependencies in registrations with newInstance scopes will not be autowired, as this would cause an endless loop. + +Registering and resolving using qualifiers +------------------------------------------ +You can register multiple concrete types to a super type. When doing so, you will need to specify a qualifier when resolving that type: +```d +// Color is an interface, Blue and Red are classes implementing that interface +dependencies.register!(Color, Blue); +dependencies.register!(Color, Red); +auto blueInstance = dependencies.resolve!(Color, Blue); +``` +If you want to autowire a type registered to multiple concrete types, specify a qualified type as template argument: +```d +class BluePaint { + @Autowire!Blue + public Color color; +} +``` +If you registered multiple concrete types to the same supertype and you do not resolve using a qualifier, a ResolveException is thrown stating that there are multiple candidates for the type to be resolved. + +Autowiring all registered instances to an array +----------------------------------------------- +If you have registered multiple concrete types to a super type, you can autowire them all to an array, in which case you can easily operate on them all: +```d +// Color is an interface, Blue and Red are classes implementing that interface + +class ColorMixer { + @Autowire + public Color[] colors; +} + +dependencies.register!(Color, Blue); +dependencies.register!(Color, Red); +auto mixer = dependencies.resolve!ColorMixer; +``` +Member mixer.colors will now contain instances of Blue and Red. The order of the instances is not guarenteed to be that of the order in which they were registered. + +Application Contexts +-------------------- +You can fine-tune dependency configuration using application contexts. Application contexts allow you to centralize all dependency configuration as well as define +how instances of certain classes should be constructed using factory methods. +###Defining and using application contexts +An application context is defined as follows: +```d +class Context : ApplicationContext { + public override void registerDependencies(shared(DependencyContainer) container) { + container.register!SomeClass; + container.register!(SomeInterface, SomeOtherClass).newInstance(); + } + + @Component + public SomeLibraryClass libraryClass() { + return new SomeLibraryClass("This class needs constructor parameters so I have to register it through an application context"); + } +} +``` +In the override *registerDependencies()* you can register all dependencies which do not need complex set-up, just like you would do when directly using the dependency container. +This override is optional. You can still register simple dependencies outside of the context (or in another context). +Complex dependencies are registered through member methods of the context. These member methods serve as factory methods which will be called when a dependency is resolved. +They are annotated with the *@Component* UDA to let the container know that these methods should be registered as dependencies. The type of the registration is the same as the return type of the method. +Factory methods are useful when you have to deal with dependencies which require constructor arguments or elaborate set-up after instantiation. +Application contexts have to be registered with a dependency container. They are registered as follows: +```d +container.registerContext!Context; +``` +All registered dependencies can now be resolved by the same dependency container. Registering a context will also register it as a dependency, meaning you can autowire the application context in other classes. +You can register as many types of application contexts as you like. + +###Autowiring application contexts +Application contexts can make use of autowired dependencies like any other dependency. When registering an application context, all its components are registered first after which the application context is autowired. +This means that after the registration of an application context some dependencies will already be resolved and instantiated. The following example illustrates how autowired members can be used in a context: +```d +class Context : ApplicationContext { + + @Autowire + public SomeClass someClass; + + @Autowire + public SomeOtherClass someOtherClass; + + public override void registerDependencies(shared(DependencyContainer) container) { + container.register!SomeClass; + } + + @Component + public SomeLibraryClass libraryClass() { + return new SomeLibraryClass(someClass, someOtherClass); + } +} +``` +As you can see, autowired dependencies can be used within factory methods. When *SomeLibraryClass* is resolved, it will be created with a resolved instance of *SomeClass* and *SomeOtherClass*. As shown, autowired dependencies can be registered within the same +application context, but don't neccesarily have to be. You can even autowire dependencies which are created within a factory method within the same application context. +Application contexts are directly autowired after they have been registered. This means that all autowired dependencies which are not registered in the application context itself need to be registered before registering the application context. + +###Controlling component registration +You can further influence how components are registered and created with additional UDAs: +```d +class Context : ApplicationContext { + @Component + @Prototype // Will create a new instance every time the dependency is resolved. + @RegisterByType!SomeInterface // Registers the dependency by the specified super type instead of the return type + public SomeClass someClass() { + return new SomeClass(); + } +} +``` + +Persistent Registration Options +------------------------------- +If you want registration options to be persistent (applicable for every call to register()), you can use the container method setPersistentRegistrationOptions(): +```d +dependencies.setPersistentRegistrationOptions(RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION); // Sets the option +dependencies.unsetPersistentRegistrationOptions(); // Clears the persistentent options ``` \ No newline at end of file diff --git a/source/poodinis/container.d b/source/poodinis/container.d index 1c077aa..0cd2c5e 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -61,6 +61,18 @@ public enum RegistrationOption { DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION } +/** + * Options which influence the process of resolving dependencies + */ +public enum ResolveOption { + /** + * Registers the type you're trying to resolve before returning it. + * This essentially makes registration optional for resolving by concerete types. + * Resolinvg will still fail when trying to resolve a dependency by supertype. + */ + registerBeforeResolving +} + /** * The dependency container maintains all dependencies registered with it. * @@ -77,6 +89,7 @@ synchronized class DependencyContainer { private Registration[] autowireStack; private RegistrationOption[] persistentRegistrationOptions; + private ResolveOption[] persistentResolveOptions; /** * Register a dependency by concrete class type. @@ -155,9 +168,11 @@ synchronized class DependencyContainer { private bool hasOption(OptionType)(OptionType[] options, shared(OptionType[]) persistentOptions, OptionType option) { foreach (presentOption; persistentOptions) { - // DEPRECATED LEGACY COMPATIBILITY - REMOVE REMOVE REMOVE REMOVE REMOVE REMOVE (SOON) - if (presentOption == RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION) { - presentOption = RegistrationOption.doNotAddConcreteTypeRegistration; + static if (is(OptionType == RegistrationOption)) { + // DEPRECATED LEGACY COMPATIBILITY - REMOVE REMOVE REMOVE REMOVE REMOVE REMOVE (SOON) + if (presentOption == RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION) { + presentOption = RegistrationOption.doNotAddConcreteTypeRegistration; + } } if (presentOption == option) { @@ -166,9 +181,11 @@ synchronized class DependencyContainer { } foreach(presentOption ; options) { - // DEPRECATED LEGACY COMPATIBILITY - REMOVE REMOVE REMOVE REMOVE REMOVE REMOVE (SOON) - if (presentOption == RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION) { - presentOption = RegistrationOption.doNotAddConcreteTypeRegistration; + static if (is(OptionType == RegistrationOption)) { + // DEPRECATED LEGACY COMPATIBILITY - REMOVE REMOVE REMOVE REMOVE REMOVE REMOVE (SOON) + if (presentOption == RegistrationOption.DO_NOT_ADD_CONCRETE_TYPE_REGISTRATION) { + presentOption = RegistrationOption.doNotAddConcreteTypeRegistration; + } } if (presentOption == option) { @@ -237,8 +254,8 @@ synchronized class DependencyContainer { * --- * You need to use the resolve method which allows you to specify a qualifier. */ - public RegistrationType resolve(RegistrationType)() { - return resolve!(RegistrationType, RegistrationType)(); + public RegistrationType resolve(RegistrationType)(ResolveOption[] resolveOptions = []) { + return resolve!(RegistrationType, RegistrationType)(resolveOptions); } /** @@ -267,7 +284,7 @@ synchronized class DependencyContainer { * container.resolve!(Animal, Dog); * --- */ - public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)() { + public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)(ResolveOption[] resolveOptions = []) { TypeInfo resolveType = typeid(RegistrationType); TypeInfo qualifierType = typeid(QualifierType); @@ -275,6 +292,12 @@ synchronized class DependencyContainer { writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString()); } + static if (__traits(compiles, new QualifierType())) { + if (hasOption(resolveOptions, persistentResolveOptions, ResolveOption.registerBeforeResolving)) { + register!(RegistrationType, QualifierType)(); + } + } + auto candidates = resolveType in registrations; if (!candidates) { throw new ResolveException("Type not registered.", resolveType); diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index 9f4cbbc..d45aa05 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -627,4 +627,11 @@ version(unittest) { container.register!(TestInterface, TestClass); container.resolve!TestClass; } + + // Test registration when resolving + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.resolve!(TestInterface, TestClass)([ResolveOption.registerBeforeResolving]); + container.resolve!TestClass; + } }