Skip to content

Commit

Permalink
Merge 97def31 into 3816dce
Browse files Browse the repository at this point in the history
  • Loading branch information
UFOMelkor committed Apr 17, 2018
2 parents 3816dce + 97def31 commit 76b3b03
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 154 deletions.
138 changes: 97 additions & 41 deletions src/DependencyInjection/Compiler/ProjectorPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,67 +18,123 @@
use ReflectionClass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;

final class ProjectorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
public function process(ContainerBuilder $container): void
{
$projectors = $container->findTaggedServiceIds(ProophEventStoreExtension::TAG_PROJECTION);
$projectorsIds = array_keys($container->findTaggedServiceIds(ProophEventStoreExtension::TAG_PROJECTION));

if (! $container->hasDefinition('prooph_event_store.projection_read_models_locator')
|| ! $container->hasDefinition('prooph_event_store.projection_manager_for_projections_locator')
|| ! $container->hasDefinition('prooph_event_store.projections_locator')
) {
return;
}

$readModelsLocator = [];
$projectionManagerForProjectionsLocator = [];
$projectionsLocator = [];

foreach ($projectors as $id => $projector) {
foreach ($projectorsIds as $id) {
$projectorDefinition = $container->getDefinition($id);
$projectorClass = new ReflectionClass($projectorDefinition->getClass());

$reflClass = new ReflectionClass($projectorDefinition->getClass());
if (! $reflClass->implementsInterface(ReadModelProjection::class) && ! $reflClass->implementsInterface(Projection::class)) {
throw new RuntimeException(sprintf('Tagged service "%" must implement "%s" or "%s" ', $id, ReadModelProjection::class, Projection::class));
}
self::assertProjectionHasAValidClass($id, $projectorClass);

$isReadModelProjector = $projectorClass->implementsInterface(ReadModelProjection::class);

$tags = $projectorDefinition->getTag(ProophEventStoreExtension::TAG_PROJECTION);
foreach ($tags as $tag) {
if (! isset($tag['projection_name'])) {
throw new RuntimeException(sprintf('"projection_name" argument is missing from on "prooph_event_store.projection" tagged service "%s"',
$id));
}

if (! isset($tag['projection_manager'])) {
throw new RuntimeException(sprintf('"projection_manager" argument is missing from on "prooph_event_store.projection" tagged service "%s"',
$id));
}

if (in_array(ReadModelProjection::class, class_implements($projectorDefinition->getClass()))) {
if (! isset($tag['read_model'])) {
throw new RuntimeException(sprintf('"read_model" argument is missing from on "prooph_event_store.projection" tagged service "%s"',
$id));
}
$container->setAlias(
sprintf('%s.%s.read_model', ProophEventStoreExtension::TAG_PROJECTION, $tag['projection_name']),
$tag['read_model']
);
self::assertProjectionTagHasAttribute($id, $tag, 'projection_name');
self::assertProjectionTagHasAttribute($id, $tag, 'projection_manager');
self::assertProjectionManagerExists($tag['projection_manager'], $id, $container);

if ($isReadModelProjector) {
self::assertProjectionTagHasAttribute($id, $tag, 'read_model');
$readModelsLocator[$tag['projection_name']] = new Reference($tag['read_model']);
}

//alias definition for using the correct ProjectionManager
$container->setAlias(
sprintf('%s.%s.projection_manager', ProophEventStoreExtension::TAG_PROJECTION, $tag['projection_name']),
$projectionManagerForProjectionsLocator[$tag['projection_name']] = new Reference(
sprintf('prooph_event_store.projection_manager.%s', $tag['projection_manager'])
);

if ($id !== sprintf('%s.%s', ProophEventStoreExtension::TAG_PROJECTION, $tag['projection_name'])) {
$container->setAlias(sprintf('%s.%s', ProophEventStoreExtension::TAG_PROJECTION, $tag['projection_name']), $id);
}
$projectionsLocator[$tag['projection_name']] = new Reference($id);
}
}

$container
->setDefinition(
'prooph_event_store.projection_read_models_locator',
new Definition(ServiceLocator::class, [$readModelsLocator])
)
->addTag('container.service_locator');
self::addServicesToLocator($container, 'prooph_event_store.projections_locator', $projectionsLocator);
self::addServicesToLocator($container, 'prooph_event_store.projection_read_models_locator', $readModelsLocator);
self::addServicesToLocator(
$container,
'prooph_event_store.projection_manager_for_projections_locator',
$projectionManagerForProjectionsLocator
);
}

private static function addServicesToLocator(
ContainerBuilder $container,
string $locatorId,
array $serviceMap
): void {
$definition = $container->getDefinition($locatorId);
$definition->replaceArgument(0, array_merge($serviceMap, $definition->getArgument(0)));
}

/**
* @param string $serviceId The id of the service that is verified
* @param ReflectionClass $projectionClass The Reflection of the service that is verified
* @throws RuntimeException if the service does implement neither ReadModelProjection nor Projection.
*/
private static function assertProjectionHasAValidClass(string $serviceId, ReflectionClass $projectionClass): void
{
if (! $projectionClass->implementsInterface(ReadModelProjection::class)
&& ! $projectionClass->implementsInterface(Projection::class)
) {
throw new RuntimeException(sprintf(
'Tagged service "%s" must implement either "%s" or "%s"',
$serviceId,
ReadModelProjection::class,
Projection::class
));
}
}

/**
* @param string $serviceId The id of the service whose tag is verified
* @param array $tag The actual tag
* @param string $attributeName The attribute that has to be available in the tag
* @throws RuntimeException if the attribute is not available in the tag
*/
private static function assertProjectionTagHasAttribute(string $serviceId, array $tag, string $attributeName): void
{
if (! isset($tag[$attributeName])) {
throw new RuntimeException(sprintf(
'"%s" attribute is missing from on "%s" tagged service "%s"',
$attributeName,
ProophEventStoreExtension::TAG_PROJECTION,
$serviceId
));
}
}

/**
* @param string $name The name of the projection manager
* @param string $taggedServiceId The projection service which has been tagged
* @param ContainerBuilder $container
* @throws RuntimeException if the projection manager does not exist
*/
private static function assertProjectionManagerExists(
string $name,
string $taggedServiceId,
ContainerBuilder $container
): void {
if (! $container->has("prooph_event_store.projection_manager.$name")) {
throw new RuntimeException(
"Projection \"$taggedServiceId\" has been tagged as projection for the manager \"$name\", "
. "but this projection manager does not exist. Please configure a projection manager \"$name\", "
. 'in the prooph_event_store configuration'
);
}
}
}
126 changes: 53 additions & 73 deletions src/DependencyInjection/ProophEventStoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class ProophEventStoreExtension extends Extension
{
public const TAG_PROJECTION = 'prooph_event_store.projection';

public function getNamespace()
public function getNamespace(): string
{
return 'http://getprooph.org/schemas/symfony-dic/prooph';
}
Expand All @@ -39,104 +39,88 @@ public function getConfiguration(array $config, ContainerBuilder $container)
return new Configuration();
}

public function load(array $configs, ContainerBuilder $container)
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$config = $this->processConfiguration($this->getConfiguration($configs, $container), $configs);

$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('event_store.xml');

$this->loadProjectionManagers($config, $container);
self::loadProjectionManagers($config, $container);

if (! empty($config['stores'])) {
$this->loadEventStores(EventStore::class, $config, $container);
}
}

public function loadProjectionManagers(array $config, ContainerBuilder $container)
private static function loadProjectionManagers(array $config, ContainerBuilder $container): void
{
$projectionManagers = [];
$projectionManagersLocator = [];
$projectionManagerForProjectionsLocator = [];
$projectionsLocator = [];
$readModelsLocator = [];

foreach ($config['projection_managers'] as $projectionManagerName => $projectionManagerConfig) {
$projectionManagerDefintion = new ChildDefinition('prooph_event_store.projection_definition');
$projectionManagerDefintion
->setFactory([new Reference('prooph_event_store.projection_factory'), 'createProjectionManager'])
->setArguments([
new Reference($projectionManagerConfig['event_store']),
isset($projectionManagerConfig['connection']) ? new Reference($projectionManagerConfig['connection']) : null,
$projectionManagerConfig['event_streams_table'],
$projectionManagerConfig['projections_table'],
]);

$projectorManagerId = sprintf('prooph_event_store.projection_manager.%s', $projectionManagerName);

$container->setDefinition(
$projectorManagerId,
$projectionManagerDefintion
$projectionManagerId = "prooph_event_store.projection_manager.$projectionManagerName";
self::defineProjectionManager($container, $projectionManagerId, $projectionManagerConfig);

self::collectProjectionsForLocators(
$projectionManagerConfig['projections'],
$projectionManagerId,
$projectionManagerForProjectionsLocator,
$projectionsLocator,
$readModelsLocator
);

$this->loadProjections($projectionManagerConfig, $projectionManagerName, $container);

$projectionManagers[$projectionManagerName] = 'prooph_event_store.'.$projectionManagerName;

// Gather maps for locators
foreach ($projectionManagerConfig['projections'] as $projectionName => $projectionConfig) {
$projectionManagerForProjectionsLocator[$projectionName] = new Reference($projectorManagerId);
$projectionsLocator[$projectionName] = new Reference(
sprintf('%s.%s', static::TAG_PROJECTION, $projectionName)
);
}

$projectionManagersLocator[$projectionManagerName] = new Reference($projectorManagerId);
$projectionManagers[$projectionManagerName] = "prooph_event_store.$projectionManagerName";
$projectionManagersLocator[$projectionManagerName] = new Reference($projectionManagerId);
}

$container->setParameter('prooph_event_store.projection_managers', $projectionManagers);

$container
->setDefinition(
'prooph_event_store.projection_managers_locator',
new Definition(ServiceLocator::class, [$projectionManagersLocator])
)
->addTag('container.service_locator');

$container
->setDefinition(
'prooph_event_store.projection_manager_for_projections_locator',
new Definition(ServiceLocator::class, [$projectionManagerForProjectionsLocator])
)
->addTag('container.service_locator');
self::defineServiceLocator($container, 'prooph_event_store.projection_managers_locator', $projectionManagersLocator);
self::defineServiceLocator($container, 'prooph_event_store.projection_manager_for_projections_locator', $projectionManagerForProjectionsLocator);
self::defineServiceLocator($container, 'prooph_event_store.projection_read_models_locator', $readModelsLocator);
self::defineServiceLocator($container, 'prooph_event_store.projections_locator', $projectionsLocator);
}

$container
->setDefinition(
'prooph_event_store.projections_locator',
new Definition(ServiceLocator::class, [$projectionsLocator])
)
->addTag('container.service_locator');
private static function defineProjectionManager(ContainerBuilder $container, string $serviceId, array $config): void
{
$projectionManagerDefinition = new ChildDefinition('prooph_event_store.projection_definition');
$projectionManagerDefinition
->setFactory([new Reference('prooph_event_store.projection_factory'), 'createProjectionManager'])
->setArguments([
new Reference($config['event_store']),
isset($config['connection']) ? new Reference($config['connection']) : null,
$config['event_streams_table'],
$config['projections_table'],
]);

$container->setDefinition($serviceId, $projectionManagerDefinition);
}

public function loadProjections(array $config, string $projectionManager, ContainerBuilder $container)
private static function defineServiceLocator(ContainerBuilder $container, string $id, array $serviceMap): void
{
foreach ($config['projections'] as $projectionName => $projectionConfig) {
$tag = [
'projection_name' => $projectionName,
'projection_manager' => $projectionManager,
];
$definition = new Definition(ServiceLocator::class, [$serviceMap]);
$definition->addTag('container.service_locator');
$container->setDefinition($id, $definition);
}

private static function collectProjectionsForLocators(
array $projections,
string $projectionManagerId,
array &$projectionManagerForProjectionsLocator,
array &$projectionsLocator,
array &$readModelsLocator
) {
foreach ($projections as $projectionName => $projectionConfig) {
if (isset($projectionConfig['read_model'])) {
$tag['read_model'] = $projectionConfig['read_model'];
$readModelsLocator[$projectionName] = new Reference($projectionConfig['read_model']);
}

$container
->setDefinition(
sprintf('%s.%s', static::TAG_PROJECTION, $projectionName),
(new Definition())
->setClass($projectionConfig['projection'])
->addTag(static::TAG_PROJECTION, $tag)
);
$projectionsLocator[$projectionName] = new Reference($projectionConfig['projection']);
$projectionManagerForProjectionsLocator[$projectionName] = new Reference($projectionManagerId);
}
}

Expand All @@ -147,13 +131,9 @@ public function loadProjections(array $config, string $projectionManager, Contai
* @param string $class
* @param array $config
* @param ContainerBuilder $container
* @param XmlFileLoader $loader
*/
private function loadEventStores(
string $class,
array $config,
ContainerBuilder $container
) {
private function loadEventStores(string $class, array $config, ContainerBuilder $container)
{
$eventStores = [];

foreach (array_keys($config['stores']) as $name) {
Expand Down
4 changes: 2 additions & 2 deletions test/Command/Fixture/config/projections.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ services:
alias: prooph_event_store.projection_manager.main_projection_manager
public: true
test.prooph_event_store.projection.black_hole_projection:
alias: prooph_event_store.projection.black_hole_projection
alias: ProophTest\Bundle\EventStore\Command\Fixture\Projection\BlackHoleProjection
public: true
test.prooph_event_store.projection.black_hole_read_model_projection:
alias: prooph_event_store.projection.black_hole_read_model_projection
alias: ProophTest\Bundle\EventStore\Command\Fixture\Projection\BlackHoleReadModelProjection
public: true

0 comments on commit 76b3b03

Please sign in to comment.