Add tests for InstanceManager and remove Decorator hook logic (avoid complex Dice logic)

This commit is contained in:
Philipp 2023-07-17 00:10:15 +02:00
parent 527622df4a
commit 93af6f0564
No known key found for this signature in database
GPG key ID: 24A7501396EB5432
14 changed files with 218 additions and 390 deletions

View file

@ -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.

89
doc/StrategyHooks.md Normal file
View file

@ -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.

View file

@ -21,7 +21,11 @@
namespace Friendica\Core\Hooks\Capabilities; 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. * Defines the key for the list of strategy-hooks.
@ -29,11 +33,5 @@ interface HookType
* @see https://refactoring.guru/design-patterns/strategy * @see https://refactoring.guru/design-patterns/strategy
*/ */
const STRATEGY = 'strategy'; const STRATEGY = 'strategy';
/** const EVENT = 'event';
* Defines the key for the list of decorator-hooks.
*
* @see https://refactoring.guru/design-patterns/decorator
*/
const DECORATOR = 'decorator';
const EVENT = 'event';
} }

View file

@ -22,7 +22,7 @@
namespace Friendica\Core\Hooks\Capabilities; namespace Friendica\Core\Hooks\Capabilities;
/** /**
* creates special instance and decorator treatments for given classes * creates special instances for given classes
*/ */
interface ICanCreateInstances interface ICanCreateInstances
{ {
@ -31,27 +31,11 @@ interface ICanCreateInstances
* *
* The instance will be build based on the registered strategy and the (unique) name * 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 $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 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 * @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" * @return object The concrete instance of the type "$class"
*/ */
public function createWithName(string $class, string $name, array $arguments = []): object; public function create(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;
} }

View file

@ -24,7 +24,7 @@ namespace Friendica\Core\Hooks\Capabilities;
use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException;
/** /**
* Register strategies and decorator/treatment handling for given classes * Register strategies for given classes
*/ */
interface ICanRegisterInstances interface ICanRegisterInstances
{ {
@ -43,21 +43,4 @@ interface ICanRegisterInstances
* @throws HookRegisterArgumentException in case the given class for the interface isn't valid or already set * @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; 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;
} }

View file

@ -36,7 +36,6 @@ use Friendica\Core\Hooks\Util\HookFileManager;
class DiceInstanceManager implements ICanCreateInstances, ICanRegisterInstances class DiceInstanceManager implements ICanCreateInstances, ICanRegisterInstances
{ {
protected $instance = []; protected $instance = [];
protected $decorator = [];
/** @var Dice */ /** @var Dice */
protected $dice; protected $dice;
@ -60,52 +59,12 @@ class DiceInstanceManager implements ICanCreateInstances, ICanRegisterInstances
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
public function registerDecorator(string $class, string $decoratorClass): ICanRegisterInstances public function create(string $class, string $name, array $arguments = []): object
{
$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
{ {
if (empty($this->instance[$class][$name])) { 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)); 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->dice->create($this->instance[$class][$name], $arguments);
return $this->create($instanceClassName, $arguments);
} }
} }

View file

@ -22,7 +22,7 @@
namespace Friendica\Core\Hooks\Util; namespace Friendica\Core\Hooks\Util;
use Friendica\Core\Addon\Capabilities\ICanLoadAddons; 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\Capabilities\ICanRegisterInstances;
use Friendica\Core\Hooks\Exceptions\HookConfigException; use Friendica\Core\Hooks\Exceptions\HookConfigException;
@ -63,7 +63,7 @@ class HookFileManager
foreach ($this->hookConfig as $hookType => $classList) { foreach ($this->hookConfig as $hookType => $classList) {
switch ($hookType) { switch ($hookType) {
case HookType::STRATEGY: case BehavioralHookType::STRATEGY:
foreach ($classList as $interface => $strategy) { foreach ($classList as $interface => $strategy) {
foreach ($strategy as $dependencyName => $names) { foreach ($strategy as $dependencyName => $names) {
if (is_array($names)) { if (is_array($names)) {
@ -76,17 +76,6 @@ class HookFileManager
} }
} }
break; 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;
} }
} }
} }

View file

@ -24,6 +24,8 @@ namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capabilities\ICanCreateInstances; use Friendica\Core\Hooks\Capabilities\ICanCreateInstances;
use Friendica\Core\Logger\Capabilities\LogChannel; 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\LoggerInterface;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
use Throwable; use Throwable;
@ -41,7 +43,7 @@ class Logger
$this->channel = $channel; $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)) { if (empty($config->get('system', 'debugging') ?? false)) {
return new NullLogger(); return new NullLogger();
@ -50,7 +52,13 @@ class Logger
$name = $config->get('system', 'logger_config') ?? ''; $name = $config->get('system', 'logger_config') ?? '';
try { 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) { } catch (Throwable $e) {
// No logger ... // No logger ...
return new NullLogger(); return new NullLogger();

View file

@ -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 Friendica\Core\Logger\Type;
use Psr\Log; use Psr\Log;
@ -31,9 +31,4 @@ return [
Type\StreamLogger::class => ['stream'], Type\StreamLogger::class => ['stream'],
], ],
], ],
H::DECORATOR => [
Log\LoggerInterface::class => [
Type\ProfilerLogger::class,
],
],
]; ];

