Skip to content

Commit

Permalink
Custom typesense bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
ragusa87 committed Dec 9, 2024
1 parent 94dee84 commit 03fd25f
Show file tree
Hide file tree
Showing 17 changed files with 745 additions and 1 deletion.
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
->in(__DIR__.'/src')
->in(__DIR__.'/tests')
->in(__DIR__.'/migrations')
->in(__DIR__.'/BibliotecaTypesenseBundle')
;

$config = new PhpCsFixer\Config('Biblioteca');
Expand Down
53 changes: 53 additions & 0 deletions BibliotecaTypesenseBundle/src/BibliotecaTypesenseBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Biblioteca\TypesenseBundle;

use Biblioteca\TypesenseBundle\Mapper\MapperInterface;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class BibliotecaTypesenseBundle extends AbstractBundle
{
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
->arrayNode('typesense')
->info('Typesense server configuration')
->isRequired()
->children()
->scalarNode('uri')
->info('The URL of the Typesense server')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('key')
->info('The API key for accessing the Typesense server')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('connection_timeout_seconds')
->defaultValue(5)
->cannotBeEmpty()
->end()
->end()
->end()
->end();
}

public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
foreach ($config['typesense'] as $key => $value) {
$container->parameters()->set('biblioteca_typesense.config.'.$key, $value);
}

$container->services()
->instanceof(MapperInterface::class)
->tag('typesense.mapper')
->autowire();

$container->import(__DIR__.'/Resources/config/services.yaml');
}
}
78 changes: 78 additions & 0 deletions BibliotecaTypesenseBundle/src/Client/ClientAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Biblioteca\TypesenseBundle\Client;

use Typesense\Aliases;
use Typesense\Analytics;
use Typesense\Client;
use Typesense\Collections;
use Typesense\Debug;
use Typesense\Health;
use Typesense\Keys;
use Typesense\Metrics;
use Typesense\MultiSearch;
use Typesense\Operations;
use Typesense\Presets;

class ClientAdapter implements ClientInterface
{
public function __construct(
private readonly Client $client,
) {
}

public function __call($name, $arguments)
{
return $this->client->$name(...$arguments);
}

public function getCollections(): Collections
{
return $this->client->getCollections();
}

public function getAliases(): Aliases
{
return $this->client->getAliases();
}

public function getKeys(): Keys
{
return $this->client->getKeys();
}

public function getDebug(): Debug
{
return $this->client->getDebug();
}

public function getMetrics(): Metrics
{
return $this->client->getMetrics();
}

public function getHealth(): Health
{
return $this->client->getHealth();
}

public function getOperations(): Operations
{
return $this->client->getOperations();
}

public function getMultiSearch(): MultiSearch
{
return $this->client->getMultiSearch();
}

public function getPresets(): Presets
{
return $this->client->getPresets();
}

public function getAnalytics(): Analytics
{
return $this->client->getAnalytics();
}
}
44 changes: 44 additions & 0 deletions BibliotecaTypesenseBundle/src/Client/ClientFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Biblioteca\TypesenseBundle\Client;

use Typesense\Client;
use Typesense\Exceptions\ConfigError;

class ClientFactory
{
public function __construct(
private readonly string $uri,
#[\SensitiveParameter] private readonly string $apiKey,
private readonly int $connectionTimeoutSeconds = 5,
) {
}

/**
* @throws ConfigError
*/
public function __invoke(): ClientInterface
{
return new ClientAdapter(new Client($this->getConfiguration()));
}

private function getConfiguration(): array
{
$urlParsed = parse_url($this->uri);
if ($urlParsed === false) {
throw new \InvalidArgumentException('Invalid URI');
}

return [
'nodes' => [
[
'host' => $urlParsed['host'],
'port' => $urlParsed['port'],
'protocol' => $urlParsed['scheme'],
],
],
'api_key' => $this->apiKey,
'connection_timeout_seconds' => $this->connectionTimeoutSeconds,
];
}
}
37 changes: 37 additions & 0 deletions BibliotecaTypesenseBundle/src/Client/ClientInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Biblioteca\TypesenseBundle\Client;

