Feature: [docgen] generate documents in an async queue

The documents are now generated in a queue, using symfony messenger. This queue should be configured:

```yaml
# app/config/messenger.yaml
framework:
    messenger:
        # reset services after consuming messages
        # reset_on_message: true

        failure_transport: failed

        transports:
            # https://symfony.com/doc/current/messenger.html#transport-configuration
            async: '%env(MESSENGER_TRANSPORT_DSN)%'
            priority:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
            failed: 'doctrine://default?queue_name=failed'

        routing:
            # ... other messages
            'Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage': priority
```

`StoredObject`s now have additionnal properties:

* status (pending, failure, ready (by default) ), which explain if the document is generated;
* a generationTrialCounter, which is incremented on each generation trial, which prevent each generation more than 5 times;

The generator computation is moved from the `DocGenTemplateController` to a `Generator` (implementing `GeneratorInterface`. 

There are new methods to `Context` which allow to normalize/denormalize context data to/from a messenger's `Message`.
This commit is contained in:
2023-02-28 15:25:47 +00:00
parent 27f13e0dd1
commit a16244a3f5
40 changed files with 1050 additions and 177 deletions

View File

@@ -3,6 +3,7 @@
namespace Chill\DocGeneratorBundle\Service\Generator;
use Chill\DocGeneratorBundle\Context\ContextManagerInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
@@ -12,7 +13,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\File\File;
class Generator
class Generator implements GeneratorInterface
{
private ContextManagerInterface $contextManager;
@@ -24,6 +25,8 @@ class Generator
private StoredObjectManagerInterface $storedObjectManager;
private const LOG_PREFIX = '[docgen generator] ';
public function __construct(
ContextManagerInterface $contextManager,
DriverInterface $driver,
@@ -48,18 +51,23 @@ class Generator
*/
public function generateDocFromTemplate(
DocGeneratorTemplate $template,
string $entityClassName,
int $entityId,
array $contextGenerationDataNormalized,
?StoredObject $destinationStoredObject = null,
bool $isTest = false,
?File $testFile = null
): ?string {
if ($destinationStoredObject instanceof StoredObject && StoredObject::STATUS_PENDING !== $destinationStoredObject->getStatus()) {
$this->logger->info(self::LOG_PREFIX.'Aborting generation of an already generated document');
throw new ObjectReadyException();
}
$this->logger->info(self::LOG_PREFIX.'Starting generation of a document', [
'entity_id' => $entityId,
'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId()
]);
$context = $this->contextManager->getContextByDocGeneratorTemplate($template);
$contextGenerationData = ['test_file' => $testFile];
$entity = $this
->entityManager
@@ -67,22 +75,38 @@ class Generator
;
if (null === $entity) {
throw new RelatedEntityNotFoundException($entityClassName, $entityId);
throw new RelatedEntityNotFoundException($template->getEntity(), $entityId);
}
$contextGenerationDataNormalized = array_merge(
$contextGenerationDataNormalized,
$context instanceof DocGeneratorContextWithPublicFormInterface ?
$context->contextGenerationDataDenormalize($template, $entity, $contextGenerationDataNormalized)
: []
);
$data = $context->getData($template, $entity, $contextGenerationDataNormalized);
$destinationStoredObjectId = $destinationStoredObject instanceof StoredObject ? $destinationStoredObject->getId() : null;
$this->entityManager->clear();
gc_collect_cycles();
if (null !== $destinationStoredObjectId) {
$destinationStoredObject = $this->entityManager->find(StoredObject::class, $destinationStoredObjectId);
}
if ($isTest && ($testFile instanceof File)) {
$dataDecrypted = file_get_contents($testFile->getPathname());
$templateDecrypted = file_get_contents($testFile->getPathname());
} else {
$dataDecrypted = $this->storedObjectManager->read($template->getFile());
$templateDecrypted = $this->storedObjectManager->read($template->getFile());
}
try {
$generatedResource = $this
->driver
->generateFromString(
$dataDecrypted,
$templateDecrypted,
$template->getFile()->getType(),
$context->getData($template, $entity, $contextGenerationData),
$data,
$template->getFile()->getFilename()
);
} catch (TemplateException $e) {
@@ -90,6 +114,11 @@ class Generator
}
if ($isTest) {
$this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [
'is_test' => true,
'entity_id' => $entityId,
'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId()
]);
return $generatedResource;
}
@@ -102,31 +131,13 @@ class Generator
$this->storedObjectManager->write($destinationStoredObject, $generatedResource);
try {
$context
->storeGenerated(
$template,
$destinationStoredObject,
$entity,
$contextGenerationData
);
} catch (\Exception $e) {
$this
->logger
->error(
'Unable to store the associated document to entity',
[
'entityClassName' => $entityClassName,
'entityId' => $entityId,
'contextKey' => $context->getName(),
]
);
throw $e;
}
$this->entityManager->flush();
$this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [
'entity_id' => $entityId,
'destination_stored_object' => $destinationStoredObject->getId(),
]);
return null;
}
}

View File

@@ -15,4 +15,12 @@ class GeneratorException extends \RuntimeException
parent::__construct("Could not generate the document", 15252,
$previous);
}
/**
* @return array
*/
public function getErrors(): array
{
return $this->errors;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Chill\DocGeneratorBundle\Service\Generator;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Symfony\Component\HttpFoundation\File\File;
interface GeneratorInterface
{
/**
* @template T of File|null
* @template B of bool
* @param B $isTest
* @param (B is true ? T : null) $testFile
* @psalm-return (B is true ? string : null)
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface|\Throwable
*/
public function generateDocFromTemplate(
DocGeneratorTemplate $template,
int $entityId,
array $contextGenerationDataNormalized,
?StoredObject $destinationStoredObject = null,
bool $isTest = false,
?File $testFile = null
): ?string;
}