View file

@ -21,7 +21,7 @@
namespace Friendica\Test\Util\Hooks\InstanceMocks; namespace Friendica\Test\Util\Hooks\InstanceMocks;
class FakeInstance class FakeInstance implements IAmADecoratedInterface
{ {
protected $aText = null; protected $aText = null;
protected $cBool = null; protected $cBool = null;
@ -39,6 +39,8 @@ class FakeInstance
$this->aText = $aText; $this->aText = $aText;
$this->cBool = $cBool; $this->cBool = $cBool;
$this->bText = $bText; $this->bText = $bText;
return '';
} }
public function getAText(): ?string public function getAText(): ?string

View file

@ -25,14 +25,14 @@ class FakeInstanceDecorator implements IAmADecoratedInterface
{ {
public static $countInstance = 0; public static $countInstance = 0;
const PREFIX = 'prefix1';
/** @var IAmADecoratedInterface */ /** @var IAmADecoratedInterface */
protected $orig; protected $orig;
protected $prefix = '';
public function __construct(IAmADecoratedInterface $orig, string $prefix = '') public function __construct(IAmADecoratedInterface $orig)
{ {
$this->orig = $orig; $this->orig = $orig;
$this->prefix = $prefix;
self::$countInstance++; self::$countInstance++;
} }
@ -44,16 +44,16 @@ class FakeInstanceDecorator implements IAmADecoratedInterface
public function getAText(): ?string public function getAText(): ?string
{ {
return $this->prefix . $this->orig->getAText(); return static::PREFIX . $this->orig->getAText();
} }
public function getBText(): ?string public function getBText(): ?string
{ {
return $this->prefix . $this->orig->getBText(); return static::PREFIX . $this->orig->getBText();
} }
public function getCBool(): ?bool public function getCBool(): ?bool
{ {
return $this->prefix . $this->orig->getCBool(); return static::PREFIX . $this->orig->getCBool();
} }
} }

View file

