Skip to content

Commit

Permalink
[FRAM-57] Entity denormalisation (#4)
Browse files Browse the repository at this point in the history
* feat: Entity denormalisation (#FRAM-57)

* ci: fix psalm types
  • Loading branch information
vincent4vx committed Dec 21, 2022
1 parent 0bf5199 commit 86f5eb2
Show file tree
Hide file tree
Showing 60 changed files with 1,742 additions and 561 deletions.
4 changes: 3 additions & 1 deletion composer.json
Expand Up @@ -16,7 +16,9 @@
},
"autoload-dev": {
"psr-4": {
"Bdf\\Prime\\Indexer\\": "tests"
"Bdf\\Prime\\Indexer\\": "tests",
"ElasticsearchTestFiles\\": "tests/Elasticsearch/_files",
"DenormalizeTestFiles\\": "tests/Denormalize/_files"
}
},
"minimum-stability": "dev",
Expand Down
@@ -0,0 +1,43 @@
<?php

namespace Bdf\Prime\Indexer\Bundle\DependencyInjection\Compiler;

use Bdf\Prime\Indexer\Resolver\MappingResolver;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* Locate all services with tag "prime.indexer.configuration" and register into the mapping resolver
*
* @see MappingResolver::register()
*/
final class RegisterIndexConfigurationCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$resolverDefinition = $container->findDefinition(MappingResolver::class);

foreach ($container->findTaggedServiceIds('prime.indexer.configuration') as $id => $_) {
try {
$definition = $container->findDefinition($id);
$definition->setPublic(true);

$className = $definition->getClass();

if (!$className || !class_exists($className)) {
continue;
}

$r = new \ReflectionClass($className);
$config = $r->newInstanceWithoutConstructor();

$resolverDefinition->addMethodCall('register', [$id, $config->entity()]);
} catch (\Exception $e) {
// Ignore
}
}
}
}
@@ -0,0 +1,35 @@
<?php

namespace Bdf\Prime\Indexer\Bundle\DependencyInjection\Compiler;

use Bdf\Prime\Indexer\Bundle\Factory\IndexFactoryInterface;
use Bdf\Prime\Indexer\IndexFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
* Register custom index factories tagged with "prime.indexer.factory"
*
* @see IndexFactory
*/
final class RegisterIndexFactoryCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$factory = $container->findDefinition(IndexFactory::class);
$factories = [];

foreach ($container->findTaggedServiceIds('prime.indexer.factory') as $id => $tags) {
/** @var class-string<IndexFactoryInterface> $factoryClass */
$factoryClass = $container->findDefinition($id)->getClass();

$factories[$factoryClass::type()] = new Reference($id);
}

$factory->replaceArgument(0, $factories);
}
}
29 changes: 12 additions & 17 deletions src/Bundle/DependencyInjection/PrimeIndexerExtension.php
Expand Up @@ -3,6 +3,7 @@
namespace Bdf\Prime\Indexer\Bundle\DependencyInjection;

use Bdf\Prime\Indexer\Bundle\Factory\IndexFactoryInterface;
use Bdf\Prime\Indexer\IndexConfigurationInterface;
use Bdf\Prime\Indexer\IndexFactory;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand Down Expand Up @@ -36,31 +37,25 @@ public function load(array $configs, ContainerBuilder $container)
->addTag('prime.indexer.factory')
;

$container->registerForAutoconfiguration(IndexConfigurationInterface::class)
->setPublic(true)
->addTag('prime.indexer.configuration')
;

$this->configureIndexes($config, $container);
$this->configureFactories($config, $container);
}

private function configureIndexes(array $config, ContainerBuilder $container): void
{
$factory = $container->findDefinition(IndexFactory::class);

foreach ($config['indexes'] as $entity => $index) {
if (!$container->hasDefinition($index)) {
$definition = $container->register($index, $index)->setAutowired(true);
$factory->addMethodCall('register', [$entity, $definition]);
}
}
}
$definition = $container->hasDefinition($index)
? $container->findDefinition($index)
: $container->register($index, $index)->setAutowired(true)
;

