diff --git a/packages/support/lib/src/manager.dart b/packages/support/lib/src/manager.dart new file mode 100644 index 0000000..5014168 --- /dev/null +++ b/packages/support/lib/src/manager.dart @@ -0,0 +1,136 @@ +import 'package:platform_container/container.dart'; +import 'package:platform_support/src/fluent.dart'; + +/// Base class for managing drivers with different implementations. +/// +/// This class provides a foundation for managing different implementations of a service, +/// similar to Laravel's Manager class. It handles driver creation, caching, and custom +/// driver registration. +abstract class Manager { + /// The container instance. + final Container container; + + /// The configuration repository instance. + final Fluent config; + + /// The registered custom driver creators. + final Map _customCreators = {}; + + /// The array of created "drivers". + final Map _drivers = {}; + + /// Create a new manager instance. + /// + /// Parameters: + /// - [container]: The container instance to use for resolving dependencies + Manager(this.container) : config = container.make(); + + /// Get the default driver name. + /// + /// This method must be implemented by concrete managers to specify which + /// driver should be used by default. Returns null if no default driver is set. + String? getDefaultDriver(); + + /// Get a driver instance. + /// + /// This method returns an instance of the requested driver. If the driver + /// has already been created, it returns the cached instance. Otherwise, + /// it creates a new instance. + /// + /// Parameters: + /// - [driver]: The name of the driver to get. If null, uses default driver. + /// + /// Returns: + /// The driver instance. + /// + /// Throws: + /// - [ArgumentError] if no driver name is provided and no default exists + T driver([String? driver]) { + driver ??= getDefaultDriver(); + + if (driver == null) { + throw ArgumentError( + 'Unable to resolve NULL driver for [${runtimeType}].'); + } + + return _drivers.putIfAbsent(driver, () => createDriver(driver!)) as T; + } + + /// Create a new driver instance. + /// + /// This method creates a new instance of the requested driver. It first checks + /// for custom creators, then looks for a create{Driver}Driver method. + /// + /// Parameters: + /// - [driver]: The name of the driver to create + /// + /// Returns: + /// The new driver instance + /// + /// Throws: + /// - [ArgumentError] if the driver is not supported + dynamic createDriver(String driver) { + // Check for custom creator + if (_customCreators.containsKey(driver)) { + return _customCreators[driver]!(container); + } + + // Look for create{Driver}Driver method + var methodName = + 'create${driver[0].toUpperCase()}${driver.substring(1)}Driver'; + var result = callDriverCreator(methodName); + if (result != null) { + return result; + } + + throw ArgumentError('Driver [$driver] not supported.'); + } + + /// Call a driver creator method. + /// + /// This method must be implemented by concrete managers to handle calling + /// the appropriate creator method based on the method name. + /// + /// Parameters: + /// - [method]: The name of the creator method to call + /// + /// Returns: + /// The created driver instance, or null if the method doesn't exist + dynamic callDriverCreator(String method); + + /// Register a custom driver creator. + /// + /// This method allows registering custom driver creators that will be used + /// instead of the default creation logic. + /// + /// Parameters: + /// - [driver]: The name of the driver + /// - [creator]: The function that creates the driver + /// + /// Returns: + /// This manager instance for method chaining + Manager extend(String driver, dynamic Function(Container) creator) { + _customCreators[driver] = creator; + return this; + } + + /// Get all of the created drivers. + /// + /// Returns a map of all driver instances that have been created. + Map getDrivers() => Map.unmodifiable(_drivers); + + /// Get the container instance used by the manager. + Container getContainer() => container; + + /// Forget all of the resolved driver instances. + /// + /// This method clears the driver cache, forcing new instances to be created + /// on next access. + /// + /// Returns: + /// This manager instance for method chaining + Manager forgetDrivers() { + _drivers.clear(); + return this; + } +} diff --git a/packages/support/test/manager_test.dart b/packages/support/test/manager_test.dart new file mode 100644 index 0000000..c581184 --- /dev/null +++ b/packages/support/test/manager_test.dart @@ -0,0 +1,122 @@ +import 'package:platform_container/container.dart'; +import 'package:platform_container/mirrors.dart'; +import 'package:platform_support/src/fluent.dart'; +import 'package:platform_support/src/manager.dart'; +import 'package:test/test.dart'; + +// Test driver interface +abstract class TestDriver { + String getName(); +} + +// Test driver implementations +class LocalDriver implements TestDriver { + @override + String getName() => 'local'; +} + +class CloudDriver implements TestDriver { + final String region; + CloudDriver(this.region); + @override + String getName() => 'cloud:$region'; +} + +// Test manager implementation +class TestManager extends Manager { + TestManager(Container container) : super(container); + + @override + String? getDefaultDriver() { + var attributes = config.getAttributes(); + print('Config attributes: $attributes'); // Debug print + return config.get('test.driver'); + } + + @override + dynamic callDriverCreator(String method) { + print('Creating driver with method: $method'); // Debug print + switch (method) { + case 'createLocalDriver': + return LocalDriver(); + case 'createCloudDriver': + var region = config.get('test.cloud.region', 'us-east-1'); + return CloudDriver(region); + default: + return null; + } + } + + String getName() => driver().getName(); +} + +void main() { + group('Manager', () { + late Container container; + late TestManager manager; + + setUp(() { + container = Container(MirrorsReflector()); + var attributes = { + 'test': {'driver': 'local'} + }; // Nested attributes + var config = Fluent(attributes); + print('Setting up config with attributes: $attributes'); // Debug print + container.registerSingleton(config); + manager = TestManager(container); + }); + + test('uses default driver when no driver specified', () { + var driver = manager.driver(); + expect(driver.getName(), equals('local')); + }); + + test('creates driver instance', () { + var driver = manager.driver('local'); + expect(driver.getName(), equals('local')); + }); + + test('caches driver instances', () { + var driver1 = manager.driver('local'); + var driver2 = manager.driver('local'); + expect(identical(driver1, driver2), isTrue); + }); + + test('supports custom driver creators', () { + manager.extend('custom', (container) => CloudDriver('custom-region')); + var driver = manager.driver('custom'); + expect(driver.getName(), equals('cloud:custom-region')); + }); + + test('throws on unsupported driver', () { + expect( + () => manager.driver('unsupported'), throwsA(isA())); + }); + + test('throws on null driver with no default', () { + // Create new container and manager for this test + var testContainer = Container(MirrorsReflector()); + testContainer.registerSingleton(Fluent()); + var testManager = TestManager(testContainer); + expect(() => testManager.driver(), throwsA(isA())); + }); + + test('forgets cached drivers', () { + var driver1 = manager.driver('local'); + manager.forgetDrivers(); + var driver2 = manager.driver('local'); + expect(identical(driver1, driver2), isFalse); + }); + + test('returns unmodifiable driver map', () { + manager.driver('local'); + var drivers = manager.getDrivers(); + expect(() => drivers['local'] = LocalDriver(), + throwsA(isA())); + }); + + test('forwards method calls to default driver', () { + expect(manager.getName(), equals('local')); + }); + }); +}