@ -54,7 +54,7 @@ class AddonLoaderTest extends MockedTest
<?php <?php
return [ return [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
@ -79,7 +79,7 @@ EOF;
'addon/testaddon1/static/hooks.config.php' => $this->content, 'addon/testaddon1/static/hooks.config.php' => $this->content,
], ],
'assertion' => [ 'assertion' => [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
@ -94,7 +94,7 @@ EOF;
'addon/testaddon2/static/hooks.config.php' => $this->content, 'addon/testaddon2/static/hooks.config.php' => $this->content,
], ],
'assertion' => [ 'assertion' => [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => ['', ''], \Psr\Log\NullLogger::class => ['', ''],
], ],
@ -118,7 +118,7 @@ EOF;
'addon/testaddon2/static/hooks.config.php' => $this->content, 'addon/testaddon2/static/hooks.config.php' => $this->content,
], ],
'assertion' => [ 'assertion' => [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],

View file

@ -22,25 +22,27 @@
namespace Friendica\Test\src\Core\Hooks\Model; namespace Friendica\Test\src\Core\Hooks\Model;
use Dice\Dice; 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\Model\DiceInstanceManager;
use Friendica\Core\Hooks\Util\HookFileManager;
use Friendica\Test\MockedTest; use Friendica\Test\MockedTest;
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance; use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance;
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator; use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator;
use Friendica\Test\Util\Hooks\InstanceMocks\IAmADecoratedInterface; use Friendica\Test\Util\Hooks\InstanceMocks\IAmADecoratedInterface;
use Mockery\MockInterface;
class InstanceManagerTest extends MockedTest 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); $this->hookFileManager = \Mockery::mock(HookFileManager::class);
$this->hookFileManager->shouldReceive('setupHooks')->withAnyArgs();
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake');
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake');
self::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($getInstanceA, $getInstanceB);
} }
protected function tearDown(): void protected function tearDown(): void
@ -50,6 +52,19 @@ class InstanceManagerTest extends MockedTest
parent::tearDown(); 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 public function dataTests(): array
{ {
return [ return [
@ -79,9 +94,9 @@ class InstanceManagerTest extends MockedTest
/** /**
* @dataProvider dataTests * @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 = []; $args = [];
@ -95,45 +110,12 @@ class InstanceManagerTest extends MockedTest
$args[] = $cBool; $args[] = $cBool;
} }
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
/** @var IAmADecoratedInterface $getInstanceA */ /** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake'); $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
/** @var IAmADecoratedInterface $getInstanceB */ /** @var IAmADecoratedInterface $getInstanceB */
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake'); $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
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]);
self::assertEquals($getInstanceA, $getInstanceB); self::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($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) 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 = []; $args = [];
if (isset($aString)) { if (isset($aString)) {
$args[] = $aString; $args[] = $aString;
} }
if (isset($bString)) {
$args[] = $bString;
}
if (isset($cBool)) { if (isset($cBool)) {
$args[] = $cBool; $args[] = $cBool;
} }
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args); $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake23');
/** @var IAmADecoratedInterface $getInstanceA */ /** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]); $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
/** @var IAmADecoratedInterface $getInstanceB */ /** @var IAmADecoratedInterface $getInstanceB */
$getInstanceB = $instance->createWithName(IAmADecoratedInterface::class, 'fake23', [$bString]); $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake23', $args);
self::assertEquals($getInstanceA, $getInstanceB); self::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($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 = []; $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager);
$instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
if (isset($aString)) { $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
$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());
} }
/** /**
* 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 * @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 = []; $args = [];
if (isset($aString)) { if (isset($aString)) {
$args[] = $aString; $args[] = $aString;
} }
if (isset($bString)) {
$args[] = $bString;
}
$dice = (new Dice())->addRules([
FakeInstance::class => [
'constructParams' => $args,
],
]);
$args = [];
if (isset($cBool)) { if (isset($cBool)) {
$args[] = $cBool; $args[] = $cBool;
} }
$prefix = 'prefix1'; $instance = new DiceInstanceManager($dice, $this->hookFileManager);
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args);
$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]);
$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class);
/** @var IAmADecoratedInterface $getInstanceA */ /** @var IAmADecoratedInterface $getInstanceA */
$getInstanceA = $instance->createWithName(IAmADecoratedInterface::class, 'fake', [$bString]); $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
/** @var IAmADecoratedInterface $getInstanceB */ /** @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::assertEquals($getInstanceA, $getInstanceB);
self::assertNotSame($getInstanceA, $getInstanceB); self::assertNotSame($getInstanceA, $getInstanceB);
self::assertEquals($prefix . $aString, $getInstanceA->getAText()); self::assertEquals($aString, $getInstanceA->getAText());
self::assertEquals($prefix . $aString, $getInstanceB->getAText()); self::assertEquals($aString, $getInstanceB->getAText());
self::assertEquals($prefix . $bString, $getInstanceA->getBText()); self::assertEquals($bString, $getInstanceA->getBText());
self::assertEquals($prefix . $bString, $getInstanceB->getBText()); self::assertEquals($bString, $getInstanceB->getBText());
self::assertEquals($prefix . $cBool, $getInstanceA->getCBool()); self::assertEquals($cBool, $getInstanceA->getCBool());
self::assertEquals($prefix . $cBool, $getInstanceB->getCBool()); self::assertEquals($cBool, $getInstanceB->getCBool());
} }
} }

View file

@ -31,12 +31,12 @@ class HookFileManagerTest extends MockedTest
<?php <?php
return [ return [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
], ],
\Friendica\Core\Hooks\Capabilities\HookType::DECORATOR => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::DECORATOR => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class, \Psr\Log\NullLogger::class,
], ],
@ -56,12 +56,12 @@ EOF,
<?php <?php
return [ return [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::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, \Psr\Log\LoggerInterface::class => \Psr\Log\NullLogger::class,
], ],
]; ];
@ -79,7 +79,7 @@ EOF,
<?php <?php
return [ return [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
@ -87,7 +87,7 @@ return [
]; ];
EOF, EOF,
'addonsArray' => [ 'addonsArray' => [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => ['null'], \Psr\Log\NullLogger::class => ['null'],
], ],
@ -104,7 +104,7 @@ EOF,
<?php <?php
return [ return [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
@ -112,7 +112,7 @@ return [
]; ];
EOF, EOF,
'addonsArray' => [ 'addonsArray' => [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => 'null', \Psr\Log\NullLogger::class => 'null',
], ],
@ -130,7 +130,7 @@ EOF,
<?php <?php
return [ return [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
@ -138,7 +138,7 @@ return [
]; ];
EOF, EOF,
'addonsArray' => [ 'addonsArray' => [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
@ -163,7 +163,7 @@ return [
]; ];
EOF, EOF,
'addonsArray' => [ 'addonsArray' => [
\Friendica\Core\Hooks\Capabilities\HookType::STRATEGY => [ \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],