DependencyInjection.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include "stormancer/BuildConfig.h"
4 #include "stormancer/Utilities/TypeReflection.h"
5 #include "stormancer/StormancerTypes.h"
6 #include "stormancer/Exceptions.h"
7 #include <memory>
8 #include <functional>
9 #include <unordered_map>
10 #include <vector>
11 #include <algorithm>
12 #include <stdexcept>
13 
17 
18 namespace Stormancer
19 {
20  class DependencyScope;
21  class ContainerBuilder;
22  class DependencyScopeImpl;
23 
24  enum class DependencyLifetime
25  {
26  InstancePerRequest,
27  InstancePerScope,
28  InstancePerMatchingScope,
29  SingleInstance
30  };
31 
32  using RegistrationId = uint64;
33  using ContractConverter = std::shared_ptr<void>(*)(std::shared_ptr<void>);
34 
36  {
37  // A Contract is made every time as<>() is called.
38  struct Contract
39  {
40  // Type of the contract (the TAs in as<TAs>())
41  uint64 typeId;
42  // Registration name (empty by default)
43  std::string name;
44  // Cast from the original type to the contract (the TAs in as<TAs>()) type.
45  ContractConverter converter;
46  };
47 
48  std::function<std::shared_ptr<void>(const DependencyScope&)> factory;
49  RegistrationId id;
50  std::vector<Contract> contracts;
51  DependencyLifetime lifetime = DependencyLifetime::InstancePerRequest;
52  std::string scopeTagToMatch;
53  uint64 actualType;
54  };
55 
68  template<typename T>
70  {
71  public:
83  template<typename TAs>
85  {
86  return namedInternal<TAs>("");
87  }
88 
97  {
98  return as<T>();
99  }
100 
122  template<typename TAs>
123  RegistrationHandle& named(std::string name)
124  {
125  if (name.empty())
126  {
127  throw std::invalid_argument("name cannot be empty");
128  }
129  return namedInternal<TAs>(std::move(name));
130  }
131 
139  RegistrationHandle& instancePerRequest() { _data.lifetime = DependencyLifetime::InstancePerRequest; return *this; }
140 
149  RegistrationHandle& instancePerScope() { _data.lifetime = DependencyLifetime::InstancePerScope; return *this; }
150 
160  RegistrationHandle& singleInstance() { _data.lifetime = DependencyLifetime::SingleInstance; return *this; }
161 
174  {
175  if (scopeTag.empty())
176  {
177  throw std::invalid_argument("scopeTag must not be empty");
178  }
179  _data.lifetime = DependencyLifetime::InstancePerMatchingScope;
180  _data.scopeTagToMatch = scopeTag;
181  return *this;
182  }
183 
184  private:
185 
186  template<typename TAs>
187  RegistrationHandle& namedInternal(std::string name)
188  {
189  static_assert(std::is_convertible<T*, TAs*>::value, "You tried to register a dependency as a type it cannot be converted to.");
190 
191  auto it = std::find_if(_data.contracts.begin(), _data.contracts.end(),
192  [&name](const RegistrationData::Contract& contract) { return contract.name == name && contract.typeId == getTypeHash<TAs>(); });
193 
194  if (it == _data.contracts.end())
195  {
197  contract.name = std::move(name);
198  contract.typeId = getTypeHash<TAs>();
199  // Need to convert directly from original type to contract type to avoid "object slicing" with multiple inheritance
200  contract.converter = [](std::shared_ptr<void> original) { return std::static_pointer_cast<void>(std::static_pointer_cast<TAs>(std::static_pointer_cast<T>(original))); };
201  _data.contracts.push_back(std::move(contract));
202  }
203 
204  return *this;
205  }
206 
207  RegistrationHandle(RegistrationData& data)
208  : _data(data)
209  {}
210 
211  friend class ContainerBuilder;
212 
213  RegistrationData& _data;
214  };
215 
216 
236  {
237  public:
238 
245  DependencyScope() = default;
246 
247  // Deleted to prevent the possibility of creating circular dependencies.
248  // If you want to copy the scope, what you actually want is most likely to create a child scope instead.
249  DependencyScope(const DependencyScope&) = delete;
250  DependencyScope& operator=(const DependencyScope&) = delete;
251 
253  DependencyScope& operator=(DependencyScope&&);
254 
267  template<typename T>
268  std::shared_ptr<T> resolve() const
269  {
270  uint64 typeHash = getTypeHash<T>();
271  auto instance = resolveInternal(typeHash, "");
272  return std::static_pointer_cast<T>(instance);
273  }
274 
286  template<typename T>
287  std::vector<std::shared_ptr<T>> resolveAll() const
288  {
289  uint64 typeHash = getTypeHash<T>();
290  auto instances = resolveAllInternal(typeHash);
291 
292  std::vector<std::shared_ptr<T>> results;
293  results.reserve(instances.size());
294  std::transform(instances.begin(), instances.end(), std::back_inserter(results), [](const std::shared_ptr<void>& ptr) { return std::static_pointer_cast<T>(ptr); });
295  return results;
296  }
297 
311  template<typename T>
312  std::shared_ptr<T> resolveNamed(const std::string& name) const
313  {
314  if (name.empty())
315  {
316  throw std::invalid_argument("name cannot be empty");
317  }
318 
319  uint64 typeHash = getTypeHash<T>();
320  auto instance = resolveInternal(typeHash, name);
321  return std::static_pointer_cast<T>(instance);
322  }
323 
329  {
330  return beginLifetimeScope("", std::function<void(ContainerBuilder&)>{});
331  }
332 
338  DependencyScope beginLifetimeScope(std::function<void(ContainerBuilder&)> builder) const
339  {
340  return beginLifetimeScope("", builder);
341  }
342 
348  DependencyScope beginLifetimeScope(std::string tag) const
349  {
350  return beginLifetimeScope(tag, std::function<void(ContainerBuilder&)>{});
351  }
352 
359  DependencyScope beginLifetimeScope(std::string tag, std::function<void(ContainerBuilder&)> builder) const;
360 
368  bool isValid() const;
369 
370  private:
371 
372  friend class DependencyScopeImpl;
373  friend class ContainerBuilder;
374 
375  DependencyScope(const ContainerBuilder& builder, std::string tag, std::shared_ptr<DependencyScopeImpl> parent);
376 
377  void throwIfNotValid() const;
378 
379  std::shared_ptr<void> resolveInternal(uint64 typeHash, const std::string& name) const;
380  std::vector<std::shared_ptr<void>> resolveAllInternal(uint64 typeHash) const;
381 
382  std::shared_ptr<DependencyScopeImpl> _impl;
383  };
384 
385 
402  {
403  public:
404 
406 
425  template<typename T>
426  RegistrationHandle<T> registerDependency(std::function<std::shared_ptr<T>(const DependencyScope&)> factory)
427  {
428  _registrations.emplace_back();
429  RegistrationData& data = _registrations.back();
430  data.factory = factory;
431  data.id = _registrationCounter;
432  data.actualType = getTypeHash<T>();
433  ++_registrationCounter;
434  return RegistrationHandle<T>(data);
435  }
436 
449  template<typename T>
450  RegistrationHandle<T> registerDependency(std::shared_ptr<T> instance)
451  {
452  return registerDependency<T>([instance](const DependencyScope&) { return instance; }).singleInstance();
453  }
454 
471  template<typename T, typename... TCtorArgs>
473 
478  template<typename T> struct All {};
479 
490 
491  private:
492 
493  friend class DependencyScopeImpl;
494 
495  ContainerBuilder(RegistrationId baseId) : _registrationCounter(baseId) {}
496 
497  template<typename T>
498  struct CtorResolver
499  {
500  static std::shared_ptr<T> resolve(const DependencyScope& scope)
501  {
502  return scope.resolve<T>();
503  }
504  };
505 
506  template<typename T>
507  struct CtorResolver<All<T>>
508  {
509  static std::vector<std::shared_ptr<T>> resolve(const DependencyScope& scope)
510  {
511  return scope.resolveAll<T>();
512  }
513  };
514 
515  RegistrationId _registrationCounter;
516  std::vector<RegistrationData> _registrations;
517  };
518 
519  //-------------------------------------------------------------------------------
520  //-------------------------------------------------------------------------------
521  // Definition is out-of-class to avoid incomplete type issue with DependencyScope
522  template<typename T, typename... TCtorArgs>
524  {
525  return registerDependency<T>([](const DependencyScope& scope)
526  {
527  (void)scope; // Suppress unused parameter warning when TCtorArgs is empty
528  return std::make_shared<T>(CtorResolver<TCtorArgs>::resolve(scope)...);
529  });
530  }
531 }
RegistrationHandle< T > registerDependency(std::function< std::shared_ptr< T >(const DependencyScope &)> factory)
Register a dependency into the container.
Definition: DependencyInjection.h:426
The RegistrationHandle allows configuring a dependency added via ContainerBuilder::registerDependency...
Definition: DependencyInjection.h:69
DependencyScope build()
Build a DependencyScope from this container.
Definition: DependencyInjection.h:35
DependencyScope()=default
Create an empty DependencyScope.
Definition: DependencyInjection.h:38
std::shared_ptr< T > resolve() const
Retrieve the dependency that was registered for the type T.
Definition: DependencyInjection.h:268
DependencyScope beginLifetimeScope() const
Create a child dependency scope.
Definition: DependencyInjection.h:328
RegistrationHandle & asSelf()
Register the dependency as its own concrete type.
Definition: DependencyInjection.h:96
std::vector< std::shared_ptr< T > > resolveAll() const
Retrieve all the dependencies that were registered for the type T.
Definition: DependencyInjection.h:287
A "type tag" struct to be used as a type argument to registerDependency() when denoting a dependency ...
Definition: DependencyInjection.h:478
RegistrationHandle & instancePerRequest()
Set this dependency to have transient instances.
Definition: DependencyInjection.h:139
An object from which dependencies can be retrieved.
Definition: DependencyInjection.h:235
RegistrationHandle & named(std::string name)
Register this dependency as a given type, with a given name.
Definition: DependencyInjection.h:123
DependencyScope beginLifetimeScope(std::function< void(ContainerBuilder &)> builder) const
Create a child dependency scope.
Definition: DependencyInjection.h:338
bool isValid() const
Check if this scope has been fully constructed.
RegistrationHandle< T > registerDependency()
Register a dependency of type T with an automatically generated factory.
Definition: DependencyInjection.h:523
RegistrationHandle< T > registerDependency(std::shared_ptr< T > instance)
Register an existing instance of type T as a dependency.
Definition: DependencyInjection.h:450
RegistrationHandle & instancePerMatchingScope(std::string scopeTag)
Set this dependency to use an instance per matching dependency scope.
Definition: DependencyInjection.h:173
RegistrationHandle & as()
Register this dependency as a given type.
Definition: DependencyInjection.h:84
RegistrationHandle & instancePerScope()
Set this dependency to have an instance per dependency scope.
Definition: DependencyInjection.h:149
The ContainerBuilder is the primary element of the dependency injection mechanism....
Definition: DependencyInjection.h:401
std::shared_ptr< T > resolveNamed(const std::string &name) const
Retrieve the dependency named name that was registered for the type T.
Definition: DependencyInjection.h:312
RegistrationHandle & singleInstance()
Set this dependency to have only one instance.
Definition: DependencyInjection.h:160
DependencyScope beginLifetimeScope(std::string tag) const
Create a child dependency scope.
Definition: DependencyInjection.h:348