1 /** 2 * Contains the implementation of application context setup. 3 * 4 * Part of the Poodinis Dependency Injection framework. 5 * 6 * Authors: 7 * Mike Bierlee, m.bierlee@lostmoment.com 8 * Copyright: 2014-2025 Mike Bierlee 9 * License: 10 * This software is licensed under the terms of the MIT license. 11 * The full terms of the license can be found in the LICENSE file. 12 */ 13 14 module poodinis.context; 15 16 import poodinis.container : DependencyContainer; 17 import poodinis.registration : Registration, existingInstance; 18 import poodinis.factory : CreatesSingleton, InstanceFactoryParameters; 19 import poodinis.autowire : autowire; 20 21 import std.traits : hasUDA, ReturnType; 22 23 class ApplicationContext { 24 void registerDependencies(shared(DependencyContainer) container) { 25 } 26 } 27 28 /** 29 * A component annotation is used for specifying which factory methods produce components in 30 * an application context. 31 */ 32 struct Component { 33 } 34 35 /** 36 * This annotation allows you to specify by which super type the component should be registered. This 37 * enables you to use type-qualified alternatives for dependencies. 38 */ 39 struct RegisterByType(Type) { 40 Type type; 41 } 42 43 /** 44 * Components with the prototype registration will be scoped as dependencies which will create 45 * new instances every time they are resolved. The factory method will be called repeatedly. 46 */ 47 struct Prototype { 48 } 49 50 /** 51 * Register dependencies through an application context. 52 * 53 * An application context allows you to fine-tune dependency set-up and instantiation. 54 * It is mostly used for dependencies which come from an external library or when you don't 55 * want to use annotations to set-up dependencies in your classes. 56 */ 57 void registerContext(Context : ApplicationContext)(shared(DependencyContainer) container) { 58 auto context = new Context(); 59 context.registerDependencies(container); 60 context.registerContextComponents(container); 61 container.register!(ApplicationContext, Context)().existingInstance(context); 62 autowire(container, context); 63 } 64 65 void registerContextComponents(ApplicationContextType : ApplicationContext)( 66 ApplicationContextType context, shared(DependencyContainer) container) { 67 foreach (memberName; __traits(allMembers, ApplicationContextType)) { 68 foreach (overload; __traits(getOverloads, ApplicationContextType, memberName)) { 69 static if (__traits(getProtection, overload) == "public" && hasUDA!(overload, Component)) { 70 auto factoryMethod = &__traits(getMember, context, memberName); 71 Registration registration = null; 72 auto createsSingleton = CreatesSingleton.yes; 73 74 foreach (attribute; __traits(getAttributes, overload)) { 75 static if (is(attribute == RegisterByType!T, T)) { 76 registration = container.register!(typeof(attribute.type), 77 ReturnType!factoryMethod); 78 } else static if (__traits(isSame, attribute, Prototype)) { 79 createsSingleton = CreatesSingleton.no; 80 } 81 } 82 83 if (registration is null) { 84 registration = container.register!(ReturnType!factoryMethod); 85 } 86 87 registration.instanceFactory.factoryParameters = InstanceFactoryParameters( 88 registration.instanceType, 89 createsSingleton, 90 null, 91 factoryMethod 92 ); 93 } 94 } 95 } 96 } 97 98 version (unittest) : // 99 100 import poodinis; 101 import poodinis.testclasses; 102 import std.exception; 103 104 //Test register component registrations from context 105 unittest { 106 auto container = new shared DependencyContainer(); 107 auto context = new TestContext(); 108 context.registerContextComponents(container); 109 auto bananaInstance = container.resolve!Banana; 110 111 assert(bananaInstance.color == "Yellow"); 112 } 113 114 //Test non-annotated methods are not registered 115 unittest { 116 auto container = new shared DependencyContainer(); 117 auto context = new TestContext(); 118 context.registerContextComponents(container); 119 assertThrown!ResolveException(container.resolve!Apple); 120 } 121 122 //Test register component by base type 123 unittest { 124 auto container = new shared DependencyContainer(); 125 auto context = new TestContext(); 126 context.registerContextComponents(container); 127 auto instance = container.resolve!Fruit; 128 assert(instance.getShape() == "Pear shaped"); 129 } 130 131 //Test register components with multiple candidates 132 unittest { 133 auto container = new shared DependencyContainer(); 134 auto context = new TestContext(); 135 context.registerContextComponents(container); 136 137 auto rabbit = container.resolve!(Animal, Rabbit); 138 assert(rabbit.getYell() == "Squeeeeeel"); 139 140 auto wolf = container.resolve!(Animal, Wolf); 141 assert(wolf.getYell() == "Wooooooooooo"); 142 } 143 144 //Test register component as prototype 145 unittest { 146 auto container = new shared DependencyContainer(); 147 auto context = new TestContext(); 148 context.registerContextComponents(container); 149 150 auto firstInstance = container.resolve!PieChart; 151 auto secondInstance = container.resolve!PieChart; 152 153 assert(firstInstance !is null && secondInstance !is null); 154 assert(firstInstance !is secondInstance); 155 } 156 157 // Test setting up simple dependencies through application context 158 unittest { 159 auto container = new shared DependencyContainer(); 160 container.registerContext!SimpleContext; 161 auto instance = container.resolve!CakeChart; 162 163 assert(instance !is null); 164 } 165 166 // Test resolving dependency from registered application context 167 unittest { 168 auto container = new shared DependencyContainer(); 169 container.registerContext!SimpleContext; 170 auto instance = container.resolve!Apple; 171 172 assert(instance !is null); 173 } 174 175 // Test autowiring application context 176 unittest { 177 auto container = new shared DependencyContainer(); 178 container.register!Apple; 179 container.registerContext!AutowiredTestContext; 180 auto instance = container.resolve!ClassWrapper; 181 182 assert(instance !is null); 183 assert(instance.someClass !is null); 184 } 185 186 // Test autowiring application context with dependencies registered in same context 187 unittest { 188 auto container = new shared DependencyContainer(); 189 container.registerContext!ComplexAutowiredTestContext; 190 auto instance = container.resolve!ClassWrapperWrapper; 191 auto wrapper = container.resolve!ClassWrapper; 192 auto someClass = container.resolve!Apple; 193 194 assert(instance !is null); 195 assert(instance.wrapper is wrapper); 196 assert(instance.wrapper.someClass is someClass); 197 } 198 199 // Test resolving registered context 200 unittest { 201 auto container = new shared DependencyContainer(); 202 container.registerContext!TestContext; 203 container.resolve!ApplicationContext; 204 }