use Typesense\Aliases;
use Typesense\Analytics;
use Typesense\Collections;
use Typesense\Debug;
use Typesense\Health;
use Typesense\Keys;
use Typesense\Metrics;
use Typesense\MultiSearch;
use Typesense\Operations;
use Typesense\Presets;

interface ClientInterface
{
public function getCollections(): Collections;

public function getAliases(): Aliases;

public function getKeys(): Keys;

public function getDebug(): Debug;

public function getMetrics(): Metrics;

public function getHealth(): Health;

public function getOperations(): Operations;

public function getMultiSearch(): MultiSearch;

public function getPresets(): Presets;

public function getAnalytics(): Analytics;
}
66 changes: 66 additions & 0 deletions BibliotecaTypesenseBundle/src/Command/TypesensePopulateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Biblioteca\TypesenseBundle\Command;

use Biblioteca\TypesenseBundle\Mapper\MapperInterface;
use Biblioteca\TypesenseBundle\Mapper\MapperLocator;
use Biblioteca\TypesenseBundle\PopulateService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'biblioteca:typesense:populate',
description: 'Populate Typesense collections',
)]
class TypesensePopulateCommand extends Command
{
public function __construct(
private PopulateService $populateService,
private MapperLocator $mapperLocator,
) {
parent::__construct();
}

protected function configure(): void
{
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$count = $this->mapperLocator->count();
if ($count === 0) {
$io->warning('No mappers found. Declare at least one service implementing '.MapperInterface::class);

return Command::SUCCESS;
}

$progress = new ProgressBar($output, 0);

foreach ($this->mapperLocator->getMappers() as $mapper) {
$name = $mapper->getMapping()->getName();
$io->writeln('Deleting collection '.$name);
$this->populateService->deleteCollection($mapper);

$io->writeln('Creating collection '.$name);
$this->populateService->createCollection($mapper);

$io->writeln('Filling collection '.$name);
$progress->start($mapper->getDataCount());
foreach ($this->populateService->fillCollection($mapper) as $_) {
$progress->advance();
}
$progress->clear();
}
$progress->finish();

$io->success('Finished');

return Command::SUCCESS;
}
}
15 changes: 15 additions & 0 deletions BibliotecaTypesenseBundle/src/Mapper/MapperInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Biblioteca\TypesenseBundle\Mapper;

interface MapperInterface
{
public function getMapping(): Mapping;

/**
* @return \generator<array<string, mixed>>
*/
public function getData(): \generator;

public function getDataCount(): ?int;
}
33 changes: 33 additions & 0 deletions BibliotecaTypesenseBundle/src/Mapper/MapperLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Biblioteca\TypesenseBundle\Mapper;

class MapperLocator
{
/**
* @param iterable<MapperInterface> $mappers
*/
public function __construct(private iterable $mappers)
{
}

/**
* @return \generator<MapperInterface>
*/
public function getMappers(): \Generator
{
foreach ($this->mappers as $mapper) {
yield $mapper;
}
}

public function addMapper(MapperInterface $mapper): void
{
$this->mappers[] = $mapper;
}

public function count(): int
{
return count($this->mappers);
}
}
40 changes: 40 additions & 0 deletions BibliotecaTypesenseBundle/src/Mapper/Mapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Biblioteca\TypesenseBundle\Mapper;

use Symfony\Component\OptionsResolver\OptionsResolver;

class Mapping
{
public function __construct(private string $name, private array $fields = [])
{
}

public function getFields(): array
{
return $this->fields;
}

public function setField(string $name, array $options): self
{
$optionResolver = new OptionsResolver();
$optionResolver->setRequired(['name', 'type']);
$optionResolver->setDefined(['facet', 'optional']);

$data = $optionResolver->resolve($options);
unset($data['optional']);
$this->fields[$name] = $data;

return $this;
}

public function getName(): string
{
return $this->name;
}

public function getCollectionOptions(): array
{
return [];
}
}
Loading

0 comments on commit 03fd25f

Please sign in to comment.