-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
271 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Prooph\EventMachine\Container; | ||
|
||
use Psr\Container\ContainerInterface; | ||
|
||
final class ReflectionBasedContainer implements ContainerInterface | ||
{ | ||
/** | ||
* @var array | ||
*/ | ||
private $aliasMap; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private $serviceFactoryMap; | ||
|
||
private $serviceFactory; | ||
|
||
public function __construct($serviceFactory, array $aliasMap = [], array $serviceFactoryMap = null) | ||
{ | ||
if(null === $serviceFactoryMap) { | ||
$serviceFactoryMap = $this->scanServiceFactory($serviceFactory); | ||
} | ||
|
||
$this->serviceFactory = $serviceFactory; | ||
$this->aliasMap = $aliasMap; | ||
$this->serviceFactoryMap = $serviceFactoryMap; | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function get($id) | ||
{ | ||
$id = $this->aliasMap[$id] ?? $id; | ||
|
||
if(!$this->has($id)) { | ||
throw ServiceNotFound::withServiceId($id); | ||
} | ||
|
||
return ([$this->serviceFactory, $this->serviceFactoryMap[$id]])(); | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function has($id) | ||
{ | ||
$id = $this->aliasMap[$id] ?? $id; | ||
|
||
return array_key_exists($id, $this->serviceFactoryMap); | ||
} | ||
|
||
/** | ||
* Cache the array and pass it to constructor again to avoid scanning of service factory | ||
* | ||
* @return array | ||
*/ | ||
public function getServiceFactoryMap(): array | ||
{ | ||
return $this->serviceFactoryMap; | ||
} | ||
|
||
private function scanServiceFactory($serviceFactory): array | ||
{ | ||
$serviceFactoryMap = []; | ||
|
||
$ref = new \ReflectionClass($serviceFactory); | ||
|
||
foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { | ||
if($returnType = $method->getReturnType()) { | ||
if(!$returnType->isBuiltin()) { | ||
$serviceFactoryMap[$method->getReturnType()->getName()] = $method->getName(); | ||
} | ||
} | ||
} | ||
|
||
return $serviceFactoryMap; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Prooph\EventMachine\Container; | ||
|
||
trait ServiceRegistry | ||
{ | ||
/** | ||
* @var array | ||
*/ | ||
private $serviceRegistry = []; | ||
|
||
private function makeSingleton(string $serviceId, callable $factory) { | ||
if(!isset($this->serviceRegistry[$serviceId])) { | ||
$this->serviceRegistry[$serviceId] = $factory(); | ||
} | ||
|
||
return $this->serviceRegistry[$serviceId]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Prooph\EventMachineTest\Container; | ||
|
||
use Codeliner\ArrayReader\ArrayReader; | ||
use Prooph\Common\Messaging\MessageFactory; | ||
use Prooph\EventMachine\Container\ReflectionBasedContainer; | ||
use Prooph\EventMachine\Container\ServiceRegistry; | ||
use Prooph\EventMachine\JsonSchema\JsonSchema; | ||
use Prooph\EventMachine\JsonSchema\JsonSchemaAssertion; | ||
use Prooph\EventMachine\JsonSchema\JustinRainbowJsonSchemaAssertion; | ||
use Prooph\EventMachine\Messaging\GenericJsonSchemaMessageFactory; | ||
use Prooph\EventMachineTest\BasicTestCase; | ||
use ProophExample\Aggregate\UserDescription; | ||
use ProophExample\Messaging\Command; | ||
|
||
final class ReflectionBasedContainerTest extends BasicTestCase | ||
{ | ||
private $serviceFactory; | ||
|
||
/** | ||
* @var ReflectionBasedContainer | ||
*/ | ||
private $reflectionBasedContainer; | ||
|
||
protected function setUp() | ||
{ | ||
$this->serviceFactory = $this->buildServiceFactory([ | ||
'event_machine' => [ | ||
'command_map' => [ | ||
Command::REGISTER_USER => JsonSchema::object([ | ||
UserDescription::IDENTIFIER => ['type' => 'string', 'minLength' => 2] | ||
]) | ||
], | ||
'event_map' => [], | ||
] | ||
]); | ||
|
||
$this->reflectionBasedContainer = new ReflectionBasedContainer($this->serviceFactory, [ | ||
'CommandFactory' => MessageFactory::class | ||
]); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function it_scans_service_factory_to_identify_factory_methods() | ||
{ | ||
$serviceFactoryMap = $this->reflectionBasedContainer->getServiceFactoryMap(); | ||
|
||
$this->assertEquals([ | ||
JsonSchemaAssertion::class => 'jsonSchemaAssertion', | ||
MessageFactory::class => 'messageFactory' | ||
], $serviceFactoryMap); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function it_uses_service_factory_method_to_get_service() | ||
{ | ||
$this->assertTrue($this->reflectionBasedContainer->has(MessageFactory::class)); | ||
|
||
/** @var MessageFactory $messageFactory */ | ||
$messageFactory = $this->reflectionBasedContainer->get(MessageFactory::class); | ||
|
||
$this->assertInstanceOf(GenericJsonSchemaMessageFactory::class, $messageFactory); | ||
|
||
//Test if config is passed correctly to message factory by checking that validation fails! | ||
$this->expectException(\InvalidArgumentException::class); | ||
$this->expectExceptionMessage('Payload validation of RegisterUser failed: [userId] Must be at least 2 characters long'); | ||
|
||
$command = $messageFactory->createMessageFromArray(Command::REGISTER_USER, [ | ||
'payload' => [UserDescription::IDENTIFIER => '1'] | ||
]); | ||
|
||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function it_uses_alias_to_get_service() | ||
{ | ||
$this->assertTrue($this->reflectionBasedContainer->has('CommandFactory')); | ||
|
||
/** @var MessageFactory $messageFactory */ | ||
$messageFactory = $this->reflectionBasedContainer->get('CommandFactory'); | ||
|
||
$this->assertInstanceOf(GenericJsonSchemaMessageFactory::class, $messageFactory); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function it_does_not_scan_service_factory_if_service_factory_map_is_provided() | ||
{ | ||
$container = new ReflectionBasedContainer($this->serviceFactory, [ | ||
'CommandFactory' => MessageFactory::class | ||
], [ | ||
JsonSchemaAssertion::class => 'jsonSchemaAssertion', | ||
]); | ||
|
||
//Container should not see message factory factory method because it is missing in the cached map | ||
$this->assertFalse($container->has('CommandMap')); | ||
$this->assertFalse($container->has(MessageFactory::class)); | ||
$this->assertTrue($container->has(JsonSchemaAssertion::class)); | ||
|
||
$assertion = $container->get(JsonSchemaAssertion::class); | ||
|
||
$this->assertInstanceOf(JsonSchemaAssertion::class, $assertion); | ||
} | ||
|
||
private function buildServiceFactory(array $appConfig) | ||
{ | ||
return new class($appConfig) { | ||
|
||
use ServiceRegistry; | ||
|
||
/** | ||
* @var ArrayReader | ||
*/ | ||
private $appConfig; | ||
|
||
public function __construct(array $appConfig) | ||
{ | ||
$this->appConfig = new ArrayReader($appConfig); | ||
} | ||
|
||
public function jsonSchemaAssertion(): JsonSchemaAssertion { | ||
return $this->makeSingleton(JsonSchemaAssertion::class, function () { | ||
return new JustinRainbowJsonSchemaAssertion(); | ||
}); | ||
} | ||
|
||
public function messageFactory(): MessageFactory | ||
{ | ||
return $this->makeSingleton(MessageFactory::class, function () { | ||
return new GenericJsonSchemaMessageFactory( | ||
$this->appConfig->arrayValue('event_machine.command_map', []), | ||
$this->appConfig->arrayValue('event_machine.event_map', []), | ||
$this->jsonSchemaAssertion() | ||
); | ||
}); | ||
} | ||
|
||
protected function nonFactoryHelperMethod() | ||
{ | ||
|
||
} | ||
|
||
public function methodReturningBuiltInType(): string | ||
{ | ||
return "not a service"; | ||
} | ||
|
||
public function methodWithoutReturnType() | ||
{ | ||
|
||
} | ||
}; | ||
} | ||
} |