private function configureFactories(array $config, ContainerBuilder $container): void
{
// $factory = $container->findDefinition(IndexFactory::class);
// $factories = [];
//
// foreach ($container->findTaggedServiceIds('prime.indexer.factory') as $id => $tags) {
// $factories[$id::type()] = new Reference($id);
// }
//
// $factory->replaceArgument(0, $factories);
$factory->addMethodCall('register', [$entity, $definition]);
}
}
}
38 changes: 38 additions & 0 deletions src/Bundle/Factory/DenormalizerIndexFactory.php
@@ -0,0 +1,38 @@
<?php

namespace Bdf\Prime\Indexer\Bundle\Factory;

use Bdf\Prime\Indexer\Denormalize\DenormalizedIndex;
use Bdf\Prime\Indexer\Denormalize\DenormalizerInterface;
use Bdf\Prime\Indexer\IndexFactory;
use Bdf\Prime\Indexer\IndexInterface;

/**
* Index factory for denormalized index
*
* @implements IndexFactoryInterface<DenormalizerInterface>
*/
final class DenormalizerIndexFactory implements IndexFactoryInterface
{
/**
* {@inheritdoc}
*
* @param DenormalizerInterface $config
*/
public function __invoke($config, IndexFactory $factory = null): IndexInterface
{
if (!$factory) {
throw new \InvalidArgumentException('$factory is mandatory');
}

return new DenormalizedIndex($config, $factory->for($config->denormalizedClass()));
}

/**
* {@inheritdoc}
*/
public static function type(): string
{
return DenormalizerInterface::class;
}
}
3 changes: 2 additions & 1 deletion src/Bundle/Factory/ElasticsearchIndexFactory.php
Expand Up @@ -7,6 +7,7 @@
use Bdf\Prime\Indexer\Elasticsearch\ElasticsearchIndex;
use Bdf\Prime\Indexer\Elasticsearch\Mapper\ElasticsearchIndexConfigurationInterface;
use Bdf\Prime\Indexer\Elasticsearch\Mapper\ElasticsearchMapper;
use Bdf\Prime\Indexer\IndexFactory;
use Bdf\Prime\Indexer\IndexInterface;
use Psr\Container\ContainerInterface;

Expand Down Expand Up @@ -40,7 +41,7 @@ public static function type(): string
/**
* {@inheritdoc}
*/
public function __invoke($config): IndexInterface
public function __invoke($config, IndexFactory $factory = null): IndexInterface
{
return new ElasticsearchIndex(
$this->container->get(ClientInterface::class),
Expand Down
5 changes: 4 additions & 1 deletion src/Bundle/Factory/IndexFactoryInterface.php
Expand Up @@ -2,13 +2,15 @@

namespace Bdf\Prime\Indexer\Bundle\Factory;

use Bdf\Prime\Indexer\IndexFactory;
use Bdf\Prime\Indexer\Exception\IndexConfigurationException;
use Bdf\Prime\Indexer\IndexInterface;

/**
* Factory for indexes
*
* @template T as object
* @method IndexInterface __invoke($config, IndexFactory $factory)
*/
interface IndexFactoryInterface
{
Expand All @@ -23,10 +25,11 @@ public static function type(): string;
* Create the index from the configuration object
*
* @param T $config The configuration object
* @param IndexFactory $factory The index factory
*
* @return IndexInterface
*
* @throws IndexConfigurationException When an error occur during creation of the index
*/
public function __invoke($config): IndexInterface;
public function __invoke($config/*, IndexFactory $factory*/): IndexInterface;
}
9 changes: 8 additions & 1 deletion src/Bundle/PrimeIndexerBundle.php
Expand Up @@ -2,12 +2,19 @@

namespace Bdf\Prime\Indexer\Bundle;

use Bdf\Prime\Indexer\Bundle\DependencyInjection\Compiler\RegisterIndexConfigurationCompilerPass;
use Bdf\Prime\Indexer\Bundle\DependencyInjection\Compiler\RegisterIndexFactoryCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
* Class PrimeIndexerBundle
*/
class PrimeIndexerBundle extends Bundle
{

public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new RegisterIndexFactoryCompilerPass());
$container->addCompilerPass(new RegisterIndexConfigurationCompilerPass());
}
}
12 changes: 11 additions & 1 deletion src/Bundle/Resources/config/prime_indexer.yaml
Expand Up @@ -18,16 +18,26 @@ services:
arguments:
- '%prime.indexer.configuration.elasticsearch%'

