Skip to content

Commit

Permalink
Wip.
Browse files Browse the repository at this point in the history
  • Loading branch information
Syndesi committed Dec 24, 2023
1 parent 2d4ff5a commit 03466c1
Showing 3 changed files with 101 additions and 63 deletions.
36 changes: 4 additions & 32 deletions src/Command/TestCommand.php
Original file line number Diff line number Diff line change
@@ -5,18 +5,16 @@
use App\Service\EtagService;
use App\Style\EmberNexusStyle;
use Ramsey\Uuid\Uuid;
use Safe\DateTime;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\OutputStyle;
use Tuupola\Base58;

#[AsCommand(name: 'test')]
class TestCommand extends Command
{
private OutputStyle $io;
private EmberNexusStyle $io;

private Base58 $encoder;

@@ -33,38 +31,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$this->io->title('Test');

$input = [
Uuid::uuid4()->getBytes(),
(new DateTime())->getTimestamp(),
];

$hashFunction = hash_init('xxh3');

foreach ($input as $item) {
hash_update($hashFunction, $item);
}

$hashResult = hash_final($hashFunction, true);
$encodedResult = $this->encoder->encode($hashResult);
$this->io->writeln($encodedResult);

$hashFunction = hash_init('xxh3');

for ($i = 0; $i < 1000; ++$i) {
hash_update($hashFunction, Uuid::uuid4()->getBytes());
hash_update($hashFunction, (new DateTime())->getTimestamp());
}

$hashResult = hash_final($hashFunction, true);
$encodedResult = $this->encoder->encode($hashResult);
$this->io->writeln($encodedResult);

$this->io->writeln('-------');

$etag = $this->etagService->getEtagForElementId(Uuid::fromString('8693498b-b58d-4210-b8ea-41c5a9adbe5f'));

$this->io->writeln($etag);

$childCollectionEtag = $this->etagService->getEtagForChildrenCollection(Uuid::fromString('7b80b203-2b82-40f5-accd-c7089fe6114e'));
$this->io->writeln($childCollectionEtag);

return Command::SUCCESS;
}
}
77 changes: 46 additions & 31 deletions src/Service/EtagService.php
Original file line number Diff line number Diff line change
@@ -2,30 +2,63 @@

namespace App\Service;

use App\Type\Etag;
use EmberNexusBundle\Service\EmberNexusConfiguration;
use Exception;
use HashContext;
use Laudis\Neo4j\Databags\Statement;
use Laudis\Neo4j\Types\DateTimeZoneId;
use Predis\Client as RedisClient;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Syndesi\CypherEntityManager\Type\EntityManager as CypherEntityManager;
use Tuupola\Base58;

use function Safe\pack;
use function Safe\unpack;

class EtagService
{
private const string HASH_ALGORITHM = 'xxh3';
private Base58 $encoder;

public function __construct(
private CypherEntityManager $cypherEntityManager,
private RedisClient $redisClient,
private EmberNexusConfiguration $emberNexusConfiguration
) {
$this->encoder = new Base58();
}

public function getEtagForChildrenCollection(UuidInterface $parentId): ?string
{
$limit = $this->emberNexusConfiguration->getCacheEtagUpperLimitInCollectionEndpoints();
$result = $this->cypherEntityManager->getClient()->runStatement(
Statement::create(
sprintf(
"MATCH ({id: \$parentId})-[:OWNS]->(children)\n".
"WITH children\n".
"LIMIT %d\n".
"WITH children ORDER BY children.id\n".
"WITH [children.id, children.updated] AS childrenList\n".
'RETURN collect(childrenList) AS childrenList',
$limit + 1
),
[
'parentId' => $parentId->toString(),
]
)
);
if (1 !== count($result)) {
throw new Exception('Unexpected result');
}
if (count($result[0]['childrenList']) > $limit) {
return null;
}

$etag = new Etag($this->emberNexusConfiguration->getCacheEtagSeed());
foreach ($result[0]['childrenList'] as $childIdUpdatedPair) {
$childId = Uuid::fromString($childIdUpdatedPair[0]);
$childUpdated = $childIdUpdatedPair[1];
if (!($childUpdated instanceof DateTimeZoneId)) {
throw new Exception(sprintf('Expected variable element.updated to be of type %s, got %s.', DateTimeZoneId::class, get_class($childUpdated)));
}
$etag->addUuid($childId);
$etag->addDatetime($childUpdated->toDateTime());
}

return $etag->getEtag();
}

public function getEtagForElementId(UuidInterface $elementId): string
@@ -48,28 +81,10 @@ public function getEtagForElementId(UuidInterface $elementId): string
throw new Exception(sprintf('Expected variable element.updated to be of type %s, got %s.', DateTimeZoneId::class, get_class($updated)));
}

$hashContext = $this->startHashContext();

hash_update($hashContext, $elementId->getBytes());
$timestamp = $updated->toDateTime()->getTimestamp();
$timestampAsBinaryString = pack('C*', ...array_reverse(unpack('C*', pack('L', $timestamp))));
hash_update($hashContext, $timestampAsBinaryString);

return $this->getEtagFromHashContext($hashContext);
}

private function getEtagFromHashContext(HashContext $hashContext): string
{
$rawHash = hash_final($hashContext, true);

return $this->encoder->encode($rawHash);
}

private function startHashContext(): HashContext
{
$hashContext = hash_init(self::HASH_ALGORITHM);
hash_update($hashContext, $this->emberNexusConfiguration->getCacheEtagSeed());
$etag = new Etag($this->emberNexusConfiguration->getCacheEtagSeed());
$etag->addUuid($elementId);
$etag->addDatetime($updated->toDateTime());

return $hashContext;
return $etag->getEtag();
}
}
51 changes: 51 additions & 0 deletions src/Type/Etag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Type;

use DateTimeInterface;
use HashContext;
use Ramsey\Uuid\UuidInterface;
use Tuupola\Base58;

use function Safe\pack;
use function Safe\unpack;

class Etag
{
private const string HASH_ALGORITHM = 'xxh3';

private HashContext $hashContext;
private Base58 $encoder;

public function __construct(
string $seed,
string $algorithm = self::HASH_ALGORITHM
) {
$this->hashContext = hash_init($algorithm);
hash_update($this->hashContext, $seed);
$this->encoder = new Base58();
}

public function addDatetime(DateTimeInterface $dateTime): self
{
$timestamp = $dateTime->getTimestamp();
$timestampAsBinaryString = pack('C*', ...array_reverse(unpack('C*', pack('L', $timestamp))));
hash_update($this->hashContext, $timestampAsBinaryString);

return $this;
}

public function addUuid(UuidInterface $uuid): self
{
hash_update($this->hashContext, $uuid->getBytes());

return $this;
}

public function getEtag(): string
{
$rawHash = hash_final($this->hashContext, true);

return $this->encoder->encode($rawHash);
}
}

0 comments on commit 03466c1

Please sign in to comment.