|
|
|
@ -1,887 +0,0 @@
|
|
|
|
|
/*
|
|
|
|
|
* This file is part of the Protevus Platform.
|
|
|
|
|
*
|
|
|
|
|
* (C) Protevus <developers@protevus.com>
|
|
|
|
|
*
|
|
|
|
|
* For the full copyright and license information, please view the LICENSE
|
|
|
|
|
* file that was distributed with this source code.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import 'package:angel3_framework/angel3_framework.dart';
|
|
|
|
|
import 'package:angel3_container/angel3_container.dart';
|
|
|
|
|
import 'package:meta/meta.dart';
|
|
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
|
import 'package:reflectable/reflectable.dart';
|
|
|
|
|
import 'discoverable_service_provider.dart';
|
|
|
|
|
|
|
|
|
|
@discoverableServiceProvider
|
|
|
|
|
abstract class ServiceProvider {
|
|
|
|
|
/// Static variable to hold the logger instance for the ServiceProvider class.
|
|
|
|
|
/// This logger is lazily initialized and can be accessed via the `logger` getter.
|
|
|
|
|
/// It can also be set explicitly for testing purposes using the `setLogger` method.
|
|
|
|
|
static Logger? _logger;
|
|
|
|
|
|
|
|
|
|
/// Returns the logger for the ServiceProvider class.
|
|
|
|
|
///
|
|
|
|
|
/// If a logger has not been explicitly set using the `setLogger` method,
|
|
|
|
|
/// this getter creates and returns a new Logger instance with the name 'ServiceProvider'.
|
|
|
|
|
///
|
|
|
|
|
/// If a logger has been set, it returns that logger instance.
|
|
|
|
|
///
|
|
|
|
|
/// This approach allows for lazy initialization of the logger and provides
|
|
|
|
|
/// flexibility for testing by allowing the logger to be explicitly set when needed.
|
|
|
|
|
static Logger get logger => _logger ?? Logger('ServiceProvider');
|
|
|
|
|
|
|
|
|
|
/// Constructs a [ServiceProvider] instance.
|
|
|
|
|
///
|
|
|
|
|
/// The [priority] parameter determines the order in which service providers are registered.
|
|
|
|
|
/// Service providers with higher priority values are registered before those with lower priority values.
|
|
|
|
|
/// By default, the priority is set to 0.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// class MyServiceProvider extends ServiceProvider {
|
|
|
|
|
/// MyServiceProvider() : super(priority: 10);
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
const ServiceProvider({this.priority = 0});
|
|
|
|
|
|
|
|
|
|
/// The priority of this service provider. Higher priority providers are registered first.
|
|
|
|
|
///
|
|
|
|
|
/// This integer value determines the order in which service providers are registered
|
|
|
|
|
/// within the Angel. Service providers with higher priority values are processed
|
|
|
|
|
/// before those with lower priority values. This allows for fine-grained control over
|
|
|
|
|
/// the initialization sequence of services, ensuring that dependencies are properly
|
|
|
|
|
/// set up before they are needed by other components.
|
|
|
|
|
///
|
|
|
|
|
/// For example, a service provider with a priority of 10 will be registered before
|
|
|
|
|
/// a service provider with a priority of 5. If two service providers have the same
|
|
|
|
|
/// priority, their registration order is not guaranteed.
|
|
|
|
|
final int priority;
|
|
|
|
|
|
|
|
|
|
/// Sets a custom logger for the ServiceProvider class.
|
|
|
|
|
///
|
|
|
|
|
/// This method allows changing the logger used by the ServiceProvider class,
|
|
|
|
|
/// which is particularly useful for testing purposes. It replaces the default
|
|
|
|
|
/// logger with the provided custom logger.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - logger: The custom Logger instance to be used.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// ServiceProvider.setLogger(MockLogger());
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Note: This method is marked as `@visibleForTesting` to indicate that it
|
|
|
|
|
/// should only be used in test environments.
|
|
|
|
|
@visibleForTesting
|
|
|
|
|
static void setLogger(Logger logger) {
|
|
|
|
|
_logger = logger;
|
|
|
|
|
print('Logger set to: $logger'); // Add this line
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers the service provider with the given container and Angel.
|
|
|
|
|
///
|
|
|
|
|
/// This method is responsible for registering the service provider's dependencies
|
|
|
|
|
/// and services into the provided [container]. It also has access to the [app]
|
|
|
|
|
/// instance for any Angel-specific configurations.
|
|
|
|
|
///
|
|
|
|
|
/// The method is annotated with [@mustCallSuper], indicating that subclasses
|
|
|
|
|
/// overriding this method must call the superclass implementation.
|
|
|
|
|
///
|
|
|
|
|
/// This implementation logs the registration process, performs a sample
|
|
|
|
|
/// registration (which should be replaced with actual logic in subclasses),
|
|
|
|
|
/// and handles any exceptions that occur during the registration process.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [app]: The Angel instance, providing access to app-wide configurations.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// Any exceptions caught during the registration process are logged
|
|
|
|
|
/// and then re-thrown.
|
|
|
|
|
@mustCallSuper
|
|
|
|
|
void register(Container container, Angel app) {
|
|
|
|
|
try {
|
|
|
|
|
logger.info('Registering ${runtimeType.toString()}');
|
|
|
|
|
// Actual registration logic
|
|
|
|
|
container.registerSingleton<String>('test', as: null);
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
print('Caught exception in register: $e'); // Add this line
|
|
|
|
|
logger.severe(
|
|
|
|
|
'Error registering ${runtimeType.toString()}', e, stackTrace);
|
|
|
|
|
print('Logged severe message in register'); // Add this line
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Boots the service provider, performing necessary setup tasks.
|
|
|
|
|
///
|
|
|
|
|
/// This method is responsible for initializing the service provider and
|
|
|
|
|
/// performing any required setup tasks. It's called during the Angel's
|
|
|
|
|
/// boot process.
|
|
|
|
|
///
|
|
|
|
|
/// The method is annotated with [@mustCallSuper], indicating that subclasses
|
|
|
|
|
/// overriding this method must call the superclass implementation.
|
|
|
|
|
///
|
|
|
|
|
/// This implementation logs the booting process, performs a sample
|
|
|
|
|
/// configuration (which should be replaced with actual logic in subclasses),
|
|
|
|
|
/// and handles any exceptions that occur during the booting process.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [app]: The Angel instance, providing access to app-wide configurations.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// Any exceptions caught during the booting process are logged
|
|
|
|
|
/// and then re-thrown.
|
|
|
|
|
///
|
|
|
|
|
/// Returns:
|
|
|
|
|
/// A [Future] that completes when the boot process is finished.
|
|
|
|
|
@mustCallSuper
|
|
|
|
|
Future<void> boot(Angel app) async {
|
|
|
|
|
try {
|
|
|
|
|
logger.info('Booting ${runtimeType.toString()}');
|
|
|
|
|
// Actual booting logic
|
|
|
|
|
await app.configure((_) => null);
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
print('Caught exception in boot: $e'); // Add this line
|
|
|
|
|
logger.severe('Error booting ${runtimeType.toString()}', e, stackTrace);
|
|
|
|
|
print('Logged severe message in boot'); // Add this line
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Indicates whether the service provider is deferred.
|
|
|
|
|
///
|
|
|
|
|
/// This getter returns a boolean value that determines if the service provider
|
|
|
|
|
/// should be loaded in a deferred manner. By default, it returns `false`,
|
|
|
|
|
/// meaning the service provider is not deferred.
|
|
|
|
|
///
|
|
|
|
|
/// Deferred loading can be useful for optimizing Angel startup time
|
|
|
|
|
/// by delaying the loading of certain services until they are actually needed.
|
|
|
|
|
///
|
|
|
|
|
/// Subclasses can override this getter to implement custom deferral logic.
|
|
|
|
|
///
|
|
|
|
|
/// Returns:
|
|
|
|
|
/// A boolean value. `false` indicates the service provider is not deferred,
|
|
|
|
|
/// while `true` would indicate it is deferred.
|
|
|
|
|
bool get isDeferred => false;
|
|
|
|
|
|
|
|
|
|
/// Returns a list of types that this service provider provides.
|
|
|
|
|
///
|
|
|
|
|
/// This getter is intended to be overridden by subclasses to specify
|
|
|
|
|
/// the types of services or dependencies that the service provider
|
|
|
|
|
/// makes available to the Angel.
|
|
|
|
|
///
|
|
|
|
|
/// By default, it returns an empty list, indicating that the base
|
|
|
|
|
/// ServiceProvider class doesn't provide any specific types.
|
|
|
|
|
///
|
|
|
|
|
/// Subclasses should override this getter to return a list of
|
|
|
|
|
/// [Type] objects representing the services they provide.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// @override
|
|
|
|
|
/// List<Type> get provides => [DatabaseService, LoggingService];
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Returns:
|
|
|
|
|
/// A [List] of [Type] objects representing the provided services.
|
|
|
|
|
List<Type> get provides => const [];
|
|
|
|
|
|
|
|
|
|
/// Determines whether the service provider should be registered.
|
|
|
|
|
///
|
|
|
|
|
/// This method is used to conditionally register the service provider based on
|
|
|
|
|
/// the current state of the Angel. By default, it always returns `true`,
|
|
|
|
|
/// meaning the service provider will be registered in all cases.
|
|
|
|
|
///
|
|
|
|
|
/// Subclasses can override this method to implement custom logic for determining
|
|
|
|
|
/// when the service provider should be registered. This can be useful for
|
|
|
|
|
/// conditional registration based on environment variables, configuration settings,
|
|
|
|
|
/// or other Angel-specific criteria.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [app]: The current [Angel] instance, which can be used to access
|
|
|
|
|
/// Angel-wide settings or configurations.
|
|
|
|
|
///
|
|
|
|
|
/// Returns:
|
|
|
|
|
/// A boolean value. `true` indicates that the service provider should be
|
|
|
|
|
/// registered, while `false` indicates it should not be registered.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// @override
|
|
|
|
|
/// bool when(Angel app) {
|
|
|
|
|
/// return app.environment == 'production';
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
bool when(Angel app) => true;
|
|
|
|
|
|
|
|
|
|
/// Determines if the service provider is ready to be registered with the Angel.
|
|
|
|
|
///
|
|
|
|
|
/// This method checks two conditions:
|
|
|
|
|
/// 1. The service provider is not deferred (checked via the [isDeferred] property).
|
|
|
|
|
/// 2. The [when] method returns true for the given [app].
|
|
|
|
|
///
|
|
|
|
|
/// If both conditions are met, the method returns true, indicating that the
|
|
|
|
|
/// service provider is ready to be registered.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [app]: The [Angel] instance to check against.
|
|
|
|
|
///
|
|
|
|
|
/// Returns:
|
|
|
|
|
/// A boolean value. `true` if the service provider is ready to be registered,
|
|
|
|
|
/// `false` otherwise.
|
|
|
|
|
bool isReadyToRegister(Angel app) => !isDeferred && when(app);
|
|
|
|
|
|
|
|
|
|
/// Registers a singleton instance of type [T] with the provided [container].
|
|
|
|
|
///
|
|
|
|
|
/// This method ensures that the same [instance] of type [T] is always returned
|
|
|
|
|
/// when requested from the container. This is useful for objects that should
|
|
|
|
|
/// maintain a single state throughout the Angel's lifecycle.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [instance]: The instance of type [T] to be registered as a singleton.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// final myService = MyService();
|
|
|
|
|
/// singleton<MyService>(container, myService);
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, any request for type [T] from the container will return
|
|
|
|
|
/// the same [instance].
|
|
|
|
|
void singleton<T>(Container container, T instance) {
|
|
|
|
|
container.registerSingleton<T>(instance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers a factory that always returns the same instance of type [T].
|
|
|
|
|
///
|
|
|
|
|
/// This method creates a singleton-like behavior using a factory function.
|
|
|
|
|
/// The first time the type [T] is requested from the container, the [factory]
|
|
|
|
|
/// function is called to create an instance. This instance is then cached and
|
|
|
|
|
/// returned for all subsequent requests.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [factory]: A function that takes a [Container] and returns an instance of [T].
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// singletonFactory<Database>(container, (c) => Database.connect());
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, any request for type [T] from the container will return
|
|
|
|
|
/// the same instance, created by the [factory] function on the first request.
|
|
|
|
|
void singletonFactory<T>(Container container, T Function(Container) factory) {
|
|
|
|
|
T? instance;
|
|
|
|
|
container.registerFactory<T>((c) {
|
|
|
|
|
instance ??= factory(c);
|
|
|
|
|
return instance!;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers a factory function for type [T] with the provided [container].
|
|
|
|
|
///
|
|
|
|
|
/// This method allows you to register a factory function that creates instances
|
|
|
|
|
/// of type [T]. The factory function will be called each time an instance of [T]
|
|
|
|
|
/// is requested from the container.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [factory]: A function that takes a [Container] and returns an instance of [T].
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// bind<Logger>(container, (c) => Logger('MyLogger'));
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, you can request an instance of [T] from the container,
|
|
|
|
|
/// and the [factory] function will be called to create a new instance each time.
|
|
|
|
|
void bind<T>(Container container, T Function(Container) factory) {
|
|
|
|
|
container.registerFactory<T>(factory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers an instance of type [T] with the provided [container].
|
|
|
|
|
///
|
|
|
|
|
/// This method creates a factory that always returns the same [instance] of type [T].
|
|
|
|
|
/// It's useful for registering existing objects into the dependency injection container.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [instance]: The instance of type [T] to be registered.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// final myService = MyService();
|
|
|
|
|
/// bindInstance<MyService>(container, myService);
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, any request for type [T] from the container will return
|
|
|
|
|
/// the same [instance].
|
|
|
|
|
void bindInstance<T>(Container container, T instance) {
|
|
|
|
|
container.registerFactory<T>((Container c) => instance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers a factory function for type [T] with the provided [container], but only if a binding for [T] doesn't already exist.
|
|
|
|
|
///
|
|
|
|
|
/// This method checks if the container already has a binding for type [T]. If it doesn't,
|
|
|
|
|
/// it registers the provided [factory] function. This is useful for providing default
|
|
|
|
|
/// implementations while allowing them to be overridden.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [factory]: A function that takes a [Container] and returns an instance of [T].
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// bindIf<Logger>(container, (c) => Logger('DefaultLogger'));
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// If [T] is not already registered in the container, the [factory] function will be
|
|
|
|
|
/// registered. Otherwise, the existing binding will be preserved.
|
|
|
|
|
void bindIf<T>(Container container, T Function(Container) factory) {
|
|
|
|
|
if (!container.has<T>()) {
|
|
|
|
|
container.registerFactory<T>(factory);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers multiple factory bindings at once with the provided container.
|
|
|
|
|
///
|
|
|
|
|
/// This method allows for batch registration of multiple factory functions
|
|
|
|
|
/// for different types in a single call. It iterates through the provided
|
|
|
|
|
/// [bindings] map and registers each factory function with its corresponding type.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [bindings]: A map where each key is a Type and each value is a factory function.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// bindMultiple(container, {
|
|
|
|
|
/// Logger: (c) => Logger('MyLogger'),
|
|
|
|
|
/// Database: (c) => Database.connect(),
|
|
|
|
|
/// UserService: (c) => UserService(c.make<Database>())
|
|
|
|
|
/// });
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Note: The factory functions in the [bindings] map should match the
|
|
|
|
|
/// signature `T Function(Container)` where T is the type being registered.
|
|
|
|
|
void bindMultiple(Container container, Map<Type, Function> bindings) {
|
|
|
|
|
bindings.forEach((type, factory) {
|
|
|
|
|
container.registerFactory(factory as Function(Container));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers an alias for an existing binding in the container.
|
|
|
|
|
///
|
|
|
|
|
/// This method creates a new binding of type [A] that resolves to the same
|
|
|
|
|
/// instance as an existing binding of type [T]. This is useful when you want
|
|
|
|
|
/// to refer to the same service using different types, such as when working
|
|
|
|
|
/// with interfaces and implementations.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register the alias with.
|
|
|
|
|
///
|
|
|
|
|
/// Type Parameters:
|
|
|
|
|
/// - [T]: The original type that is already bound in the container.
|
|
|
|
|
/// - [A]: The alias type that will be registered to resolve to the same instance as [T].
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// // Assuming UserService is already registered
|
|
|
|
|
/// alias<UserService, IUserService>(container);
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After calling this method, requests for type [A] from the container will
|
|
|
|
|
/// return the same instance as requests for type [T].
|
|
|
|
|
///
|
|
|
|
|
/// Note: This method assumes that [T] is already registered in the container
|
|
|
|
|
/// and that instances of [T] can be cast to [A]. If these conditions are not met,
|
|
|
|
|
/// it may result in runtime errors.
|
|
|
|
|
void alias<T, A>(Container container) {
|
|
|
|
|
container.registerFactory<A>((c) => c.make<T>() as A);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers a scoped instance of type [T] with the provided [container].
|
|
|
|
|
///
|
|
|
|
|
/// A scoped instance is created once per scope and reused within that scope.
|
|
|
|
|
/// This is useful for objects that should have a single instance per request
|
|
|
|
|
/// or transaction, but not shared across the entire Angel.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [factory]: A function that takes a [Container] and returns an instance of [T].
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// scoped<RequestContext>(container, (c) => RequestContext());
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, a new instance of [T] will be created for each scope,
|
|
|
|
|
/// but within the same scope, the same instance will be reused.
|
|
|
|
|
void scoped<T>(Container container, T Function(Container) factory) {
|
|
|
|
|
container.registerScoped<T>(factory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers a transient factory for type [T] with the provided [container].
|
|
|
|
|
///
|
|
|
|
|
/// A transient factory creates a new instance every time it is requested, even within the same scope.
|
|
|
|
|
/// This is useful for objects that should not be shared and need a fresh instance each time they are used.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [factory]: A function that takes a [Container] and returns a new instance of [T].
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// transient<Logger>(container, (c) => Logger('NewLogger'));
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, each request for type [T] from the container will return a new instance
|
|
|
|
|
/// created by the [factory] function, regardless of the scope or previous requests.
|
|
|
|
|
void transient<T>(Container container, T Function(Container) factory) {
|
|
|
|
|
container.registerTransient<T>(factory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers a constant value of type [T] with the provided [container].
|
|
|
|
|
///
|
|
|
|
|
/// This method allows you to register a constant value that will always be
|
|
|
|
|
/// returned when requesting an instance of type [T] from the container.
|
|
|
|
|
/// The registered value cannot be changed after registration.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [value]: The constant value of type [T] to be registered.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// constant<int>(container, 42);
|
|
|
|
|
/// constant<String>(container, 'AppName');
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, any request for type [T] from the container will
|
|
|
|
|
/// always return the registered [value].
|
|
|
|
|
void constant<T>(Container container, T value) {
|
|
|
|
|
container.registerConstant<T>(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers a lazy-loaded singleton of type [T] with the provided [container].
|
|
|
|
|
///
|
|
|
|
|
/// This method registers a factory function that creates a singleton instance of type [T]
|
|
|
|
|
/// only when it is first requested from the container. Subsequent requests will return
|
|
|
|
|
/// the same instance.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [container]: The dependency injection container to register with.
|
|
|
|
|
/// - [factory]: A function that takes a [Container] and returns an instance of [T].
|
|
|
|
|
///
|
|
|
|
|
/// The lazy initialization can help improve performance by deferring the creation
|
|
|
|
|
/// of the singleton until it's actually needed.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// lazySingleton<Database>(container, (c) => Database.connect());
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// After registration, the first request for type [T] from the container will trigger
|
|
|
|
|
/// the [factory] function to create the instance. All subsequent requests will return
|
|
|
|
|
/// the same instance without calling the factory again.
|
|
|
|
|
void lazySingleton<T>(Container container, T Function(Container) factory) {
|
|
|
|
|
container.registerLazySingleton<T>(factory);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A container that manages service providers for dependency injection.
|
|
|
|
|
///
|
|
|
|
|
/// This class extends the base [Container] and adds functionality to handle
|
|
|
|
|
/// [ServiceProvider] instances. It allows for adding, configuring, booting,
|
|
|
|
|
/// and discovering service providers.
|
|
|
|
|
///
|
|
|
|
|
/// The container maintains an internal list of providers, sorted by their priority.
|
|
|
|
|
/// It provides methods to add providers manually, configure and boot them with
|
|
|
|
|
/// an [Angel] instance, and automatically discover providers using reflection.
|
|
|
|
|
///
|
|
|
|
|
/// Key features:
|
|
|
|
|
/// - Add providers with priority-based sorting
|
|
|
|
|
/// - Configure providers with the Angel instance
|
|
|
|
|
/// - Boot providers when the Angel starts
|
|
|
|
|
/// - Automatically discover and instantiate providers using reflection
|
|
|
|
|
///
|
|
|
|
|
/// Usage:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// var container = ServiceProviderContainer(myReflector);
|
|
|
|
|
/// container.addProvider(MyServiceProvider());
|
|
|
|
|
/// container.configureProviders(app);
|
|
|
|
|
/// container.bootProviders(app);
|
|
|
|
|
/// container.discoverProviders();
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// This container is designed to work with the [ServiceProvider] abstract class
|
|
|
|
|
/// and integrates with the Angel framework's [Angel] class.
|
|
|
|
|
class ServiceProviderContainer extends Container {
|
|
|
|
|
/// Static logger instance for the ServiceProviderContainer class.
|
|
|
|
|
///
|
|
|
|
|
/// This logger is used throughout the ServiceProviderContainer class to log
|
|
|
|
|
/// information, warnings, and errors related to service provider operations.
|
|
|
|
|
/// It is initialized with the name 'ServiceProviderContainer' to easily
|
|
|
|
|
/// identify log messages originating from this class.
|
|
|
|
|
static final Logger _logger = Logger('ServiceProviderContainer');
|
|
|
|
|
|
|
|
|
|
/// A list to store all registered [ServiceProvider] instances.
|
|
|
|
|
///
|
|
|
|
|
/// This private field maintains an ordered collection of service providers
|
|
|
|
|
/// that have been added to the container. The order of providers in this list
|
|
|
|
|
/// is significant, as it determines the sequence in which they are configured
|
|
|
|
|
/// and booted.
|
|
|
|
|
///
|
|
|
|
|
/// Service providers are typically added to this list through the [addProvider]
|
|
|
|
|
/// method, which also sorts them based on their priority.
|
|
|
|
|
final List<ServiceProvider> _providers = [];
|
|
|
|
|
|
|
|
|
|
/// Constructs a new [ServiceProviderContainer] instance.
|
|
|
|
|
///
|
|
|
|
|
/// This constructor initializes a [ServiceProviderContainer] with the provided [reflector].
|
|
|
|
|
/// The [reflector] is passed to the superclass constructor and is used for reflection-based
|
|
|
|
|
/// operations, such as discovering and instantiating service providers.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [reflector]: An instance of a reflector that complies with the requirements of the
|
|
|
|
|
/// superclass [Container]. This reflector is used for introspection and dynamic
|
|
|
|
|
/// instantiation of service providers.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// var container = ServiceProviderContainer(myReflector);
|
|
|
|
|
/// ```
|
|
|
|
|
ServiceProviderContainer(super.reflector);
|
|
|
|
|
|
|
|
|
|
/// Adds a [ServiceProvider] to the container and sorts the providers by priority.
|
|
|
|
|
///
|
|
|
|
|
/// This method performs the following operations:
|
|
|
|
|
/// 1. Adds the given [provider] to the internal list of providers.
|
|
|
|
|
/// 2. Sorts the list of providers based on their priority in descending order.
|
|
|
|
|
/// 3. Logs the addition of the provider.
|
|
|
|
|
///
|
|
|
|
|
/// If any exception occurs during this process, it logs the error and rethrows the exception.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [provider]: The [ServiceProvider] instance to be added to the container.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// Any exception that occurs during the addition or sorting process.
|
|
|
|
|
///
|
|
|
|
|
/// Example:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// container.addProvider(MyServiceProvider());
|
|
|
|
|
/// ```
|
|
|
|
|
void addProvider(ServiceProvider provider) {
|
|
|
|
|
try {
|
|
|
|
|
_providers.add(provider);
|
|
|
|
|
_providers.sort((a, b) => b.priority.compareTo(a.priority));
|
|
|
|
|
_logger.info('Added provider: ${provider.runtimeType.toString()}');
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe('Error adding provider', e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Boots all registered service providers that are ready to be registered.
|
|
|
|
|
///
|
|
|
|
|
/// This method iterates through all registered service providers and calls
|
|
|
|
|
/// their `boot` method if they are ready to be registered. It handles the
|
|
|
|
|
/// booting process for each provider and logs any errors that occur.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [app]: The [Angel] instance that providers will use for booting.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// Any exception that occurs during the booting process of a provider.
|
|
|
|
|
/// The exception is logged before being rethrown.
|
|
|
|
|
///
|
|
|
|
|
/// Note:
|
|
|
|
|
/// - Only providers that return true for `isReadyToRegister(app)` are booted.
|
|
|
|
|
/// - If an error occurs while booting a provider, it's logged and rethrown,
|
|
|
|
|
/// which may interrupt the booting process for subsequent providers.
|
|
|
|
|
void bootProviders(Angel app) {
|
|
|
|
|
for (var provider in _providers) {
|
|
|
|
|
if (provider.isReadyToRegister(app)) {
|
|
|
|
|
try {
|
|
|
|
|
provider.boot(app);
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe(
|
|
|
|
|
'Error booting provider: ${provider.runtimeType}', e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Configures all registered service providers that are ready to be registered.
|
|
|
|
|
///
|
|
|
|
|
/// This method iterates through all registered service providers and calls
|
|
|
|
|
/// their `register` method if they are ready to be registered. It handles the
|
|
|
|
|
/// configuration process for each provider and logs any errors that occur.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [app]: The [Angel] instance that providers will use for configuration.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// Any exception that occurs during the configuration process of a provider.
|
|
|
|
|
/// The exception is logged before being rethrown.
|
|
|
|
|
///
|
|
|
|
|
/// Note:
|
|
|
|
|
/// - Only providers that return true for `isReadyToRegister(app)` are configured.
|
|
|
|
|
/// - If an error occurs while configuring a provider, it's logged and rethrown,
|
|
|
|
|
/// which may interrupt the configuration process for subsequent providers.
|
|
|
|
|
void configureProviders(Angel app) {
|
|
|
|
|
for (var provider in _providers) {
|
|
|
|
|
if (provider.isReadyToRegister(app)) {
|
|
|
|
|
try {
|
|
|
|
|
provider.register(this, app);
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe('Error configuring provider: ${provider.runtimeType}',
|
|
|
|
|
e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Discovers and adds service providers to the container using reflection.
|
|
|
|
|
///
|
|
|
|
|
/// This method iterates through all classes annotated with [discoverableServiceProvider],
|
|
|
|
|
/// checks if they are non-abstract subclasses of [ServiceProvider], and if so,
|
|
|
|
|
/// instantiates and adds them to the container.
|
|
|
|
|
///
|
|
|
|
|
/// The discovery process includes:
|
|
|
|
|
/// 1. Iterating through annotated classes.
|
|
|
|
|
/// 2. Checking if each class is a non-abstract ServiceProvider.
|
|
|
|
|
/// 3. Instantiating the provider and adding it to the container.
|
|
|
|
|
/// 4. Logging successful additions and any errors encountered.
|
|
|
|
|
///
|
|
|
|
|
/// If an error occurs during the instantiation of a specific provider,
|
|
|
|
|
/// it is logged as a warning, and the method continues with the next provider.
|
|
|
|
|
/// If a general error occurs during the discovery process, it is logged
|
|
|
|
|
/// as a severe error and rethrown.
|
|
|
|
|
///
|
|
|
|
|
/// This method is useful for automatically registering service providers
|
|
|
|
|
/// without manual instantiation and addition.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// Any exception that occurs during the general discovery process,
|
|
|
|
|
/// excluding individual provider instantiation errors.
|
|
|
|
|
void discoverProviders() {
|
|
|
|
|
try {
|
|
|
|
|
for (var classMirror in discoverableServiceProvider.annotatedClasses) {
|
|
|
|
|
if (_isServiceProvider(classMirror) && !classMirror.isAbstract) {
|
|
|
|
|
try {
|
|
|
|
|
var provider = classMirror.newInstance('', []) as ServiceProvider;
|
|
|
|
|
addProvider(provider);
|
|
|
|
|
_logger.info(
|
|
|
|
|
'Discovered and added provider: ${classMirror.simpleName}');
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.warning(
|
|
|
|
|
'Error instantiating provider: ${classMirror.simpleName}',
|
|
|
|
|
e,
|
|
|
|
|
stackTrace);
|
|
|
|
|
// Continue to the next provider instead of rethrowing
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe('Error discovering providers', e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Determines if a given ClassMirror represents a subclass of ServiceProvider.
|
|
|
|
|
///
|
|
|
|
|
/// This method traverses the inheritance hierarchy of the provided [classMirror]
|
|
|
|
|
/// to check if it is a subclass of ServiceProvider. It does this by comparing
|
|
|
|
|
/// the simple name of each superclass with 'ServiceProvider'.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [classMirror]: The ClassMirror to check.
|
|
|
|
|
///
|
|
|
|
|
/// Returns:
|
|
|
|
|
/// true if the class is a subclass of ServiceProvider, false otherwise.
|
|
|
|
|
///
|
|
|
|
|
/// Note:
|
|
|
|
|
/// This method assumes that the ServiceProvider class is named exactly
|
|
|
|
|
/// 'ServiceProvider'. It will not detect classes that implement an interface
|
|
|
|
|
/// named ServiceProvider or classes that are renamed through imports.
|
|
|
|
|
bool _isServiceProvider(ClassMirror classMirror) {
|
|
|
|
|
ClassMirror? currentMirror = classMirror;
|
|
|
|
|
while (currentMirror != null) {
|
|
|
|
|
if (currentMirror.simpleName == 'ServiceProvider') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
currentMirror = currentMirror.superclass;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extension on [Angel] to provide service provider functionality.
|
|
|
|
|
extension ServiceProviderExtension on Angel {
|
|
|
|
|
/// A static logger instance for the ServiceProviderExtension.
|
|
|
|
|
///
|
|
|
|
|
/// This logger is used throughout the ServiceProviderExtension to log
|
|
|
|
|
/// information, warnings, and errors related to service provider operations.
|
|
|
|
|
/// It is initialized with the name 'ServiceProviderExtension' to easily
|
|
|
|
|
/// identify log messages originating from this extension.
|
|
|
|
|
static final Logger _logger = Logger('ServiceProviderExtension');
|
|
|
|
|
|
|
|
|
|
/// Adds a [ServiceProvider] to the Angel's container.
|
|
|
|
|
///
|
|
|
|
|
/// This method attempts to add the given [provider] to the Angel's
|
|
|
|
|
/// container. It performs the following steps:
|
|
|
|
|
///
|
|
|
|
|
/// 1. Checks if the Angel's container is an instance of [ServiceProviderContainer].
|
|
|
|
|
/// 2. If it is, adds the provider to the container using the [addProvider] method.
|
|
|
|
|
/// 3. If it's not, throws an exception indicating that the container must be a [ServiceProviderContainer].
|
|
|
|
|
///
|
|
|
|
|
/// If any error occurs during this process, it logs a severe error message
|
|
|
|
|
/// and rethrows the exception.
|
|
|
|
|
///
|
|
|
|
|
/// Parameters:
|
|
|
|
|
/// - [provider]: The [ServiceProvider] instance to be added to the container.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// - [Exception] if the Angel's container is not a [ServiceProviderContainer].
|
|
|
|
|
/// - Any other exception that occurs during the process of adding the provider.
|
|
|
|
|
///
|
|
|
|
|
/// Usage:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// app.useServiceProvider(MyServiceProvider());
|
|
|
|
|
/// ```
|
|
|
|
|
void useServiceProvider(ServiceProvider provider) {
|
|
|
|
|
try {
|
|
|
|
|
if (container is ServiceProviderContainer) {
|
|
|
|
|
(container as ServiceProviderContainer).addProvider(provider);
|
|
|
|
|
} else {
|
|
|
|
|
throw Exception('Angel container must be a ServiceProviderContainer');
|
|
|
|
|
}
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe('Error using service provider', e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Configures all registered service providers in the Angel's container.
|
|
|
|
|
///
|
|
|
|
|
/// This method attempts to configure the service providers by calling the
|
|
|
|
|
/// `configureProviders` method on the Angel's container. It performs
|
|
|
|
|
/// the following steps:
|
|
|
|
|
///
|
|
|
|
|
/// 1. Checks if the Angel's container is an instance of [ServiceProviderContainer].
|
|
|
|
|
/// 2. If it is, calls the `configureProviders` method on the container, passing `this` (the Angel).
|
|
|
|
|
/// 3. If it's not, throws an exception indicating that the container must be a [ServiceProviderContainer].
|
|
|
|
|
///
|
|
|
|
|
/// If any error occurs during this process, it logs a severe error message
|
|
|
|
|
/// and rethrows the exception.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// - [Exception] if the Angel's container is not a [ServiceProviderContainer].
|
|
|
|
|
/// - Any other exception that occurs during the configuration process.
|
|
|
|
|
///
|
|
|
|
|
/// Usage:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// app.configureProviders();
|
|
|
|
|
/// ```
|
|
|
|
|
void configureProviders() {
|
|
|
|
|
try {
|
|
|
|
|
if (container is ServiceProviderContainer) {
|
|
|
|
|
(container as ServiceProviderContainer).configureProviders(this);
|
|
|
|
|
} else {
|
|
|
|
|
throw Exception('Angel container must be a ServiceProviderContainer');
|
|
|
|
|
}
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe('Error configuring providers', e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Registers and boots all service providers in the Angel's container.
|
|
|
|
|
///
|
|
|
|
|
/// This method performs the following operations:
|
|
|
|
|
/// 1. Checks if the Angel's container is an instance of [ServiceProviderContainer].
|
|
|
|
|
/// 2. If it is, it casts the container to [ServiceProviderContainer].
|
|
|
|
|
/// 3. Calls [configureProviders] on the container to register the providers.
|
|
|
|
|
/// 4. Calls [bootProviders] on the container to initialize the providers.
|
|
|
|
|
///
|
|
|
|
|
/// If the container is not a [ServiceProviderContainer], it throws an exception.
|
|
|
|
|
///
|
|
|
|
|
/// This method is typically called after all service providers have been added
|
|
|
|
|
/// to the container, either manually or through discovery.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// - [Exception] if the Angel's container is not a [ServiceProviderContainer].
|
|
|
|
|
/// - Any other exception that occurs during the registration or booting process.
|
|
|
|
|
/// These exceptions are logged before being rethrown.
|
|
|
|
|
///
|
|
|
|
|
/// Usage:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// app.registerAndBootProviders();
|
|
|
|
|
/// ```
|
|
|
|
|
void registerAndBootProviders() {
|
|
|
|
|
try {
|
|
|
|
|
if (container is ServiceProviderContainer) {
|
|
|
|
|
var providerContainer = container as ServiceProviderContainer;
|
|
|
|
|
providerContainer.configureProviders(this);
|
|
|
|
|
providerContainer.bootProviders(this);
|
|
|
|
|
} else {
|
|
|
|
|
throw Exception('Angel container must be a ServiceProviderContainer');
|
|
|
|
|
}
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe('Error registering and booting providers', e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Discovers, registers, and boots all service providers in the Angel.
|
|
|
|
|
///
|
|
|
|
|
/// This method performs a complete lifecycle for service providers:
|
|
|
|
|
/// 1. Discovers providers using reflection (if the container supports it).
|
|
|
|
|
/// 2. Configures the discovered providers.
|
|
|
|
|
/// 3. Registers and boots the providers.
|
|
|
|
|
///
|
|
|
|
|
/// The process includes:
|
|
|
|
|
/// - Checking if the Angel's container is a [ServiceProviderContainer].
|
|
|
|
|
/// - Calling [discoverProviders] to find and instantiate providers.
|
|
|
|
|
/// - Calling [configureProviders] to set up the providers.
|
|
|
|
|
/// - Calling [registerAndBootProviders] to finalize provider initialization.
|
|
|
|
|
///
|
|
|
|
|
/// If the container is not a [ServiceProviderContainer], an exception is thrown.
|
|
|
|
|
///
|
|
|
|
|
/// This method is typically called once during Angel startup to set up
|
|
|
|
|
/// all service providers automatically.
|
|
|
|
|
///
|
|
|
|
|
/// Throws:
|
|
|
|
|
/// - [Exception] if the Angel's container is not a [ServiceProviderContainer].
|
|
|
|
|
/// - Any exceptions that occur during the discovery, configuration, or booting process.
|
|
|
|
|
/// These exceptions are logged before being rethrown.
|
|
|
|
|
///
|
|
|
|
|
/// Usage:
|
|
|
|
|
/// ```dart
|
|
|
|
|
/// app.discoverAndRegisterProviders();
|
|
|
|
|
/// ```
|
|
|
|
|
void discoverAndRegisterProviders() {
|
|
|
|
|
try {
|
|
|
|
|
if (container is ServiceProviderContainer) {
|
|
|
|
|
(container as ServiceProviderContainer).discoverProviders();
|
|
|
|
|
configureProviders();
|
|
|
|
|
registerAndBootProviders();
|
|
|
|
|
} else {
|
|
|
|
|
throw Exception('Angel container must be a ServiceProviderContainer');
|
|
|
|
|
}
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
_logger.severe(
|
|
|
|
|
'Error discovering and registering providers', e, stackTrace);
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|