Bdf\Prime\Indexer\Resolver\MappingResolver:
class: Bdf\Prime\Indexer\Resolver\MappingResolver
arguments: ['@service_container']
public: true

Bdf\Prime\Indexer\IndexFactory:
class: Bdf\Prime\Indexer\IndexFactory
arguments:
- {'Bdf\Prime\Indexer\Elasticsearch\Mapper\ElasticsearchIndexConfigurationInterface': '@Bdf\Prime\Indexer\Bundle\Factory\ElasticsearchIndexFactory'}
- []
- '@Bdf\Prime\Indexer\Resolver\MappingResolver'
public: true

Bdf\Prime\Indexer\Bundle\Factory\ElasticsearchIndexFactory:
class: Bdf\Prime\Indexer\Bundle\Factory\ElasticsearchIndexFactory
arguments: ['@service_container']
tags: ['prime.indexer.factory']

Bdf\Prime\Indexer\Bundle\Factory\DenormalizerIndexFactory:
class: Bdf\Prime\Indexer\Bundle\Factory\DenormalizerIndexFactory
tags: ['prime.indexer.factory']

# Commands
Bdf\Prime\Indexer\Elasticsearch\Console\ShowCommand:
Expand Down
103 changes: 103 additions & 0 deletions src/Denormalize/DenormalizedIndex.php
@@ -0,0 +1,103 @@
<?php

namespace Bdf\Prime\Indexer\Denormalize;

use Bdf\Collection\Stream\Streams;
use Bdf\Prime\Indexer\IndexInterface;
use Bdf\Prime\Indexer\QueryInterface;

/**
* Index adapter for handle denormalizer
*/
final class DenormalizedIndex implements IndexInterface
{
private DenormalizerInterface $denormalizer;
private IndexInterface $index;

/**
* DenormalizedIndex constructor.
*
* @param DenormalizerInterface $denormalizer
* @param IndexInterface $index
*/
public function __construct(DenormalizerInterface $denormalizer, IndexInterface $index)
{
$this->denormalizer = $denormalizer;
$this->index = $index;
}

/**
* {@inheritdoc}
*/
public function config()
{
return $this->denormalizer;
}

/**
* {@inheritdoc}
*/
public function add($entity): void
{
$this->index->add($this->denormalizer->denormalize($entity));
}

/**
* {@inheritdoc}
*/
public function contains($entity): bool
{
return $this->index->contains($this->denormalizer->denormalize($entity));
}

/**
* {@inheritdoc}
*/
public function remove($entity): void
{
$this->index->remove($this->denormalizer->denormalize($entity));
}

/**
* {@inheritdoc}
*/
public function update($entity, ?array $attributes = null): void
{
$this->index->update($this->denormalizer->denormalize($entity), $attributes);
}

/**
* {@inheritdoc}
*/
public function query(bool $withDefaultScope = true): QueryInterface
{
return $this->index->query($withDefaultScope);
}

/**
* {@inheritdoc}
*/
public function create(iterable $entities = [], array $options = []): void
{
$this->index->create(
Streams::wrap($entities)->map([$this->denormalizer, 'denormalize']),
$options
);
}

/**
* {@inheritdoc}
*/
public function drop(): void
{
$this->index->drop();
}

/**
* {@inheritdoc}
*/
public function __call(string $name, array $arguments): QueryInterface
{
return $this->index->$name(...$arguments);
}
}

0 comments on commit 86f5eb2

Please sign in to comment.