From 93af6f0564b28cd8cd30ec28dcced1d2e6e72881 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 17 Jul 2023 00:10:15 +0200 Subject: [PATCH] Add tests for InstanceManager and remove Decorator hook logic (avoid complex Dice logic) --- doc/AddonsStrategyDecorator.md | 159 --------------- doc/StrategyHooks.md | 89 +++++++++ .../{HookType.php => BehavioralHookType.php} | 14 +- .../Capabilities/ICanCreateInstances.php | 20 +- .../Capabilities/ICanRegisterInstances.php | 19 +- src/Core/Hooks/Model/DiceInstanceManager.php | 45 +---- src/Core/Hooks/Util/HookFileManager.php | 15 +- src/Core/Logger/Factory/Logger.php | 12 +- static/hooks.config.php | 7 +- .../Util/Hooks/InstanceMocks/FakeInstance.php | 4 +- .../InstanceMocks/FakeInstanceDecorator.php | 12 +- .../src/Core/Addon/Model/AddonLoaderTest.php | 8 +- .../Core/Hooks/Model/InstanceManagerTest.php | 182 ++++++++---------- .../Core/Hooks/Util/HookFileManagerTest.php | 22 +-- 14 files changed, 218 insertions(+), 390 deletions(-) delete mode 100644 doc/AddonsStrategyDecorator.md create mode 100644 doc/StrategyHooks.md rename src/Core/Hooks/Capabilities/{HookType.php => BehavioralHookType.php} (82%) diff --git a/doc/AddonsStrategyDecorator.md b/doc/AddonsStrategyDecorator.md deleted file mode 100644 index 51456da6c7..0000000000 --- a/doc/AddonsStrategyDecorator.md +++ /dev/null @@ -1,159 +0,0 @@ -Friendica strategy and decorator Hooks -=========================================== - -* [Home](help) - -## Strategy hooks - -This type of hook is based on the [Strategy Design Pattern](https://refactoring.guru/design-patterns/strategy). - -A strategy class defines a possible implementation of a given interface based on a unique name. -Every name is possible as long as it's unique and not `null`. -Using an empty name (`''`) is possible as well and should be used as the "default" implementation. -To register a strategy, use the [`ICanRegisterInstance`](../src/Core/Hooks/Capabilities/ICanRegisterInstances.php) interface. - -After registration, a caller can automatically create this instance with the [`ICanCreateInstances`](../src/Core/Hooks/Capabilities/ICanCreateInstances.php) interface and the chosen name. - -This is useful in case there are different, possible implementations for the same purpose, like for logging, locking, caching, ... - -Normally, a config entry is used to choose the right implementation at runtime. -And if no config entry is set, the "default" implementation should be used. - -### Example - -```php -interface ExampleInterface -{ - public function testMethod(); -} - -public class ConcreteClassA implements ExampleInterface -{ - public function testMethod() - { - echo "concrete class A"; - } -} - -public class ConcreteClassB implements ExampleInterface -{ - public function testMethod() - { - echo "concrete class B"; - } -} - -/** @var \Friendica\Core\Hooks\Capabilities\ICanRegisterInstances $instanceRegister */ -$instanceRegister->registerStrategy(ExampleInterface::class, ConcreteClassA::class, 'A'); -$instanceRegister->registerStrategy(ExampleInterface::class, ConcreteClassB::class, 'B'); - -/** @var \Friendica\Core\Hooks\Capabilities\ICanCreateInstances $instanceManager */ -/** @var ConcreteClassA $concreteClass */ -$concreteClass = $instanceManager->createWithName(ExampleInterface::class, 'A'); - -$concreteClass->testMethod(); -// output: -// "concrete class A"; -``` - -## Decorator hooks - -This type of hook is based on the [Decorator Design Pattern](https://refactoring.guru/design-patterns/decorator). - -A decorator class extends a given strategy instance (see [Strategy hooks](#strategy-hooks)]). -To register a decorator, use the [`ICanRegisterInstance`](../src/Core/Hooks/Capabilities/ICanRegisterInstances.php) interface. - -After registration, a caller can automatically create an instance with the [`ICanCreateInstances`](../src/Core/Hooks/Capabilities/ICanCreateInstances.php) interface and the decorator will wrap its logic around the call. - -This is useful in case you want to extend a given class but the given class isn't responsible for these business logic. Or you want to extend an interface without knowing the concrete implementation. -For example profiling logger calls, Friendica is using a [`ProfilerLogger`](../src/Core/Logger/Type/ProfilerLogger.php), which wraps all other logging implementations and traces each log call. - -Normally, a config entry is used to enable/disable decorator. - -### Example - -```php -interface ExampleInterface -{ - public function testMethod(); -} - -public class ConcreteClassA implements ExampleInterface -{ - public function testMethod() - { - echo "concrete class A"; - } -} - -public class DecoratorClassA implements ExampleInterface -{ - /** @var ExampleInterface */ - protected $example; - - public function __construct(ExampleInterface $example) - { - $this->example = $example; - } - - public function testMethod() - { - echo "decorated!\n"; - $this->example->testMethod(); - } -} - -/** @var \Friendica\Core\Hooks\Capabilities\ICanRegisterInstances $instanceRegister */ -$instanceRegister->registerStrategy(ExampleInterface::class, ConcreteClassA::class, 'A'); -$instanceRegister->registerDecorator(ExampleInterface::class, DecoratorClassA::class); - -/** @var \Friendica\Core\Hooks\Capabilities\ICanCreateInstances $instanceManager */ -/** @var ConcreteClassA $concreteClass */ -$concreteClass = $instanceManager->createWithName(ExampleInterface::class, 'A'); - -$concreteClass->testMethod(); -// output: -// "decorated!" -// "concrete class A"; -``` - -## hooks.config.php - -To avoid registering all strategies and decorators manually inside the code, Friendica introduced the [`hooks.config.php`](../static/hooks.config.php) file. - -There, you can register all kind of strategies and decorators in one file. - -### [`HookType::STRATEGY`](../src/Core/Hooks/Capabilities/HookType.php) - -For each given interface, a list of key-value pairs can be set, where the key is the concrete implementation class and the value is an array of unique names. - -### [`HookType::DECORATOR`](../src/Core/Hooks/Capabilities/HookType.php) - -For each given interface, a list of concrete decorator classes can be set. - -### Example - -```php -use Friendica\Core\Hooks\Capabilities\HookType as H; - -return [ - H::STRATEGY => [ - ExampleInterface::class => [ - ConcreteClassA::class => ['A'], - ConcreteClassB::class => ['B'], - ], - ], - H::DECORATOR => [ - ExampleInterface::class => [ - DecoratorClassA::class, - ], - ], -]; -``` - -## Addons - -The hook logic is useful for decoupling the Friendica core logic, but its primary goal is to modularize Friendica in creating addons. - -Therefor you can either use the interfaces directly as shown above, or you can place your own `hooks.config.php` file inside a `static` directory directly under your addon core directory. -Friendica will automatically search these config files for each **activated** addon and register the given hooks. diff --git a/doc/StrategyHooks.md b/doc/StrategyHooks.md new file mode 100644 index 0000000000..cb355f93f0 --- /dev/null +++ b/doc/StrategyHooks.md @@ -0,0 +1,89 @@ +Friendica strategy Hooks +=========================================== + +* [Home](help) + +## Strategy hooks + +This type of hook is based on the [Strategy Design Pattern](https://refactoring.guru/design-patterns/strategy). + +A strategy class defines a possible implementation of a given interface based on a unique name. +Every name is possible as long as it's unique and not `null`. +Using an empty name (`''`) is possible as well and should be used as the "default" implementation. +To register a strategy, use the [`ICanRegisterInstance`](../src/Core/Hooks/Capabilities/ICanRegisterInstances.php) interface. + +After registration, a caller can automatically create this instance with the [`ICanCreateInstances`](../src/Core/Hooks/Capabilities/ICanCreateInstances.php) interface and the chosen name. + +This is useful in case there are different, possible implementations for the same purpose, like for logging, locking, caching, ... + +Normally, a config entry is used to choose the right implementation at runtime. +And if no config entry is set, the "default" implementation should be used. + +### Example + +```php +interface ExampleInterface +{ + public function testMethod(); +} + +public class ConcreteClassA implements ExampleInterface +{ + public function testMethod() + { + echo "concrete class A"; + } +} + +public class ConcreteClassB implements ExampleInterface +{ + public function testMethod() + { + echo "concrete class B"; + } +} + +/** @var \Friendica\Core\Hooks\Capabilities\ICanRegisterInstances $instanceRegister */ +$instanceRegister->registerStrategy(ExampleInterface::class, ConcreteClassA::class, 'A'); +$instanceRegister->registerStrategy(ExampleInterface::class, ConcreteClassB::class, 'B'); + +/** @var \Friendica\Core\Hooks\Capabilities\ICanCreateInstances $instanceManager */ +/** @var ConcreteClassA $concreteClass */ +$concreteClass = $instanceManager->create(ExampleInterface::class, 'A'); + +$concreteClass->testMethod(); +// output: +// "concrete class A"; +``` + +## hooks.config.php + +To avoid registering all strategies manually inside the code, Friendica introduced the [`hooks.config.php`](../static/hooks.config.php) file. + +There, you can register all kind of strategies in one file. + +### [`HookType::STRATEGY`](../src/Core/Hooks/Capabilities/HookType.php) + +For each given interface, a list of key-value pairs can be set, where the key is the concrete implementation class and the value is an array of unique names. + +### Example + +```php +use Friendica\Core\Hooks\Capabilities\BehavioralHookType as H; + +return [ + H::STRATEGY => [ + ExampleInterface::class => [ + ConcreteClassA::class => ['A'], + ConcreteClassB::class => ['B'], + ], + ], +]; +``` + +## Addons + +The hook logic is useful for decoupling the Friendica core logic, but its primary goal is to modularize Friendica in creating addons. + +Therefor you can either use the interfaces directly as shown above, or you can place your own `hooks.config.php` file inside a `static` directory directly under your addon core directory. +Friendica will automatically search these config files for each **activated** addon and register the given hooks. diff --git a/src/Core/Hooks/Capabilities/HookType.php b/src/Core/Hooks/Capabilities/BehavioralHookType.php similarity index 82% rename from src/Core/Hooks/Capabilities/HookType.php rename to src/Core/Hooks/Capabilities/BehavioralHookType.php index 6fea41e17a..6336406e63 100644 --- a/src/Core/Hooks/Capabilities/HookType.php +++ b/src/Core/Hooks/Capabilities/BehavioralHookType.php @@ -21,7 +21,11 @@ namespace Friendica\Core\Hooks\Capabilities; -interface HookType +/** + * An enum of hook types, based on behavioral design patterns + * @see https://refactoring.guru/design-patterns/behavioral-patterns + */ +interface BehavioralHookType { /** * Defines the key for the list of strategy-hooks. @@ -29,11 +33,5 @@ interface HookType * @see https://refactoring.guru/design-patterns/strategy */ const STRATEGY = 'strategy'; - /** - * Defines the key for the list of decorator-hooks. - * - * @see https://refactoring.guru/design-patterns/decorator - */ - const DECORATOR = 'decorator'; - const EVENT = 'event'; + const EVENT = 'event'; } diff --git a/src/Core/Hooks/Capabilities/ICanCreateInstances.php b/src/Core/Hooks/Capabilities/ICanCreateInstances.php index 9714304746..f2e4b8b0a1 100644 --- a/src/Core/Hooks/Capabilities/ICanCreateInstances.php +++ b/src/Core/Hooks/Capabilities/ICanCreateInstances.php @@ -22,7 +22,7 @@ namespace Friendica\Core\Hooks\Capabilities; /** - * creates special instance and decorator treatments for given classes + * creates special instances for given classes */ interface ICanCreateInstances { @@ -31,27 +31,11 @@ interface ICanCreateInstances * * The instance will be build based on the registered strategy and the (unique) name * - * In case, there are registered decorators for this class as well, all decorators of the list will be wrapped - * around the instance before returning it - * * @param string $class The fully-qualified name of the given class or interface which will get returned * @param string $name An arbitrary identifier to find a concrete instance strategy. * @param array $arguments Additional arguments, which can be passed to the constructor of "$class" at runtime * * @return object The concrete instance of the type "$class" */ - public function createWithName(string $class, string $name, array $arguments = []): object; - - /** - * Returns a new instance of a given class - * - * In case, there are registered decorators for this class as well, all decorators of the list will be wrapped - * around the instance before returning it - * - * @param string $class The fully-qualified name of the given class or interface which will get returned - * @param array $arguments Additional arguments, which can be passed to the constructor of "$class" at runtime - * - * @return object The concrete instance of the type "$class" - */ - public function create(string $class, array $arguments = []): object; + public function create(string $class, string $name, array $arguments = []): object; } diff --git a/src/Core/Hooks/Capabilities/ICanRegisterInstances.php b/src/Core/Hooks/Capabilities/ICanRegisterInstances.php index 23a77ab35b..f7689bbee7 100644 --- a/src/Core/Hooks/Capabilities/ICanRegisterInstances.php +++ b/src/Core/Hooks/Capabilities/ICanRegisterInstances.php @@ -24,7 +24,7 @@ namespace Friendica\Core\Hooks\Capabilities; use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; /** - * Register strategies and decorator/treatment handling for given classes + * Register strategies for given classes */ interface ICanRegisterInstances { @@ -43,21 +43,4 @@ interface ICanRegisterInstances * @throws HookRegisterArgumentException in case the given class for the interface isn't valid or already set */ public function registerStrategy(string $interface, string $class, ?string $name = null): self; - - /** - * Register a new decorator for a given class or interface - * - * @see https://refactoring.guru/design-patterns/decorator - * - * @note Decorator attach new behaviors to classes without changing them or without letting them know about it. - * - * @param string $class The fully-qualified class or interface name, which gets decorated by a class - * @param string $decoratorClass The fully-qualified name of the class which mimics the given class or interface and adds new functionality - * A placeholder for dependencies is possible as well - * - * @return $this This interface for chain-calls - * - * @throws HookRegisterArgumentException in case the given class for the class or interface isn't valid - */ - public function registerDecorator(string $class, string $decoratorClass): self; } diff --git a/src/Core/Hooks/Model/DiceInstanceManager.php b/src/Core/Hooks/Model/DiceInstanceManager.php index efbee444aa..7c35dfe4df 100644 --- a/src/Core/Hooks/Model/DiceInstanceManager.php +++ b/src/Core/Hooks/Model/DiceInstanceManager.php @@ -36,7 +36,6 @@ use Friendica\Core\Hooks\Util\HookFileManager; class DiceInstanceManager implements ICanCreateInstances, ICanRegisterInstances { protected $instance = []; - protected $decorator = []; /** @var Dice */ protected $dice; @@ -60,52 +59,12 @@ class DiceInstanceManager implements ICanCreateInstances, ICanRegisterInstances } /** {@inheritDoc} */ - public function registerDecorator(string $class, string $decoratorClass): ICanRegisterInstances - { - $this->decorator[$class][] = $decoratorClass; - - return $this; - } - - /** {@inheritDoc} */ - public function create(string $class, array $arguments = []): object - { - $instanceClassName = $class; - $instanceRule = $this->dice->getRule($instanceClassName) ?? []; - - $instanceRule = array_replace_recursive($instanceRule, [ - 'constructParams' => $arguments, - ]); - - $this->dice = $this->dice->addRule($instanceClassName, $instanceRule); - - foreach ($this->decorator[$class] ?? [] as $decorator) { - $dependencyRule = $this->dice->getRule($decorator); - for ($i = 0; $i < count($dependencyRule['call'] ?? []); $i++) { - $dependencyRule['call'][$i][1] = [[Dice::INSTANCE => $instanceClassName]]; - } - $dependencyRule['constructParams'] = $arguments; - $dependencyRule['substitutions'] = [ - $class => $instanceClassName, - ]; - - $this->dice = $this->dice->addRule($decorator, $dependencyRule); - - $instanceClassName = $decorator; - } - - return $this->dice->create($instanceClassName); - } - - /** {@inheritDoc} */ - public function createWithName(string $class, string $name, array $arguments = []): object + public function create(string $class, string $name, array $arguments = []): object { if (empty($this->instance[$class][$name])) { throw new HookInstanceException(sprintf('The class with the name %s isn\'t registered for the class or interface %s', $name, $class)); } - $instanceClassName = $this->instance[$class][$name]; - - return $this->create($instanceClassName, $arguments); + return $this->dice->create($this->instance[$class][$name], $arguments); } } diff --git a/src/Core/Hooks/Util/HookFileManager.php b/src/Core/Hooks/Util/HookFileManager.php index e25641e367..b83f2b49c6 100644 --- a/src/Core/Hooks/Util/HookFileManager.php +++ b/src/Core/Hooks/Util/HookFileManager.php @@ -22,7 +22,7 @@ namespace Friendica\Core\Hooks\Util; use Friendica\Core\Addon\Capabilities\ICanLoadAddons; -use Friendica\Core\Hooks\Capabilities\HookType; +use Friendica\Core\Hooks\Capabilities\BehavioralHookType; use Friendica\Core\Hooks\Capabilities\ICanRegisterInstances; use Friendica\Core\Hooks\Exceptions\HookConfigException; @@ -63,7 +63,7 @@ class HookFileManager foreach ($this->hookConfig as $hookType => $classList) { switch ($hookType) { - case HookType::STRATEGY: + case BehavioralHookType::STRATEGY: foreach ($classList as $interface => $strategy) { foreach ($strategy as $dependencyName => $names) { if (is_array($names)) { @@ -76,17 +76,6 @@ class HookFileManager } } break; - case HookType::DECORATOR: - foreach ($classList as $interface => $decorators) { - if (is_array($decorators)) { - foreach ($decorators as $decorator) { - $instanceRegister->registerDecorator($interface, $decorator); - } - } else { - $instanceRegister->registerDecorator($interface, $decorators); - } - } - break; } } } diff --git a/src/Core/Logger/Factory/Logger.php b/src/Core/Logger/Factory/Logger.php index 3fde878a34..81f8887efc 100644 --- a/src/Core/Logger/Factory/Logger.php +++ b/src/Core/Logger/Factory/Logger.php @@ -24,6 +24,8 @@ namespace Friendica\Core\Logger\Factory; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hooks\Capabilities\ICanCreateInstances; use Friendica\Core\Logger\Capabilities\LogChannel; +use Friendica\Core\Logger\Type\ProfilerLogger as ProfilerLoggerClass; +use Friendica\Util\Profiler; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Throwable; @@ -41,7 +43,7 @@ class Logger $this->channel = $channel; } - public function create(ICanCreateInstances $createInstances, IManageConfigValues $config): LoggerInterface + public function create(ICanCreateInstances $createInstances, IManageConfigValues $config, Profiler $profiler): LoggerInterface { if (empty($config->get('system', 'debugging') ?? false)) { return new NullLogger(); @@ -50,7 +52,13 @@ class Logger $name = $config->get('system', 'logger_config') ?? ''; try { - return $createInstances->createWithName(LoggerInterface::class, $name, [$this->channel]); + /** @var LoggerInterface $logger */ + $logger = $createInstances->create(LoggerInterface::class, $name, [$this->channel]); + if ($config->get('system', 'profiling') ?? false) { + return new ProfilerLoggerClass($logger, $profiler); + } else { + return $logger; + } } catch (Throwable $e) { // No logger ... return new NullLogger(); diff --git a/static/hooks.config.php b/static/hooks.config.php index bc235913c5..d46762f871 100644 --- a/static/hooks.config.php +++ b/static/hooks.config.php @@ -19,7 +19,7 @@ * */ -use Friendica\Core\Hooks\Capabilities\HookType as H; +use Friendica\Core\Hooks\Capabilities\BehavioralHookType as H; use Friendica\Core\Logger\Type; use Psr\Log; @@ -31,9 +31,4 @@ return [ Type\StreamLogger::class => ['stream'], ], ], - H::DECORATOR => [ - Log\LoggerInterface::class => [ - Type\ProfilerLogger::class, - ], - ], ]; diff --git a/tests/Util/Hooks/InstanceMocks/FakeInstance.php b/tests/Util/Hooks/InstanceMocks/FakeInstance.php index 9a0e9dd6fa..64fe2c5adf 100644 --- a/tests/Util/Hooks/InstanceMocks/FakeInstance.php +++ b/tests/Util/Hooks/InstanceMocks/FakeInstance.php @@ -21,7 +21,7 @@ namespace Friendica\Test\Util\Hooks\InstanceMocks; -class FakeInstance +class FakeInstance implements IAmADecoratedInterface { protected $aText = null; protected $cBool = null; @@ -39,6 +39,8 @@ class FakeInstance $this->aText = $aText; $this->cBool = $cBool; $this->bText = $bText; + + return ''; } public function getAText(): ?string diff --git a/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php b/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php index af4db96c44..40ab78a257 100644 --- a/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php +++ b/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php @@ -25,14 +25,14 @@ class FakeInstanceDecorator implements IAmADecoratedInterface { public static $countInstance = 0; + const PREFIX = 'prefix1'; + /** @var IAmADecoratedInterface */ protected $orig; - protected $prefix = ''; - public function __construct(IAmADecoratedInterface $orig, string $prefix = '') + public function __construct(IAmADecoratedInterface $orig) { $this->orig = $orig; - $this->prefix = $prefix; self::$countInstance++; } @@ -44,16 +44,16 @@ class FakeInstanceDecorator implements IAmADecoratedInterface public function getAText(): ?string { - return $this->prefix . $this->orig->getAText(); + return static::PREFIX . $this->orig->getAText(); } public function getBText(): ?string { - return $this->prefix . $this->orig->getBText(); + return static::PREFIX . $this->orig->getBText(); } public function getCBool(): ?bool { - return $this->prefix . $this->orig->getCBool(); + return static::PREFIX . $this->orig->getCBool(); } } diff --git a/tests/src/Core/Addon/Model/AddonLoaderTest.php b/tests/src/Core/Addon/Model/AddonLoaderTest.php index 4496cd42f6..be125535f3 100644 --- a/tests/src/Core/Addon/Model/AddonLoaderTest.php +++ b/tests/src/Core/Addon/Model/AddonLoaderTest.php @@ -54,7 +54,7 @@ class AddonLoaderTest extends MockedTest [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], @@ -79,7 +79,7 @@ EOF; 'addon/testaddon1/static/hooks.config.php' => $this->content, ], 'assertion' => [ - \Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], @@ -94,7 +94,7 @@ EOF; 'addon/testaddon2/static/hooks.config.php' => $this->content, ], 'assertion' => [ - \Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => ['', ''], ], @@ -118,7 +118,7 @@ EOF; 'addon/testaddon2/static/hooks.config.php' => $this->content, ], 'assertion' => [ - \Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], diff --git a/tests/src/Core/Hooks/Model/InstanceManagerTest.php b/tests/src/Core/Hooks/Model/InstanceManagerTest.php index 200500d63e..de2bdc6b83 100644 --- a/tests/src/Core/Hooks/Model/InstanceManagerTest.php +++ b/tests/src/Core/Hooks/Model/InstanceManagerTest.php @@ -22,25 +22,27 @@ namespace Friendica\Test\src\Core\Hooks\Model; use Dice\Dice; +use Friendica\Core\Hooks\Exceptions\HookInstanceException; +use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; use Friendica\Core\Hooks\Model\DiceInstanceManager; +use Friendica\Core\Hooks\Util\HookFileManager; use Friendica\Test\MockedTest; use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance; use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator; use Friendica\Test\Util\Hooks\InstanceMocks\IAmADecoratedInterface; +use Mockery\MockInterface; class InstanceManagerTest extends MockedTest { - public function testEqualButNotSameInstance() + /** @var HookFileManager|MockInterface */ + protected $hookFileManager; + + protected function setUp(): void { - $instance = new DiceInstanceManager(new Dice()); + parent::setUp(); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class); - - $getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake'); - $getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake'); - - self::assertEquals($getInstanceA, $getInstanceB); - self::assertNotSame($getInstanceA, $getInstanceB); + $this->hookFileManager = \Mockery::mock(HookFileManager::class); + $this->hookFileManager->shouldReceive('setupHooks')->withAnyArgs(); } protected function tearDown(): void @@ -50,6 +52,19 @@ class InstanceManagerTest extends MockedTest parent::tearDown(); } + public function testEqualButNotSameInstance() + { + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); + + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); + + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake'); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake'); + + self::assertEquals($getInstanceA, $getInstanceB); + self::assertNotSame($getInstanceA, $getInstanceB); + } + public function dataTests(): array { return [ @@ -79,9 +94,9 @@ class InstanceManagerTest extends MockedTest /** * @dataProvider dataTests */ - public function testInstanceWithConstructorAnonymArgs(string $aString = null, bool $cBool = null, string $bString = null) + public function testInstanceWithArgs(string $aString = null, bool $cBool = null, string $bString = null) { - $instance = new DiceInstanceManager(new Dice()); + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); $args = []; @@ -95,45 +110,12 @@ class InstanceManagerTest extends MockedTest $args[] = $cBool; } - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake'); + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args); /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake'); - - self::assertEquals($getInstanceA, $getInstanceB); - self::assertNotSame($getInstanceA, $getInstanceB); - self::assertEquals($aString, $getInstanceA->getAText()); - self::assertEquals($aString, $getInstanceB->getAText()); - self::assertEquals($bString, $getInstanceA->getBText()); - self::assertEquals($bString, $getInstanceB->getBText()); - self::assertEquals($cBool, $getInstanceA->getCBool()); - self::assertEquals($cBool, $getInstanceB->getCBool()); - } - - /** - * @dataProvider dataTests - */ - public function testInstanceConstructorAndGetInstanceWithNamedArgs(string $aString = null, bool $cBool = null, string $bString = null) - { - $instance = new DiceInstanceManager(new Dice()); - - $args = []; - - if (isset($aString)) { - $args[] = $aString; - } - if (isset($cBool)) { - $args[] = $cBool; - } - - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - - /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]); - /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake', $args); self::assertEquals($getInstanceA, $getInstanceB); self::assertNotSame($getInstanceA, $getInstanceB); @@ -150,24 +132,27 @@ class InstanceManagerTest extends MockedTest */ public function testInstanceWithTwoStrategies(string $aString = null, bool $cBool = null, string $bString = null) { - $instance = new DiceInstanceManager(new Dice()); + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); $args = []; if (isset($aString)) { $args[] = $aString; } + if (isset($bString)) { + $args[] = $bString; + } if (isset($cBool)) { $args[] = $cBool; } - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake23'); /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]); + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args); /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake23', [$bString]); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake23', $args); self::assertEquals($getInstanceA, $getInstanceB); self::assertNotSame($getInstanceA, $getInstanceB); @@ -180,79 +165,74 @@ class InstanceManagerTest extends MockedTest } /** - * @dataProvider dataTests + * Test the exception in case the interface was already registered */ - public function testDecorator(string $aString = null, bool $cBool = null, string $bString = null) + public function testDoubleRegister() { - $instance = new DiceInstanceManager(new Dice()); + self::expectException(HookRegisterArgumentException::class); + self::expectExceptionMessage(sprintf('A class with the name %s is already set for the interface %s', 'fake', IAmADecoratedInterface::class)); - $args = []; - - if (isset($aString)) { - $args[] = $aString; - } - if (isset($cBool)) { - $args[] = $cBool; - } - - $prefix = 'prefix1'; - - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args); - $instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]); - - /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]); - /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake23', [$bString]); - - self::assertEquals(2, FakeInstanceDecorator::$countInstance); - self::assertEquals($getInstanceA, $getInstanceB); - self::assertNotSame($getInstanceA, $getInstanceB); - self::assertEquals($prefix . $aString, $getInstanceA->getAText()); - self::assertEquals($prefix . $aString, $getInstanceB->getAText()); - self::assertEquals($prefix . $bString, $getInstanceA->getBText()); - self::assertEquals($prefix . $bString, $getInstanceB->getBText()); - self::assertEquals($prefix . $cBool, $getInstanceA->getCBool()); - self::assertEquals($prefix . $cBool, $getInstanceB->getCBool()); + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); } /** + * Test the exception in case the name of the instance isn't registered + */ + public function testWrongInstanceName() + { + self::expectException(HookInstanceException::class ); + self::expectExceptionMessage(sprintf('The class with the name %s isn\'t registered for the class or interface %s', 'fake', IAmADecoratedInterface::class)); + + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); + $instance->create(IAmADecoratedInterface::class, 'fake'); + } + + /** + * Test in case there are already some rules + * * @dataProvider dataTests */ - public function testTwoDecoratorWithPrefix(string $aString = null, bool $cBool = null, string $bString = null) + public function testWithGivenRules(string $aString = null, bool $cBool = null, string $bString = null) { - $instance = new DiceInstanceManager(new Dice()); - $args = []; if (isset($aString)) { $args[] = $aString; } + if (isset($bString)) { + $args[] = $bString; + } + + $dice = (new Dice())->addRules([ + FakeInstance::class => [ + 'constructParams' => $args, + ], + ]); + + $args = []; + if (isset($cBool)) { $args[] = $cBool; } - $prefix = 'prefix1'; + $instance = new DiceInstanceManager($dice, $this->hookFileManager); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args); - $instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]); - $instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]); + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args); /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake23', [$bString]); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake', $args); - self::assertEquals(4, FakeInstanceDecorator::$countInstance); self::assertEquals($getInstanceA, $getInstanceB); self::assertNotSame($getInstanceA, $getInstanceB); - self::assertEquals($prefix . $aString, $getInstanceA->getAText()); - self::assertEquals($prefix . $aString, $getInstanceB->getAText()); - self::assertEquals($prefix . $bString, $getInstanceA->getBText()); - self::assertEquals($prefix . $bString, $getInstanceB->getBText()); - self::assertEquals($prefix . $cBool, $getInstanceA->getCBool()); - self::assertEquals($prefix . $cBool, $getInstanceB->getCBool()); + self::assertEquals($aString, $getInstanceA->getAText()); + self::assertEquals($aString, $getInstanceB->getAText()); + self::assertEquals($bString, $getInstanceA->getBText()); + self::assertEquals($bString, $getInstanceB->getBText()); + self::assertEquals($cBool, $getInstanceA->getCBool()); + self::assertEquals($cBool, $getInstanceB->getCBool()); } } diff --git a/tests/src/Core/Hooks/Util/HookFileManagerTest.php b/tests/src/Core/Hooks/Util/HookFileManagerTest.php index cbd9a63b1a..746bfa7ca3 100644 --- a/tests/src/Core/Hooks/Util/HookFileManagerTest.php +++ b/tests/src/Core/Hooks/Util/HookFileManagerTest.php @@ -31,12 +31,12 @@ class HookFileManagerTest extends MockedTest [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], ], - \Friendica\Core\Hooks\Capabilities\HookType::DECORATOR => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::DECORATOR => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class, ], @@ -56,12 +56,12 @@ EOF, [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => '', ], ], - \Friendica\Core\Hooks\Capabilities\HookType::DECORATOR => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::DECORATOR => [ \Psr\Log\LoggerInterface::class => \Psr\Log\NullLogger::class, ], ]; @@ -79,7 +79,7 @@ EOF, [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], @@ -87,7 +87,7 @@ return [ ]; EOF, 'addonsArray' => [ - \Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => ['null'], ], @@ -104,7 +104,7 @@ EOF, [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], @@ -112,7 +112,7 @@ return [ ]; EOF, 'addonsArray' => [ - \Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => 'null', ], @@ -130,7 +130,7 @@ EOF, [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], @@ -138,7 +138,7 @@ return [ ]; EOF, 'addonsArray' => [ - \Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ], @@ -163,7 +163,7 @@ return [ ]; EOF, 'addonsArray' => [ - \Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ \Psr\Log\LoggerInterface::class => [ \Psr\Log\NullLogger::class => [''], ],