1 /+ 2 == oceandrift/di == 3 Copyright Elias Batek 2024. 4 Distributed under the Boost Software License, Version 1.0. 5 +/ 6 /++ 7 Lightweight Dependency Injection (DI) framework 8 9 $(LIST 10 * Inversion of Control (IoC). 11 * Convention over configuration. 12 * Injects dependencies via constructor parameters. 13 * Supports structs as well (… as classes and interfaces). 14 * No clutter – this library is a single, readily comprehensible file. 15 * No external dependencies. $(I Only the D standard library is used.) 16 ) 17 18 19 ### About Dependency Injection 20 21 $(SIDEBAR 22 Dependency Injection is commonly abbreviated as $(B DI). 23 ) 24 25 $(BLOCKQUOTE 26 How does it work? 27 ) 28 29 Dependency instances only need to be created once and can be used for all other dependent objects. 30 As there is only one object of each type, these instances can be called “singletons”. 31 32 They can be stored in one big repository, the [DIContainer|dependency container], 33 and retrieved later as needed. 34 35 #### Declaration of dependencies 36 37 One of the most useful ways to specify the dependencies of a type (as in `class` or `struct`) 38 is to declare a constructor with them as parameters. 39 40 --- 41 // Example: `LoginService` depends on `PasswordHashUtil`, `DatabaseClient` and `Logger`. 42 class LoginService { 43 public this( 44 PasswordHashUtil passwordHashUtil, 45 DatabaseClient databaseClient, 46 Logger logger, 47 ) { 48 // … 49 } 50 } 51 --- 52 53 #### Retrieval of dependencies 54 55 Getting the dependencies from the container into the service object is where the DI framework comes in. 56 While the user could manually retrieve them from the container and pass them to the constructor 57 (i.e. `new LoginService( container.get!PasswordHashUtil(), container.get!Logger(), container.get!DatabaseClient() )`, 58 this would get tedious quickly. 59 The framework comes to the resuce. 60 61 --- 62 auto di = new DI(); 63 // … 64 65 LoginService service = di.resolve!LoginService(); 66 --- 67 68 #### Registration of dependencies 69 70 But where did the `PasswordHashUtil`, the `Logger` and the `DatabaseClient` come from? 71 How did they get into the DI container? 72 73 ##### Standalone dependencies 74 75 Let’s assume the `PasswordHashUtil` has zero dependencies itself – it is a self-contained service class. 76 Its constructor has either no parameters 77 or isn’t declared explicitly (and it uses the implicit default constructor). 78 79 The DI framework can easily construct a singleton instance (of the dependency type) on the fly. 80 This happens automatically the first time one is needed. 81 82 --- 83 class PasswordHashUtil { 84 public string hash(string password) { /* … */ } 85 public bool verify(string hash, string userPassword) { /* … */ } 86 } 87 --- 88 89 ###### Custom dependency registrations 90 91 In case a user-provided `PasswordHashUtil` instance has been registered in advance, 92 the framework will skip the construction and use that one instead. 93 94 ##### Transitive dependencies 95 96 Aka $(I dependencies of a dependency). 97 98 The 2nd dependency of the aforementioned `LoginService` is `Logger`. 99 For illustration purposes, we’ll assume the `Logger` class depends on `Formatter`, 100 a type that implements formatting facilities. 101 `Logger` is dependency-less class (like `PasswordHashUtil` was). 102 103 The DI framework will retrieve a `Formatter` before it can construct the `Logger`. 104 Since `Formatter` has not dependencies, the framework can construct it as mentioned in the previous chapter. 105 If it there is one in the dependency container already, it will be used instead 106 (and the construction will be skipped). 107 108 The next step is to construct a `Logger` which receives the `Formatter` through its constructor. 109 110 --- 111 class Logger { 112 public this(Formatter formatter) { 113 // … 114 } 115 public void log(string message) { /* … */ } 116 } 117 --- 118 119 ###### Custom dependency registrations 120 121 In case a user-provided `Logger` instance has been registered in advance, 122 the framework will skip all construction steps and use that one instead. 123 124 $(NOTE 125 If the user-provided `Logger` uses a $(B different) `Formatter` than the one in the dependency container, 126 the `Logger` of the resulting `LoginService` will use said different `Formatter`, 127 not the one from the container. 128 129 This has no impact on a direct dependencies of `Logger`. 130 If `Logger` itself depended on `Formatter`, it would still receive the one from the dependency container. 131 Which in turn would lead to `LoginService.formatter` being different to `Logger.formatter`. 132 133 Logically, this distinction would be no longer relevant, if the user registered their `Formatter` instance 134 with the framework in addition to their custom `Logger` one. 135 ) 136 137 ##### Complex dependencies 138 139 Unlike the dependencies shown in the previous two chapters, however, 140 the `DatabaseClient` doesn’t depend on a bunch of other services. 141 Instead it is constructed from four strings (socket, username, password, database name). 142 143 While the injection of a `DatabaseClient` into dependent services principally works like before, 144 the framework cannot instantiate a new one by itself. 145 Hence it is that an instance has to be registered with the framework in advance. 146 A user-created `DatabaseClient` can provided by passing it to [oceandrift.di.DI.register|register()]. 147 148 The framework will pick up on it later, when it constructs `LoginService` 149 or, failing that, if no custom instance has been registered beforehand, 150 crash the program as soon as it falls back to instantiating one on its own. 151 152 --- 153 class DatabaseClient { 154 public this( 155 string socket, 156 string username, 157 string password, 158 string databaseName, 159 ) { 160 // … 161 } 162 public Statement prepare(string sql) { /* … */ } 163 public void disconnect() { /* … */ } 164 } 165 166 // Construct a DatabaseClient and register it with the DI framework. 167 auto databaseClient = new DatabaseClient(cfg.socket, cfg,username, cfg.password, cfg.dbName); 168 di.register(databaseClient); 169 --- 170 171 172 ### Unconventional use 173 174 This chapter deals with solutions to overcome the conventions of the framework. 175 (Malicious gossip has it that these are “design limitations”. Don’t listen to them…) 176 177 #### Injecting non-singleton instances 178 179 Non-singleton objects are sometimes also called “transient” or “prototype”. 180 181 By default, the framework will instantiate a singleton instance for each dependency type 182 and use it for all dependent objects. 183 184 When this behavior is not desired, it’s recommended to instantiate a new object in the constructor. 185 Check out [oceandrift.di.DI.makeNew|makeNew!(T)]. 186 187 $(TIP 188 The DI framework can inject a reference to itself. 189 190 --- 191 public this( 192 DI di, 193 ) { 194 this.dependency = di.makeNew!Dependency(); 195 } 196 --- 197 ) 198 199 Alternatively, one could also create a factory type and use that as a dependency instead. 200 201 #### Injecting dependencies that are primitive/unsupported types 202 203 The recommended way to inject primitives types (like integers, booleans, floating-point numbers, or enums) 204 or other unsupported types (e.g. strings, other arrays, associative arrays, class pointers or pointers in general) 205 is to wrap them in a struct. 206 207 For pointers, deferencing them might also be an option. 208 209 210 211 212 Examples: 213 214 ### Bootstrapping 215 216 Bootstrapping the framework is as simple as: 217 218 --- 219 auto di = new DI(); 220 --- 221 +/ 222 module oceandrift.di; 223 224 /++ 225 ### Extended version of the front-page example 226 +/ 227 @safe unittest { 228 static // exclude from docs 229 class Dependency { 230 } 231 232 static // exclude from docs 233 class Foo { 234 private Dependency d; 235 236 public this(Dependency d) { 237 this.d = d; 238 } 239 } 240 241 // Bootstrap the DI framework. 242 auto di = new DI(); 243 244 // Then let it resolve the whole dependency tree 245 // and construct dependencies as needed. 246 Foo foo = di.resolve!Foo(); 247 248 // The DI framework has constructed a new instance of `Dependency` 249 // and supplied it to the constructor of `Foo`. 250 assert(foo.d !is null); 251 } 252 253 /++ 254 ### User-supplied dependency instances 255 256 $(BLOCKQUOTE 257 How about types the framework cannot construct on its own? 258 259 … or instaces that have been constructed in before and could be reused? 260 ) 261 +/ 262 @safe unittest { 263 static // exclude from docs 264 class Dependency { 265 private int number; 266 267 public this(int number) { 268 this.number = number; 269 } 270 } 271 272 static // exclude from docs 273 class Foo { 274 private Dependency d; 275 276 public this(Dependency d) { 277 this.d = d; 278 } 279 } 280 281 auto di = new DI(); // exclude from docs 282 283 // Construct an instance of the dependency manually. 284 // Then register it with the framework. 285 auto dep = new Dependency(128); 286 di.register(dep); 287 288 // alternative syntax variants (They all come down to the same thing.): 289 di.register!Dependency(dep); 290 di.register!Dependency = dep; 291 292 // Resolve dependencies of `Foo` and create instance. 293 Foo foo = di.resolve!Foo(); 294 295 // The DI framework constructed a new instance of `Dependency` 296 // and supplied it to the constructor of `Foo`. 297 assert(foo.d !is null); 298 } 299 300 /++ 301 ### Injecting dependencies that are interfaces 302 303 It’s really straightforward: 304 Tell the framework which implementation type to use for dependencies of an interface type. 305 +/ 306 @system unittest { 307 static // exclude from docs 308 interface Logger { 309 void log(string message); 310 } 311 312 static // exclude from docs 313 class StdLogger : Logger { 314 private int lines = 0; 315 316 void log(string message) { 317 ++lines; 318 // …writeln(message);… 319 } 320 } 321 322 static // exclude from docs 323 class Service { 324 private Logger logger; 325 326 this(Logger logger) { 327 this.logger = logger; 328 } 329 330 void doSomething() { 331 this.logger.log("Did something."); 332 } 333 334 } 335 336 auto di = new DI(); // exclude from docs 337 338 // Register `StdLogger` as the implementation to construct for the 339 // `Logger` interface. 340 di.registerInterface!(Logger, StdLogger); 341 342 // Now the framework is set up to 343 // construct an instance of the `Service` class. 344 Service service1 = di.resolve!Service(); 345 346 // Our service instance is using the `StdLogger` implementation 347 // that has been registered a few lines above. 348 service1.doSomething(); 349 service1.doSomething(); 350 assert(di.resolve!StdLogger().lines == 2); 351 } 352 353 /++ 354 ### Injecting dependencies that are interfaces (Part II) 355 356 What if we had a type with a complex constructor, 357 one that the framework cannot instantiate on its own? 358 +/ 359 @system unittest { 360 static // exclude from docs 361 interface Logger { 362 void log(string message); 363 } 364 365 static // exclude from docs 366 class FileLogger : Logger { 367 private int lines = 0; 368 369 this(string logFilePath) { 370 // … 371 } 372 373 void log(string message) { 374 ++lines; 375 // … 376 } 377 } 378 379 static // exclude from docs 380 class Service { 381 private Logger logger; 382 383 this(Logger logger) { 384 this.logger = logger; 385 } 386 387 void doSomething() { 388 this.logger.log("Did something."); 389 } 390 391 } 392 393 auto di = new DI(); // exclude from docs 394 395 // Easy. Construct one and register it with the framework. 396 auto fileLogger = new FileLogger("/dev/null"); 397 di.registerInterface!Logger(fileLogger); 398 399 // Now the framework is set up to 400 // retrieve an instance of the `Service` class. 401 Service service2 = di.resolve!Service(); 402 403 // Just for the record: 404 // The file logger starts with a line count of `0`. 405 assert(fileLogger.lines == 0); 406 407 // Let’s use the service and see whether the supplied logger is used. 408 service2.doSomething(); 409 assert(fileLogger.lines == 1); // alright! 410 } 411 412 /++ 413 ### DI-constructor generator 414 415 > All that typing gets tedious quickly, doesn’t it? 416 417 The framework can generate all the constructor boilerplate. 418 It’s as easy as: 419 420 $(NUMBERED_LIST 421 * Annotate all dependency fields with [dependency|@dependency]. 422 * Add `mixin` [DIConstructor] to your type. 423 This generates a constructor with a parameter for each `@dependency` field 424 and a body that assigns the values to the corresponding fields. 425 ) 426 +/ 427 @safe unittest { 428 static // exclude from docs 429 class Dependency1 { 430 } 431 432 static // exclude from docs 433 class Dependency2 { 434 } 435 436 static // exclude from docs 437 class Foo { 438 // Mark dependencies with the attribute `@dependency`: 439 private @dependency { 440 Dependency1 d1; 441 Dependency2 d2; 442 } 443 444 // Let the framework generate the constructor: 445 mixin DIConstructor; 446 } 447 448 auto di = new DI(); 449 Foo foo = di.resolve!Foo(); 450 451 // It works: 452 assert(foo.d1 !is null); 453 assert(foo.d2 !is null); 454 } 455 456 import std.conv : to; 457 import std.traits : Parameters; 458 459 private enum bool isClass(T) = (is(T == class)); 460 private enum bool isInterface(T) = (is(T == interface)); 461 private enum bool isStruct(T) = (is(T == struct)); 462 private enum bool isStructPointer(T) = (is(typeof(*T) == struct)); 463 464 private enum bool hasConstructors(T) = __traits(hasMember, T, "__ctor"); 465 466 private template getConstructors(T) if (hasConstructors!T) { 467 alias getConstructors = __traits(getOverloads, T, "__ctor"); 468 } 469 470 private template hasParentClass(T) if (isClass!T) { 471 static if (is(T Parents == super) && Parents.length) 472 enum hasParentClass = true; 473 else 474 enum hasParentClass = false; 475 } 476 477 private template ParentClass(T) if (isClass!T) { 478 static if (is(T Parents == super) && Parents.length) 479 alias ParentClass = Parents[0]; 480 else 481 static assert(0, "No parent class for type `" ~ T.stringof ~ "`."); 482 } 483 484 unittest { 485 static class Parent { 486 } 487 488 static class Child : Parent { 489 } 490 491 static class GrandChild : Child { 492 } 493 494 static assert(hasParentClass!Parent); 495 static assert(hasParentClass!Child); 496 static assert(hasParentClass!GrandChild); 497 498 static assert(is(ParentClass!Parent == Object)); 499 static assert(is(ParentClass!Child == Parent)); 500 static assert(is(ParentClass!GrandChild == Child)); 501 } 502 503 private template MemberSymbols(alias T, args...) { 504 import std.meta; 505 506 alias MemberSymbols = AliasSeq!(); 507 static foreach (arg; args) { 508 MemberSymbols = AliasSeq!(MemberSymbols, __traits(getMember, T, arg)); 509 } 510 } 511 512 unittest { 513 static class Dings { 514 int x; 515 string y; 516 } 517 518 alias mSyms = MemberSymbols!(Dings, "x", "y"); 519 assert(mSyms.length == 2); 520 assert(is(typeof(mSyms[0]) == int)); 521 assert(is(typeof(mSyms[1]) == string)); 522 } 523 524 private template DerivedMemberSymbols(T, args...) { 525 alias DerivedMemberSymbols = MemberSymbols!(T, __traits(derivedMembers, T)); 526 } 527 528 unittest { 529 static class Dings { 530 int x; 531 string y; 532 } 533 534 alias mSyms = DerivedMemberSymbols!(Dings); 535 assert(mSyms.length == 2); 536 assert(is(typeof(mSyms[0]) == int)); 537 assert(is(typeof(mSyms[1]) == string)); 538 } 539 540 private template callerParameterListString(params...) { 541 private string impl() { 542 string r = ""; 543 foreach (idx, P; params) { 544 static if (isStruct!P) { 545 r ~= '*'; 546 } 547 r ~= "param" ~ idx.to!string() ~ ','; 548 } 549 return r; 550 } 551 552 enum callerParameterListString = impl(); 553 } 554 555 private { 556 template keyOf(T) if (isClass!T || isInterface!T || isStructPointer!T) { 557 private static immutable string keyOf = T.mangleof; 558 } 559 560 template keyOf(T) if (isStruct!T) { 561 private static immutable string keyOf = keyOf!(T*); 562 } 563 } 564 565 /++ 566 Determines whether a type `T` is supported to be used as dependency by the framework. 567 568 Currently this includes: 569 $(LIST 570 * Classes – `class` 571 * Interfaces – `interface` 572 * Structs – `struct` 573 * Struct pointers – `struct*` 574 ) 575 576 See_Also: 577 [isConstructableByDI] that determines whether a type can be constructed by the framework on its own. 578 +/ 579 public enum bool isSupportedDependencyType(T) = (isClass!T || isInterface!T || isStruct!T || isStructPointer!T); 580 581 /++ 582 Determines whether a type `T` is denied for user registration. 583 +/ 584 public enum bool isForbiddenType(T) = is(T == Container) || is(T == DI); 585 586 /++ 587 Determines whether a type `T` is applicable for user registration. 588 +/ 589 public enum bool isntForbiddenType(T) = !(isForbiddenType!T); 590 591 /++ 592 Determines $(I why) a type `T` is not constructable by the DI framework. 593 594 Returns: 595 $(LIST 596 * `string` = reason 597 * `null` = is constructable, in fact 598 ) 599 +/ 600 public template determineWhyNotConstructableByDI(T) { 601 602 /// ditto 603 public static immutable string determineWhyNotConstructableByDI = impl(); 604 605 private string impl() { 606 static if (isSupportedDependencyType!T == false) { 607 return "DI cannot construct an instance of type `" 608 ~ T.stringof 609 ~ "` that is not a supported dependency type in the first place."; 610 } else static if (isInterface!T) { 611 return "DI cannot construct an instance of type `" 612 ~ T.stringof 613 ~ "` that is an `interface`."; 614 } else static if (hasConstructors!T == false) { 615 return null; 616 } else { 617 alias ctors = getConstructors!T; 618 if (ctors.length > 1) { 619 return "DI cannot construct an instance of type `" 620 ~ T.stringof 621 ~ "` with multiple constructors."; 622 } 623 624 alias params = Parameters!(ctors[0]); 625 foreach (idx, P; params) { 626 if (isSupportedDependencyType!P == false) { 627 // Trick the detection of unreachable statements found in older compilers. 628 bool neverTrue = false; 629 if (neverTrue) { 630 break; 631 } 632 633 return "DI cannot construct an instance of type `" 634 ~ T.stringof 635 ~ "` because its dependency #" 636 ~ idx.to!string 637 ~ " `" 638 ~ P.stringof 639 ~ "` is not a supported type."; 640 } 641 } 642 643 return null; 644 } 645 } 646 } 647 648 /++ 649 Determines whether a type `T` is constructable by the DI framework. 650 651 See_Also: 652 [isSupportedDependencyType] that determines whether a type can be used as a dependency. 653 +/ 654 public enum bool isConstructableByDI(T) = (determineWhyNotConstructableByDI!T is null); 655 656 @safe unittest { 657 class Class { 658 } 659 660 class ClassWithIntegerParamCtor { 661 this(int) { 662 } 663 } 664 665 interface Interface { 666 } 667 668 struct Struct { 669 } 670 671 assert(isConstructableByDI!Class); 672 assert(isConstructableByDI!ClassWithIntegerParamCtor == false); 673 assert(isConstructableByDI!Interface == false); 674 assert(isConstructableByDI!Struct); 675 assert(isConstructableByDI!int == false); 676 assert(isConstructableByDI!string == false); 677 assert(isConstructableByDI!(int[]) == false); 678 } 679 680 /++ 681 Dependency Container 682 683 Used to store singleton instances of dependency types. 684 This is the underlying container implementation used by [DI]. 685 +/ 686 private final class Container { 687 @safe pure nothrow: 688 689 private { 690 alias voidptr = void*; 691 voidptr[string] _data; 692 } 693 694 /// 695 public this() { 696 this.setSelf(); 697 } 698 699 /++ 700 Returns a stored value by key 701 +/ 702 void* get(string key) @nogc { 703 void** ptrptr = (key in _data); 704 if (ptrptr is null) { 705 return null; 706 } 707 708 return *ptrptr; 709 } 710 711 private T getTImpl(T)() @nogc { 712 void* ptr = this.get(keyOf!T); 713 return (function(void* ptr) @trusted => cast(T) ptr)(ptr); 714 } 715 716 /++ 717 Returns a stored value by class, interface or struct-pointer type 718 +/ 719 T get(T)() @nogc if (isClass!T || isInterface!T || isStructPointer!T) { 720 return getTImpl!T(); 721 } 722 723 /++ 724 Returns a stored value by struct 725 +/ 726 T* get(T)() @nogc if (isStruct!T) { 727 return getTImpl!(T*)(); 728 } 729 730 /++ 731 Determines whether a value matching the provided key is stored 732 +/ 733 bool has(string key) @nogc { 734 return (this.get(key) !is null); 735 } 736 737 /// ditto 738 bool has(T)() @nogc { 739 return this.has(keyOf!T); 740 } 741 742 // CAUTION: This function cannot be exposed publicly for @safe-ty guarantees 743 private void set(string key, void* value) { 744 _data[key] = value; 745 } 746 747 private void setTImpl(T)(T value) { 748 pragma(inline, true); 749 void* ptr = (function(T value) @trusted => cast(void*) value)(value); 750 this.set(keyOf!T, ptr); 751 } 752 753 /++ 754 Stores the provided class instance 755 +/ 756 void set(T)(T value) if ((isClass!T || isInterface!T) && (isntForbiddenType!T)) { 757 this.setTImpl!T(value); 758 } 759 760 /++ 761 Stores the provided pointer to a struct instance 762 +/ 763 void set(T)(T* value) if (isStruct!T) { 764 this.setTImpl!(T*)(value); 765 } 766 767 private void setSelf() { 768 this.setTImpl!Container(this); 769 } 770 771 private void setDI(DI value) { 772 this.setTImpl!DI(value); 773 } 774 } 775 776 /// ditto 777 public alias DIContainer = Container; 778 779 /++ 780 Dependency Injection 781 782 This is the flagship class of the framework. 783 +/ 784 final class DI { 785 private { 786 Container _container; 787 } 788 789 /// 790 this(DIContainer container) @safe pure nothrow { 791 // main ctor 792 _container = container; 793 _container.setDI = this; 794 } 795 796 /// 797 this() @safe pure nothrow { 798 this(new Container()); 799 } 800 801 private auto resolveImpl(T)() if (isSupportedDependencyType!T) { 802 pragma(inline, true); 803 804 auto instance = _container.get!T(); 805 806 if (instance is null) { 807 instance = this.makeNew!T(); 808 this.register!T = instance; 809 } 810 811 return instance; 812 } 813 814 /++ 815 Returns the singleton instance of the requested type. 816 817 Automatically constructs a new one if needed. 818 +/ 819 T resolve(T)() if (isClass!T || isInterface!T || isStructPointer!T) { 820 return this.resolveImpl!T(); 821 } 822 823 /// ditto 824 T* resolve(T)() if (isStruct!T) { 825 return this.resolveImpl!(T*)(); 826 } 827 828 /++ 829 Stores the provided instance of type `T` in the DI container. 830 This registered instance will be injected for dependencies of type `T`. 831 832 $(TIP 833 This function can be used to supply instances of types the DI framework cannot construct on its own 834 for further use by the DI. 835 836 Nonetheless, types must be [isSupportedDependencyType|supported dependency types]. 837 ) 838 839 $(NOTE 840 Overrides a previously stored instance if applicable. 841 ) 842 843 See_Also: 844 [registerInterface] to setup which implementation class (resp. instance) 845 to inject for dependencies that specify an $(I interface) instead of a concrete type. 846 +/ 847 void register(T)(T value) @safe pure nothrow { 848 static assert( 849 isClass!T || isInterface!T || isStructPointer!T, 850 "Cannot store instance of type `" ~ T.stringof ~ "`. Not a class, interface or struct-pointer." 851 ); 852 static assert( 853 isntForbiddenType!T, 854 "Cannot override the referenced framework instance of type `" ~ T.stringof ~ "`." 855 ); 856 857 _container.set(value); 858 } 859 860 /++ 861 Stores the provided instance of type `TClass` in the DI container 862 to be injected for dependencies of both types `TInterface` and `TClass` later. 863 +/ 864 void registerInterface(TInterface, TClass)(TClass value) @safe pure nothrow { 865 this.register!TClass(value); 866 this.register!TInterface(value); 867 } 868 869 /// ditto 870 void registerInterface(TInterface, TClass)() { 871 this.registerInterface!(TInterface, TClass)( 872 this.resolve!TClass() 873 ); 874 } 875 876 private T* makeNew(T)() if (isStruct!T) { 877 return new T(); 878 } 879 880 private T makeNew(T)() if (isStructPointer!T) { 881 return new typeof(*T)(); 882 } 883 884 /++ 885 Instantiates a new instance of the specified type `T`. 886 887 Dependencies will be assigned from the underlying container. 888 +/ 889 T makeNew(T)() if (isClass!T || isInterface!T) { 890 static if (isConstructableByDI!T == false) { 891 // not constructable --> crash 892 assert(false, determineWhyNotConstructableByDI!T); 893 } else { 894 // construct 895 return this.makeNewImpl!T(); 896 } 897 } 898 899 private { 900 T makeNewImplWithDependencies(T)() { 901 pragma(inline, true); 902 903 alias ctors = getConstructors!T; 904 static assert(ctors.length == 1, "Seems like there's a bug in `isConstructableByDI`."); 905 alias ctorParams = Parameters!(ctors[0]); 906 907 static foreach (idx, P; ctorParams) { 908 static if (isStruct!P) { 909 version (diNoPassByValue) { 910 static assert( 911 false, 912 "Passing dependency #" 913 ~ idx.to!string() 914 ~ " `" 915 ~ P.stringof 916 ~ "` by value (copy) to `" 917 ~ T.stringof 918 ~ "`.\n Use a pointer (`" 919 ~ P.stringof 920 ~ "*`) instead. [`-version=diNoPassByValue`]" 921 ); 922 } 923 mixin(`P* param` ~ idx.to!string() ~ ';'); 924 } else { 925 mixin(`P param` ~ idx.to!string() ~ ';'); 926 } 927 928 mixin(`param` ~ idx.to!string()) = this.resolve!P(); 929 } 930 931 return mixin(`new T(` ~ callerParameterListString!(ctorParams) ~ `)`); 932 } 933 934 T makeNewImpl(T)() { 935 pragma(inline, true); 936 937 static if (hasConstructors!T) { 938 return this.makeNewImplWithDependencies!T(); 939 } else { 940 // There is no explicit ctor available, use default one. 941 return new T(); 942 } 943 } 944 } 945 } 946 947 /++ 948 UDA to mark fields as dependency for $(I Field Assignment Constructor Application). 949 +/ 950 enum dependency; 951 952 private template hasDependencyUDA(alias T) { 953 import std.traits : hasUDA; 954 955 enum hasDependencyUDA = hasUDA!(T, dependency); 956 } 957 958 // undocumented 959 enum _diConstructorUDA; 960 961 /++ 962 Generates a constructor with a parameter for each [dependency|@dependency] field 963 and assigns the passed value to the corresponding field. 964 +/ 965 mixin template DIConstructor() { 966 import oceandrift.di : dependency, diConstructorString, _diConstructorUDA; 967 968 mixin(diConstructorString!(typeof(this))); 969 } 970 971 /++ 972 Generates code for a constructor with a parameter for each [dependency|@dependency] field 973 and assigns the passed value to the corresponding field. 974 975 --- 976 class MyType { 977 mixin(diConstructorString!(typeof(this))); 978 } 979 --- 980 981 See_Also: [DIConstructor] 982 +/ 983 template diConstructorString(T) { 984 private string impl() { 985 import std.meta; 986 import std.traits; 987 988 alias deps = Filter!(hasDependencyUDA, DerivedMemberSymbols!T); 989 990 static if (isStruct!T && (deps.length == 0)) { 991 return ""; 992 } else { 993 string r = "public this("; 994 995 // determine parent dependencies 996 static if (isClass!T && hasParentClass!T) { 997 alias parentCtor = getSymbolsByUDA!(ParentClass!T, _diConstructorUDA); 998 static assert(parentCtor.length <= 1, "Misuse of @_diConstructorUDA detected."); 999 1000 static if (parentCtor.length == 1) { 1001 alias depsParent = ParameterIdentifierTuple!parentCtor; 1002 } else { 1003 enum depsParent = []; 1004 } 1005 } else { 1006 enum depsParent = []; 1007 } 1008 1009 // parent ctor params 1010 static foreach (d; depsParent) { 1011 r ~= "typeof(super." ~ d ~ ")" ~ d ~ ","; 1012 } 1013 1014 // params 1015 static foreach (d; deps) { 1016 r ~= "typeof(this." ~ __traits(identifier, d) ~ ")" ~ __traits(identifier, d) ~ ","; 1017 } 1018 1019 r ~= ")@_diConstructorUDA @safe pure nothrow @nogc{"; 1020 1021 // parent ctor 1022 static if (depsParent.length > 0) { 1023 r ~= "super("; 1024 static foreach (d; depsParent) { 1025 r ~= d ~ ','; 1026 } 1027 r ~= ");"; 1028 } 1029 1030 // assignments 1031 static foreach (d; deps) { 1032 r ~= "this." ~ __traits(identifier, d) ~ '=' ~ __traits(identifier, d) ~ ';'; 1033 } 1034 1035 r ~= '}'; 1036 1037 return r; 1038 } 1039 } 1040 1041 // undocumented 1042 enum string diConstructorString = impl(); 1043 } 1044 1045 @safe unittest { 1046 static class Point { 1047 mixin DIConstructor; 1048 @dependency: 1049 int x; 1050 int y; 1051 } 1052 1053 const p = new Point(12, 24); 1054 assert(p.x == 12); 1055 assert(p.y == 24); 1056 } 1057 1058 @safe unittest { 1059 static class Point { 1060 mixin DIConstructor; 1061 int x; 1062 int y; 1063 } 1064 1065 const p = new Point(); 1066 } 1067 1068 @safe unittest { 1069 static struct Point { 1070 mixin DIConstructor; 1071 private @dependency: 1072 int x; 1073 int y; 1074 } 1075 1076 const p = Point(12, 24); 1077 assert(p.x == 12); 1078 assert(p.y == 24); 1079 } 1080 1081 @safe unittest { 1082 static struct Point { 1083 mixin DIConstructor; 1084 int x; 1085 int y; 1086 } 1087 1088 const p = Point(); 1089 } 1090 1091 @safe unittest { 1092 static class Point { 1093 mixin DIConstructor; 1094 int x; 1095 int y; 1096 } 1097 1098 const p = new Point(); 1099 } 1100 1101 @safe unittest { 1102 static class Point { 1103 mixin DIConstructor; 1104 @dependency: 1105 const int x; 1106 const int y; 1107 } 1108 1109 const p = new Point(1, 2); 1110 assert(p.x == 1); 1111 assert(p.y == 2); 1112 } 1113 1114 @safe unittest { 1115 static class Dimension1 { 1116 mixin DIConstructor; 1117 @dependency: 1118 int x; 1119 } 1120 1121 static class Dimension2 : Dimension1 { 1122 mixin DIConstructor; 1123 @dependency: 1124 int y; 1125 } 1126 1127 static class Dimension3 : Dimension2 { 1128 mixin DIConstructor; 1129 @dependency: 1130 int z; 1131 } 1132 1133 const d1 = new Dimension1(8); 1134 assert(d1.x == 8); 1135 1136 const d2 = new Dimension2(12, 24); 1137 assert(d2.x == 12); 1138 assert(d2.y == 24); 1139 1140 const d3 = new Dimension3(1, 5, 9); 1141 assert(d3.x == 1); 1142 assert(d3.y == 5); 1143 assert(d3.z == 9); 1144 } 1145 1146 // == Container Tests 1147 1148 @safe unittest { 1149 static struct Foo { 1150 int i = 10; 1151 } 1152 1153 static class Bar { 1154 int i = 10; 1155 } 1156 1157 auto c = new Container(); 1158 assert(c.has!Foo() == false); 1159 assert(c.has!Bar() == false); 1160 assert(c.get!Foo() is null); 1161 assert(c.get!Bar() is null); 1162 1163 auto origFoo = new Foo(); 1164 origFoo.i = 2; 1165 auto origBar = new Bar(); 1166 origBar.i = 3; 1167 1168 c.set(origFoo); 1169 c.set(origBar); 1170 assert(c.has!Foo()); 1171 assert(c.has!Bar()); 1172 assert(c.get!Foo() !is null); 1173 assert(c.get!Bar() !is null); 1174 1175 auto cFoo = c.get!Foo(); 1176 auto cBar = c.get!Bar(); 1177 assert(cFoo.i == 2); 1178 assert(cBar.i == 3); 1179 assert(cFoo is origFoo); 1180 assert(cBar is origBar); 1181 1182 cFoo.i = 4; 1183 assert(origFoo.i == 4); 1184 1185 c.set!Foo(null); 1186 assert(c.has!Foo() == false); 1187 1188 c.set!Bar(null); 1189 assert(c.has!Bar() == false); 1190 } 1191 1192 // == DI Tests 1193 1194 @safe unittest { 1195 static class Bar { 1196 int i = 10; 1197 } 1198 1199 static class Foo { 1200 Bar bar; 1201 1202 this(Bar bar) { 1203 this.bar = bar; 1204 } 1205 } 1206 1207 auto di = new DI(); 1208 Foo foo = di.resolve!Foo(); 1209 assert(foo !is null); 1210 assert(foo.bar !is null); 1211 1212 // Test singleton behavior 1213 assert(foo.bar.i == 10); 1214 Bar bar = di.resolve!Bar(); 1215 bar.i = 2; 1216 assert(foo.bar.i == 2); 1217 } 1218 1219 @safe unittest { 1220 static struct Bar { 1221 int i = 10; 1222 } 1223 1224 static class Foo { 1225 Bar bar; 1226 1227 this(Bar bar) { 1228 this.bar = bar; 1229 } 1230 } 1231 1232 auto di = new DI(); 1233 Bar* bar = di.resolve!Bar(); 1234 bar.i = 2; 1235 1236 Foo foo = di.resolve!Foo(); 1237 bar.i = 3; 1238 assert(foo.bar.i == 2); 1239 } 1240 1241 @safe unittest { 1242 static class H1 { 1243 } 1244 1245 static class H2 { 1246 this(H1 d) { 1247 } 1248 } 1249 1250 static class H3 { 1251 this(H2 d) { 1252 } 1253 } 1254 1255 static class H4 { 1256 this(H3 d) { 1257 } 1258 } 1259 1260 static class H5 { 1261 this(H4 d, H1 d2) { 1262 } 1263 } 1264 1265 static class H6 { 1266 this(H5 d) { 1267 } 1268 } 1269 1270 auto di = new DI(); 1271 H6 h6 = di.resolve!H6(); 1272 assert(h6 !is null); 1273 }