BaseExporter.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Service\Export;
  4. use App\ApiResources\Export\ExportRequest;
  5. use App\Entity\Core\File;
  6. use App\Enum\Core\FileTypeEnum;
  7. use App\Enum\Core\FileVisibilityEnum;
  8. use App\Repository\Access\AccessRepository;
  9. use App\Repository\Core\FileRepository;
  10. use App\Service\Export\Model\ExportModelInterface;
  11. use App\Service\File\FileManager;
  12. use App\Service\Utils\FileUtils;
  13. use App\Service\ServiceIterator\EncoderIterator;
  14. use App\Service\Utils\StringsUtils;
  15. use Doctrine\ORM\EntityManagerInterface;
  16. use Psr\Log\LoggerInterface;
  17. use Symfony\Contracts\Service\Attribute\Required;
  18. use Twig\Environment;
  19. /**
  20. * Classe de base des services d'export
  21. */
  22. abstract class BaseExporter
  23. {
  24. // dependencies injections
  25. protected AccessRepository $accessRepository;
  26. protected FileRepository $fileRepository;
  27. protected Environment $twig;
  28. protected EncoderIterator $encoderIterator;
  29. protected EntityManagerInterface $entityManager;
  30. protected LoggerInterface $logger;
  31. protected FileManager $fileManager;
  32. protected FileUtils $fileUtils;
  33. #[Required]
  34. public function setAccessRepository(AccessRepository $accessRepository): void
  35. { $this->accessRepository = $accessRepository; }
  36. #[Required]
  37. public function setFileRepository(FileRepository $fileRepository): void
  38. { $this->fileRepository = $fileRepository; }
  39. #[Required]
  40. public function setTwig(Environment $twig): void
  41. { $this->twig = $twig; }
  42. #[Required]
  43. public function setEncoderIterator(EncoderIterator $encoderIterator): void
  44. { $this->encoderIterator = $encoderIterator; }
  45. #[Required]
  46. public function setEntityManager(EntityManagerInterface $entityManager): void
  47. { $this->entityManager = $entityManager; }
  48. #[Required]
  49. public function setFileManager(FileManager $fileManager): void
  50. { $this->fileManager = $fileManager; }
  51. #[Required]
  52. public function setLogger(LoggerInterface $logger): void
  53. { $this->logger = $logger; }
  54. #[Required]
  55. public function setFileUtils(FileUtils $fileUtils): void
  56. { $this->fileUtils = $fileUtils; }
  57. public function support(ExportRequest $exportRequest): bool
  58. {
  59. return false;
  60. }
  61. /**
  62. * Exécute l'opération d'export correspondant à la requête passée
  63. * en paramètre
  64. *
  65. * @param ExportRequest $exportRequest
  66. * @return File
  67. */
  68. public function export(ExportRequest $exportRequest): File
  69. {
  70. $requesterId = $exportRequest->getRequesterId();
  71. $requester = $this->accessRepository->find($requesterId);
  72. if ($requester === null) {
  73. throw new \RuntimeException('Unable to determine the user; abort.');
  74. }
  75. // Génère le modèle à partir de l'exportRequest
  76. $model = $this->buildModel($exportRequest);
  77. // Génère le html à partir du template et du service
  78. $html = $this->render($model);
  79. // Encode le html au format voulu
  80. $content = $this->encode($html, $exportRequest->getFormat()->value);
  81. // Met à jour ou créé l'enregistrement du fichier en base
  82. if ($exportRequest->getFileId() !== null) {
  83. $file = $this->fileRepository->find($exportRequest->getFileId());
  84. } else {
  85. // Todo: voir si ce else est nécessaire une fois tous les exports implémentés (c'est
  86. // juste au cas où le record File n'existe pas encore en base)
  87. $organization = $requester->getOrganization();
  88. if ($organization === null) {
  89. throw new \RuntimeException('Unable to determine the organization of the curent user; abort.');
  90. }
  91. $file = $this->prepareFile($exportRequest, false);
  92. }
  93. return $this->fileManager->write($file, $content, $requester);
  94. }
  95. /**
  96. * Create a pending file record in the database
  97. *
  98. * @param ExportRequest $exportRequest
  99. * @param bool $flushFile
  100. * @return File
  101. */
  102. public function prepareFile(ExportRequest $exportRequest, bool $flushFile = true): File
  103. {
  104. $requesterId = $exportRequest->getRequesterId();
  105. $requester = $this->accessRepository->find($requesterId);
  106. if ($requester === null) {
  107. throw new \RuntimeException('Unable to determine the current user; abort.');
  108. }
  109. $filename = $this->getFileBasename($exportRequest);
  110. if (!preg_match('/^.+\.' . $exportRequest->getFormat()->value . '$/i', $filename)) {
  111. $filename .= '.' . $exportRequest->getFormat()->value;
  112. }
  113. return $this->fileManager->prepareFile(
  114. $requester,
  115. $filename,
  116. $this->getFileType(),
  117. $requester,
  118. true,
  119. FileVisibilityEnum::NOBODY,
  120. $this->fileUtils->getMimeTypeFromExt($exportRequest->getFormat()->value),
  121. $flushFile
  122. );
  123. }
  124. /**
  125. * Construit le modèle de données qui servira au render du template
  126. *
  127. * @param ExportRequest $exportRequest
  128. * @return ExportModelInterface
  129. */
  130. public function buildModel(ExportRequest $exportRequest): ExportModelInterface
  131. {
  132. throw new \RuntimeException('not implemented error');
  133. }
  134. /**
  135. * Retourne le nom par défaut de cet export,
  136. * utilisé pour trouver le template twig ou encore pour nommer
  137. * le fichier exporté.
  138. *
  139. * @return string
  140. */
  141. protected function getBasename(): string
  142. {
  143. $arr = explode('\\', static::class);
  144. $classname = end($arr);
  145. return StringsUtils::camelToSnake(
  146. preg_replace(
  147. '/^([\w]+)Exporter$/',
  148. '$1',
  149. $classname,
  150. 1)
  151. );
  152. }
  153. /**
  154. * Return the path of the twig template for this export
  155. * @return string
  156. */
  157. protected function getTemplatePath(): string
  158. {
  159. return '@templates/export/' . $this->getBasename() . '.html.twig';
  160. }
  161. /**
  162. * Génère le rendu HTML à partir du template twig et du modèle de données
  163. *
  164. * Attention : Les liens vers les fichiers statiques ou dynamiques doivent être des chemins d'accès absolus pour
  165. * être traités par wkhtmltoX; Les images peuvent être incluses au format base64;
  166. * @see App\Service\Twig\AssetsExtension
  167. *
  168. * @param ExportModelInterface $model
  169. * @return string Rendu HTML
  170. */
  171. protected function render(ExportModelInterface $model): string
  172. {
  173. try {
  174. return $this->twig->render(
  175. $this->getTemplatePath(),
  176. ['model' => $model]
  177. );
  178. }
  179. catch (\Twig\Error\LoaderError | \Twig\Error\RuntimeError | \Twig\Error\SyntaxError $e) {
  180. throw new \RuntimeException('error during template rendering : ' . $e);
  181. }
  182. }
  183. /**
  184. * Encode le html au format demandé
  185. *
  186. * @param string $html
  187. * @param string $format @see ExportFormatEnum
  188. * @return string
  189. */
  190. protected function encode(string $html, string $format): string
  191. {
  192. return $this->encoderIterator->getEncoderFor($format)->encode($html);
  193. }
  194. /**
  195. * Retourne le nom du fichier exporté
  196. *
  197. * @param ExportRequest $exportRequest
  198. * @return string
  199. */
  200. protected function getFileBasename(ExportRequest $exportRequest): string
  201. {
  202. return $this->getBasename();
  203. }
  204. /**
  205. * Retourne le type de fichier tel qu'il apparait au niveau du champ File.type
  206. *
  207. * @return FileTypeEnum
  208. */
  209. protected function getFileType(): FileTypeEnum
  210. {
  211. return FileTypeEnum::UNKNOWN;
  212. }
  213. }