diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f21d7184..6524f215 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -37,7 +37,7 @@ jobs: strategy: matrix: - php-version: ['8.2', '8.3', '8.4'] + php-version: ['8.4', '8.5'] steps: - name: Setup PHP diff --git a/.gitignore b/.gitignore index 4cdb362b..7756f250 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Composer dependencies (installed with composer install) vendor +composer.lock # PhpStorm IDE configuration and cache .idea diff --git a/Command/ExportTranslationsCommand.php b/Command/ExportTranslationsCommand.php index c8fe10ea..6fe186d1 100644 --- a/Command/ExportTranslationsCommand.php +++ b/Command/ExportTranslationsCommand.php @@ -57,16 +57,24 @@ protected function configure(): void { $this->addOption( - 'locales', 'l', InputOption::VALUE_OPTIONAL, - 'Only export files for given locales. e.g. "--locales=en,de"', null + 'locales', + 'l', + InputOption::VALUE_OPTIONAL, + 'Only export files for given locales. e.g. "--locales=en,de"', + null ); $this->addOption( - 'domains', 'd', InputOption::VALUE_OPTIONAL, - 'Only export files for given domains. e.g. "--domains=messages,validators"', null + 'domains', + 'd', + InputOption::VALUE_OPTIONAL, + 'Only export files for given domains. e.g. "--domains=messages,validators"', + null ); $this->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'Force the output format.', null); $this->addOption( - 'override', 'o', InputOption::VALUE_NONE, + 'override', + 'o', + InputOption::VALUE_NONE, 'Only export modified phrases (app/Resources/translations are exported fully anyway)' ); $this->addOption('export-path', 'p', InputOption::VALUE_REQUIRED, 'Export files to given path.'); @@ -164,15 +172,10 @@ protected function exportFile(FileInterface $file) /** * If the output file exists we merge existing translations with those from the database. - * - * @param FileInterface $file - * @param string $outputFile - * @param array $translations - * @return array */ - protected function mergeExistingTranslations($file, $outputFile, $translations) + protected function mergeExistingTranslations(FileInterface $file, string $outputFile, array $translations): array { - if (file_exists($outputFile)) { + if (file_exists($outputFile) && method_exists($this->translator, 'getLoader')) { $extension = pathinfo($outputFile, PATHINFO_EXTENSION); $loader = $this->translator->getLoader($extension); $messageCatalogue = $loader->load($outputFile, $file->getLocale(), $file->getDomain()); diff --git a/Command/ImportTranslationsCommand.php b/Command/ImportTranslationsCommand.php index 04a9e672..ce216d4b 100644 --- a/Command/ImportTranslationsCommand.php +++ b/Command/ImportTranslationsCommand.php @@ -12,10 +12,11 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Finder\Finder; use Symfony\Component\Form\Form; use Symfony\Component\HttpKernel\Bundle\BundleInterface; -use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Validator\Validation; use Symfony\Contracts\Translation\TranslatorInterface; @@ -53,13 +54,13 @@ )] class ImportTranslationsCommand extends Command { - /** - * @param TranslatorInterface $translator - */ public function __construct( private readonly TranslatorInterface $translator, private readonly LocaleManagerInterface $localeManager, private readonly FileImporter $fileImporter, + private readonly KernelInterface $kernel, + #[Autowire('%kernel.project_dir%')] + private readonly string $projectDir, ) { parent::__construct(); } @@ -106,7 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $bundleName = $this->input->getArgument('bundle'); if ($bundleName) { - $bundle = $this->getApplication()->getKernel()->getBundle($bundleName); + $bundle = $this->kernel->getBundle($bundleName); $this->importBundleTranslationFiles($bundle, $locales, $domains, (bool)$this->input->getOption('globals')); } else { if (!$this->input->getOption('import-path')) { @@ -141,7 +142,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->importTranslationFilesFromPath($importPath, $locales, $domains); } - if ($this->input->getOption('cache-clear')) { + if ($this->input->getOption('cache-clear') && method_exists($this->translator, 'removeLocalesCacheFiles')) { $this->output->writeln('Removing translations cache files ...'); $this->translator->removeLocalesCacheFiles($locales); } @@ -181,8 +182,8 @@ protected function importTranslationFilesFromPath($path, array $locales, array $ protected function importComponentTranslationFiles(array $locales, array $domains) { $classes = [ - Validation::class => '/Resources/translations', - Form::class => '/Resources/translations', + Validation::class => '/Resources/translations', + Form::class => '/Resources/translations', AuthenticationException::class => '/../Resources/translations', ]; @@ -203,14 +204,10 @@ protected function importComponentTranslationFiles(array $locales, array $domain /** * Imports application translation files. */ - protected function importAppTranslationFiles(array $locales, array $domains) + protected function importAppTranslationFiles(array $locales, array $domains): void { - if (Kernel::MAJOR_VERSION >= 4) { - $translationPath = $this->getApplication()->getKernel()->getProjectDir() . '/translations'; - $finder = $this->findTranslationsFiles($translationPath, $locales, $domains, false); - } else { - $finder = $this->findTranslationsFiles($this->getApplication()->getKernel()->getRootDir(), $locales, $domains); - } + $translationPath = $this->projectDir . '/translations'; + $finder = $this->findTranslationsFiles($translationPath, $locales, $domains, false); $this->importTranslationFiles($finder); } @@ -221,7 +218,7 @@ protected function importAppTranslationFiles(array $locales, array $domains) */ protected function importBundlesTranslationFiles(array $locales, array $domains, $global = false) { - $bundles = $this->getApplication()->getKernel()->getBundles(); + $bundles = $this->kernel->getBundles(); foreach ($bundles as $bundle) { $this->importBundleTranslationFiles($bundle, $locales, $domains, $global); @@ -230,20 +227,11 @@ protected function importBundlesTranslationFiles(array $locales, array $domains, /** * Imports translation files form the specific bundles. - * - * @param array $locales - * @param array $domains - * @param boolean $global */ - protected function importBundleTranslationFiles(BundleInterface $bundle, $locales, $domains, $global = false) + protected function importBundleTranslationFiles(BundleInterface $bundle, array $locales, array $domains, bool $global = false): void { if ($global) { - $kernel = $this->getApplication()->getKernel(); - if (Kernel::MAJOR_VERSION >= 4) { - $path = $kernel->getProjectDir() . '/app'; - } else { - $path = $kernel->getRootDir(); - } + $path = $this->projectDir . '/app'; $path .= '/Resources/' . $bundle->getName() . '/translations'; @@ -261,7 +249,7 @@ protected function importBundleTranslationFiles(BundleInterface $bundle, $locale $bundle->getPath() . '/Resources/translations', ]; - foreach($paths as $path) { + foreach ($paths as $path) { $this->output->writeln(sprintf('# %s:', $bundle->getName())); $finder = $this->findTranslationsFiles($path, $locales, $domains, false); $this->importTranslationFiles($finder); @@ -270,10 +258,8 @@ protected function importBundleTranslationFiles(BundleInterface $bundle, $locale /** * Imports some translations files. - * - * @param Finder $finder */ - protected function importTranslationFiles($finder) + protected function importTranslationFiles(?Finder $finder): void { if (!$finder instanceof Finder) { $this->output->writeln('No file to import'); @@ -297,20 +283,17 @@ protected function importTranslationFiles($finder) /** * Return a Finder object if $path has a Resources/translations folder. - * - * @param string $path - * @return Finder */ - protected function findTranslationsFiles($path, array $locales, array $domains, $autocompletePath = true) + protected function findTranslationsFiles(string $path, array $locales, array $domains, $autocompletePath = true): ?Finder { $finder = null; - if (preg_match('#^win#i', PHP_OS)) { + if (0 === stripos(PHP_OS_FAMILY, "win")) { $path = preg_replace('#' . preg_quote(DIRECTORY_SEPARATOR, '#') . '#', '/', $path); } if (true === $autocompletePath) { - $dir = (str_starts_with((string) $path, $this->getApplication()->getKernel()->getProjectDir() . '/Resources')) ? $path : $path . '/Resources/translations'; + $dir = (str_starts_with((string) $path, $this->projectDir . '/Resources')) ? $path : $path . '/Resources/translations'; } else { $dir = $path; } @@ -327,12 +310,9 @@ protected function findTranslationsFiles($path, array $locales, array $domains, return (null !== $finder && $finder->count() > 0) ? $finder : null; } - /** - * @return string - */ - protected function getFileNamePattern(array $locales, array $domains) + protected function getFileNamePattern(array $locales, array $domains): string { - $formats = $this->translator->getFormats(); + $formats = method_exists($this->translator, 'getFormats') ? $this->translator->getFormats() : []; if (count($domains)) { $regex = sprintf('/((%s)\.(%s)\.(%s))/', implode('|', $domains), implode('|', $locales), implode('|', $formats)); diff --git a/Controller/RestController.php b/Controller/RestController.php index 08243013..51b871b9 100644 --- a/Controller/RestController.php +++ b/Controller/RestController.php @@ -10,6 +10,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * @author Cédric Girard @@ -18,8 +19,6 @@ class RestController extends AbstractController { use CsrfCheckerTrait; - private $csrfTokenManager; - public function __construct( private readonly DataGridRequestHandler $dataGridRequestHandler, private readonly DataGridFormatter $dataGridFormatter, @@ -43,7 +42,7 @@ public function listByProfileAction(Request $request, string $token): JsonRespon } /** - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public function updateAction(Request $request, int $id): JsonResponse { @@ -55,7 +54,7 @@ public function updateAction(Request $request, int $id): JsonResponse } /** - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public function deleteAction(int $id): JsonResponse { @@ -73,7 +72,7 @@ public function deleteAction(int $id): JsonResponse } /** - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public function deleteTranslationAction(int $id, string $locale): JsonResponse { diff --git a/Controller/TranslationController.php b/Controller/TranslationController.php index 2df5b8b9..5993b25c 100644 --- a/Controller/TranslationController.php +++ b/Controller/TranslationController.php @@ -6,11 +6,11 @@ use Lexik\Bundle\TranslationBundle\Form\Type\TransUnitType; use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; -use Lexik\Bundle\TranslationBundle\Translation\TranslatorDecorator; use Lexik\Bundle\TranslationBundle\Util\Csrf\CsrfCheckerTrait; use Lexik\Bundle\TranslationBundle\Util\Overview\StatsAggregator; use Lexik\Bundle\TranslationBundle\Util\Profiler\TokenFinder; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\SubmitButton; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -23,8 +23,14 @@ class TranslationController extends AbstractController { use CsrfCheckerTrait; - public function __construct(private readonly StorageInterface $translationStorage, private readonly StatsAggregator $statsAggregator, private readonly TransUnitFormHandler $transUnitFormHandler, private readonly TranslatorInterface $lexikTranslator, private readonly TranslatorInterface $translator, private readonly LocaleManagerInterface $localeManager, private readonly ?TokenFinder $tokenFinder) - { + public function __construct( + private readonly StorageInterface $translationStorage, + private readonly StatsAggregator $statsAggregator, + private readonly TransUnitFormHandler $transUnitFormHandler, + private readonly TranslatorInterface $translator, + private readonly LocaleManagerInterface $localeManager, + private readonly ?TokenFinder $tokenFinder + ) { } /** @@ -34,7 +40,13 @@ public function overviewAction(): Response { $stats = $this->statsAggregator->getStats(); - return $this->render('@LexikTranslation/Translation/overview.html.twig', ['layout' => $this->getParameter('lexik_translation.base_layout'), 'locales' => $this->getManagedLocales(), 'domains' => $this->translationStorage->getTransUnitDomains(), 'latestTrans' => $this->translationStorage->getLatestUpdatedAt(), 'stats' => $stats]); + return $this->render('@LexikTranslation/Translation/overview.html.twig', [ + 'layout' => $this->getParameter('lexik_translation.base_layout'), + 'locales' => $this->getManagedLocales(), + 'domains' => $this->translationStorage->getTransUnitDomains(), + 'latestTrans' => $this->translationStorage->getLatestUpdatedAt(), + 'stats' => $stats, + ]); } /** @@ -47,7 +59,14 @@ public function gridAction(): Response $tokens = $this->tokenFinder->find(); } - return $this->render('@LexikTranslation/Translation/grid.html.twig', ['layout' => $this->getParameter('lexik_translation.base_layout'), 'inputType' => $this->getParameter('lexik_translation.grid_input_type'), 'autoCacheClean' => $this->getParameter('lexik_translation.auto_cache_clean'), 'toggleSimilar' => $this->getParameter('lexik_translation.grid_toggle_similar'), 'locales' => $this->getManagedLocales(), 'tokens' => $tokens]); + return $this->render('@LexikTranslation/Translation/grid.html.twig', [ + 'layout' => $this->getParameter('lexik_translation.base_layout'), + 'inputType' => $this->getParameter('lexik_translation.grid_input_type'), + 'autoCacheClean' => $this->getParameter('lexik_translation.auto_cache_clean'), + 'toggleSimilar' => $this->getParameter('lexik_translation.grid_toggle_similar'), + 'locales' => $this->getManagedLocales(), + 'tokens' => $tokens, + ]); } /** @@ -55,8 +74,8 @@ public function gridAction(): Response */ public function invalidateCacheAction(Request $request): Response { - if (method_exists($this->lexikTranslator, 'removeLocalesCacheFiles')) { - $this->lexikTranslator->removeLocalesCacheFiles($this->getManagedLocales()); + if (method_exists($this->translator, 'removeLocalesCacheFiles')) { + $this->translator->removeLocalesCacheFiles($this->getManagedLocales()); } $message = $this->translator->trans('translations.cache_removed', [], 'LexikTranslationBundle'); @@ -67,7 +86,7 @@ public function invalidateCacheAction(Request $request): Response return new JsonResponse(['message' => $message]); } - $request->getSession()->getFlashBag()->add('success', $message); + $this->addFlash('success', $message); return $this->redirect($this->generateUrl('lexik_translation_grid')); } @@ -82,22 +101,22 @@ public function newAction(Request $request): Response if ($this->transUnitFormHandler->process($form, $request)) { $message = $this->translator->trans('translations.successfully_added', [], 'LexikTranslationBundle'); - $request->getSession()->getFlashBag()->add('success', $message); + $this->addFlash('success', $message); - $redirectUrl = $form->get('save_add')->isClicked() ? 'lexik_translation_new' : 'lexik_translation_grid'; + /** @var SubmitButton $btn */ + $btn = $form->get('save_add'); + $redirectUrl = $btn->isClicked() ? 'lexik_translation_new' : 'lexik_translation_grid'; return $this->redirect($this->generateUrl($redirectUrl)); } - return $this->render('@LexikTranslation/Translation/new.html.twig', ['layout' => $this->getParameter('lexik_translation.base_layout'), 'form' => $form->createView()]); + return $this->render('@LexikTranslation/Translation/new.html.twig', ['layout' => $this->getParameter('lexik_translation.base_layout'), 'form' => $form->createView()]); } /** * Returns managed locales. - * - * @return array */ - protected function getManagedLocales() + protected function getManagedLocales(): array { return $this->localeManager->getLocales(); } diff --git a/DependencyInjection/Compiler/RegisterMappingPass.php b/DependencyInjection/Compiler/RegisterMappingPass.php index 7bdbc892..ac183d75 100644 --- a/DependencyInjection/Compiler/RegisterMappingPass.php +++ b/DependencyInjection/Compiler/RegisterMappingPass.php @@ -5,10 +5,10 @@ use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; /** * Doctrine metadata pass to add a driver to load model class mapping. @@ -26,7 +26,7 @@ public function process(ContainerBuilder $container): void $name = empty($storage['object_manager']) ? 'default' : $storage['object_manager']; - $ormDriverId = sprintf('doctrine.orm.%s_metadata_driver', $name); + $ormDriverId = sprintf('doctrine.orm.%s_metadata_driver', $name); $mongodbDriverId = sprintf('doctrine_mongodb.odm.%s_metadata_driver', $name); if (StorageInterface::STORAGE_ORM == $storage['type'] && $container->hasDefinition($ormDriverId)) { diff --git a/DependencyInjection/Compiler/TranslatorPass.php b/DependencyInjection/Compiler/TranslatorPass.php index eda7d70b..1a1b8427 100644 --- a/DependencyInjection/Compiler/TranslatorPass.php +++ b/DependencyInjection/Compiler/TranslatorPass.php @@ -4,9 +4,9 @@ use Lexik\Bundle\TranslationBundle\Translation\Exporter\ExporterCollector; use Lexik\Bundle\TranslationBundle\Translation\Importer\FileImporter; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; /** @@ -21,6 +21,13 @@ class TranslatorPass implements CompilerPassInterface */ public function process(ContainerBuilder $container): void { + // Keep the compatibility with old versions of the bundle (options fallback_locale and managed_locales). + $this->processEnabledLocales($container); + $this->processFallbacksLocales($container); + + $this->processCacheDir($container); + $this->processRessources($container); + // loaders $loaders = []; $loadersReferences = []; @@ -37,29 +44,13 @@ public function process(ContainerBuilder $container): void } } - // Find the translator service by alias or class name - $translatorId = null; if ($container->hasDefinition('lexik_translation.translator')) { - $translatorId = 'lexik_translation.translator'; - } elseif ($container->hasAlias('lexik_translation.translator')) { - $translatorId = (string) $container->getAlias('lexik_translation.translator'); - } elseif ($container->hasDefinition('Lexik\Bundle\TranslationBundle\Translation\Translator')) { - $translatorId = 'Lexik\Bundle\TranslationBundle\Translation\Translator'; - } - - if ($translatorId && $container->hasDefinition($translatorId)) { - $translatorDef = $container->findDefinition($translatorId); + $translatorDef = $container->findDefinition('lexik_translation.translator'); $serviceRefs = [...$loadersReferencesById, ...['event_dispatcher' => new Reference('event_dispatcher')]]; - // Use named arguments if available, otherwise use numeric indices - if ($translatorDef->getArguments() && array_key_exists('$container', $translatorDef->getArguments())) { - $translatorDef->replaceArgument('$container', ServiceLocatorTagPass::register($container, $serviceRefs)); - $translatorDef->replaceArgument('$loaderIds', $loaders); - } else { - $translatorDef->replaceArgument(0, ServiceLocatorTagPass::register($container, $serviceRefs)); - $translatorDef->replaceArgument(3, $loaders); - } + $translatorDef->replaceArgument('$container', ServiceLocatorTagPass::register($container, $serviceRefs)); + $translatorDef->replaceArgument('$loaderIds', $loaders); } if ($container->hasDefinition(FileImporter::class)) { @@ -73,4 +64,68 @@ public function process(ContainerBuilder $container): void } } } + + private function processRessources(ContainerBuilder $container): void + { + $translator = $container->getDefinition('translator.default'); + + $defaultOptions = $translator->getArgument(4); + + // If the resources type is set to "database", we don't want to load any resource file, as they will be loaded from the database. + $ressourcesType = $container->getParameter('lexik_translation.resources_type'); + + if ('database' === $ressourcesType) { + $defaultOptions['resource_files'] = []; + $translator->replaceArgument(4, $defaultOptions); + return; + } + + // If the option "managed_locales_only" is set to true, we only want to load the resource files for the enabled locales. + if (true === $container->getParameter('lexik_translation.managed_locales_only')) { + $enabledLocales = $translator->getArgument(5); + + $defaultOptions['resource_files'] = array_filter($defaultOptions['resource_files'], static function ($locale) use ($enabledLocales) { + return in_array($locale, $enabledLocales, true); + }, \ARRAY_FILTER_USE_KEY); + + $translator->replaceArgument(4, $defaultOptions); + } + } + + private function processFallbacksLocales(ContainerBuilder $container): void + { + $fallbackLocale = $container->getParameter('lexik_translation.fallback_locale'); + + if (!empty($fallbackLocale)) { + $translator = $container->findDefinition('translator.default'); + $translator->addMethodCall('setFallbackLocales', [ $fallbackLocale ]); + } + } + + private function processEnabledLocales(ContainerBuilder $container): void + { + $enabledLocales = $container->getParameter('kernel.enabled_locales'); + + if (empty($enabledLocales)) { + $fallbackEnabledLocales = $container->getParameter('lexik_translation.managed_locales'); + if (!empty($fallbackEnabledLocales)) { + $enabledLocales = $fallbackEnabledLocales; + } + $translator = $container->findDefinition('translator.default'); + $translator->replaceArgument(5, $enabledLocales); + } + } + + private function processCacheDir(ContainerBuilder $container): void + { + $translator = $container->findDefinition('translator.default'); + $options = $translator->getArgument(4); + $cacheDir = $options['cache_dir'] ?? null; + if (null !== $cacheDir) { + $translatorDef = $container->findDefinition('lexik_translation.translator'); + $defaultOptions = $translatorDef->getArgument('$options'); + $defaultOptions['cache_dir'] = $cacheDir; + $translatorDef->replaceArgument('$options', $defaultOptions); + } + } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index f71c858f..f21dd3df 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -40,19 +40,19 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->arrayNode('fallback_locale') - ->isRequired() - ->requiresAtLeastOneElement() + ->setDeprecated('lexik/translation-bundle', '8.0', 'The "%node%" option is deprecated. Use "framework.translator.fallbacks" instead.') ->prototype('scalar')->end() + ->defaultValue([]) ->beforeNormalization() ->ifString() - ->then(fn($value) => [$value]) + ->then(fn ($value) => [$value]) ->end() ->end() ->arrayNode('managed_locales') - ->isRequired() - ->requiresAtLeastOneElement() + ->info('Locales managed in the translation grid. If not set, falls back to "framework.enabled_locales".') ->prototype('scalar')->end() + ->defaultValue([]) ->end() ->scalarNode('grid_input_type') @@ -60,7 +60,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue('text') ->validate() ->ifNotInArray($inputTypes) - ->thenInvalid('The input type "%s" is not supported. Please use one of the following types: '.implode(', ', $inputTypes)) + ->thenInvalid('The input type "%s" is not supported. Please use one of the following types: ' . implode(', ', $inputTypes)) ->end() ->end() @@ -84,7 +84,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue(StorageInterface::STORAGE_ORM) ->validate() ->ifNotInArray($storages) - ->thenInvalid('The storage "%s" is not supported. Please use one of the following storage: '.implode(', ', $storages)) + ->thenInvalid('The storage "%s" is not supported. Please use one of the following storage: ' . implode(', ', $storages)) ->end() ->end() ->scalarNode('object_manager') @@ -100,7 +100,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue('all') ->validate() ->ifNotInArray($registrationTypes) - ->thenInvalid('Invalid registration type "%s". Please use one of the following types: '.implode(', ', $registrationTypes)) + ->thenInvalid('Invalid registration type "%s". Please use one of the following types: ' . implode(', ', $registrationTypes)) ->end() ->end() ->booleanNode('managed_locales_only') diff --git a/DependencyInjection/LexikTranslationExtension.php b/DependencyInjection/LexikTranslationExtension.php index d4d8c680..d20b13ba 100644 --- a/DependencyInjection/LexikTranslationExtension.php +++ b/DependencyInjection/LexikTranslationExtension.php @@ -2,26 +2,21 @@ namespace Lexik\Bundle\TranslationBundle\DependencyInjection; -use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use Symfony\Component\Validator\Validation; -use Symfony\Component\Form\Form; -use Symfony\Component\Security\Core\Exception\AuthenticationException; use Doctrine\ORM\Events; -use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; +use Lexik\Bundle\TranslationBundle\LexikTranslationBundle; use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; -use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Definition\Processor; -use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Parameter; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\DependencyInjection\Reference; /** * This is the class that loads and manages your bundle configuration @@ -41,15 +36,23 @@ public function load(array $configs, ContainerBuilder $container): void $configuration = new Configuration(); $config = $processor->processConfiguration($configuration, $configs); - $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.yaml'); - // set parameters - sort($config['managed_locales']); - $container->setParameter('lexik_translation.managed_locales', $config['managed_locales']); + // Resolve managed_locales: fallback to framework.enabled_locales if not set + $managedLocales = $config['managed_locales']; + if (empty($managedLocales)) { + $managedLocales = $container->hasParameter('kernel.enabled_locales') + ? $container->getParameter('kernel.enabled_locales') + : []; + } + sort($managedLocales); + + $container->setParameter('lexik_translation.managed_locales', $managedLocales); $container->setParameter('lexik_translation.fallback_locale', $config['fallback_locale']); $container->setParameter('lexik_translation.storage', $config['storage']); $container->setParameter('lexik_translation.resources_type', $config['resources_registration']['type']); + $container->setParameter('lexik_translation.managed_locales_only', $config['resources_registration']['managed_locales_only']); $container->setParameter('lexik_translation.base_layout', $config['base_layout']); $container->setParameter('lexik_translation.grid_input_type', $config['grid_input_type']); $container->setParameter('lexik_translation.grid_toggle_similar', $config['grid_toggle_similar']); @@ -60,7 +63,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('lexik_translation.exporter.json.hierarchical_format', $config['exporter']['json_hierarchical_format']); $container->setParameter('lexik_translation.exporter.yml.use_tree', $config['exporter']['use_yml_tree']); - $objectManager = $config['storage']['object_manager'] ?? null; + $objectManager = $config['storage']['object_manager'] ?? 'default'; $this->buildTranslationStorageDefinition($container, $config['storage']['type'], $objectManager); @@ -71,14 +74,12 @@ public function load(array $configs, ContainerBuilder $container): void if (true === $config['dev_tools']['enable']) { $this->buildDevServicesDefinition($container); } - - $this->registerTranslatorConfiguration($config, $container); } /** * @param int $cacheInterval */ - public function buildCacheCleanListenerDefinition(ContainerBuilder $container, $cacheInterval) + public function buildCacheCleanListenerDefinition(ContainerBuilder $container, $cacheInterval): void { $listener = new Definition(); $listener->setClass('%lexik_translation.listener.clean_translation_cache.class%'); @@ -89,7 +90,7 @@ public function buildCacheCleanListenerDefinition(ContainerBuilder $container, $ $listener->addArgument(new Reference(LocaleManagerInterface::class)); $listener->addArgument($cacheInterval); - $listener->addTag('kernel.event_listener', ['event' => 'kernel.request', 'method' => 'onKernelRequest']); + $listener->addTag('kernel.event_listener', ['event' => 'kernel.request', 'method' => 'onKernelRequest']); $container->setDefinition('lexik_translation.listener.clean_translation_cache', $listener); } @@ -116,12 +117,12 @@ public function prepend(ContainerBuilder $container): void * @param string $objectManager * @throws \RuntimeException */ - protected function buildTranslationStorageDefinition(ContainerBuilder $container, $storage, $objectManager) + protected function buildTranslationStorageDefinition(ContainerBuilder $container, $storage, string $objectManager): void { $container->setParameter('lexik_translation.storage.type', $storage); - if (StorageInterface::STORAGE_ORM == $storage) { - $args = [new Reference('doctrine'), $objectManager ?? 'default']; + if (StorageInterface::STORAGE_ORM === $storage) { + $args = [new Reference('doctrine'), $objectManager]; // Create XML driver for backward compatibility if (class_exists(SimplifiedXmlDriver::class)) { @@ -140,8 +141,8 @@ protected function buildTranslationStorageDefinition(ContainerBuilder $container $container->setDefinition('lexik_translation.orm.listener', $metadataListener); - } elseif (StorageInterface::STORAGE_MONGODB == $storage) { - $args = [new Reference('doctrine_mongodb'), $objectManager ?? 'default']; + } elseif (StorageInterface::STORAGE_MONGODB === $storage) { + $args = [new Reference('doctrine_mongodb'), $objectManager]; $this->createDoctrineMappingDriver($container, 'lexik_translation.mongodb.metadata.xml', '%doctrine_mongodb.odm.metadata.xml.class%'); } else { @@ -149,9 +150,9 @@ protected function buildTranslationStorageDefinition(ContainerBuilder $container } $args[] = [ - 'trans_unit' => new Parameter(sprintf('lexik_translation.%s.trans_unit.class', $storage)), + 'trans_unit' => new Parameter(sprintf('lexik_translation.%s.trans_unit.class', $storage)), 'translation' => new Parameter(sprintf('lexik_translation.%s.translation.class', $storage)), - 'file' => new Parameter(sprintf('lexik_translation.%s.file.class', $storage)) + 'file' => new Parameter(sprintf('lexik_translation.%s.file.class', $storage)) ]; $storageDefinition = new Definition(); @@ -168,7 +169,7 @@ protected function buildTranslationStorageDefinition(ContainerBuilder $container * @param string $driverId * @param string $driverClass */ - protected function createDoctrineMappingDriver(ContainerBuilder $container, $driverId, $driverClass) + protected function createDoctrineMappingDriver(ContainerBuilder $container, $driverId, $driverClass): void { $driverDefinition = new Definition($driverClass, [ [dirname(__DIR__) . '/Resources/config/model' => 'Lexik\Bundle\TranslationBundle\Model'], @@ -185,11 +186,11 @@ protected function createDoctrineMappingDriver(ContainerBuilder $container, $dri * @param ContainerBuilder $container * @param string $driverId */ - protected function createDoctrineAttributeDriver(ContainerBuilder $container, $driverId) + protected function createDoctrineAttributeDriver(ContainerBuilder $container, $driverId): void { // Calculate bundle path using ReflectionClass to get the actual bundle location // This works even when the bundle is installed via Composer or symlinked - $bundleReflection = new \ReflectionClass(\Lexik\Bundle\TranslationBundle\LexikTranslationBundle::class); + $bundleReflection = new \ReflectionClass(LexikTranslationBundle::class); $bundleDir = dirname($bundleReflection->getFileName()); $modelPath = $bundleDir . '/Model'; @@ -213,7 +214,7 @@ protected function createDoctrineAttributeDriver(ContainerBuilder $container, $d /** * Load dev tools. */ - protected function buildDevServicesDefinition(ContainerBuilder $container) + protected function buildDevServicesDefinition(ContainerBuilder $container): void { $container ->getDefinition('lexik_translation.data_grid.request_handler') @@ -225,103 +226,4 @@ protected function buildDevServicesDefinition(ContainerBuilder $container) $container->setDefinition($container->getParameter('lexik_translation.token_finder.class'), $tokenFinderDefinition); } - - /** - * Register the "lexik_translation.translator" service configuration. - */ - protected function registerTranslatorConfiguration(array $config, ContainerBuilder $container) - { - // use the Lexik translator decorator as default translator service - $alias = $container->setAlias('translator', 'lexik_translation.translator'); - $alias->setPublic(true); - - // Get the inner translator (the actual Symfony translator) for adding resources - // The decorator will delegate to it - $innerTranslator = $container->hasDefinition('lexik_translation.translator.inner') - ? $container->findDefinition('lexik_translation.translator.inner') - : $container->findDefinition('translator'); - - $innerTranslator->addMethodCall('setFallbackLocales', [$config['fallback_locale']]); - - // For adding file resources, we'll add them to the inner translator - $translator = $innerTranslator; - - $registration = $config['resources_registration']; - - // Discover translation directories - if ('all' === $registration['type'] || 'files' === $registration['type']) { - $dirs = []; - - if (class_exists(Validation::class)) { - $r = new \ReflectionClass(Validation::class); - - $dirs[] = dirname($r->getFilename()).'/Resources/translations'; - } - - if (class_exists(Form::class)) { - $r = new \ReflectionClass(Form::class); - - $dirs[] = dirname($r->getFilename()).'/Resources/translations'; - } - - if (class_exists(AuthenticationException::class)) { - $r = new \ReflectionClass(AuthenticationException::class); - - if (is_dir($dir = dirname($r->getFilename()).'/../Resources/translations')) { - $dirs[] = $dir; - } - } - - $overridePath = $container->getParameter('kernel.project_dir').'/Resources/%s/translations'; - - foreach ($container->getParameter('kernel.bundles') as $bundle => $class) { - $reflection = new \ReflectionClass($class); - - if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/translations')) { - $dirs[] = $dir; - } - - if (is_dir($dir = dirname($reflection->getFileName(), 2).'/translations')) { - $dirs[] = $dir; - } - - if (is_dir($dir = sprintf($overridePath, $bundle))) { - $dirs[] = $dir; - } - } - - if (is_dir($dir = $container->getParameter('kernel.project_dir').'/Resources/translations')) { - $dirs[] = $dir; - } - - if (Kernel::MAJOR_VERSION >= 4 && is_dir($dir = $container->getParameter('kernel.project_dir').'/translations')) { - $dirs[] = $dir; - } - - // Register translation resources - if (count($dirs) > 0) { - foreach ($dirs as $dir) { - $container->addResource(new DirectoryResource($dir)); - } - - $finder = Finder::create(); - $finder->files(); - - if (true === $registration['managed_locales_only']) { - // only look for managed locales - $finder->name(sprintf('/(.*\.(%s)\.\w+$)/', implode('|', $config['managed_locales']))); - } else { - $finder->filter(fn(\SplFileInfo $file) => 2 === substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename())); - } - - $finder->in($dirs); - - foreach ($finder as $file) { - // filename is domain.locale.format - [$domain, $locale, $format] = explode('.', $file->getBasename(), 3); - $translator->addMethodCall('addResource', [$format, (string) $file, $locale, $domain]); - } - } - } - } } diff --git a/Dockerfile b/Dockerfile index 986dde95..7db9c737 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.3-cli-bullseye +FROM php:8.4-cli-bullseye ENV COMPOSER_ALLOW_SUPERUSER=1 @@ -21,8 +21,4 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer WORKDIR /app -COPY . . - -RUN composer install --prefer-dist --no-progress - CMD ["composer", "test"] diff --git a/Document/File.php b/Document/File.php index 806971ab..d57ca4a5 100644 --- a/Document/File.php +++ b/Document/File.php @@ -2,18 +2,14 @@ namespace Lexik\Bundle\TranslationBundle\Document; -use Lexik\Bundle\TranslationBundle\Model\File as FileModel; -use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use DateTime; +use Lexik\Bundle\TranslationBundle\Model\File as FileModel; /** * @author Cédric Girard */ -class File extends FileModel implements FileInterface +class File extends FileModel { - /** - * {@inheritdoc} - */ public function prePersist(): void { $now = new DateTime("now"); @@ -22,9 +18,6 @@ public function prePersist(): void $this->updatedAt = $now->format('U'); } - /** - * {@inheritdoc} - */ public function preUpdate(): void { $now = new DateTime("now"); diff --git a/Document/TransUnit.php b/Document/TransUnit.php index 0fc0b0f9..bec506cc 100644 --- a/Document/TransUnit.php +++ b/Document/TransUnit.php @@ -2,9 +2,8 @@ namespace Lexik\Bundle\TranslationBundle\Document; -use Lexik\Bundle\TranslationBundle\Model\TransUnit as TransUnitModel; use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; -use MongoTimestamp; +use Lexik\Bundle\TranslationBundle\Model\TransUnit as TransUnitModel; /** * @author Cédric Girard @@ -12,16 +11,10 @@ class TransUnit extends TransUnitModel implements TransUnitInterface { /** - * Convert all MongoTimestamp object to time. + * @deprecated No longer needed since the legacy mongo extension is no longer supported. */ public function convertMongoTimestamp(): void { - $this->createdAt = ($this->createdAt instanceof MongoTimestamp) ? $this->createdAt->sec : $this->createdAt; - $this->updatedAt = ($this->updatedAt instanceof MongoTimestamp) ? $this->updatedAt->sec : $this->updatedAt; - - foreach ($this->getTranslations() as $translation) { - $translation->convertMongoTimestamp(); - } } /** diff --git a/Document/TransUnitRepository.php b/Document/TransUnitRepository.php index bb40979a..ac7639ea 100644 --- a/Document/TransUnitRepository.php +++ b/Document/TransUnitRepository.php @@ -4,7 +4,7 @@ use Doctrine\ODM\MongoDB\Query\Builder; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Lexik\Bundle\TranslationBundle\Model\File as ModelFile; +use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use MongoDB\BSON\ObjectId; use MongoDB\BSON\Regex; @@ -17,10 +17,8 @@ class TransUnitRepository extends DocumentRepository { /** * Returns all domain available in database. - * - * @return array */ - public function getAllDomains() + public function getAllDomains(): array { $results = $this->createQueryBuilder() ->distinct('domain') @@ -115,7 +113,7 @@ public function getAllDomainsByLocale(): array } } - usort($domainsByLocale, function ($a, $b) { + usort($domainsByLocale, static function ($a, $b) { $result = strcmp((string) $a['locale'], (string) $b['locale']); if (0 === $result) { $result = strcmp((string) $a['domain'], (string) $b['domain']); @@ -133,7 +131,7 @@ public function getAllDomainsByLocale(): array * @param string $domain * @return array */ - public function getAllByLocaleAndDomain($locale, $domain) + public function getAllByLocaleAndDomain(string $locale, string $domain): array { $results = $this->createQueryBuilder() ->hydrate(false) @@ -148,7 +146,7 @@ public function getAllByLocaleAndDomain($locale, $domain) $i = 0; $index = null; while ($i < $item['translations'] && null === $index) { - if ($item['translations'][$i]['locale'] == $locale) { + if ($item['translations'][$i]['locale'] === $locale) { $index = $i; } $i++; @@ -216,11 +214,15 @@ public function getTransUnitList(?array $locales = null, int $rows = 20, int $pa /** * Count the number of trans unit. + * @param array $criteria */ - public function count(?array $locales = null, ?array $filters = null): int + public function count(array $criteria = []): int { $builder = $this->createQueryBuilder(); + $filters = $criteria['filters'] ?? null; + $locales = $criteria['locales'] ?? null; + $this->addTransUnitFilters($builder, $filters); $this->addTranslationFilter($builder, $locales, $filters); @@ -233,11 +235,8 @@ public function count(?array $locales = null, ?array $filters = null): int /** * Returns all translations for the given file. - * - * @param boolean $onlyUpdated - * @return array */ - public function getTranslationsForFile(ModelFile $file, $onlyUpdated) + public function getTranslationsForFile(FileInterface $file, bool $onlyUpdated): array { $builder = $this->createQueryBuilder() ->hydrate(false) @@ -252,12 +251,12 @@ public function getTranslationsForFile(ModelFile $file, $onlyUpdated) $content = null; $i = 0; while ($i < (is_countable($result['translations']) ? count($result['translations']) : 0) && null === $content) { - if ($file->getLocale() == $result['translations'][$i]['locale']) { + if ($file->getLocale() === $result['translations'][$i]['locale']) { if ($onlyUpdated) { // Handle MongoDB Timestamp objects - they have a 'sec' property $createdAt = $result['translations'][$i]['createdAt'] ?? null; $updatedAt = $result['translations'][$i]['updatedAt'] ?? null; - + if ($createdAt && $updatedAt) { $createdAtSec = \is_object($createdAt) && \property_exists($createdAt, 'sec') ? $createdAt->sec : $createdAt; $updatedAtSec = \is_object($updatedAt) && \property_exists($updatedAt, 'sec') ? $updatedAt->sec : $updatedAt; @@ -282,7 +281,7 @@ public function getTranslationsForFile(ModelFile $file, $onlyUpdated) /** * Add conditions according to given filters. */ - protected function addTransUnitFilters(Builder $builder, ?array $filters = null) + protected function addTransUnitFilters(Builder $builder, ?array $filters = null): void { if (isset($filters['_search']) && $filters['_search']) { if (!empty($filters['domain'])) { @@ -341,71 +340,53 @@ public function getLatestTranslationUpdatedAt() ->getQuery() ->getSingleResult(); - if (!isset($result['translations'], $result['translations'][0])) { + if (!isset($result['translations'][0])) { return null; } return new \DateTime(date('Y-m-d H:i:s', $result['translations'][0]['updated_at']->sec)); } - /** - * @return array - */ - public function countByDomains() + public function countByDomains(): array { - $reduce = <<createAggregationBuilder(); + $results = $aggregationBuilder + ->group() + ->field('_id')->expression('$domain') + ->field('number')->sum(1) + ->execute(); - $results = $this->createQueryBuilder() - ->group([], - []) // @todo: group and reduce won't work anymore, but this method seems to be untested - ->reduce($reduce) - ->hydrate(false) - ->getQuery() - ->execute(); + $counts = []; + foreach ($results as $row) { + $counts[] = [ + 'domain' => $row['_id'], + 'number' => $row['number'], + ]; + } - return $results[0]['count'] ?? []; + return $counts; } - /** - * @param string $domain - * @return array - */ - public function countTranslationsByLocales($domain) + public function countTranslationsByLocales(string $domain): array { - $reduce = <<createAggregationBuilder(); + $results = $aggregationBuilder + ->match() + ->field('domain')->equals($domain) + ->unwind('$translations') + ->group() + ->field('_id')->expression('$translations.locale') + ->field('number')->sum(1) + ->execute(); - $results = $this->createQueryBuilder() - ->field('domain')->equals($domain) - ->group([], []) // @todo: won't work, untested - ->reduce($reduce) - ->hydrate(false) - ->getQuery() - ->execute(); + $counts = []; + foreach ($results as $row) { + $counts[] = [ + 'locale' => $row['_id'], + 'number' => $row['number'], + ]; + } - return $results[0]['count'] ?? []; + return $counts; } } diff --git a/Document/Translation.php b/Document/Translation.php index b7bd6b4a..1bc124da 100644 --- a/Document/Translation.php +++ b/Document/Translation.php @@ -2,8 +2,9 @@ namespace Lexik\Bundle\TranslationBundle\Document; -use Lexik\Bundle\TranslationBundle\Model\Translation as TranslationModel; +use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use Lexik\Bundle\TranslationBundle\Manager\TranslationInterface; +use Lexik\Bundle\TranslationBundle\Model\Translation as TranslationModel; /** * @author Cédric Girard @@ -11,25 +12,13 @@ class Translation extends TranslationModel implements TranslationInterface { // Relationship mapping is defined in XML: Resources/config/doctrine/Translation.mongodb-odm.xml - protected $file; - - public function setFile($file): void - { - $this->file = $file; - } - - public function getFile() - { - return $this->file; - } + protected ?FileInterface $file = null; /** - * Convert all MongoTimestamp object to time. + * @deprecated No longer needed since the legacy mongo extension is no longer supported. */ public function convertMongoTimestamp(): void { - $this->createdAt = ($this->createdAt instanceof \MongoTimestamp) ? $this->createdAt->sec : $this->createdAt; - $this->updatedAt = ($this->updatedAt instanceof \MongoTimestamp) ? $this->updatedAt->sec : $this->updatedAt; } /** diff --git a/Entity/File.php b/Entity/File.php index 0f65c5ee..9c5fa7a7 100644 --- a/Entity/File.php +++ b/Entity/File.php @@ -6,7 +6,6 @@ use Doctrine\ORM\Mapping\HasLifecycleCallbacks; use Doctrine\ORM\Mapping\PrePersist; use Doctrine\ORM\Mapping\PreUpdate; -use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use Lexik\Bundle\TranslationBundle\Model\File as FileModel; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; @@ -15,7 +14,7 @@ */ #[HasLifecycleCallbacks] #[UniqueEntity(fields: ['hash'])] -class File extends FileModel implements FileInterface +class File extends FileModel { protected $id; @@ -23,9 +22,6 @@ class File extends FileModel implements FileInterface // Relationship mapping is defined in XML: Resources/config/doctrine/File.orm.xml protected Collection $translations; - /** - * {@inheritdoc} - */ #[PrePersist] public function prePersist(): void { @@ -33,9 +29,6 @@ public function prePersist(): void $this->updatedAt = new \DateTime("now"); } - /** - * {@inheritdoc} - */ #[PreUpdate] public function preUpdate(): void { diff --git a/Entity/TransUnit.php b/Entity/TransUnit.php index b097fd2e..5b72be6b 100644 --- a/Entity/TransUnit.php +++ b/Entity/TransUnit.php @@ -2,15 +2,15 @@ namespace Lexik\Bundle\TranslationBundle\Entity; +use DateTime; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping\HasLifecycleCallbacks; use Doctrine\ORM\Mapping\PrePersist; use Doctrine\ORM\Mapping\PreUpdate; -use Lexik\Bundle\TranslationBundle\Entity\Translation; +use Lexik\Bundle\TranslationBundle\Document\Translation as DocumentTranslation; use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; use Lexik\Bundle\TranslationBundle\Model\TransUnit as TransUnitModel; -use Lexik\Bundle\TranslationBundle\Document\Translation as DocumentTranslation; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -use DateTime; /** * @author Cédric Girard @@ -23,12 +23,10 @@ class TransUnit extends TransUnitModel implements TransUnitInterface // translations property is inherited from TransUnitModel // Relationship mapping is defined in XML: Resources/config/doctrine/TransUnit.orm.xml - protected $translations; + protected Collection $translations; /** * Add translations - * - * @param \Lexik\Bundle\TranslationBundle\Entity\Translation $translations */ public function addTranslation(DocumentTranslation|Translation $translation): void { diff --git a/Entity/TransUnitRepository.php b/Entity/TransUnitRepository.php index 277aa5f1..d272a19d 100644 --- a/Entity/TransUnitRepository.php +++ b/Entity/TransUnitRepository.php @@ -2,11 +2,10 @@ namespace Lexik\Bundle\TranslationBundle\Entity; -use Lexik\Bundle\TranslationBundle\Util\Doctrine\SingleColumnArrayHydrator; -use Doctrine\ORM\Query; -use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\EntityRepository; -use Lexik\Bundle\TranslationBundle\Model\File as ModelFile; +use Doctrine\ORM\QueryBuilder; +use Lexik\Bundle\TranslationBundle\Manager\FileInterface; +use Lexik\Bundle\TranslationBundle\Util\Doctrine\SingleColumnArrayHydrator; /** * Repository for TransUnit entity. @@ -17,10 +16,8 @@ class TransUnitRepository extends EntityRepository { /** * Returns all domain available in database. - * - * @return array */ - public function getAllDomainsByLocale() + public function getAllDomainsByLocale(): array { return $this->createQueryBuilder('tu') ->select('te.locale, tu.domain') @@ -34,10 +31,8 @@ public function getAllDomainsByLocale() /** * Returns all domains for each locale. - * - * @return array */ - public function getAllByLocaleAndDomain($locale, $domain): mixed + public function getAllByLocaleAndDomain(string $locale, string $domain): array { return $this->createQueryBuilder('tu') ->select('tu, te') @@ -68,12 +63,8 @@ public function getAllDomains(): mixed /** * Returns some trans units with their translations. - * - * @param int $rows - * @param int $page - * @return array */ - public function getTransUnitList(?array $locales = null, $rows = 20, $page = 1, ?array $filters = null): mixed + public function getTransUnitList(?array $locales = null, int $rows = 20, int $page = 1, ?array $filters = null): array { $this->loadCustomHydrator(); @@ -130,10 +121,7 @@ public function count(array $criteria = []): int return (int) $builder->getQuery()->getSingleScalarResult(); } - /** - * @return array - */ - public function countByDomains(): mixed + public function countByDomains(): array { return $this->createQueryBuilder('tu') ->select('COUNT(DISTINCT tu.id) AS number, tu.domain') @@ -144,11 +132,8 @@ public function countByDomains(): mixed /** * Returns all translations for the given file. - * - * @param boolean $onlyUpdated - * @return array */ - public function getTranslationsForFile(ModelFile $file, $onlyUpdated): array + public function getTranslationsForFile(FileInterface $file, bool $onlyUpdated): array { $builder = $this->createQueryBuilder('tu') ->select('tu.key, te.content') diff --git a/Entity/Translation.php b/Entity/Translation.php index 6c609059..8d05ec8b 100644 --- a/Entity/Translation.php +++ b/Entity/Translation.php @@ -2,14 +2,15 @@ namespace Lexik\Bundle\TranslationBundle\Entity; +use DateTime; use Doctrine\ORM\Mapping\HasLifecycleCallbacks; use Doctrine\ORM\Mapping\PrePersist; use Doctrine\ORM\Mapping\PreUpdate; +use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use Lexik\Bundle\TranslationBundle\Manager\TranslationInterface; -use Lexik\Bundle\TranslationBundle\Model\TransUnit; use Lexik\Bundle\TranslationBundle\Model\Translation as TranslationModel; +use Lexik\Bundle\TranslationBundle\Model\TransUnit; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -use DateTime; /** * @author Cédric Girard @@ -23,17 +24,7 @@ class Translation extends TranslationModel implements TranslationInterface // Relationship mappings are defined in XML: Resources/config/doctrine/Translation.orm.xml protected $transUnit; - protected $file; - - public function setFile($file): void - { - $this->file = $file; - } - - public function getFile() - { - return $this->file; - } + protected ?FileInterface $file = null; // modifiedManually is inherited from TranslationModel @@ -73,7 +64,7 @@ public function getTransUnit(): TransUnit #[PrePersist] public function prePersist(): void { - $now = new DateTime("now"); + $now = new DateTime("now"); $this->createdAt = $now; $this->updatedAt = $now; } diff --git a/Entity/TranslationRepository.php b/Entity/TranslationRepository.php index 08e763d2..20567553 100644 --- a/Entity/TranslationRepository.php +++ b/Entity/TranslationRepository.php @@ -2,8 +2,8 @@ namespace Lexik\Bundle\TranslationBundle\Entity; -use Doctrine\ORM\EntityRepository; use Datetime; +use Doctrine\ORM\EntityRepository; /** * Repository for Translation entity. diff --git a/EventDispatcher/CleanTranslationCacheListener.php b/EventDispatcher/CleanTranslationCacheListener.php index 1213559f..b6b0af4c 100644 --- a/EventDispatcher/CleanTranslationCacheListener.php +++ b/EventDispatcher/CleanTranslationCacheListener.php @@ -4,8 +4,8 @@ use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; -use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -32,10 +32,10 @@ public function onKernelRequest(RequestEvent $event) $finder = new Finder(); $finder->files() - ->in($this->cacheDirectory.'/translations') - ->date('< '.$lastUpdateTime->format('Y-m-d H:i:s')); + ->in($this->cacheDirectory . '/translations') + ->date('< ' . $lastUpdateTime->format('Y-m-d H:i:s')); - if ($finder->count() > 0) { + if ($finder->count() > 0 && method_exists($this->translator, 'removeLocalesCacheFiles')) { $this->translator->removeLocalesCacheFiles($this->localeManager->getLocales()); } } @@ -53,15 +53,15 @@ private function isCacheExpired() return true; } - $cache_file = $this->cacheDirectory.'/translations/cache_timestamp'; - $cache_dir =$this->cacheDirectory.'/translations'; + $cache_file = $this->cacheDirectory . '/translations/cache_timestamp'; + $cache_dir = $this->cacheDirectory . '/translations'; if ('\\' === DIRECTORY_SEPARATOR) { $cache_file = strtr($cache_file, '/', '\\'); $cache_dir = strtr($cache_dir, '/', '\\'); } if (!\is_dir($cache_dir) && !\mkdir($cache_dir, 0777, true) && !\is_dir($cache_dir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $cache_dir)); - } + } if (!\file_exists($cache_file)) { \touch($cache_file); return true; @@ -77,7 +77,7 @@ private function isCacheExpired() private function checkCacheFolder() { - if (!is_dir($dirName = $this->cacheDirectory.'/translations') && !mkdir($dirName) && !is_dir($dirName)) { + if (!is_dir($dirName = $this->cacheDirectory . '/translations') && !mkdir($dirName) && !is_dir($dirName)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $dirName)); } } diff --git a/EventDispatcher/DatabaseResourcesListener.php b/EventDispatcher/DatabaseResourcesListener.php deleted file mode 100644 index e0b27edb..00000000 --- a/EventDispatcher/DatabaseResourcesListener.php +++ /dev/null @@ -1,80 +0,0 @@ - - */ -class DatabaseResourcesListener implements EventSubscriberInterface -{ - private bool $resourcesAdded = false; - - public function __construct( - #[Autowire(service: 'translator')] - private readonly TranslatorInterface $translator, - #[Autowire(service: 'Lexik\Bundle\TranslationBundle\Translation\Loader')] - private readonly DatabaseLoader $databaseLoader, - #[Autowire([ - 'resources_type' => '%lexik_translation.resources_type%' - ])] - private readonly array $options - ) { - } - - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::REQUEST => ['addDatabaseResources', 10], - ]; - } - - public function addDatabaseResources(RequestEvent $event): void - { - if ($this->resourcesAdded) { - return; - } - - $resourcesType = $this->options['resources_type'] ?? 'all'; - if ('all' !== $resourcesType && 'database' !== $resourcesType) { - return; - } - - // Get database resources via event - $eventDispatcher = $event->getKernel()->getContainer()->get('event_dispatcher'); - $getResourcesEvent = new GetDatabaseResourcesEvent(); - $eventDispatcher->dispatch($getResourcesEvent); - - $resources = $getResourcesEvent->getResources(); - - // Add resources to translator if it's our decorator - if ($this->translator instanceof TranslatorDecorator) { - $this->translator->addDatabaseResources(); - } elseif (method_exists($this->translator, 'addResource')) { - // Fallback for direct translator access - foreach ($resources as $resource) { - $locale = $resource['locale']; - $domain = $resource['domain'] ?? 'messages'; - $this->translator->addResource('database', 'DB', $locale, $domain); - } - } - // Note: If neither works, the DatabaseLoader will still be called - // automatically when translations are requested for a locale/domain - - $this->resourcesAdded = true; - } -} diff --git a/EventDispatcher/GetDatabaseResourcesListener.php b/EventDispatcher/GetDatabaseResourcesListener.php index 14cd70f6..6df69e25 100644 --- a/EventDispatcher/GetDatabaseResourcesListener.php +++ b/EventDispatcher/GetDatabaseResourcesListener.php @@ -3,6 +3,7 @@ namespace Lexik\Bundle\TranslationBundle\EventDispatcher; use Lexik\Bundle\TranslationBundle\EventDispatcher\Event\GetDatabaseResourcesEvent; +use Lexik\Bundle\TranslationBundle\Storage\DoctrineORMStorage; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; /** @@ -19,10 +20,10 @@ public function __construct( /** * Query the database to get translation resources and set it on the event. */ - public function onGetDatabaseResources(GetDatabaseResourcesEvent $event) + public function onGetDatabaseResources(GetDatabaseResourcesEvent $event): void { // prevent errors on command such as cache:clear if doctrine schema has not been updated yet - if (StorageInterface::STORAGE_ORM == $this->storageType && !$this->storage->translationsTablesExist()) { + if (StorageInterface::STORAGE_ORM === $this->storageType && $this->storage instanceof DoctrineORMStorage && !$this->storage->translationsTablesExist()) { $resources = []; } else { $resources = $this->storage->getTransUnitDomainsByLocale(); diff --git a/Form/Handler/TransUnitFormHandler.php b/Form/Handler/TransUnitFormHandler.php index 68bbc4f7..8e5272db 100644 --- a/Form/Handler/TransUnitFormHandler.php +++ b/Form/Handler/TransUnitFormHandler.php @@ -2,10 +2,10 @@ namespace Lexik\Bundle\TranslationBundle\Form\Handler; -use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitManagerInterface; use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use Lexik\Bundle\TranslationBundle\Manager\FileManagerInterface; +use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitManagerInterface; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; @@ -41,8 +41,8 @@ public function createFormData() public function getFormOptions() { return [ - 'domains' => $this->storage->getTransUnitDomains(), - 'data_class' => $this->storage->getModelClass('trans_unit'), + 'domains' => $this->storage->getTransUnitDomains(), + 'data_class' => $this->storage->getModelClass('trans_unit'), 'translation_class' => $this->storage->getModelClass('translation'), ]; } diff --git a/Form/Type/TransUnitType.php b/Form/Type/TransUnitType.php index 9f6cfc91..15cda4da 100644 --- a/Form/Type/TransUnitType.php +++ b/Form/Type/TransUnitType.php @@ -2,11 +2,11 @@ namespace Lexik\Bundle\TranslationBundle\Form\Type; -use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -20,14 +20,14 @@ class TransUnitType extends AbstractType /** * {@inheritdoc} */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('key', TextType::class, ['label' => 'translations.key']); - $builder->add('domain', ChoiceType::class, ['label' => 'translations.domain', 'choices' => array_merge( + $builder->add('domain', ChoiceType::class, ['label' => 'translations.domain', 'choices' => array_merge( array_combine($options['default_domain'], $options['default_domain']), array_combine($options['domains'], $options['domains']) )]); - $builder->add('translations', CollectionType::class, ['entry_type' => TranslationType::class, 'label' => 'translations.page_title', 'required' => false, 'entry_options' => ['data_class' => $options['translation_class']]]); + $builder->add('translations', CollectionType::class, ['entry_type' => TranslationType::class, 'label' => 'translations.page_title', 'required' => false, 'entry_options' => ['data_class' => $options['translation_class']]]); $builder->add('save', SubmitType::class, ['label' => 'translations.save']); $builder->add('save_add', SubmitType::class, ['label' => 'translations.save_add']); } @@ -35,15 +35,15 @@ public function buildForm(FormBuilderInterface $builder, array $options) /** * {@inheritdoc} */ - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { - $resolver->setDefaults(['data_class' => null, 'default_domain' => ['messages'], 'domains' => [], 'translation_class' => null, 'translation_domain' => 'LexikTranslationBundle']); + $resolver->setDefaults(['data_class' => null, 'default_domain' => ['messages'], 'domains' => [], 'translation_class' => null, 'translation_domain' => 'LexikTranslationBundle']); } /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return $this->getBlockPrefix(); } @@ -51,7 +51,7 @@ public function getName() /** * {@inheritdoc} */ - public function getBlockPrefix() + public function getBlockPrefix(): string { return 'lxk_trans_unit'; } diff --git a/Form/Type/TranslationType.php b/Form/Type/TranslationType.php index 2adb5a69..3c9c1ef6 100644 --- a/Form/Type/TranslationType.php +++ b/Form/Type/TranslationType.php @@ -6,8 +6,8 @@ use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -20,7 +20,7 @@ class TranslationType extends AbstractType /** * {@inheritdoc} */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('locale', HiddenType::class); $builder->add('content', TextareaType::class, ['required' => false]); @@ -29,7 +29,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) /** * {@inheritdoc} */ - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { $view->vars['label'] = $form['locale']->getData(); } @@ -37,15 +37,15 @@ public function buildView(FormView $view, FormInterface $form, array $options) /** * {@inheritdoc} */ - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { - $resolver->setDefaults(['data_class' => null, 'translation_domain' => 'LexikTranslationBundle']); + $resolver->setDefaults(['data_class' => null, 'translation_domain' => 'LexikTranslationBundle']); } /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return $this->getBlockPrefix(); } @@ -53,7 +53,7 @@ public function getName() /** * {@inheritdoc} */ - public function getBlockPrefix() + public function getBlockPrefix(): string { return 'lxk_translation'; } diff --git a/Manager/FileInterface.php b/Manager/FileInterface.php index 3f889c49..55b42e1f 100644 --- a/Manager/FileInterface.php +++ b/Manager/FileInterface.php @@ -2,8 +2,6 @@ namespace Lexik\Bundle\TranslationBundle\Manager; -use Symfony\Component\HttpFoundation\File\File; - /** * File interface. * @@ -11,4 +9,17 @@ */ interface FileInterface { + public function getId(); + public function setDomain(string $domain): void; + public function getDomain(): string; + public function setLocale(string $locale): void; + public function getLocale(): string; + public function setExtention(string $extention): void; + public function getExtention(): string; + public function setPath(string $path): void; + public function getPath(): string; + public function setName(string $name): void; + public function getName(): string; + public function setHash(string $hash): void; + public function getHash(): string; } diff --git a/Manager/FileManager.php b/Manager/FileManager.php index 743b4782..540411a0 100644 --- a/Manager/FileManager.php +++ b/Manager/FileManager.php @@ -12,21 +12,16 @@ */ class FileManager implements FileManagerInterface { - /** - * Construct. - * - * @param string $rootDir - */ public function __construct( private readonly StorageInterface $storage, - private $rootDir, + private readonly string $rootDir, ) { } /** * {@inheritdoc} */ - public function getFor($name, $path = null) + public function getFor(string $name, ?string $path = null): FileInterface { if (null === $path) { $path = sprintf('%s/Resources/translations', $this->rootDir); @@ -42,7 +37,7 @@ public function getFor($name, $path = null) /** * {@inheritdoc} */ - public function create($name, $path, $flush = false) + public function create(string $name, string $path, bool $flush = false): FileInterface { $path = $this->getFileRelativePath($path); @@ -64,23 +59,16 @@ public function create($name, $path, $flush = false) /** * Returns the has for the given file. - * - * @param string $name - * @param string $relativePath - * @return string */ - protected function generateHash($name, $relativePath) + protected function generateHash(string $name, string $relativePath): string { return md5($relativePath . DIRECTORY_SEPARATOR . $name); } /** * Returns the relative according to the kernel.root_dir value. - * - * @param string $filePath - * @return string */ - protected function getFileRelativePath($filePath) + protected function getFileRelativePath(string $filePath): string { $commonParts = []; @@ -98,7 +86,7 @@ protected function getFileRelativePath($filePath) $i = 0; while ($i < count($rootDirParts)) { - if (isset($rootDirParts[$i], $filePathParts[$i]) && $rootDirParts[$i] == $filePathParts[$i]) { + if (isset($rootDirParts[$i], $filePathParts[$i]) && $rootDirParts[$i] === $filePathParts[$i]) { $commonParts[] = $rootDirParts[$i]; } $i++; diff --git a/Manager/FileManagerInterface.php b/Manager/FileManagerInterface.php index 4c66fc4c..b734f121 100644 --- a/Manager/FileManagerInterface.php +++ b/Manager/FileManagerInterface.php @@ -3,6 +3,7 @@ namespace Lexik\Bundle\TranslationBundle\Manager; use Lexik\Bundle\TranslationBundle\Entity\File; + /** * File manager interface. * @@ -12,20 +13,12 @@ interface FileManagerInterface { /** * Create a new file. - * - * @param string $name - * @param string $path - * @return File */ - public function create($name, $path, $flush = false); + public function create(string $name, string $path, bool $flush = false): FileInterface; /** * Returns a translation file according to the given name and path. * If path is null, app/Resources/translations will be used as default path. - * - * @param string $name - * @param string $path - * @return File */ - public function getFor($name, $path = null); + public function getFor(string $name, ?string $path = null): FileInterface; } diff --git a/Manager/LocaleManager.php b/Manager/LocaleManager.php index 64eb8194..dcb5cad5 100644 --- a/Manager/LocaleManager.php +++ b/Manager/LocaleManager.php @@ -8,17 +8,14 @@ class LocaleManager implements LocaleManagerInterface { /** - * Constructor + * @param list $managedLocales */ public function __construct( protected array $managedLocales ) { } - /** - * @return array - */ - public function getLocales() + public function getLocales(): array { return $this->managedLocales; } diff --git a/Manager/TransUnitInterface.php b/Manager/TransUnitInterface.php index a23c78aa..1a48b1a5 100644 --- a/Manager/TransUnitInterface.php +++ b/Manager/TransUnitInterface.php @@ -3,6 +3,9 @@ namespace Lexik\Bundle\TranslationBundle\Manager; use Doctrine\Common\Collections\Collection; +use Lexik\Bundle\TranslationBundle\Document\Translation as DocumentTranslation; +use Lexik\Bundle\TranslationBundle\Entity\Translation; + /** * TransUnit manager interface. * @@ -10,32 +13,23 @@ */ interface TransUnitInterface { - /** - * @return TranslationInterface[] - */ - public function getTranslations(): array|Collection; - - /** - * @param string $locale - * - * @return bool - */ + public function getId(); + + public function addTranslation(DocumentTranslation|Translation $translation): void; + + public function removeTranslation(DocumentTranslation|Translation $translation): void; + + public function getTranslations(): Collection; + public function hasTranslation(string $locale): bool; - /** - * @param string $locale - * - * @return TranslationInterface - */ public function getTranslation(string $locale): ?TranslationInterface; - /** - * @param string $key - */ public function setKey(string $key): void; - /** - * @param string $domain - */ + public function getKey(): string; + public function setDomain(string $domain): void; + + public function getDomain(): string; } diff --git a/Manager/TransUnitManager.php b/Manager/TransUnitManager.php index 8676787f..4fa82e83 100644 --- a/Manager/TransUnitManager.php +++ b/Manager/TransUnitManager.php @@ -4,7 +4,6 @@ use Lexik\Bundle\TranslationBundle\Model\Translation; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; -use Lexik\Bundle\TranslationBundle\Storage\PropelStorage; /** * Class to manage TransUnit entities or documents. @@ -67,7 +66,7 @@ public function addTranslation( $content, ?FileInterface $file = null, bool $flush = false - ): ?TranslationInterface{ + ): ?TranslationInterface { $translation = null; if (!$transUnit->hasTranslation(locale: $locale)) { $class = $this->storage->getModelClass(name: 'translation'); @@ -103,15 +102,15 @@ public function updateTranslation(TransUnitInterface $transUnit, $locale, $conte $found = false; while ($i < $end && !$found) { - $found = ($transUnit->getTranslations()->get($i)->getLocale() == $locale); + $found = ($transUnit->getTranslations()->get($i)->getLocale() === $locale); $i++; } if ($found) { - /* @var Translation $translation */ + /* @var TranslationInterface $translation */ $translation = $transUnit->getTranslations()->get($i - 1); if ($merge) { - if ($translation->isModifiedManually() || $translation->getContent() == $content) { + if ($translation->isModifiedManually() || $translation->getContent() === $content) { return null; } @@ -127,10 +126,6 @@ public function updateTranslation(TransUnitInterface $transUnit, $locale, $conte $translation->setContent($content); } - if (null !== $translation && $this->storage instanceof PropelStorage) { - $this->storage->persist($translation); - } - if ($flush) { $this->storage->flush(); } @@ -141,7 +136,7 @@ public function updateTranslation(TransUnitInterface $transUnit, $locale, $conte /** * {@inheritdoc} */ - public function updateTranslationsContent(TransUnitInterface $transUnit, array $translations, $flush = false): void + public function updateTranslationsContent(TransUnitInterface $transUnit, array $translations, $flush = false): void { foreach ($translations as $locale => $content) { if (!empty($content)) { @@ -153,11 +148,8 @@ public function updateTranslationsContent(TransUnitInterface $transUnit, array $ $originalContent = $translation->getContent(); $translation = $this->updateTranslation($transUnit, $locale, $content); - $contentUpdated = ($translation->getContent() != $originalContent); + $contentUpdated = ($translation->getContent() !== $originalContent); - if ($this->storage instanceof PropelStorage) { - $this->storage->persist($transUnit); - } } else { //We need to get a proper file for this translation $file = $this->getTranslationFile($transUnit, $locale); @@ -178,7 +170,7 @@ public function updateTranslationsContent(TransUnitInterface $transUnit, array $ /** * Get the proper File for this TransUnit and locale */ - public function getTranslationFile(TransUnitInterface &$transUnit, string $locale) + public function getTranslationFile(TransUnitInterface $transUnit, string $locale) { $file = null; foreach ($transUnit->getTranslations() as $translation) { @@ -197,10 +189,7 @@ public function getTranslationFile(TransUnitInterface &$transUnit, string $local return $file; } - /** - * @return bool - */ - public function delete(TransUnitInterface $transUnit) + public function delete(TransUnitInterface $transUnit): bool { try { $this->storage->remove($transUnit); @@ -213,11 +202,7 @@ public function delete(TransUnitInterface $transUnit) } } - /** - * @param string $locale - * @return bool - */ - public function deleteTranslation(TransUnitInterface $transUnit, $locale) + public function deleteTranslation(TransUnitInterface $transUnit, string $locale): bool { try { $translation = $transUnit->getTranslation($locale); diff --git a/Manager/TransUnitManagerInterface.php b/Manager/TransUnitManagerInterface.php index ca6f8b95..8e4d88ae 100644 --- a/Manager/TransUnitManagerInterface.php +++ b/Manager/TransUnitManagerInterface.php @@ -48,12 +48,15 @@ public function addTranslation(TransUnitInterface $transUnit, $locale, $content, * @param boolean $merge * @return TranslationInterface */ - public function updateTranslation(TransUnitInterface $transUnit, $locale, $content, $flush = false, bool $merge = false): ?TranslationInterface; + public function updateTranslation(TransUnitInterface $transUnit, string $locale, $content, bool $flush = false, bool $merge = false): ?TranslationInterface; /** - * Update the content of each translations for the given trans unit. - * - * @param boolean $flush + * Update the content of each translation for the given trans unit. */ public function updateTranslationsContent(TransUnitInterface $transUnit, array $translations, bool $flush = false): void; + + public function delete(TransUnitInterface $transUnit): bool; + + public function deleteTranslation(TransUnitInterface $transUnit, string $locale): bool; + } diff --git a/Model/File.php b/Model/File.php index 9dbca1b8..24d2ea4b 100644 --- a/Model/File.php +++ b/Model/File.php @@ -6,6 +6,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use Symfony\Component\Validator\Constraints as Assert; /** @@ -14,7 +15,7 @@ * @author Cédric Girard */ #[ORM\MappedSuperclass] -abstract class File +abstract class File implements FileInterface { protected $id; diff --git a/Model/TransUnit.php b/Model/TransUnit.php index 3d6ba81f..d4e995c7 100644 --- a/Model/TransUnit.php +++ b/Model/TransUnit.php @@ -2,15 +2,15 @@ namespace Lexik\Bundle\TranslationBundle\Model; -use Doctrine\Common\Collections\Collection; +use DateTime; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Lexik\Bundle\TranslationBundle\Document\Translation as DocumentTranslation; +use Lexik\Bundle\TranslationBundle\Entity\Translation; use Lexik\Bundle\TranslationBundle\Manager\TranslationInterface; use Symfony\Component\Validator\Constraints as Assert; -use Lexik\Bundle\TranslationBundle\Entity\Translation; -use Lexik\Bundle\TranslationBundle\Document\Translation as DocumentTranslation; -use DateTime; /** * This class represent a trans unit which contain translations for a given domain and key. @@ -34,9 +34,9 @@ abstract class TransUnit protected string $domain; /** - * @var \Doctrine\Common\Collections\Collection + * @var Collection */ - protected $translations; + protected Collection $translations; #[ORM\Column(name: 'created_at', type: Types::DATETIME_MUTABLE, nullable: true)] protected DateTime|string $createdAt; @@ -105,8 +105,6 @@ public function getDomain(): string /** * Add translations - * - * @param Translation $translations */ public function addTranslation(DocumentTranslation|Translation $translation): void { @@ -115,8 +113,6 @@ public function addTranslation(DocumentTranslation|Translation $translation): vo /** * Remove translations - * - * @param Translation $translations */ public function removeTranslation(DocumentTranslation|Translation $translation): void { @@ -125,10 +121,8 @@ public function removeTranslation(DocumentTranslation|Translation $translation): /** * Get translations - * - * @return \Doctrine\Common\Collections\Collection */ - public function getTranslations(): array|Collection + public function getTranslations(): Collection { return $this->translations; } @@ -146,13 +140,11 @@ public function hasTranslation(string $locale): bool /** * Return the content of translation for the given locale. - * - * @param string $locale */ public function getTranslation(string $locale): ?TranslationInterface { foreach ($this->getTranslations() as $translation) { - if ($translation->getLocale() == $locale) { + if ($translation->getLocale() === $locale) { return $translation; } } @@ -173,9 +165,9 @@ public function setTranslations(Collection $collection): void } /** - * Return transaltions with not blank content. + * Return translations with not blank content. * - * @return \Doctrine\Common\Collections\Collection + * @return Collection */ public function filterNotBlankTranslations(): Collection { diff --git a/Model/Translation.php b/Model/Translation.php index bd529f32..b0b842d7 100644 --- a/Model/Translation.php +++ b/Model/Translation.php @@ -2,9 +2,10 @@ namespace Lexik\Bundle\TranslationBundle\Model; +use DateTime; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; -use DateTime; +use Lexik\Bundle\TranslationBundle\Manager\FileInterface; use Symfony\Component\Validator\Constraints as Assert; /** @@ -17,7 +18,7 @@ abstract class Translation { #[ORM\Column(name: 'locale', type: Types::STRING, length: 10)] #[Assert\NotBlank] - protected $locale; + protected string $locale; #[ORM\Column(name: 'content', type: Types::TEXT, nullable: true)] #[Assert\NotBlank(groups: ['contentNotBlank'])] @@ -29,18 +30,15 @@ abstract class Translation #[ORM\Column(name: 'updated_at', type: Types::DATETIME_MUTABLE, nullable: true)] protected $updatedAt; - /** - * @var boolean - */ - #[ORM\Column(name: 'modified_manually', type: Types::BOOLEAN, nullable: true)] - protected $modifiedManually = false; + #[ORM\Column(name: 'modified_manually', type: Types::BOOLEAN, options: ['default' => false])] + protected bool $modifiedManually = false; + + protected ?FileInterface $file = null; /** * Set locale - * - * @param string $locale */ - public function setLocale($locale) + public function setLocale(string $locale): void { $this->locale = $locale; $this->content = ''; @@ -48,10 +46,8 @@ public function setLocale($locale) /** * Get locale - * - * @return string */ - public function getLocale() + public function getLocale(): string { return $this->locale; } @@ -76,7 +72,6 @@ public function getContent() return $this->content; } - /** * Get createdAt * @@ -97,19 +92,23 @@ public function getUpdatedAt() return $this->updatedAt; } - /** - * @return bool - */ - public function isModifiedManually() + public function isModifiedManually(): bool { return $this->modifiedManually; } - /** - * @param bool $modifiedManually - */ - public function setModifiedManually($modifiedManually) + public function setModifiedManually(bool $modifiedManually): void { $this->modifiedManually = $modifiedManually; } + + public function setFile(FileInterface $file): void + { + $this->file = $file; + } + + public function getFile(): ?FileInterface + { + return $this->file; + } } diff --git a/Resources/config/services.yaml b/Resources/config/services.yaml index 3219ba58..28f83d47 100644 --- a/Resources/config/services.yaml +++ b/Resources/config/services.yaml @@ -51,38 +51,27 @@ services: # Translator lexik_translation.translator: class: Lexik\Bundle\TranslationBundle\Translation\Translator + decorates: 'translator' + decoration_on_invalid: ignore arguments: + $translator: '@.inner' $container: '@service_container' - $formatter: '@translator.formatter.default' - $defaultLocale: '%kernel.default_locale%' $loaderIds: [] $options: cache_dir: '%kernel.cache_dir%/translations' debug: '%kernel.debug%' - resource_files: [] - scanned_directories: [] - cache_vary: [] resources_type: '%lexik_translation.resources_type%' - calls: - - [setConfigCacheFactory, ['@config_cache_factory']] - tags: - - { name: kernel.locale_aware } public: true Lexik\Bundle\TranslationBundle\Translation\Translator: alias: lexik_translation.translator public: true - # TranslatorDecorator alias for backward compatibility - Lexik\Bundle\TranslationBundle\Translation\TranslatorDecorator: - alias: lexik_translation.translator - public: true - Lexik\Bundle\TranslationBundle\Storage\StorageInterface: alias: lexik_translation.translation_storage # Loader - Lexik\Bundle\TranslationBundle\Translation\Loader: + Lexik\Bundle\TranslationBundle\Translation\Loader\DatabaseLoader: class: '%lexik_translation.loader.database.class%' arguments: - '@lexik_translation.translation_storage' diff --git a/Resources/doc/index.md b/Resources/doc/index.md index bb1e6c5b..187fca74 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -52,13 +52,29 @@ Configuration #### Minimum configuration -You must at least define the fallback locale(s) as for the `framework.translator` node, and define all locales you will manage. +The bundle decorates the Symfony Translator service instead of extending it. This means the bundle now leverages the standard Symfony translator configuration. As a result, `fallback_locale` and `managed_locales` are no longer required: + +- `fallback_locale` is **deprecated**. Use `framework.translator.fallbacks` instead. +- `managed_locales` is now **optional**. If not set, it falls back to `framework.enabled_locales`. + +**Minimal setup** — the bundle works with no specific configuration if your `framework` config already defines `enabled_locales` and `translator.fallbacks`: + +```yml +# config/packages/framework.yaml +framework: + default_locale: en + enabled_locales: [en, fr, de] + translator: + fallbacks: [en] +``` + +**Legacy setup** — for backward compatibility, you can still use the bundle's own options. They will override the framework values: ```yml -# app/config/config.yml +# config/packages/lexik_translation.yaml lexik_translation: - fallback_locale: [en] # (required) default locale(s) to use - managed_locales: [en, fr, de] # (required) locales that the bundle has to manage + fallback_locale: [en] # (deprecated) use framework.translator.fallbacks instead + managed_locales: [en, fr, de] # (optional) falls back to framework.enabled_locales ``` #### Additional configuration options diff --git a/Storage/AbstractDoctrineStorage.php b/Storage/AbstractDoctrineStorage.php index 73c32ad4..d04ad6c3 100644 --- a/Storage/AbstractDoctrineStorage.php +++ b/Storage/AbstractDoctrineStorage.php @@ -3,11 +3,12 @@ namespace Lexik\Bundle\TranslationBundle\Storage; use Doctrine\Persistence\ObjectManager; -use Symfony\Bridge\Doctrine\ManagerRegistry; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; -use Lexik\Bundle\TranslationBundle\Entity\TransUnitRepository; -use Lexik\Bundle\TranslationBundle\Entity\FileRepository; use Lexik\Bundle\TranslationBundle\Document\FileRepository as DocumentFileRepository; +use Lexik\Bundle\TranslationBundle\Document\TransUnitRepository as DocumentTransUnitRepository; +use Lexik\Bundle\TranslationBundle\Entity\FileRepository; +use Lexik\Bundle\TranslationBundle\Entity\TransUnitRepository; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; +use Symfony\Bridge\Doctrine\ManagerRegistry; /** * Common doctrine storage logic. @@ -30,22 +31,24 @@ protected function getManager(): ObjectManager /** * Returns the TransUnit repository. - * - * @return object */ - protected function getTransUnitRepository(): TransUnitRepository + protected function getTransUnitRepository(): TransUnitRepository|DocumentTransUnitRepository { - return $this->getManager()->getRepository($this->classes['trans_unit']); + /** @var TransUnitRepository|DocumentTransUnitRepository $repository */ + $repository = $this->getManager()->getRepository($this->classes['trans_unit']); + + return $repository; } /** * Returns the File repository. - * - * @return object */ protected function getFileRepository(): FileRepository|DocumentFileRepository { - return $this->getManager()->getRepository($this->classes['file']); + /** @var FileRepository|DocumentFileRepository $repository */ + $repository = $this->getManager()->getRepository($this->classes['file']); + + return $repository; } /** @@ -75,9 +78,9 @@ public function flush($entity = null): void /** * {@inheritdoc} */ - public function clear($entityName = null): void + public function clear(): void { - $this->getManager()->clear($entityName); + $this->getManager()->clear(); } /** @@ -121,22 +124,18 @@ public function getTransUnitDomains(): mixed */ public function getTransUnitById($id): ?TransUnitInterface { - return $this->getTransUnitRepository()->findOneById($id); + return $this->getTransUnitRepository()->find($id); } /** * Returns a TransUnit by its key and domain. - * - * @param string $key - * @param string $domain - * @return TransUnitInterface */ public function getTransUnitByKeyAndDomain(string $key, string $domain): ?TransUnitInterface { $key = mb_substr($key, 0, 255, 'UTF-8'); $fields = [ - 'key' => $key, + 'key' => $key, 'domain' => $domain, ]; @@ -146,7 +145,7 @@ public function getTransUnitByKeyAndDomain(string $key, string $domain): ?TransU /** * {@inheritdoc} */ - public function getTransUnitDomainsByLocale() + public function getTransUnitDomainsByLocale(): array { return $this->getTransUnitRepository()->getAllDomainsByLocale(); } diff --git a/Storage/DoctrineMongoDBStorage.php b/Storage/DoctrineMongoDBStorage.php index 33de8881..cac72ba7 100644 --- a/Storage/DoctrineMongoDBStorage.php +++ b/Storage/DoctrineMongoDBStorage.php @@ -1,6 +1,7 @@ getTransUnitRepository()->countByDomains(); } @@ -29,7 +30,7 @@ public function getCountTransUnitByDomains(): array /** * {@inheritdoc} */ - public function getCountTranslationByLocales(string $domain):array + public function getCountTranslationByLocales(string $domain): array { return $this->getTransUnitRepository()->countTranslationsByLocales($domain); } diff --git a/Storage/DoctrineORMStorage.php b/Storage/DoctrineORMStorage.php index 038d467b..ab16d135 100644 --- a/Storage/DoctrineORMStorage.php +++ b/Storage/DoctrineORMStorage.php @@ -2,13 +2,8 @@ namespace Lexik\Bundle\TranslationBundle\Storage; -use Doctrine\DBAL\DriverManager; -use Doctrine\DBAL\Driver\PDO\SQLite\Driver as SQLiteDriver; -use Doctrine\DBAL\Exception\ConnectionException; -use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; -use Doctrine\ORM\Configuration; -use Doctrine\ORM\EntityManager; use DateTime; +use Doctrine\ORM\EntityManager; /** * Doctrine ORM storage class. @@ -19,58 +14,22 @@ class DoctrineORMStorage extends AbstractDoctrineStorage { /** * Returns true if translation tables exist. - * - * @return boolean */ - public function translationsTablesExist() + public function translationsTablesExist(): bool { /** @var EntityManager $em */ $em = $this->getManager(); - $connection = $em->getConnection(); - - // listDatabases() is not available for SQLite - if (!$connection->getDriver() instanceof SQLiteDriver) { - // init a tmp connection without dbname/path/url in case it does not exist yet - $params = $connection->getParams(); - if (isset($params['master'])) { - $params = $params['master']; - } - - unset($params['dbname'], $params['path'], $params['url']); - try { - $configuration = new Configuration(); - if (class_exists(DefaultSchemaManagerFactory::class)) { - $configuration->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); - } + try { + $tables = [ + $em->getClassMetadata($this->getModelClass('trans_unit'))->getTableName(), + $em->getClassMetadata($this->getModelClass('translation'))->getTableName(), + ]; - $tmpConnection = DriverManager::getConnection($params, $configuration); - $schemaManager = method_exists($tmpConnection, 'createSchemaManager') - ? $tmpConnection->createSchemaManager() - : $tmpConnection->getSchemaManager(); - - $dbExists = in_array($connection->getDatabase(), $schemaManager->listDatabases()); - $tmpConnection->close(); - } catch (ConnectionException|\Exception) { - $dbExists = false; - } - - if (!$dbExists) { - return false; - } + return $em->getConnection()->createSchemaManager()->tablesExist($tables); + } catch (\Exception) { + return false; } - - // checks tables exist - $tables = [ - $em->getClassMetadata($this->getModelClass('trans_unit'))->getTableName(), - $em->getClassMetadata($this->getModelClass('translation'))->getTableName(), - ]; - - $schemaManager = method_exists($connection, 'createSchemaManager') - ? $connection->createSchemaManager() - : $connection->getSchemaManager(); - - return $schemaManager->tablesExist($tables); } /** diff --git a/Storage/Listener/DoctrineORMListener.php b/Storage/Listener/DoctrineORMListener.php index a18ab6f3..c95052b0 100644 --- a/Storage/Listener/DoctrineORMListener.php +++ b/Storage/Listener/DoctrineORMListener.php @@ -3,11 +3,11 @@ namespace Lexik\Bundle\TranslationBundle\Storage\Listener; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; class DoctrineORMListener { - public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void { $params = $eventArgs->getEntityManager()->getConnection()->getParams(); @@ -15,10 +15,10 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) return; } - /** @var ClassMetadataInfo $metadata */ + /** @var ClassMetadata $metadata */ $metadata = $eventArgs->getClassMetadata(); - if (!str_contains((string) $metadata->getName(), 'TranslationBundle')) { + if (!str_contains($metadata->getName(), 'TranslationBundle')) { return; } diff --git a/Storage/StorageInterface.php b/Storage/StorageInterface.php index dcd5ee53..9514289b 100644 --- a/Storage/StorageInterface.php +++ b/Storage/StorageInterface.php @@ -2,9 +2,9 @@ namespace Lexik\Bundle\TranslationBundle\Storage; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; use DateTime; use Lexik\Bundle\TranslationBundle\Manager\FileInterface; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; /** * Translation storage interface. @@ -16,7 +16,7 @@ interface StorageInterface /** * All the available config storage types. */ - public const STORAGE_ORM = 'orm'; + public const STORAGE_ORM = 'orm'; public const STORAGE_MONGODB = 'mongodb'; /** @@ -42,10 +42,8 @@ public function flush($entity = null); /** * Clear managed objects. - * - * @param string $entityName */ - public function clear($entityName = null); + public function clear(): void; /** * Returns the class's namespace according to the given name. @@ -122,7 +120,7 @@ public function getTransUnitList(?array $locales = null, ?int $rows = 20, ?int $ * * @return int */ - public function countTransUnits(?array $locales = null, ?array $filters = null): int; + public function countTransUnits(?array $locales = null, ?array $filters = null): int; /** * Returns all translations for the given file. diff --git a/Tests/Command/ImportTranslationsCommandTest.php b/Tests/Command/ImportTranslationsCommandTest.php index 24e46e4c..be0107ef 100644 --- a/Tests/Command/ImportTranslationsCommandTest.php +++ b/Tests/Command/ImportTranslationsCommandTest.php @@ -2,17 +2,15 @@ namespace Lexik\Bundle\TranslationBundle\Tests\Command; -use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand; use Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand; -use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Tester\CommandTester; -use Lexik\Bundle\TranslationBundle\Command\ImportTranslationsCommand; /** * Test the translations import command, with option and arguments @@ -48,8 +46,8 @@ public function setUp(): void */ private static function addDoctrineCommands(DropCommand $dropCommand, CreateCommand $createCommand) { - static::$application->add($dropCommand); - static::$application->add($createCommand); + static::$application->addCommand($dropCommand); + static::$application->addCommand($createCommand); } /** @@ -81,27 +79,18 @@ private static function runCommand($commandName, $options = []) * * @group command */ - public function testExecute() + public function testExecute(): void { - $container = self::$kernel->getContainer(); - static::$application->add( - command: new ImportTranslationsCommand( - translator: $container->get('lexik_translation.translator'), - localeManager: $container->get(LocaleManagerInterface::class), - fileImporter: $container->get('lexik_translation.importer.file') - ) - ); - $command = static::$application->find("lexik:translations:import"); $commandTester = new CommandTester($command); $commandTester->execute( [ - 'command' => $command->getName(), - 'bundle' => 'LexikTranslationBundle', + 'command' => $command->getName(), + 'bundle' => 'LexikTranslationBundle', '--cache-clear' => true, - '--force' => true, - '--locales' => ['en', 'fr'], + '--force' => true, + '--locales' => ['en', 'fr'], ] ); diff --git a/Tests/Fixtures/test.fr.php b/Tests/Fixtures/test.fr.php index a4293d56..238b9d4d 100644 --- a/Tests/Fixtures/test.fr.php +++ b/Tests/Fixtures/test.fr.php @@ -1,5 +1,6 @@ 'Hey mec :D', + 'key.dude' => 'Hey mec :D', 'key.stuff' => 'Truc cool', ]; diff --git a/Tests/Unit/BaseUnitTestCase.php b/Tests/Unit/BaseUnitTestCase.php index 88a9eb55..b2b2cc4c 100644 --- a/Tests/Unit/BaseUnitTestCase.php +++ b/Tests/Unit/BaseUnitTestCase.php @@ -2,23 +2,23 @@ namespace Lexik\Bundle\TranslationBundle\Tests\Unit; -use Doctrine\DBAL\DriverManager; -use Doctrine\ODM\MongoDB\Mapping\Driver\SimplifiedXmlDriver as XmlDriver; use Doctrine\Common\DataFixtures\Executor\MongoDBExecutor; use Doctrine\Common\DataFixtures\Executor\ORMExecutor; use Doctrine\Common\DataFixtures\Purger\MongoDBPurger; use Doctrine\Common\DataFixtures\Purger\ORMPurger; +use Doctrine\DBAL\DriverManager; use Doctrine\ODM\MongoDB\Configuration; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory; +use Doctrine\ODM\MongoDB\Mapping\Driver\SimplifiedXmlDriver as XmlDriver; use Doctrine\ODM\MongoDB\MongoDBException; use Doctrine\ODM\MongoDB\SchemaManager; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping\ClassMetadataFactory as DoctrineClassMetadataFactory; use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; -use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\ORMSetup as Setup; +use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ObjectManager; use Lexik\Bundle\TranslationBundle\Storage\DoctrineMongoDBStorage; use Lexik\Bundle\TranslationBundle\Storage\DoctrineORMStorage; @@ -37,13 +37,13 @@ */ abstract class BaseUnitTestCase extends TestCase { - final const ENTITY_TRANS_UNIT_CLASS = 'Lexik\Bundle\TranslationBundle\Entity\TransUnit'; - final const ENTITY_TRANSLATION_CLASS = 'Lexik\Bundle\TranslationBundle\Entity\Translation'; - final const ENTITY_FILE_CLASS = 'Lexik\Bundle\TranslationBundle\Entity\File'; + final public const ENTITY_TRANS_UNIT_CLASS = 'Lexik\Bundle\TranslationBundle\Entity\TransUnit'; + final public const ENTITY_TRANSLATION_CLASS = 'Lexik\Bundle\TranslationBundle\Entity\Translation'; + final public const ENTITY_FILE_CLASS = 'Lexik\Bundle\TranslationBundle\Entity\File'; - final const DOCUMENT_TRANS_UNIT_CLASS = 'Lexik\Bundle\TranslationBundle\Document\TransUnit'; - final const DOCUMENT_TRANSLATION_CLASS = 'Lexik\Bundle\TranslationBundle\Document\Translation'; - final const DOCUMENT_FILE_CLASS = 'Lexik\Bundle\TranslationBundle\Document\File'; + final public const DOCUMENT_TRANS_UNIT_CLASS = 'Lexik\Bundle\TranslationBundle\Document\TransUnit'; + final public const DOCUMENT_TRANSLATION_CLASS = 'Lexik\Bundle\TranslationBundle\Document\Translation'; + final public const DOCUMENT_FILE_CLASS = 'Lexik\Bundle\TranslationBundle\Document\File'; /** * Create a storage class form doctrine ORM. @@ -55,9 +55,9 @@ protected function getORMStorage(EntityManager $em) $registryMock = $this->getDoctrineRegistryMock($em); $storage = new DoctrineORMStorage($registryMock, 'default', [ - 'trans_unit' => self::ENTITY_TRANS_UNIT_CLASS, + 'trans_unit' => self::ENTITY_TRANS_UNIT_CLASS, 'translation' => self::ENTITY_TRANSLATION_CLASS, - 'file' => self::ENTITY_FILE_CLASS, + 'file' => self::ENTITY_FILE_CLASS, ]); return $storage; @@ -71,9 +71,9 @@ protected function getMongoDBStorage(DocumentManager $dm): DoctrineMongoDBStorag $registryMock = $this->getDoctrineRegistryMock($dm); $storage = new DoctrineMongoDBStorage($registryMock, 'default', [ - 'trans_unit' => self::DOCUMENT_TRANS_UNIT_CLASS, + 'trans_unit' => self::DOCUMENT_TRANS_UNIT_CLASS, 'translation' => self::DOCUMENT_TRANSLATION_CLASS, - 'file' => self::DOCUMENT_FILE_CLASS, + 'file' => self::DOCUMENT_FILE_CLASS, ]); return $storage; @@ -152,7 +152,7 @@ protected function getMockSqliteEntityManager($mockCustomHydrator = false) // xml driver $xmlDriver = new SimplifiedXmlDriver( [ - __DIR__ . '/../../Resources/config/model' => 'Lexik\Bundle\TranslationBundle\Model', + __DIR__ . '/../../Resources/config/model' => 'Lexik\Bundle\TranslationBundle\Model', __DIR__ . '/../../Resources/config/doctrine' => 'Lexik\Bundle\TranslationBundle\Entity', ] ); @@ -161,7 +161,10 @@ protected function getMockSqliteEntityManager($mockCustomHydrator = false) [ __DIR__ . '/../../Model', __DIR__ . '/../../Entity', - ], false, null, null + ], + false, + null, + null ); $config->setMetadataDriverImpl($xmlDriver); @@ -201,7 +204,7 @@ protected function getMockSqliteEntityManager($mockCustomHydrator = false) protected function getMockMongoDbDocumentManager(): DocumentManager { $prefixes = [ - __DIR__ . '/../../Resources/config/model' => 'Lexik\Bundle\TranslationBundle\Model', + __DIR__ . '/../../Resources/config/model' => 'Lexik\Bundle\TranslationBundle\Model', __DIR__ . '/../../Resources/config/doctrine' => 'Lexik\Bundle\TranslationBundle\Document', ]; $xmlDriver = new XmlDriver($prefixes); @@ -224,7 +227,7 @@ protected function getMockMongoDbDocumentManager(): DocumentManager $server = MONGO_SERVER; $driverOptions = [ 'typeMap' => [ - 'root' => 'array', + 'root' => 'array', 'document' => 'array', ], ]; @@ -232,7 +235,6 @@ protected function getMockMongoDbDocumentManager(): DocumentManager $dm = DocumentManager::create($conn, $config); - return $dm; } } diff --git a/Tests/Unit/EventDispatcher/CleanTranslationCacheListenerTest.php b/Tests/Unit/EventDispatcher/CleanTranslationCacheListenerTest.php index 6d380437..1ca31eed 100644 --- a/Tests/Unit/EventDispatcher/CleanTranslationCacheListenerTest.php +++ b/Tests/Unit/EventDispatcher/CleanTranslationCacheListenerTest.php @@ -2,16 +2,16 @@ namespace Lexik\Bundle\TranslationBundle\Tests\Unit\EventDispatcher; +use Lexik\Bundle\TranslationBundle\EventDispatcher\CleanTranslationCacheListener; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; use Lexik\Bundle\TranslationBundle\Translation\Translator; -use Lexik\Bundle\TranslationBundle\EventDispatcher\CleanTranslationCacheListener; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Translation\MessageSelector; -use Symfony\Component\Finder\Finder; -use PHPUnit\Framework\TestCase; /** * Unit test for CleanTranslationCacheListener class @@ -20,7 +20,6 @@ */ class CleanTranslationCacheListenerTest extends TestCase { - private $tempDir; public function setUp(): void @@ -32,7 +31,7 @@ public function testDefaultLocale() { $request = Request::create('/'); - $date = new \DateTime; + $date = new \DateTime(); if (!\file_exists($this->tempDir)) { \mkdir($this->tempDir); @@ -49,7 +48,7 @@ public function testDefaultLocale() $container = $this->getMock(ContainerInterface::class); - $translator = $this->getMock(Translator::class, [], [$container, new MessageSelector]); + $translator = $this->getMock(Translator::class, [], [$container, new MessageSelector()]); $translator->expects($this->any())->method('removeLocalesCacheFiles')->will($this->returnValue(true)); diff --git a/Tests/Unit/Repository/Document/FileRepositoryTest.php b/Tests/Unit/Repository/Document/FileRepositoryTest.php index 3d6bffc0..9f431376 100644 --- a/Tests/Unit/Repository/Document/FileRepositoryTest.php +++ b/Tests/Unit/Repository/Document/FileRepositoryTest.php @@ -52,7 +52,7 @@ public function assertFilesPath($expected, $result) { $i = 0; foreach ($result as $file) { - $this->assertEquals($expected[$i], $file->getPath().'/'.$file->getName()); + $this->assertEquals($expected[$i], $file->getPath() . '/' . $file->getName()); $i++; } } diff --git a/Tests/Unit/Repository/Document/TransUnitRepositoryTest.php b/Tests/Unit/Repository/Document/TransUnitRepositoryTest.php index 0b37f883..9d10ec88 100644 --- a/Tests/Unit/Repository/Document/TransUnitRepositoryTest.php +++ b/Tests/Unit/Repository/Document/TransUnitRepositoryTest.php @@ -93,16 +93,20 @@ public function testCount() $dm = $this->loadDatabase(); $repository = $dm->getRepository(self::DOCUMENT_TRANS_UNIT_CLASS); - $this->assertEquals(3, $repository->count(null, [])); - $this->assertEquals(3, $repository->count(['fr', 'de', 'en'], [])); - $this->assertEquals(3, $repository->count(['fr', 'it'], [])); - $this->assertEquals(3, $repository->count(['fr', 'de'], ['_search' => false, 'key' => 'good'])); - $this->assertEquals(1, $repository->count(['fr', 'de'], ['_search' => true, 'key' => 'good'])); - $this->assertEquals(1, $repository->count(['en', 'de'], ['_search' => true, 'domain' => 'super'])); - $this->assertEquals(1, - $repository->count(['en', 'fr', 'de'], ['_search' => true, 'key' => 'hel', 'domain' => 'uper'])); - $this->assertEquals(2, - $repository->count(['en', 'de'], ['_search' => true, 'key' => 'say', 'domain' => 'ssa'])); + $this->assertEquals(3, $repository->count(['locales' => null, 'filters' => []])); + $this->assertEquals(3, $repository->count(['locales' => ['fr', 'de', 'en'], 'filters' => []])); + $this->assertEquals(3, $repository->count(['locales' => ['fr', 'it'], 'filters' => []])); + $this->assertEquals(3, $repository->count(['locales' => ['fr', 'de'], 'filters' => ['_search' => false, 'key' => 'good']])); + $this->assertEquals(1, $repository->count(['locales' => ['fr', 'de'], 'filters' => ['_search' => true, 'key' => 'good']])); + $this->assertEquals(1, $repository->count(['locales' => ['en', 'de'], 'filters' => ['_search' => true, 'domain' => 'super']])); + $this->assertEquals( + 1, + $repository->count(['locales' => ['en', 'fr', 'de'], 'filters' => ['_search' => true, 'key' => 'hel', 'domain' => 'uper']]) + ); + $this->assertEquals( + 2, + $repository->count(['locales' => ['en', 'de'], 'filters' => ['_search' => true, 'key' => 'say', 'domain' => 'ssa']]) + ); } /** @@ -140,8 +144,12 @@ public function testGetTransUnitList() ]; $this->assertSameTransUnit($expected, $result); - $result = $repository->getTransUnitList(['fr', 'de'], 10, 1, - ['sidx' => 'key', 'sord' => 'DESC', '_search' => true, 'domain' => 'mess']); + $result = $repository->getTransUnitList( + ['fr', 'de'], + 10, + 1, + ['sidx' => 'key', 'sord' => 'DESC', '_search' => true, 'domain' => 'mess'] + ); $expected = [ [ 'key' => 'key.say_wtf', @@ -160,8 +168,12 @@ public function testGetTransUnitList() ]; $this->assertSameTransUnit($expected, $result); - $result = $repository->getTransUnitList(['fr', 'de'], 10, 1, - ['sidx' => 'key', 'sord' => 'DESC', '_search' => true, 'domain' => 'mess', 'key' => 'oo']); + $result = $repository->getTransUnitList( + ['fr', 'de'], + 10, + 1, + ['sidx' => 'key', 'sord' => 'DESC', '_search' => true, 'domain' => 'mess', 'key' => 'oo'] + ); $expected = [ [ 'key' => 'key.say_goodbye', @@ -173,8 +185,12 @@ public function testGetTransUnitList() ]; $this->assertSameTransUnit($expected, $result); - $result = $repository->getTransUnitList(['fr', 'en'], 10, 1, - ['sidx' => 'key', 'sord' => 'DESC', '_search' => true, 'fr' => 'alu']); + $result = $repository->getTransUnitList( + ['fr', 'en'], + 10, + 1, + ['sidx' => 'key', 'sord' => 'DESC', '_search' => true, 'fr' => 'alu'] + ); $expected = [ [ 'key' => 'key.say_hello', diff --git a/Tests/Unit/Repository/Entity/FileRepositoryTest.php b/Tests/Unit/Repository/Entity/FileRepositoryTest.php index 859fef35..178f22f6 100644 --- a/Tests/Unit/Repository/Entity/FileRepositoryTest.php +++ b/Tests/Unit/Repository/Entity/FileRepositoryTest.php @@ -52,7 +52,7 @@ public function assertFilesPath($expected, $result) { $i = 0; foreach ($result as $file) { - $this->assertEquals($expected[$i], $file->getPath().'/'.$file->getName()); + $this->assertEquals($expected[$i], $file->getPath() . '/' . $file->getName()); $i++; } } diff --git a/Tests/Unit/Repository/Entity/TransUnitRepositoryTest.php b/Tests/Unit/Repository/Entity/TransUnitRepositoryTest.php index d6f6ffd9..7dec2058 100644 --- a/Tests/Unit/Repository/Entity/TransUnitRepositoryTest.php +++ b/Tests/Unit/Repository/Entity/TransUnitRepositoryTest.php @@ -22,9 +22,9 @@ public function testGetAllDomainsByLocale() $results = $repository->getAllDomainsByLocale(); $expected = [ ['locale' => 'de', 'domain' => 'superTranslations'], - ['locale' => 'en', 'domain' => 'messages'], - ['locale' => 'en', 'domain' => 'superTranslations'], - ['locale' => 'fr', 'domain' => 'messages'], + ['locale' => 'en', 'domain' => 'messages'], + ['locale' => 'en', 'domain' => 'superTranslations'], + ['locale' => 'fr', 'domain' => 'messages'], ['locale' => 'fr', 'domain' => 'superTranslations'] ]; @@ -124,11 +124,11 @@ public function testGetTranslationsForFile() $em = $this->loadDatabase(); $repository = $em->getRepository(self::ENTITY_TRANS_UNIT_CLASS); - $file = $em->getRepository(self::ENTITY_FILE_CLASS)->findOneBy(['domain' => 'messages', 'locale' => 'fr', 'extention' => 'yml']); + $file = $em->getRepository(self::ENTITY_FILE_CLASS)->findOneBy(['domain' => 'messages', 'locale' => 'fr', 'extention' => 'yml']); $this->assertInstanceOf(self::ENTITY_FILE_CLASS, $file); $result = $repository->getTranslationsForFile($file, false); - $expected = ['key.say_goodbye' => 'au revoir', 'key.say_wtf' => 'c\'est quoi ce bordel !?!']; + $expected = ['key.say_goodbye' => 'au revoir', 'key.say_wtf' => 'c\'est quoi ce bordel !?!']; $this->assertEquals($expected, $result); // update a translation and then get translations with onlyUpdated = true diff --git a/Tests/Unit/Translation/Exporter/JsonExporterTest.php b/Tests/Unit/Translation/Exporter/JsonExporterTest.php index 159bb80d..6960f1e5 100644 --- a/Tests/Unit/Translation/Exporter/JsonExporterTest.php +++ b/Tests/Unit/Translation/Exporter/JsonExporterTest.php @@ -16,8 +16,8 @@ class JsonExporterTest extends TestCase public function tearDown(): void { - if (file_exists(__DIR__.$this->outFileName)) { - unlink(__DIR__.$this->outFileName); + if (file_exists(__DIR__ . $this->outFileName)) { + unlink(__DIR__ . $this->outFileName); } } @@ -26,7 +26,7 @@ public function tearDown(): void */ public function testExport() { - $outFile = __DIR__.$this->outFileName; + $outFile = __DIR__ . $this->outFileName; $exporter = new JsonExporter(); diff --git a/Tests/Unit/Translation/Exporter/PhpExporterTest.php b/Tests/Unit/Translation/Exporter/PhpExporterTest.php index b3a108de..c1439149 100644 --- a/Tests/Unit/Translation/Exporter/PhpExporterTest.php +++ b/Tests/Unit/Translation/Exporter/PhpExporterTest.php @@ -16,10 +16,10 @@ class PhpExporterTest extends TestCase public function tearDown(): void { - $outFile = __DIR__.$this->outFileName; + $outFile = __DIR__ . $this->outFileName; - if (file_exists(__DIR__.$this->outFileName)) { - unlink(__DIR__.$this->outFileName); + if (file_exists(__DIR__ . $this->outFileName)) { + unlink(__DIR__ . $this->outFileName); } } @@ -28,7 +28,7 @@ public function tearDown(): void */ public function testExport() { - $outFile = __DIR__.$this->outFileName; + $outFile = __DIR__ . $this->outFileName; $exporter = new PhpExporter(); diff --git a/Tests/Unit/Translation/Exporter/YamlExporterTest.php b/Tests/Unit/Translation/Exporter/YamlExporterTest.php index b076f49c..ffb29c21 100644 --- a/Tests/Unit/Translation/Exporter/YamlExporterTest.php +++ b/Tests/Unit/Translation/Exporter/YamlExporterTest.php @@ -16,10 +16,10 @@ class YamlExporterTest extends TestCase public function tearDown(): void { - $outFile = __DIR__.$this->outFileName; + $outFile = __DIR__ . $this->outFileName; - if (file_exists(__DIR__.$this->outFileName)) { - unlink(__DIR__.$this->outFileName); + if (file_exists(__DIR__ . $this->outFileName)) { + unlink(__DIR__ . $this->outFileName); } } @@ -28,13 +28,14 @@ public function tearDown(): void */ public function testExport() { - $outFile = __DIR__.$this->outFileName; + $outFile = __DIR__ . $this->outFileName; $exporter = new YamlExporter(); // export empty array $exporter->export($outFile, []); - $expectedContent = '{ }'; + // Expectation changed from '{ }' to '{}', because apparently after an update, an empty array is exported differently. + $expectedContent = '{}'; $this->assertEquals($expectedContent, trim(file_get_contents($outFile))); // export array with values @@ -59,7 +60,7 @@ public function testCreateMultiArray() $this->assertEquals($expected, $result); $result = $exporter->createMultiArray(['foo.bar.baz' => 'foobar', 'foo.foobaz' => 'bazbar']); - $expected = ['foo' => ['foobaz' => 'bazbar', 'bar' => ['baz' => 'foobar']]]; + $expected = ['foo' => ['foobaz' => 'bazbar', 'bar' => ['baz' => 'foobar']]]; $this->assertEquals($expected, $result); } @@ -75,15 +76,15 @@ public function testflattenArray() $this->assertEquals($expected, $result); $result = $exporter->flattenArray( - ['bundle' => ['foo' => ['foobaz' => 'bazbar', 'bar' => ['baz0' => 'foobar', 'baz1' => 'foobaz']]]] + ['bundle' => ['foo' => ['foobaz' => 'bazbar', 'bar' => ['baz0' => 'foobar', 'baz1' => 'foobaz']]]] ); - $expected = ['bundle.foo' => ['foobaz' => 'bazbar', 'bar' => ['baz0' => 'foobar', 'baz1' => 'foobaz']]]; + $expected = ['bundle.foo' => ['foobaz' => 'bazbar', 'bar' => ['baz0' => 'foobar', 'baz1' => 'foobaz']]]; $this->assertEquals($expected, $result); $result = $exporter->flattenArray( - ['bundle' => ['foo' => ['foobaz' => 'bazbar', 'bar' => ['baz' => 'foobar']]]] + ['bundle' => ['foo' => ['foobaz' => 'bazbar', 'bar' => ['baz' => 'foobar']]]] ); - $expected = ['bundle.foo' => ['foobaz' => 'bazbar', 'bar' => ['baz' => 'foobar']]]; + $expected = ['bundle.foo' => ['foobaz' => 'bazbar', 'bar' => ['baz' => 'foobar']]]; $this->assertEquals($expected, $result); } } diff --git a/Tests/Unit/Translation/Importer/FileImporterTest.php b/Tests/Unit/Translation/Importer/FileImporterTest.php index 49a54cbb..e7f2e17e 100644 --- a/Tests/Unit/Translation/Importer/FileImporterTest.php +++ b/Tests/Unit/Translation/Importer/FileImporterTest.php @@ -2,13 +2,13 @@ namespace Lexik\Bundle\TranslationBundle\Tests\Unit\Translation\Importer; -use Lexik\Bundle\TranslationBundle\Translation\Importer\FileImporter; use Lexik\Bundle\TranslationBundle\Manager\FileManager; use Lexik\Bundle\TranslationBundle\Manager\TransUnitManager; use Lexik\Bundle\TranslationBundle\Tests\Unit\BaseUnitTestCase; -use Symfony\Component\Translation\Loader\YamlFileLoader; -use Symfony\Component\Translation\Loader\PhpFileLoader; +use Lexik\Bundle\TranslationBundle\Translation\Importer\FileImporter; use Symfony\Component\Finder\SplFileInfo; +use Symfony\Component\Translation\Loader\PhpFileLoader; +use Symfony\Component\Translation\Loader\YamlFileLoader; /** * FileImporter tests. @@ -37,7 +37,7 @@ public function testImport() $this->assertDatabaseEntries($em, 0); // import files - $files = [new SplFileInfo(__DIR__.'/../../../Fixtures/test.en.yml', '', ''), new SplFileInfo(__DIR__.'/../../../Fixtures/test.fr.php', '', '')]; + $files = [new SplFileInfo(__DIR__ . '/../../../Fixtures/test.en.yml', '', ''), new SplFileInfo(__DIR__ . '/../../../Fixtures/test.fr.php', '', '')]; foreach ($files as $file) { $importer->import($file); diff --git a/Tests/Unit/Translation/Loader/DatabaseLoaderTest.php b/Tests/Unit/Translation/Loader/DatabaseLoaderTest.php index ccdb0eb8..305c6828 100644 --- a/Tests/Unit/Translation/Loader/DatabaseLoaderTest.php +++ b/Tests/Unit/Translation/Loader/DatabaseLoaderTest.php @@ -3,8 +3,8 @@ namespace Lexik\Bundle\TranslationBundle\Tests\Unit\Translation\Loader; use Lexik\Bundle\TranslationBundle\Entity\TransUnit; -use Lexik\Bundle\TranslationBundle\Translation\Loader\DatabaseLoader; use Lexik\Bundle\TranslationBundle\Tests\Unit\BaseUnitTestCase; +use Lexik\Bundle\TranslationBundle\Translation\Loader\DatabaseLoader; use Symfony\Component\Translation\MessageCatalogue; /** @@ -31,7 +31,7 @@ public function testLoad() $this->assertEquals('it', $catalogue->getLocale()); $catalogue = $loader->load(null, 'fr'); - $expectedTranslations = ['messages' => ['key.say_goodbye' => 'au revoir', 'key.say_wtf' => 'c\'est quoi ce bordel !?!']]; + $expectedTranslations = ['messages' => ['key.say_goodbye' => 'au revoir', 'key.say_wtf' => 'c\'est quoi ce bordel !?!']]; $this->assertInstanceOf(MessageCatalogue::class, $catalogue); $this->assertEquals($expectedTranslations, $catalogue->all()); $this->assertEquals('fr', $catalogue->getLocale()); diff --git a/Tests/Unit/Translation/Manager/TransUnitManagerTest.php b/Tests/Unit/Translation/Manager/TransUnitManagerTest.php index e0cbc0d4..f89c46c8 100644 --- a/Tests/Unit/Translation/Manager/TransUnitManagerTest.php +++ b/Tests/Unit/Translation/Manager/TransUnitManagerTest.php @@ -7,8 +7,8 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\UnitOfWork as ORMUnitOfWork; use Lexik\Bundle\TranslationBundle\Entity\TransUnit; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitManager; use Lexik\Bundle\TranslationBundle\Manager\FileManager; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitManager; use Lexik\Bundle\TranslationBundle\Storage\DoctrineMongoDBStorage; use Lexik\Bundle\TranslationBundle\Storage\DoctrineORMStorage; use Lexik\Bundle\TranslationBundle\Tests\Unit\BaseUnitTestCase; diff --git a/Tests/Unit/Translation/TranslatorTest.php b/Tests/Unit/Translation/TranslatorTest.php index 71c2a306..88c8e958 100644 --- a/Tests/Unit/Translation/TranslatorTest.php +++ b/Tests/Unit/Translation/TranslatorTest.php @@ -4,11 +4,11 @@ use Lexik\Bundle\TranslationBundle\EventDispatcher\Event\GetDatabaseResourcesEvent; use Lexik\Bundle\TranslationBundle\EventDispatcher\GetDatabaseResourcesListener; -use Lexik\Bundle\TranslationBundle\Translation\Translator; use Lexik\Bundle\TranslationBundle\Tests\Unit\BaseUnitTestCase; +use Lexik\Bundle\TranslationBundle\Translation\Translator; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Translation\Formatter\MessageFormatter; -use Symfony\Component\DependencyInjection\Container; /** * Translator tests. @@ -26,8 +26,8 @@ public function testAddDatabaseResources(): void $this->createSchema($em); $this->loadFixtures($em); - if (file_exists(sys_get_temp_dir().'/database.resources.php')) { - unlink(sys_get_temp_dir().'/database.resources.php'); + if (file_exists(sys_get_temp_dir() . '/database.resources.php')) { + unlink(sys_get_temp_dir() . '/database.resources.php'); } $translator = $this->createTranslator($em, sys_get_temp_dir()); @@ -36,13 +36,13 @@ public function testAddDatabaseResources(): void $expected = [ 'de' => [ ['database', 'DB', 'superTranslations'] - ], + ], 'en' => [ - ['database', 'DB', 'messages'], + ['database', 'DB', 'messages'], ['database', 'DB', 'superTranslations'] - ], + ], 'fr' => [ - ['database', 'DB', 'messages'], + ['database', 'DB', 'messages'], ['database', 'DB', 'superTranslations'], ] ]; @@ -58,29 +58,29 @@ public function testAddDatabaseResources(): void */ public function testRemoveCacheFile(): void { - $cacheDir = __DIR__.'/../../../vendor/test_cache_dir'; + $cacheDir = __DIR__ . '/../../../vendor/test_cache_dir'; $this->createFakeCacheFiles($cacheDir); $translator = $this->createTranslator($this->getMockSqliteEntityManager(), $cacheDir); // remove locale 'fr' - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr.php')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr.php.meta')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr_FR.php')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr_FR.php.meta')); + $this->assertFileExists($cacheDir . '/catalogue.fr.php'); + $this->assertFileExists($cacheDir . '/catalogue.fr.php.meta'); + $this->assertFileExists($cacheDir . '/catalogue.fr_FR.php'); + $this->assertFileExists($cacheDir . '/catalogue.fr_FR.php.meta'); $translator->removeCacheFile('fr'); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr.php')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr.php.meta')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr_FR.php')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr_FR.php.meta')); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr.php'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr.php.meta'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr_FR.php'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr_FR.php.meta'); // remove locale 'en' - $this->assertTrue(file_exists($cacheDir.'/catalogue.en.php')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.en.php.meta')); + $this->assertFileExists($cacheDir . '/catalogue.en.php'); + $this->assertFileExists($cacheDir . '/catalogue.en.php.meta'); $translator->removeCacheFile('en'); - $this->assertFalse(file_exists($cacheDir.'/catalogue.en.php')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.en.php.meta')); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.en.php'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.en.php.meta'); } /** @@ -88,29 +88,29 @@ public function testRemoveCacheFile(): void */ public function testRemoveLocalesCacheFiles(): void { - $cacheDir = __DIR__.'/../../../vendor/test_cache_dir'; + $cacheDir = __DIR__ . '/../../../vendor/test_cache_dir'; $this->createFakeCacheFiles($cacheDir); $translator = $this->createTranslator($this->getMockSqliteEntityManager(), $cacheDir); - $this->assertTrue(file_exists($cacheDir.'/database.resources.php')); - $this->assertTrue(file_exists($cacheDir.'/database.resources.php.meta')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr.php')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr.php.meta')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr_FR.php')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.fr_FR.php.meta')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.en.php')); - $this->assertTrue(file_exists($cacheDir.'/catalogue.en.php.meta')); + $this->assertFileExists($cacheDir . '/database.resources.php'); + $this->assertFileExists($cacheDir . '/database.resources.php.meta'); + $this->assertFileExists($cacheDir . '/catalogue.fr.php'); + $this->assertFileExists($cacheDir . '/catalogue.fr.php.meta'); + $this->assertFileExists($cacheDir . '/catalogue.fr_FR.php'); + $this->assertFileExists($cacheDir . '/catalogue.fr_FR.php.meta'); + $this->assertFileExists($cacheDir . '/catalogue.en.php'); + $this->assertFileExists($cacheDir . '/catalogue.en.php.meta'); $translator->removeLocalesCacheFiles(['fr', 'en']); - $this->assertFalse(file_exists($cacheDir.'/database.resources.php')); - $this->assertFalse(file_exists($cacheDir.'/database.resources.php.meta')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr.php')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr.php.meta')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr_FR.php')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.fr_FR.php.meta')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.en.php')); - $this->assertFalse(file_exists($cacheDir.'/catalogue.en.php.meta')); + $this->assertFileDoesNotExist($cacheDir . '/database.resources.php'); + $this->assertFileDoesNotExist($cacheDir . '/database.resources.php.meta'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr.php'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr.php.meta'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr_FR.php'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.fr_FR.php.meta'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.en.php'); + $this->assertFileDoesNotExist($cacheDir . '/catalogue.en.php.meta'); } protected function createTranslator($em, $cacheDir): TranslatorMock @@ -128,22 +128,18 @@ protected function createTranslator($em, $cacheDir): TranslatorMock $container->set('event_dispatcher', $dispatcher); $container->compile(); - $loaderIds = []; + $innerTranslator = new \Symfony\Component\Translation\Translator('en', new MessageFormatter()); + $options = [ 'cache_dir' => $cacheDir, 'debug' => true, - 'resource_files' => [], - 'cache_vary' => [], - 'scanned_directories' => [], - 'enabled_locales' => ['en', 'fr'], - 'default_locale' => 'en', + 'resources_type' => 'all', ]; return new TranslatorMock( + translator: $innerTranslator, container: $container, - formatter: new MessageFormatter(), - defaultLocale: 'en', - loaderIds: $loaderIds, + loaderIds: [], options: $options ); } @@ -154,47 +150,30 @@ protected function createFakeCacheFiles($cacheDir): void mkdir($cacheDir); } - touch($cacheDir.'/catalogue.fr.php'); - touch($cacheDir.'/catalogue.fr.php.meta'); + touch($cacheDir . '/catalogue.fr.php'); + touch($cacheDir . '/catalogue.fr.php.meta'); - touch($cacheDir.'/catalogue.fr_FR.php'); - touch($cacheDir.'/catalogue.fr_FR.php.meta'); + touch($cacheDir . '/catalogue.fr_FR.php'); + touch($cacheDir . '/catalogue.fr_FR.php.meta'); - touch($cacheDir.'/catalogue.en.php'); - touch($cacheDir.'/catalogue.en.php.meta'); + touch($cacheDir . '/catalogue.en.php'); + touch($cacheDir . '/catalogue.en.php.meta'); - touch($cacheDir.'/database.resources.php'); - touch($cacheDir.'/database.resources.php.meta'); + touch($cacheDir . '/database.resources.php'); + touch($cacheDir . '/database.resources.php.meta'); } } class TranslatorMock extends Translator { public array $dbResources = []; - public array $options = [ - 'cache_dir' => '', - 'debug' => false, - 'resource_files' => [], - 'cache_vary' => [], - 'scanned_directories' => [], - 'enabled_locales' => [], - 'default_locale' => 'en', - 'loader_ids' => [], - 'formatter' => null, - 'container' => null - ]; public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null): void { - if(empty( $domain )) { - var_dump('Domain is empty in TranslatorMock::addResource'); - var_dump($format, $resource, $locale); - exit; - } if ('database' === $format) { $this->dbResources[$locale][] = [$format, $resource, $domain]; } parent::addResource($format, $resource, $locale, $domain); } -} +} \ No newline at end of file diff --git a/Tests/Unit/Util/DataGrid/DataGridFormatterTest.php b/Tests/Unit/Util/DataGrid/DataGridFormatterTest.php index 8f881f9a..40857a0e 100644 --- a/Tests/Unit/Util/DataGrid/DataGridFormatterTest.php +++ b/Tests/Unit/Util/DataGrid/DataGridFormatterTest.php @@ -4,8 +4,8 @@ use Lexik\Bundle\TranslationBundle\Manager\LocaleManager; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; -use Lexik\Bundle\TranslationBundle\Util\DataGrid\DataGridFormatter; use Lexik\Bundle\TranslationBundle\Tests\Unit\BaseUnitTestCase; +use Lexik\Bundle\TranslationBundle\Util\DataGrid\DataGridFormatter; /** * @author Cédric Girard @@ -20,7 +20,7 @@ public function testCreateListResponse() $datas = [['id' => 2, 'key' => 'key.say_goodbye', 'domain' => 'messages', 'translations' => [['locale' => 'fr', 'content' => 'au revoir'], ['locale' => 'en', 'content' => 'good bye']]], ['id' => 1, 'key' => 'key.say_hello', 'domain' => 'superTranslations', 'translations' => [['locale' => 'fr', 'content' => 'salut'], ['locale' => 'de', 'content' => 'heil']]], ['id' => 3, 'key' => 'key.say_wtf', 'domain' => 'messages', 'translations' => [['locale' => 'fr', 'content' => 'c\'est quoi ce bordel !?!'], ['locale' => 'xx', 'content' => 'xxx xxx xxx']]]]; $total = 3; - $expected = ['translations' => [['_id' => 2, '_domain' => 'messages', '_key' => 'key.say_goodbye', 'de' => '', 'en' => 'good bye', 'fr' => 'au revoir'], ['_id' => 1, '_domain' => 'superTranslations', '_key' => 'key.say_hello', 'de' => 'heil', 'en' => '', 'fr' => 'salut'], ['_id' => 3, '_domain' => 'messages', '_key' => 'key.say_wtf', 'de' => '', 'en' => '', 'fr' => 'c\'est quoi ce bordel !?!']], 'total' => 3]; + $expected = ['translations' => [['_id' => 2, '_domain' => 'messages', '_key' => 'key.say_goodbye', 'de' => '', 'en' => 'good bye', 'fr' => 'au revoir'], ['_id' => 1, '_domain' => 'superTranslations', '_key' => 'key.say_hello', 'de' => 'heil', 'en' => '', 'fr' => 'salut'], ['_id' => 3, '_domain' => 'messages', '_key' => 'key.say_wtf', 'de' => '', 'en' => '', 'fr' => 'c\'est quoi ce bordel !?!']], 'total' => 3]; $formatter = new DataGridFormatter(new LocaleManager(['de', 'en', 'fr']), StorageInterface::STORAGE_ORM); $this->assertEquals(json_encode($expected, JSON_HEX_APOS), $formatter->createListResponse($datas, $total)->getContent()); diff --git a/Tests/Unit/Util/DataGrid/DataGridRequestHandlerTest.php b/Tests/Unit/Util/DataGrid/DataGridRequestHandlerTest.php index 7f62f49c..49672ba5 100644 --- a/Tests/Unit/Util/DataGrid/DataGridRequestHandlerTest.php +++ b/Tests/Unit/Util/DataGrid/DataGridRequestHandlerTest.php @@ -2,13 +2,12 @@ namespace Lexik\Bundle\TranslationBundle\Tests\Unit\Util\DataGrid; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitManager; use Lexik\Bundle\TranslationBundle\Manager\FileManagerInterface; -use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; use Lexik\Bundle\TranslationBundle\Manager\LocaleManager; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitManager; +use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; use Lexik\Bundle\TranslationBundle\Tests\Unit\BaseUnitTestCase; use Lexik\Bundle\TranslationBundle\Util\DataGrid\DataGridRequestHandler; -use Symfony\Component\HttpFoundation\Request; /** * @author Cédric Girard diff --git a/Tests/app/test/config.yml b/Tests/app/test/config.yml index 503d2db9..a224fda4 100644 --- a/Tests/app/test/config.yml +++ b/Tests/app/test/config.yml @@ -4,9 +4,12 @@ imports: framework: fragments: ~ secret: afasfsf + default_locale: 'en' + enabled_locales: ['en', 'pl', 'de', 'sv'] translator: enabled: true + fallbacks: ['en'] -lexik_translation: - fallback_locale: en - managed_locales: [en, pl, de, sv] +#lexik_translation: +# fallback_locale: en +# managed_locales: [en, pl, de, sv] diff --git a/Tests/app/test/database.php b/Tests/app/test/database.php index fcb43a7b..19a0dafb 100644 --- a/Tests/app/test/database.php +++ b/Tests/app/test/database.php @@ -3,7 +3,7 @@ if (ORM_TYPE == "doctrine") { $container->loadFromExtension( 'doctrine', - ['orm' => ['mappings' => ['Mapping' => ['type' => 'xml', 'prefix' => 'Lexik\Bundle\TranslationBundle\Entity', 'is_bundle' => false, 'dir' => '%kernel.project_dir%/Resources/config/doctrine']]], 'dbal' => ['charset' => 'UTF8', 'driver' => DB_ENGINE, 'host' => DB_HOST, 'port' => DB_PORT, 'dbname' => DB_NAME, 'user' => DB_USER, 'password' => DB_PASSWD]] + ['orm' => ['mappings' => ['Mapping' => ['type' => 'xml', 'prefix' => 'Lexik\Bundle\TranslationBundle\Entity', 'is_bundle' => false, 'dir' => '%kernel.project_dir%/Resources/config/doctrine']]], 'dbal' => ['charset' => 'UTF8', 'driver' => DB_ENGINE, 'host' => DB_HOST, 'port' => DB_PORT, 'dbname' => DB_NAME, 'user' => DB_USER, 'password' => DB_PASSWD]] ); } else { throw new Exception("Currently only doctrine is supported"); diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 7dd2f835..83bf11e9 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -1,4 +1,5 @@ hierarchicalFormat ? $this->hierarchicalFormat($translations) : $translations, JSON_PRETTY_PRINT)); @@ -30,9 +30,9 @@ public function export($file, $translations) /** * {@inheritdoc} */ - public function support($format) + public function support(string $format): bool { - return ('json' == $format); + return ('json' === $format); } /** diff --git a/Translation/Exporter/PhpExporter.php b/Translation/Exporter/PhpExporter.php index 89140636..f9ba8e8b 100644 --- a/Translation/Exporter/PhpExporter.php +++ b/Translation/Exporter/PhpExporter.php @@ -12,7 +12,7 @@ class PhpExporter implements ExporterInterface /** * {@inheritdoc} */ - public function export($file, $translations) + public function export(string $file, array $translations): bool { $phpContent = sprintf("createXmlDocument(); @@ -39,9 +39,9 @@ public function export($file, $translations) /** * {@inheritdoc} */ - public function support($format) + public function support(string $format): bool { - return ('xlf' == $format || 'xliff' == $format); + return ('xlf' === $format || 'xliff' === $format); } /** @@ -49,7 +49,7 @@ public function support($format) * * @return \DOMDocument */ - protected function createXmlDocument() + protected function createXmlDocument(): \DOMDocument { $dom = new \DOMDocument('1.0', 'utf-8'); $dom->formatOutput = true; @@ -59,11 +59,9 @@ protected function createXmlDocument() /** * Add root nodes to a document. - * - * @param string|null $targetLanguage - * @return \DOMElement + * @throws \DOMException */ - protected function addRootNodes(\DOMDocument $dom, $targetLanguage = null) + protected function addRootNodes(\DOMDocument $dom, ?string $targetLanguage = null): \DOMElement { $xliff = $dom->appendChild($dom->createElement('xliff')); $xliff->appendChild(new \DOMAttr('xmlns', 'urn:oasis:names:tc:xliff:document:1.2')); @@ -78,28 +76,26 @@ protected function addRootNodes(\DOMDocument $dom, $targetLanguage = null) $fileNode->appendChild(new \DOMAttr('target-language', $targetLanguage)); } - $bodyNode = $fileNode->appendChild($dom->createElement('body')); + $body = $dom->createElement('body'); + $fileNode->appendChild($body); - return $bodyNode; + return $body; } /** * Create a new trans-unit node. * - * @param int $id - * @param string $key - * @param string $value - * @return \DOMElement + * @throws \DOMException */ - protected function createTranslationNode(\DOMDocument $dom, $id, $key, $value) + protected function createTranslationNode(\DOMDocument $dom, int $id, string $key, string $value): \DOMElement { $translationNode = $dom->createElement('trans-unit'); - $translationNode->appendChild(new \DOMAttr('id', $id)); - + $translationNode->appendChild(new \DOMAttr('id', (string) $id)); + /** * @see http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#approved */ - if ($value != '') { + if ($value !== '') { $translationNode->appendChild(new \DOMAttr('approved', 'yes')); } diff --git a/Translation/Exporter/YamlExporter.php b/Translation/Exporter/YamlExporter.php index 59beb1b1..c369c908 100644 --- a/Translation/Exporter/YamlExporter.php +++ b/Translation/Exporter/YamlExporter.php @@ -20,7 +20,7 @@ public function __construct( /** * {@inheritdoc} */ - public function export($file, $translations) + public function export(string $file, array $translations): bool { if ($this->createTree) { $result = $this->createMultiArray($translations); @@ -122,8 +122,8 @@ protected function flattenArray($array, $prefix = '') /** * {@inheritdoc} */ - public function support($format) + public function support(string $format): bool { - return ('yml' == $format || 'yaml' == $format); + return ('yml' === $format || 'yaml' === $format); } } diff --git a/Translation/Importer/FileImporter.php b/Translation/Importer/FileImporter.php index 46019e65..d8c100c2 100644 --- a/Translation/Importer/FileImporter.php +++ b/Translation/Importer/FileImporter.php @@ -2,14 +2,14 @@ namespace Lexik\Bundle\TranslationBundle\Translation\Importer; -use Symfony\Component\Finder\SplFileInfo; -use Lexik\Bundle\TranslationBundle\Entity\Translation; -use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; use Lexik\Bundle\TranslationBundle\Document\TransUnit as TransUnitDocument; +use Lexik\Bundle\TranslationBundle\Entity\Translation; use Lexik\Bundle\TranslationBundle\Manager\FileManagerInterface; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitManagerInterface; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; use Lexik\Bundle\TranslationBundle\Manager\TranslationInterface; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitManagerInterface; +use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; +use Symfony\Component\Finder\SplFileInfo; /** * Import a translation file into the database. @@ -123,10 +123,7 @@ public function import(SplFileInfo $file, $forceUpdate = false, $merge = false) $this->storage->flush(); - // clear only Lexik entities - foreach (['file', 'trans_unit', 'translation'] as $name) { - $this->storage->clear($this->storage->getModelClass($name)); - } + $this->storage->clear(); return $imported; } diff --git a/Translation/Translator.php b/Translation/Translator.php index b6dad880..e99d09b2 100644 --- a/Translation/Translator.php +++ b/Translation/Translator.php @@ -3,140 +3,120 @@ namespace Lexik\Bundle\TranslationBundle\Translation; use Lexik\Bundle\TranslationBundle\EventDispatcher\Event\GetDatabaseResourcesEvent; -use Symfony\Component\Translation\Loader\LoaderInterface; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\ConfigCacheFactoryInterface; use Symfony\Component\Finder\Finder; -use Psr\Container\ContainerInterface; -use Symfony\Component\Translation\Formatter\MessageFormatter; -use Symfony\Bundle\FrameworkBundle\Translation\Translator as SymfonyTranslator; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\MessageCatalogueInterface; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -use Symfony\Contracts\Translation\LocaleAwareInterface; -use Symfony\Component\Translation\TranslatorBagInterface; /** - * Translator service class that decorates Symfony's Translator. + * Decorator for Symfony Translator service to add database translation resources. * - * Uses composition instead of inheritance to be compatible with Symfony 8 - * where the Translator class is final. + * This decorator wraps the original translator service and adds functionality + * to load translations from the database. It implements TranslatorInterface + * to maintain compatibility with Symfony 8 where Translator is final. * * @author Cédric Girard */ -class Translator implements TranslatorInterface, LocaleAwareInterface, TranslatorBagInterface +class Translator implements TranslatorInterface { - private SymfonyTranslator $translator; - protected array $resourceLocales = []; - protected array $resources = []; - protected array $resourceFiles = []; - protected array $scannedDirectories = []; - protected string $cacheFile; private bool $isResourcesLoaded = false; - - /** @var array For tracking database resources (mainly for testing) */ - public array $dbResources = []; + private string $cacheFile; public function __construct( - protected ContainerInterface $container, - MessageFormatter $formatter, - string $defaultLocale, - protected array $loaderIds, - protected array $options + private $translator, + private readonly ContainerInterface $container, + private readonly array $loaderIds, + private array $options ) { - $this->resourceLocales = []; - $this->resources = []; - $this->resourceFiles = []; - $this->scannedDirectories = []; - - $this->options['resource_files'] = $this->options['resource_files'] ?? []; - $this->options['scanned_directories'] = $this->options['scanned_directories'] ?? []; - $this->options['cache_vary'] = $this->options['cache_vary'] ?? []; $this->options['cache_dir'] = $this->options['cache_dir'] ?? sys_get_temp_dir(); $this->options['debug'] = $this->options['debug'] ?? false; $this->options['resources_type'] = $this->options['resources_type'] ?? 'all'; - $this->cacheFile = sprintf('%s/database.resources.php', $this->options['cache_dir']); + } - // Filter out custom options that Symfony translator doesn't recognize - // Only pass valid Symfony translator options - $symfonyOptions = [ - 'cache_dir' => $this->options['cache_dir'], - 'debug' => $this->options['debug'], - ]; - - // Add other valid Symfony options if they exist - $validSymfonyOptions = ['cache_dir', 'debug', 'resource_files', 'scanned_directories', 'cache_vary']; - foreach ($validSymfonyOptions as $key) { - if (isset($this->options[$key])) { - $symfonyOptions[$key] = $this->options[$key]; - } - } + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory): void + { + $this->translator->setConfigCacheFactory($configCacheFactory); + } - // Create the inner Symfony translator - $this->translator = new SymfonyTranslator( - container: $this->container, - formatter: $formatter, - defaultLocale: $defaultLocale, - loaderIds: $this->loaderIds, - options: $symfonyOptions, - enabledLocales: [] - ); + public function addLoader(string $format, LoaderInterface $loader): void + { + $this->translator->addLoader($format, $loader); } - /** - * {@inheritdoc} - */ - public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null): void { - $this->loadDatabaseResourcesIfNeeded(); - return $this->translator->trans($id, $parameters, $domain, $locale); + $this->translator->addResource($format, $resource, $locale, $domain); + } + + public function setLocale(string $locale): void + { + $this->translator->setLocale($locale); } - /** - * {@inheritdoc} - */ public function getLocale(): string { return $this->translator->getLocale(); } - /** - * {@inheritdoc} - */ - public function setLocale(string $locale): void + public function setFallbackLocales(array $locales): void { - $this->translator->setLocale($locale); + $this->translator->setFallbackLocales($locales); } - /** - * {@inheritdoc} - */ - public function getCatalogue(?string $locale = null): \Symfony\Component\Translation\MessageCatalogueInterface + public function getFallbackLocales(): array { - $this->loadDatabaseResourcesIfNeeded(); - return $this->translator->getCatalogue($locale); + return $this->translator->getFallbackLocales(); } - /** - * {@inheritdoc} - */ - public function getCatalogues(): array + public function addGlobalParameter(string $id, string|int|float|TranslatableInterface $value): void { - return $this->translator->getCatalogues(); + $this->translator->addGlobalParameter($id, $value); } - /** - * Load database resources if needed. - */ - private function loadDatabaseResourcesIfNeeded(): void + public function getGlobalParameters(): array { - $resourcesType = $this->options['resources_type'] ?? 'all'; + return $this->translator->getGlobalParameters(); + } + + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + $resourcesType = $this->options['resources_type']; if (!$this->isResourcesLoaded && ('all' === $resourcesType || 'database' === $resourcesType)) { $this->addDatabaseResources(); } + + return $this->translator->trans($id, $parameters, $domain, $locale); + } + + public function getCatalogue(?string $locale = null): MessageCatalogueInterface + { + return $this->translator->getCatalogue($locale); + } + + public function getCatalogues(): array + { + return $this->translator->getCatalogues(); + } + + public function warmUp(string $cacheDir, ?string $buildDir = null): array + { + return $this->translator->warmUp($cacheDir, $buildDir); } /** * Add all resources available in database. + * + * This method is called by DatabaseResourcesListener to register + * database translation resources with the translator. */ public function addDatabaseResources(): void { @@ -159,17 +139,8 @@ public function addDatabaseResources(): void $resources = include $this->cacheFile; } - // Use reflection to access the addResource method on the inner translator - $reflection = new \ReflectionClass($this->translator); - $addResourceMethod = $reflection->getMethod('addResource'); - foreach ($resources as $resource) { - $locale = $resource['locale']; - $domain = $resource['domain'] ?? 'messages'; - $addResourceMethod->invoke($this->translator, 'database', 'DB', $locale, $domain); - - // Track for testing purposes - $this->dbResources[$locale][] = ['database', 'DB', $domain]; + $this->addResource('database', 'DB', $resource['locale'], $resource['domain'] ?? 'messages'); } $this->isResourcesLoaded = true; @@ -177,11 +148,8 @@ public function addDatabaseResources(): void /** * Remove the cache file corresponding to the given locale. - * - * @param string $locale - * @return boolean */ - public function removeCacheFile($locale) + public function removeCacheFile(string $locale): bool { if (!file_exists($this->cacheFile)) { return true; @@ -189,7 +157,7 @@ public function removeCacheFile($locale) $localeExploded = explode('_', $locale); $finder = new Finder(); - $finder->files()->in($this->options['cache_dir'])->name(sprintf( '/catalogue\.%s.*\.php$/', $localeExploded[0])); + $finder->files()->in($this->options['cache_dir'])->name(sprintf('/catalogue\.%s.*\.php$/', $localeExploded[0])); $deleted = true; foreach ($finder as $file) { @@ -197,7 +165,7 @@ public function removeCacheFile($locale) $this->invalidateSystemCacheForFile($path); $deleted = unlink($path); - $metadata = $path.'.meta'; + $metadata = $path . '.meta'; if (file_exists($metadata)) { $this->invalidateSystemCacheForFile($metadata); unlink($metadata); @@ -210,7 +178,7 @@ public function removeCacheFile($locale) /** * Remove the cache file corresponding to each given locale. */ - public function removeLocalesCacheFiles(array $locales) + public function removeLocalesCacheFiles(array $locales): void { foreach ($locales as $locale) { $this->removeCacheFile($locale); @@ -223,7 +191,7 @@ public function removeLocalesCacheFiles(array $locales) unlink($file); } - $metadata = $file.'.meta'; + $metadata = $file . '.meta'; if (file_exists($metadata)) { $this->invalidateSystemCacheForFile($metadata); unlink($metadata); @@ -233,17 +201,15 @@ public function removeLocalesCacheFiles(array $locales) } /** - * @param string $path - * * @throws \RuntimeException */ - protected function invalidateSystemCacheForFile($path) + protected function invalidateSystemCacheForFile(string $path): void { if (ini_get('apc.enabled') && function_exists('apc_delete_file')) { if (apc_exists($path) && !apc_delete_file($path)) { throw new \RuntimeException(sprintf('Failed to clear APC Cache for file %s', $path)); } - } elseif ('cli' === php_sapi_name() ? ini_get('opcache.enable_cli') : ini_get('opcache.enable')) { + } elseif ('cli' === PHP_SAPI ? ini_get('opcache.enable_cli') : ini_get('opcache.enable')) { if (function_exists("opcache_invalidate") && !opcache_invalidate($path, true)) { throw new \RuntimeException(sprintf('Failed to clear OPCache for file %s', $path)); } @@ -255,11 +221,11 @@ protected function invalidateSystemCacheForFile($path) * * @return array */ - public function getFormats() + public function getFormats(): array { $allFormats = []; - foreach ($this->loaderIds as $id => $formats) { + foreach ($this->loaderIds as $formats) { foreach ($formats as $format) { if ('database' !== $format) { $allFormats[] = $format; @@ -273,18 +239,17 @@ public function getFormats() /** * Returns a loader according to the given format. * - * @param string $format - * @throws \RuntimeException - * @return LoaderInterface + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ - public function getLoader($format) + public function getLoader(string $format): LoaderInterface { $loader = null; $i = 0; $ids = array_keys($this->loaderIds); while ($i < count($ids) && null === $loader) { - if (in_array($format, $this->loaderIds[$ids[$i]])) { + if (\in_array($format, $this->loaderIds[ $ids[ $i ] ], true)) { $loader = $this->container->get($ids[$i]); } $i++; @@ -296,47 +261,4 @@ public function getLoader($format) return $loader; } - - /** - * Set fallback locales. - */ - public function setFallbackLocales(array $locales): void - { - $this->translator->setFallbackLocales($locales); - } - - /** - * Get fallback locales. - */ - public function getFallbackLocales(): array - { - return $this->translator->getFallbackLocales(); - } - - /** - * Warms up the cache. - */ - public function warmUp(string $cacheDir): array - { - return $this->translator->warmUp($cacheDir); - } - - /** - * Set config cache factory. - */ - public function setConfigCacheFactory(\Symfony\Component\Config\ConfigCacheFactoryInterface $configCacheFactory): void - { - $this->translator->setConfigCacheFactory($configCacheFactory); - } - - /** - * Add resource to the inner translator. - */ - public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null): void - { - // Use reflection to access the protected addResource method - $reflection = new \ReflectionClass($this->translator); - $addResourceMethod = $reflection->getMethod('addResource'); - $addResourceMethod->invoke($this->translator, $format, $resource, $locale, $domain); - } } diff --git a/Translation/TranslatorDecorator.php b/Translation/TranslatorDecorator.php deleted file mode 100644 index 99849f1d..00000000 --- a/Translation/TranslatorDecorator.php +++ /dev/null @@ -1,195 +0,0 @@ - - */ -class TranslatorDecorator implements TranslatorInterface -{ - private bool $isResourcesLoaded = false; - private string $cacheFile; - - public function __construct( - private readonly TranslatorInterface $translator, - private readonly \Psr\Container\ContainerInterface $container, - private readonly array $options - ) { - $this->options['cache_dir'] = $this->options['cache_dir'] ?? sys_get_temp_dir(); - $this->options['debug'] = $this->options['debug'] ?? false; - $this->options['resources_type'] = $this->options['resources_type'] ?? 'all'; - $this->cacheFile = sprintf('%s/database.resources.php', $this->options['cache_dir']); - } - - /** - * {@inheritdoc} - */ - public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string - { - // Database resources are loaded via DatabaseResourcesListener event subscriber - // No need to load here as the loader is registered and will be called automatically - return $this->translator->trans($id, $parameters, $domain, $locale); - } - - /** - * {@inheritdoc} - */ - public function getLocale(): string - { - return $this->translator->getLocale(); - } - - /** - * {@inheritdoc} - */ - public function setLocale(string $locale): void - { - $this->translator->setLocale($locale); - } - - /** - * Add all resources available in database. - * - * This method is called by DatabaseResourcesListener to register - * database translation resources with the translator. - */ - public function addDatabaseResources(): void - { - if ($this->isResourcesLoaded) { - return; - } - - $cache = new ConfigCache($this->cacheFile, $this->options['debug'] ?? false); - - if (!$cache->isFresh()) { - $event = new GetDatabaseResourcesEvent(); - $this->container->get('event_dispatcher')->dispatch($event); - - $resources = $event->getResources(); - $metadata = []; - - foreach ($resources as $resource) { - $metadata[] = new DatabaseFreshResource($resource['locale'], $resource['domain'] ?? 'messages'); - } - - $content = sprintf("write($content, $metadata); - } else { - $resources = include $this->cacheFile; - } - - // Add resources using reflection to access protected addResource method - // or use the public API if available in Symfony 8 - $reflection = new \ReflectionClass($this->translator); - if ($reflection->hasMethod('addResource')) { - $addResource = $reflection->getMethod('addResource'); - $addResource->setAccessible(true); - foreach ($resources as $resource) { - $addResource->invoke( - $this->translator, - 'database', - 'DB', - $resource['locale'], - $resource['domain'] ?? 'messages' - ); - } - } - - $this->isResourcesLoaded = true; - } - - /** - * Remove the cache file corresponding to the given locale. - * - * @param string $locale - * @return boolean - */ - public function removeCacheFile(string $locale): bool - { - if (!file_exists($this->cacheFile)) { - return true; - } - - $localeExploded = explode('_', $locale); - $finder = new Finder(); - $finder->files()->in($this->options['cache_dir'])->name(sprintf('/catalogue\.%s.*\.php$/', $localeExploded[0])); - $deleted = true; - foreach ($finder as $file) { - $path = $file->getRealPath(); - $this->invalidateSystemCacheForFile($path); - $deleted = unlink($path); - - $metadata = $path.'.meta'; - if (file_exists($metadata)) { - $this->invalidateSystemCacheForFile($metadata); - unlink($metadata); - } - } - - return $deleted; - } - - /** - * Remove the cache file corresponding to each given locale. - */ - public function removeLocalesCacheFiles(array $locales): void - { - foreach ($locales as $locale) { - $this->removeCacheFile($locale); - } - - // also remove database.resources.php cache file - $file = sprintf('%s/database.resources.php', $this->options['cache_dir']); - if (file_exists($file)) { - $this->invalidateSystemCacheForFile($file); - unlink($file); - } - - $metadata = $file.'.meta'; - if (file_exists($metadata)) { - $this->invalidateSystemCacheForFile($metadata); - unlink($metadata); - } - - $this->isResourcesLoaded = false; - } - - /** - * @param string $path - * - * @throws \RuntimeException - */ - protected function invalidateSystemCacheForFile(string $path): void - { - if (ini_get('apc.enabled') && function_exists('apc_delete_file')) { - if (apc_exists($path) && !apc_delete_file($path)) { - throw new \RuntimeException(sprintf('Failed to clear APC Cache for file %s', $path)); - } - } elseif ('cli' === php_sapi_name() ? ini_get('opcache.enable_cli') : ini_get('opcache.enable')) { - if (function_exists("opcache_invalidate") && !opcache_invalidate($path, true)) { - throw new \RuntimeException(sprintf('Failed to clear OPCache for file %s', $path)); - } - } - } - - /** - * Get the decorated translator instance. - * Useful for accessing methods not in TranslatorInterface. - */ - public function getDecoratedTranslator(): TranslatorInterface - { - return $this->translator; - } -} diff --git a/Util/Csrf/CsrfCheckerTrait.php b/Util/Csrf/CsrfCheckerTrait.php index 5a4de918..128b2855 100644 --- a/Util/Csrf/CsrfCheckerTrait.php +++ b/Util/Csrf/CsrfCheckerTrait.php @@ -2,8 +2,8 @@ namespace Lexik\Bundle\TranslationBundle\Util\Csrf; -use Symfony\Component\Security\Csrf\CsrfTokenManager; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Csrf\CsrfTokenManager; use Symfony\Contracts\Service\Attribute\Required; /** diff --git a/Util/DataGrid/DataGridFormatter.php b/Util/DataGrid/DataGridFormatter.php index 95b6ee6b..20487632 100644 --- a/Util/DataGrid/DataGridFormatter.php +++ b/Util/DataGrid/DataGridFormatter.php @@ -3,53 +3,41 @@ namespace Lexik\Bundle\TranslationBundle\Util\DataGrid; use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; use Symfony\Component\HttpFoundation\JsonResponse; -use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; /** * @author Cédric Girard */ class DataGridFormatter { - /** - * Constructor. - * - * @param string $storage - */ - public function __construct(protected LocaleManagerInterface $localeManager, protected $storage) - { + public function __construct( + protected LocaleManagerInterface $localeManager, + protected string $storage + ) { } /** * Returns a JSON response with formatted data. - * - * @param array $transUnits - * @param integer $total - * @return \Symfony\Component\HttpFoundation\JsonResponse */ - public function createListResponse($transUnits, $total) + public function createListResponse(array $transUnits, int $total): JsonResponse { - return new JsonResponse(['translations' => $this->format($transUnits), 'total' => $total]); + return new JsonResponse(['translations' => $this->format($transUnits), 'total' => $total]); } /** * Returns a JSON response with formatted data. - * - * @return \Symfony\Component\HttpFoundation\JsonResponse */ - public function createSingleResponse(mixed $transUnit) + public function createSingleResponse(mixed $transUnit): JsonResponse { return new JsonResponse($this->formatOne($transUnit)); } /** - * Format the tanslations list. - * - * @param array $transUnits - * @return array + * Format the translations list. */ - protected function format($transUnits) + protected function format(array $transUnits): array { $formatted = []; @@ -62,19 +50,20 @@ protected function format($transUnits) /** * Format a single TransUnit. - * - * @param array $transUnit - * @return array */ - protected function formatOne($transUnit) + protected function formatOne(TransUnitInterface|array $transUnit): array { if (is_object($transUnit)) { $transUnit = $this->toArray($transUnit); - } elseif (StorageInterface::STORAGE_MONGODB == $this->storage) { + } elseif (StorageInterface::STORAGE_MONGODB === $this->storage) { $transUnit['id'] = $transUnit['_id']->{'$id'}; } - $formatted = ['_id' => $transUnit['id'], '_domain' => $transUnit['domain'], '_key' => $transUnit['key']]; + $formatted = [ + '_id' => $transUnit['id'], + '_domain' => $transUnit['domain'], + '_key' => $transUnit['key'], + ]; // add locales in the same order as in managed_locales param foreach ($this->localeManager->getLocales() as $locale) { @@ -83,7 +72,7 @@ protected function formatOne($transUnit) // then fill locales value foreach ($transUnit['translations'] as $translation) { - if (in_array($translation['locale'], $this->localeManager->getLocales())) { + if (in_array($translation['locale'], $this->localeManager->getLocales(), true)) { $formatted[$translation['locale']] = $translation['content']; } } @@ -93,15 +82,18 @@ protected function formatOne($transUnit) /** * Convert a trans unit into an array. - * - * @return array */ - protected function toArray(TransUnitInterface $transUnit) + protected function toArray(TransUnitInterface $transUnit): array { - $data = ['id' => $transUnit->getId(), 'domain' => $transUnit->getDomain(), 'key' => $transUnit->getKey(), 'translations' => []]; + $data = [ + 'id' => $transUnit->getId(), + 'domain' => $transUnit->getDomain(), + 'key' => $transUnit->getKey(), + 'translations' => [], + ]; foreach ($transUnit->getTranslations() as $translation) { - $data['translations'][] = ['locale' => $translation->getLocale(), 'content' => $translation->getContent()]; + $data['translations'][] = ['locale' => $translation->getLocale(), 'content' => $translation->getContent()]; } return $data; diff --git a/Util/DataGrid/DataGridRequestHandler.php b/Util/DataGrid/DataGridRequestHandler.php index 05a536fc..db847bc5 100644 --- a/Util/DataGrid/DataGridRequestHandler.php +++ b/Util/DataGrid/DataGridRequestHandler.php @@ -2,9 +2,10 @@ namespace Lexik\Bundle\TranslationBundle\Util\DataGrid; +use Lexik\Bundle\TranslationBundle\Document\TransUnit as TransUnitDocument; use Lexik\Bundle\TranslationBundle\Manager\FileManagerInterface; use Lexik\Bundle\TranslationBundle\Manager\LocaleManagerInterface; -use Lexik\Bundle\TranslationBundle\Document\TransUnit as TransUnitDocument; +use Lexik\Bundle\TranslationBundle\Manager\TransUnitInterface; use Lexik\Bundle\TranslationBundle\Manager\TransUnitManagerInterface; use Lexik\Bundle\TranslationBundle\Model\TransUnit; use Lexik\Bundle\TranslationBundle\Storage\StorageInterface; @@ -152,12 +153,8 @@ public function getByToken($token) /** * Updates a trans unit from the request. - * - * @param integer $id - * @throws NotFoundHttpException - * @return \Lexik\Bundle\TranslationBundle\Model\TransUnit */ - public function updateFromRequest($id, Request $request) + public function updateFromRequest(int $id, Request $request): TransUnitInterface { $transUnit = $this->storage->getTransUnitById($id); @@ -203,7 +200,7 @@ protected function filterTokenTranslations($transUnits, $count, $parameters) if (count($filters) > 0) { $end = count($transUnits); - for ($i=0; $i<$end; $i++) { + for ($i = 0; $i < $end; $i++) { $match = true; foreach ($filters as $column => $str) { diff --git a/Util/Overview/StatsAggregator.php b/Util/Overview/StatsAggregator.php index 38add9b9..c3b012f6 100644 --- a/Util/Overview/StatsAggregator.php +++ b/Util/Overview/StatsAggregator.php @@ -32,9 +32,9 @@ public function getStats() foreach ($this->localeManager->getLocales() as $locale) { $localeCount = $byLocale[$locale] ?? 0; - $stats[$domain][$locale] = ['keys' => $total, + $stats[$domain][$locale] = ['keys' => $total, 'translated' => $localeCount, - 'completed' => ($total > 0) ? floor(($localeCount / $total) * 100) : 0, + 'completed' => ($total > 0) ? floor(($localeCount / $total) * 100) : 0, ]; } } diff --git a/composer.json b/composer.json index 766aee90..b8aa5253 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require": { - "php": "^8.1|^8.2|^8.3|^8.4", + "php": "^8.2|^8.3|^8.4", "doctrine/orm": "^3", "monolog/monolog": "^3.2", "symfony/asset": "^7.0|^8.0", @@ -41,7 +41,7 @@ "doctrine/annotations": "^2", "doctrine/cache": "^1.4|^2.0", "doctrine/data-fixtures": "^1.1|^2.0", - "doctrine/doctrine-bundle": "^2.2", + "doctrine/doctrine-bundle": "^2.2 || ^3.0", "doctrine/mongodb-odm": "^2.7", "doctrine/mongodb-odm-bundle": "^5.0", "friendsofphp/php-cs-fixer": "^3.64", @@ -73,7 +73,6 @@ }, "config": { "platform": { - "php": "8.2.0", "ext-mongodb": "2.1.4" }, "sort-packages": true diff --git a/composer.lock b/composer.lock index fffd5241..8bf274fb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2f22c916da236e6c350a1e7cab9f61bf", + "content-hash": "b8d2269beaad4139f7b57df745e01ca2", "packages": [ { "name": "doctrine/collections", @@ -94,16 +94,16 @@ }, { "name": "doctrine/dbal", - "version": "4.4.2", + "version": "4.4.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "476f7f0fa6ea4aa5364926db7fabdf6049075722" + "reference": "61e730f1658814821a85f2402c945f3883407dec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/476f7f0fa6ea4aa5364926db7fabdf6049075722", - "reference": "476f7f0fa6ea4aa5364926db7fabdf6049075722", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/61e730f1658814821a85f2402c945f3883407dec", + "reference": "61e730f1658814821a85f2402c945f3883407dec", "shasum": "" }, "require": { @@ -180,7 +180,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.4.2" + "source": "https://github.com/doctrine/dbal/tree/4.4.3" }, "funding": [ { @@ -196,7 +196,7 @@ "type": "tidelift" } ], - "time": "2026-02-26T12:12:19+00:00" + "time": "2026-03-20T08:52:12+00:00" }, { "name": "doctrine/deprecations", @@ -248,63 +248,58 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.18.2", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546" + "reference": "75f1bf75d0ba099f23e7d43ebd804df5bec58c29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546", - "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/75f1bf75d0ba099f23e7d43ebd804df5bec58c29", + "reference": "75f1bf75d0ba099f23e7d43ebd804df5bec58c29", "shasum": "" }, "require": { - "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/dbal": "^4.0", "doctrine/deprecations": "^1.0", - "doctrine/persistence": "^3.1 || ^4", + "doctrine/persistence": "^4", "doctrine/sql-formatter": "^1.0.1", - "php": "^8.1", - "symfony/cache": "^6.4 || ^7.0", - "symfony/config": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/service-contracts": "^2.5 || ^3" + "php": "^8.4", + "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/service-contracts": "^3" }, "conflict": { - "doctrine/annotations": ">=3.0", - "doctrine/cache": "< 1.11", - "doctrine/orm": "<2.17 || >=4.0", - "symfony/var-exporter": "< 6.4.1 || 7.0.0", - "twig/twig": "<2.13 || >=3.0 <3.0.4" + "doctrine/orm": "<3.0 || >=4.0", + "twig/twig": "<3.0.4" }, "require-dev": { - "doctrine/annotations": "^1 || ^2", - "doctrine/cache": "^1.11 || ^2.0", "doctrine/coding-standard": "^14", - "doctrine/orm": "^2.17 || ^3.1", - "friendsofphp/proxy-manager-lts": "^1.0", - "phpstan/phpstan": "2.1.1", + "doctrine/orm": "^3.4.4", + "phpstan/phpstan": "^2.1.13", "phpstan/phpstan-phpunit": "2.0.3", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^10.5.53 || ^12.3.10", - "psr/log": "^1.1.4 || ^2.0 || ^3.0", - "symfony/doctrine-messenger": "^6.4 || ^7.0", - "symfony/expression-language": "^6.4 || ^7.0", - "symfony/messenger": "^6.4 || ^7.0", - "symfony/property-info": "^6.4 || ^7.0", - "symfony/security-bundle": "^6.4 || ^7.0", - "symfony/stopwatch": "^6.4 || ^7.0", - "symfony/string": "^6.4 || ^7.0", - "symfony/twig-bridge": "^6.4 || ^7.0", - "symfony/validator": "^6.4 || ^7.0", - "symfony/var-exporter": "^6.4.1 || ^7.0.1", - "symfony/web-profiler-bundle": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0", - "twig/twig": "^2.14.7 || ^3.0.4" + "phpstan/phpstan-symfony": "^2.0.9", + "phpunit/phpunit": "^12.3.10", + "psr/log": "^3.0", + "symfony/doctrine-messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.0 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/stopwatch": "^6.4 || ^7.0 || ^8.0", + "symfony/string": "^6.4 || ^7.0 || ^8.0", + "symfony/twig-bridge": "^6.4 || ^7.0 || ^8.0", + "symfony/validator": "^6.4 || ^7.0 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", + "twig/twig": "^3.21.1" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -349,7 +344,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2" + "source": "https://github.com/doctrine/DoctrineBundle/tree/3.2.4" }, "funding": [ { @@ -365,41 +360,48 @@ "type": "tidelift" } ], - "time": "2025-12-20T21:35:32+00:00" + "time": "2026-06-09T19:11:55+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "3.7.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b" + "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/1e380c6dd8ac8488217f39cff6b77e367f1a644b", - "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/20505da78735744fb4a42a3bb9a416b345ad6f7c", + "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "^2.4 || ^3.0", + "doctrine/dbal": "^4", + "doctrine/doctrine-bundle": "^3", "doctrine/migrations": "^3.2", - "php": "^7.2 || ^8.0", - "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" + "php": "^8.4", + "psr/log": "^3", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/deprecation-contracts": "^3", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/http-foundation": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/service-contracts": "^3.0" }, "require-dev": { "composer/semver": "^3.0", - "doctrine/coding-standard": "^12 || ^14", - "doctrine/orm": "^2.6 || ^3", - "phpstan/phpstan": "^1.4 || ^2", - "phpstan/phpstan-deprecation-rules": "^1 || ^2", - "phpstan/phpstan-phpunit": "^1 || ^2", - "phpstan/phpstan-strict-rules": "^1.1 || ^2", - "phpstan/phpstan-symfony": "^1.3 || ^2", - "phpunit/phpunit": "^8.5 || ^9.5", - "symfony/phpunit-bridge": "^6.3 || ^7 || ^8", - "symfony/var-exporter": "^5.4 || ^6 || ^7 || ^8" + "doctrine/coding-standard": "^14", + "doctrine/orm": "^3", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpstan/phpstan-symfony": "^2", + "phpunit/phpunit": "^12.5", + "symfony/var-exporter": "^6.4 || ^7 || ^8" }, "type": "symfony-bundle", "autoload": { @@ -434,7 +436,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.7.0" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/4.0.0" }, "funding": [ { @@ -450,7 +452,7 @@ "type": "tidelift" } ], - "time": "2025-11-15T19:02:59+00:00" + "time": "2025-12-05T08:14:38+00:00" }, { "name": "doctrine/event-manager", @@ -635,30 +637,29 @@ }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.4" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^14", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -685,7 +686,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/2.1.0" }, "funding": [ { @@ -701,7 +702,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2026-01-05T06:47:08+00:00" }, { "name": "doctrine/lexer", @@ -782,16 +783,16 @@ }, { "name": "doctrine/migrations", - "version": "3.9.6", + "version": "3.9.7", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "ffd8355cdd8505fc650d9604f058bf62aedd80a1" + "reference": "96cb2a89b56c9efb0bac38e606dc0b0f13e650ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/ffd8355cdd8505fc650d9604f058bf62aedd80a1", - "reference": "ffd8355cdd8505fc650d9604f058bf62aedd80a1", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/96cb2a89b56c9efb0bac38e606dc0b0f13e650ec", + "reference": "96cb2a89b56c9efb0bac38e606dc0b0f13e650ec", "shasum": "" }, "require": { @@ -865,7 +866,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.9.6" + "source": "https://github.com/doctrine/migrations/tree/3.9.7" }, "funding": [ { @@ -881,20 +882,20 @@ "type": "tidelift" } ], - "time": "2026-02-11T06:46:11+00:00" + "time": "2026-04-23T19:33:20+00:00" }, { "name": "doctrine/orm", - "version": "3.6.2", + "version": "3.6.7", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "4262eb495b4d2a53b45de1ac58881e0091f2970f" + "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/4262eb495b4d2a53b45de1ac58881e0091f2970f", - "reference": "4262eb495b4d2a53b45de1ac58881e0091f2970f", + "url": "https://api.github.com/repos/doctrine/orm/zipball/bc217c0e19c3a9eadfa67697143b87c9ba01272c", + "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c", "shasum": "" }, "require": { @@ -967,25 +968,26 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.6.2" + "source": "https://github.com/doctrine/orm/tree/3.6.7" }, - "time": "2026-01-30T21:41:41+00:00" + "time": "2026-05-25T16:45:47+00:00" }, { "name": "doctrine/persistence", - "version": "4.1.1", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09" + "reference": "49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", - "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b", + "reference": "49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b", "shasum": "" }, "require": { + "doctrine/deprecations": "^1", "doctrine/event-manager": "^1 || ^2", "php": "^8.1", "psr/cache": "^1.0 || ^2.0 || ^3.0" @@ -996,13 +998,13 @@ "phpstan/phpstan-phpunit": "^2", "phpstan/phpstan-strict-rules": "^2", "phpunit/phpunit": "^10.5.58 || ^12", - "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0", - "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0" + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0 || ^8.0" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Persistence\\": "src/Persistence" + "Doctrine\\Persistence\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1046,7 +1048,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/4.1.1" + "source": "https://github.com/doctrine/persistence/tree/4.2.0" }, "funding": [ { @@ -1062,7 +1064,7 @@ "type": "tidelift" } ], - "time": "2025-10-16T20:13:18+00:00" + "time": "2026-04-26T12:12:52+00:00" }, { "name": "doctrine/sql-formatter", @@ -1426,28 +1428,25 @@ }, { "name": "symfony/asset", - "version": "v7.4.6", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "d944ae87e4697af05aadeacfc5e603c3c18ef4fb" + "reference": "4bd4d143b7e53f40d45877df52eb2b18282bdac4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/d944ae87e4697af05aadeacfc5e603c3c18ef4fb", - "reference": "d944ae87e4697af05aadeacfc5e603c3c18ef4fb", + "url": "https://api.github.com/repos/symfony/asset/zipball/4bd4d143b7e53f40d45877df52eb2b18282bdac4", + "reference": "4bd4d143b7e53f40d45877df52eb2b18282bdac4", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "conflict": { - "symfony/http-foundation": "<6.4" + "php": ">=8.4.1" }, "require-dev": { - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0" + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1475,7 +1474,7 @@ "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset/tree/v7.4.6" + "source": "https://github.com/symfony/asset/tree/v8.1.0" }, "funding": [ { @@ -1495,38 +1494,33 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/cache", - "version": "v7.4.13", + "version": "v8.0.14", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "4c09e18a92cce126cc0d1155825279fca8cd0673" + "reference": "76ec84217eb39e4787d03928bba86a25cc859f76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/4c09e18a92cce126cc0d1155825279fca8cd0673", - "reference": "4c09e18a92cce126cc0d1155825279fca8cd0673", + "url": "https://api.github.com/repos/symfony/cache/zipball/76ec84217eb39e4787d03928bba86a25cc859f76", + "reference": "76ec84217eb39e4787d03928bba86a25cc859f76", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^3.6", - "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^7.4|^8.0" }, "conflict": { - "doctrine/dbal": "<3.6", "ext-redis": "<6.1", - "ext-relay": "<0.12.1", - "symfony/dependency-injection": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/var-dumper": "<6.4" + "ext-relay": "<0.12.1" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -1535,16 +1529,16 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^3.6|^4", + "doctrine/dbal": "^4.3", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1579,7 +1573,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.13" + "source": "https://github.com/symfony/cache/tree/v8.0.14" }, "funding": [ { @@ -1599,20 +1593,20 @@ "type": "tidelift" } ], - "time": "2026-05-24T08:43:14+00:00" + "time": "2026-06-17T15:00:27+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "225e8a254166bd3442e370c6f50145465db63831" + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/225e8a254166bd3442e370c6f50145465db63831", - "reference": "225e8a254166bd3442e370c6f50145465db63831", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", "shasum": "" }, "require": { @@ -1659,7 +1653,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1" }, "funding": [ { @@ -1679,38 +1673,37 @@ "type": "tidelift" } ], - "time": "2026-05-05T15:33:14+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/config", - "version": "v7.4.7", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5" + "reference": "c18ae45733f9a5006ba81a13047d08a93e0dea1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/6c17162555bfb58957a55bb0e43e00035b6ae3d5", - "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5", + "url": "https://api.github.com/repos/symfony/config/zipball/c18ae45733f9a5006ba81a13047d08a93e0dea1c", + "reference": "c18ae45733f9a5006ba81a13047d08a93e0dea1c", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^7.1|^8.0", - "symfony/polyfill-ctype": "~1.8" + "symfony/filesystem": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/finder": "<6.4", "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1738,7 +1731,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.7" + "source": "https://github.com/symfony/config/tree/v8.1.1" }, "funding": [ { @@ -1758,51 +1751,43 @@ "type": "tidelift" } ], - "time": "2026-03-06T10:41:14+00:00" + "time": "2026-06-09T10:54:51+00:00" }, { "name": "symfony/console", - "version": "v7.4.7", + "version": "v8.0.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + "reference": "e2a77289192c413abfe54f1d507159f911a20728" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "url": "https://api.github.com/repos/symfony/console/zipball/e2a77289192c413abfe54f1d507159f911a20728", + "reference": "e2a77289192c413abfe54f1d507159f911a20728", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1836,7 +1821,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.7" + "source": "https://github.com/symfony/console/tree/v8.0.14" }, "funding": [ { @@ -1856,43 +1841,40 @@ "type": "tidelift" } ], - "time": "2026-03-06T14:06:20+00:00" + "time": "2026-06-16T12:45:11+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.7", + "version": "v8.0.14", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db" + "reference": "55d83c634f0bd7d6b5f64be8950dc389dca17a17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", - "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/55d83c634f0bd7d6b5f64be8950dc389dca17a17", + "reference": "55d83c634f0bd7d6b5f64be8950dc389dca17a17", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^3.6", - "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" + "symfony/var-exporter": "^7.4|^8.0" }, "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<6.4", - "symfony/finder": "<6.4", - "symfony/yaml": "<6.4" + "ext-psr": "<1.1|>=2" }, "provide": { "psr/container-implementation": "1.1|2.0", "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/config": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -1920,7 +1902,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.7" + "source": "https://github.com/symfony/dependency-injection/tree/v8.0.14" }, "funding": [ { @@ -1940,20 +1922,20 @@ "type": "tidelift" } ], - "time": "2026-03-03T07:48:48+00:00" + "time": "2026-06-27T06:17:20+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -1991,7 +1973,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -2011,72 +1993,63 @@ "type": "tidelift" } ], - "time": "2026-04-13T15:52:40+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v7.4.7", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "4fc5e2dd41be3c0b6321e0373072782edeff45ed" + "reference": "883547207ff3b77c5cec9f4f16dd330ccd7b2849" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/4fc5e2dd41be3c0b6321e0373072782edeff45ed", - "reference": "4fc5e2dd41be3c0b6321e0373072782edeff45ed", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/883547207ff3b77c5cec9f4f16dd330ccd7b2849", + "reference": "883547207ff3b77c5cec9f4f16dd330ccd7b2849", "shasum": "" }, "require": { "doctrine/event-manager": "^2", "doctrine/persistence": "^3.1|^4", - "php": ">=8.2", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { "doctrine/collections": "<1.8", - "doctrine/dbal": "<3.6", + "doctrine/dbal": "<4.3", "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.15", - "symfony/cache": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/form": "<6.4.6|>=7,<7.0.6", - "symfony/http-foundation": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/lock": "<6.4", - "symfony/messenger": "<6.4", - "symfony/property-info": "<6.4", - "symfony/security-bundle": "<6.4", - "symfony/security-core": "<6.4", - "symfony/validator": "<7.4" + "doctrine/orm": "<3.4", + "symfony/property-info": "<8.0" }, "require-dev": { "doctrine/collections": "^1.8|^2.0", "doctrine/data-fixtures": "^1.1|^2", - "doctrine/dbal": "^3.6|^4", - "doctrine/orm": "^2.15|^3", + "doctrine/dbal": "^4.3", + "doctrine/orm": "^3.4", "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/doctrine-messenger": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/form": "^7.2|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/type-info": "^7.1.8|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^8.1", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/doctrine-messenger": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/var-dumper": "^7.4|^8.0" }, "type": "symfony-bridge", "autoload": { @@ -2104,7 +2077,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v7.4.7" + "source": "https://github.com/symfony/doctrine-bridge/tree/v8.1.1" }, "funding": [ { @@ -2124,37 +2097,36 @@ "type": "tidelift" } ], - "time": "2026-03-05T08:16:50+00:00" + "time": "2026-06-12T08:43:41+00:00" }, { "name": "symfony/error-handler", - "version": "v7.4.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa" + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", - "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d8aeb1abd3fef84795567850d3a567bdb5945ee5", + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", "symfony/polyfill-php85": "^1.32", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/var-dumper": "^7.4|^8.0" }, "conflict": { - "symfony/deprecation-contracts": "<2.5", - "symfony/http-kernel": "<6.4" + "symfony/deprecation-contracts": "<2.5" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", + "symfony/console": "^7.4|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ @@ -2186,7 +2158,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.8" + "source": "https://github.com/symfony/error-handler/tree/v8.1.0" }, "funding": [ { @@ -2206,28 +2178,29 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.9", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101" + "reference": "abd6c11dc468725d1627302ad10f6cd486e9e3d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101", - "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abd6c11dc468725d1627302ad10f6cd486e9e3d0", + "reference": "abd6c11dc468725d1627302ad10f6cd486e9e3d0", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<6.4", + "symfony/security-http": "<7.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -2236,14 +2209,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -2271,7 +2244,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.1" }, "funding": [ { @@ -2291,20 +2264,20 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:18:21+00:00" + "time": "2026-06-09T12:28:30+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -2351,7 +2324,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -2371,29 +2344,30 @@ "type": "tidelift" } ], - "time": "2026-01-05T13:30:16+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.6", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", - "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/99aec13b82b4967ec5088222c4a3ecca955949c2", + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0|^8.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -2421,7 +2395,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.6" + "source": "https://github.com/symfony/filesystem/tree/v8.1.0" }, "funding": [ { @@ -2441,27 +2415,27 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/finder", - "version": "v7.4.6", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "url": "https://api.github.com/repos/symfony/finder/zipball/e2989e762c70f9490fa3a00a0ac0fae5aa97a531", + "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4.1" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -2489,7 +2463,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.6" + "source": "https://github.com/symfony/finder/tree/v8.1.1" }, "funding": [ { @@ -2509,62 +2483,54 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:40:50+00:00" + "time": "2026-06-27T09:05:56+00:00" }, { "name": "symfony/form", - "version": "v7.4.7", + "version": "v8.0.14", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "5f24175103fd0a62b98442207c240688210fd88b" + "reference": "36b007caa7fa08be969fee0bf341f6b99dd15000" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/5f24175103fd0a62b98442207c240688210fd88b", - "reference": "5f24175103fd0a62b98442207c240688210fd88b", + "url": "https://api.github.com/repos/symfony/form/zipball/36b007caa7fa08be969fee0bf341f6b99dd15000", + "reference": "36b007caa7fa08be969fee0bf341f6b99dd15000", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/options-resolver": "^7.3|^8.0", - "symfony/polyfill-ctype": "~1.8", + "php": ">=8.4", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/options-resolver": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-intl-icu": "^1.21", - "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/property-access": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<6.4", - "symfony/error-handler": "<6.4", - "symfony/framework-bundle": "<6.4", - "symfony/http-kernel": "<6.4", "symfony/intl": "<7.4", - "symfony/translation": "<6.4.3|>=7.0,<7.0.3", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.4" + "symfony/validator": "<7.4" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/html-sanitizer": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", "symfony/intl": "^7.4|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/security-csrf": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4.3|^7.0.3|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4.12|^7.1.5|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -2592,7 +2558,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.4.7" + "source": "https://github.com/symfony/form/tree/v8.0.14" }, "funding": [ { @@ -2612,37 +2578,37 @@ "type": "tidelift" } ], - "time": "2026-03-05T12:30:09+00:00" + "time": "2026-06-16T16:02:37+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.4.7", + "version": "v8.0.14", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "c94bc78c85d76af67918404a95d44940f66a7c2f" + "reference": "74134f73e391698bf78b3dd05bb25a0bdfccbabb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c94bc78c85d76af67918404a95d44940f66a7c2f", - "reference": "c94bc78c85d76af67918404a95d44940f66a7c2f", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/74134f73e391698bf78b3dd05bb25a0bdfccbabb", + "reference": "74134f73e391698bf78b3dd05bb25a0bdfccbabb", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.2", - "symfony/cache": "^6.4.12|^7.0|^8.0", + "php": ">=8.4", + "symfony/cache": "^7.4|^8.0", "symfony/config": "^7.4.4|^8.0.4", "symfony/dependency-injection": "^7.4.4|^8.0.4", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^7.3|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^7.1|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", - "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php85": "^1.32", "symfony/routing": "^7.4|^8.0" }, @@ -2650,79 +2616,62 @@ "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/asset": "<6.4", - "symfony/asset-mapper": "<6.4", - "symfony/clock": "<6.4", - "symfony/console": "<6.4", - "symfony/dom-crawler": "<6.4", - "symfony/dotenv": "<6.4", + "symfony/console": "<7.4", "symfony/form": "<7.4", - "symfony/http-client": "<6.4", - "symfony/lock": "<6.4", - "symfony/mailer": "<6.4", + "symfony/json-streamer": "<7.4", "symfony/messenger": "<7.4", - "symfony/mime": "<6.4", - "symfony/property-access": "<6.4", - "symfony/property-info": "<6.4", - "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", - "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", - "symfony/security-core": "<6.4", - "symfony/security-csrf": "<7.2", - "symfony/serializer": "<7.2.5", - "symfony/stopwatch": "<6.4", - "symfony/translation": "<7.3", - "symfony/twig-bridge": "<6.4", - "symfony/twig-bundle": "<6.4", - "symfony/validator": "<6.4", - "symfony/web-profiler-bundle": "<6.4", - "symfony/webhook": "<7.2", + "symfony/mime": "<7.4.9|>=8.0,<8.0.9", + "symfony/security-csrf": "<7.4", + "symfony/serializer": "<7.4", + "symfony/translation": "<7.4", + "symfony/webhook": "<7.4", "symfony/workflow": "<7.4" }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", - "symfony/asset": "^6.4|^7.0|^8.0", - "symfony/asset-mapper": "^6.4|^7.0|^8.0", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dom-crawler": "^6.4|^7.0|^8.0", - "symfony/dotenv": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/asset": "^7.4|^8.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/dotenv": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", "symfony/form": "^7.4|^8.0", - "symfony/html-sanitizer": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/json-streamer": "^7.3|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/mailer": "^6.4|^7.0|^8.0", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/json-streamer": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/mailer": "^7.4|^8.0", "symfony/messenger": "^7.4|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/notifier": "^6.4|^7.0|^8.0", - "symfony/object-mapper": "^7.3|^8.0", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6|^8.0", - "symfony/scheduler": "^6.4.4|^7.0.4|^8.0", - "symfony/security-bundle": "^6.4|^7.0|^8.0", - "symfony/semaphore": "^6.4|^7.0|^8.0", - "symfony/serializer": "^7.2.5|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/translation": "^7.3|^8.0", - "symfony/twig-bundle": "^6.4|^7.0|^8.0", - "symfony/type-info": "^7.1.8|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.4.9|^8.0.9", + "symfony/notifier": "^7.4|^8.0", + "symfony/object-mapper": "^7.4.9|^8.0.9", + "symfony/polyfill-intl-icu": "^1.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/scheduler": "^7.4|^8.0", + "symfony/security-bundle": "^7.4|^8.0", + "symfony/semaphore": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0", + "symfony/type-info": "^7.4.1|^8.0.1", + "symfony/uid": "^7.4|^8.0", "symfony/validator": "^7.4|^8.0", - "symfony/web-link": "^6.4|^7.0|^8.0", - "symfony/webhook": "^7.2|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/webhook": "^7.4|^8.0", "symfony/workflow": "^7.4|^8.0", - "symfony/yaml": "^7.3|^8.0", - "twig/twig": "^3.12" + "symfony/yaml": "^7.4|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -2750,7 +2699,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.4.7" + "source": "https://github.com/symfony/framework-bundle/tree/v8.0.14" }, "funding": [ { @@ -2770,41 +2719,40 @@ "type": "tidelift" } ], - "time": "2026-03-06T15:39:55+00:00" + "time": "2026-06-27T08:56:37+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.13", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "bc354f47c62301e990b7874fa662326368508e2c" + "reference": "6a168c8fcee806b57ac020244da14293d1f9a883" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bc354f47c62301e990b7874fa662326368508e2c", - "reference": "bc354f47c62301e990b7874fa662326368508e2c", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6a168c8fcee806b57ac020244da14293d1f9a883", + "reference": "6a168c8fcee806b57ac020244da14293d1f9a883", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.1" }, "conflict": { - "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + "doctrine/dbal": "<4.3" }, "require-dev": { - "doctrine/dbal": "^3.6|^4", + "doctrine/dbal": "^4.3", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -2832,7 +2780,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.13" + "source": "https://github.com/symfony/http-foundation/tree/v8.1.1" }, "funding": [ { @@ -2852,78 +2800,63 @@ "type": "tidelift" } ], - "time": "2026-05-24T11:20:33+00:00" + "time": "2026-06-12T08:43:41+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.13", + "version": "v8.0.14", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9df847980c436451f4f51d1284491bb4356dd989" + "reference": "6685b981881100e5f92a98fb6c5d6739af6a8569" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9df847980c436451f4f51d1284491bb4356dd989", - "reference": "9df847980c436451f4f51d1284491bb4356dd989", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6685b981881100e5f92a98fb6c5d6739af6a8569", + "reference": "6685b981881100e5f92a98fb6c5d6739af6a8569", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<6.4", - "symfony/cache": "<6.4", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<6.4", "symfony/flex": "<2.10", - "symfony/form": "<6.4", - "symfony/http-client": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", - "symfony/translation": "<6.4", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.4", - "symfony/validator": "<6.4", - "symfony/var-dumper": "<6.4", - "twig/twig": "<3.12" + "twig/twig": "<3.21" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", - "symfony/dom-crawler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^7.1|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/serializer": "^7.1|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "twig/twig": "^3.21" }, "type": "library", "autoload": { @@ -2951,7 +2884,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.13" + "source": "https://github.com/symfony/http-kernel/tree/v8.0.14" }, "funding": [ { @@ -2971,24 +2904,24 @@ "type": "tidelift" } ], - "time": "2026-05-27T08:31:43+00:00" + "time": "2026-06-27T09:16:44+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.4.0", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b38026df55197f9e39a44f3215788edf83187b80" + "reference": "88f9c561f678a02d54b897014049fa839e33ff82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", - "reference": "b38026df55197f9e39a44f3215788edf83187b80", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -3022,7 +2955,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" + "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" }, "funding": [ { @@ -3042,7 +2975,7 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/orm-pack", @@ -3098,27 +3031,24 @@ }, { "name": "symfony/password-hasher", - "version": "v7.4.6", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "376755eb9c9857d78aedb68341ad2f46d1908b29" + "reference": "6934d16beaa4677f2c4584229fff1b51099dd7af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/376755eb9c9857d78aedb68341ad2f46d1908b29", - "reference": "376755eb9c9857d78aedb68341ad2f46d1908b29", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/6934d16beaa4677f2c4584229fff1b51099dd7af", + "reference": "6934d16beaa4677f2c4584229fff1b51099dd7af", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "conflict": { - "symfony/security-core": "<6.4" + "php": ">=8.4.1" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0" + "symfony/console": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3150,7 +3080,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v7.4.6" + "source": "https://github.com/symfony/password-hasher/tree/v8.1.0" }, "funding": [ { @@ -3170,7 +3100,7 @@ "type": "tidelift" } ], - "time": "2026-02-11T16:03:16+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3257,16 +3187,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { @@ -3315,7 +3245,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" }, "funding": [ { @@ -3335,20 +3265,20 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-05-26T05:58:03+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.33.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c" + "reference": "445c90e341fccda10311019cf82ff73bb7343945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", - "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/445c90e341fccda10311019cf82ff73bb7343945", + "reference": "445c90e341fccda10311019cf82ff73bb7343945", "shasum": "" }, "require": { @@ -3403,7 +3333,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.38.0" }, "funding": [ { @@ -3423,20 +3353,20 @@ "type": "tidelift" } ], - "time": "2025-06-20T22:24:30+00:00" + "time": "2026-05-25T11:52:53+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -3488,7 +3418,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -3508,20 +3438,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.38.1", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -3573,7 +3503,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -3593,100 +3523,20 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" - }, - { - "name": "symfony/polyfill-php83", - "version": "v1.38.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "8339098cae28673c15cce00d80734af0453054e2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", - "reference": "8339098cae28673c15cce00d80734af0453054e2", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.33.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { @@ -3733,7 +3583,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { @@ -3753,7 +3603,7 @@ "type": "tidelift" } ], - "time": "2025-06-24T13:30:11+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-php85", @@ -3837,25 +3687,25 @@ }, { "name": "symfony/property-access", - "version": "v7.4.4", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1" + "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", - "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", + "url": "https://api.github.com/repos/symfony/property-access/zipball/9261ef060f26cc7b728f67f141ba19b98a6209a9", + "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" + "php": ">=8.4.1", + "symfony/property-info": "^7.4.4|^8.0.4" }, "require-dev": { - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3894,7 +3744,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.4.4" + "source": "https://github.com/symfony/property-access/tree/v8.1.0" }, "funding": [ { @@ -3914,41 +3764,37 @@ "type": "tidelift" } ], - "time": "2026-01-05T08:47:25+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/property-info", - "version": "v7.4.7", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "02501d75fd834345da3ecdd8e3200ced39e370f8" + "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/02501d75fd834345da3ecdd8e3200ced39e370f8", - "reference": "02501d75fd834345da3ecdd8e3200ced39e370f8", + "url": "https://api.github.com/repos/symfony/property-info/zipball/4721e8c56d0cd2378e0ef9a9899f810008b859f7", + "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0|^8.0", + "php": ">=8.4.1", + "symfony/string": "^7.4|^8.0", "symfony/type-info": "^7.4.7|^8.0.7" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", - "phpdocumentor/type-resolver": "<1.5.1", - "symfony/cache": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/serializer": "<6.4" + "phpdocumentor/type-resolver": "<1.5.1" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2|^6.0", "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3984,7 +3830,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.4.7" + "source": "https://github.com/symfony/property-info/tree/v8.1.0" }, "funding": [ { @@ -4004,38 +3850,33 @@ "type": "tidelift" } ], - "time": "2026-03-04T15:53:26+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/routing", - "version": "v7.4.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d" + "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3a162171bb008e5e0f15dce6581373a4c0e8390d", - "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d", + "url": "https://api.github.com/repos/symfony/routing/zipball/fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", + "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3" }, - "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" - }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4069,7 +3910,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.13" + "source": "https://github.com/symfony/routing/tree/v8.1.0" }, "funding": [ { @@ -4089,50 +3930,42 @@ "type": "tidelift" } ], - "time": "2026-05-24T11:20:33+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/security-core", - "version": "v7.4.4", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "958a70725a8d669bec6721f4cd318d209712e944" + "reference": "2350e2f04858a7d50e758852512f9f5c3091a37b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/958a70725a8d669bec6721f4cd318d209712e944", - "reference": "958a70725a8d669bec6721f4cd318d209712e944", + "url": "https://api.github.com/repos/symfony/security-core/zipball/2350e2f04858a7d50e758852512f9f5c3091a37b", + "reference": "2350e2f04858a7d50e758852512f9f5c3091a37b", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.4.1", "symfony/event-dispatcher-contracts": "^2.5|^3", - "symfony/password-hasher": "^6.4|^7.0|^8.0", + "symfony/password-hasher": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/http-foundation": "<6.4", - "symfony/ldap": "<6.4", - "symfony/translation": "<6.4.3|>=7.0,<7.0.3", - "symfony/validator": "<6.4" - }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "psr/container": "^1.1|^2.0", "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/ldap": "^6.4|^7.0|^8.0", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4.3|^7.0.3|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/ldap": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4160,7 +3993,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.4.4" + "source": "https://github.com/symfony/security-core/tree/v8.1.1" }, "funding": [ { @@ -4180,33 +4013,31 @@ "type": "tidelift" } ], - "time": "2026-01-14T09:36:49+00:00" + "time": "2026-06-09T10:54:51+00:00" }, { "name": "symfony/security-csrf", - "version": "v7.4.6", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "d01adcd3141bec95e4cfd338f6b4482f1dd6a42b" + "reference": "c865a8ee0d30b14545d7e5349b8e443f4fa9dc3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/d01adcd3141bec95e4cfd338f6b4482f1dd6a42b", - "reference": "d01adcd3141bec95e4cfd338f6b4482f1dd6a42b", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/c865a8ee0d30b14545d7e5349b8e443f4fa9dc3f", + "reference": "c865a8ee0d30b14545d7e5349b8e443f4fa9dc3f", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/security-core": "^6.4|^7.0|^8.0" - }, - "conflict": { - "symfony/http-foundation": "<6.4" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/security-core": "^7.4|^8.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0" + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4234,7 +4065,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v7.4.6" + "source": "https://github.com/symfony/security-csrf/tree/v8.1.0" }, "funding": [ { @@ -4254,20 +4085,20 @@ "type": "tidelift" } ], - "time": "2026-02-11T16:03:16+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { @@ -4321,7 +4152,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -4341,24 +4172,24 @@ "type": "tidelift" } ], - "time": "2026-03-28T09:44:51+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.4.0", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "8a24af0a2e8a872fb745047180649b8418303084" + "reference": "21c07b026905d596e8379caeb115d87aa479499d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8a24af0a2e8a872fb745047180649b8418303084", - "reference": "8a24af0a2e8a872fb745047180649b8418303084", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/21c07b026905d596e8379caeb115d87aa479499d", + "reference": "21c07b026905d596e8379caeb115d87aa479499d", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -4387,7 +4218,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.4.0" + "source": "https://github.com/symfony/stopwatch/tree/v8.1.0" }, "funding": [ { @@ -4407,39 +4238,38 @@ "type": "tidelift" } ], - "time": "2025-08-04T07:05:15+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/string", - "version": "v7.4.6", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4478,7 +4308,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.6" + "source": "https://github.com/symfony/string/tree/v8.1.0" }, "funding": [ { @@ -4498,38 +4328,31 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/translation", - "version": "v7.4.6", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce" + "reference": "342b4218630dc2cf284cedcb2080c80b13404014" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce", + "url": "https://api.github.com/repos/symfony/translation/zipball/342b4218630dc2cf284cedcb2080c80b13404014", + "reference": "342b4218630dc2cf284cedcb2080c80b13404014", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5.3|^3.3" + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" }, "conflict": { "nikic/php-parser": "<5.0", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<6.4", - "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<6.4", - "symfony/yaml": "<6.4" + "symfony/service-contracts": "<2.5" }, "provide": { "symfony/translation-implementation": "2.3|3.0" @@ -4537,17 +4360,17 @@ "require-dev": { "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/routing": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4578,7 +4401,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.6" + "source": "https://github.com/symfony/translation/tree/v8.1.1" }, "funding": [ { @@ -4598,20 +4421,20 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-06-06T11:11:44+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -4624,7 +4447,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -4660,7 +4483,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -4680,71 +4503,64 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/twig-bridge", - "version": "v7.4.7", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "c67219ca6b79a57b64e36bbb2cd8ba741286587e" + "reference": "471e3b9537f514664ab8595668cd34c65cfa5d93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/c67219ca6b79a57b64e36bbb2cd8ba741286587e", - "reference": "c67219ca6b79a57b64e36bbb2cd8ba741286587e", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/471e3b9537f514664ab8595668cd34c65cfa5d93", + "reference": "471e3b9537f514664ab8595668cd34c65cfa5d93", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.4.1", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.21" + "twig/twig": "^3.25" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/console": "<6.4", - "symfony/form": "<6.4.32|>7,<7.3.10|>7.4,<7.4.4|>8.0,<8.0.4", - "symfony/http-foundation": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/mime": "<6.4", - "symfony/serializer": "<6.4", - "symfony/translation": "<6.4", - "symfony/workflow": "<6.4" + "symfony/form": "<7.4.4|>8.0,<8.0.4", + "symfony/mime": "<7.4.9|>8.0,<8.0.9" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^5.2|^6.0", - "symfony/asset": "^6.4|^7.0|^8.0", - "symfony/asset-mapper": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/emoji": "^7.1|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/form": "^6.4.32|~7.3.10|^7.4.4|^8.0.4", - "symfony/html-sanitizer": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^7.3|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/asset": "^7.4|^8.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4.4|^8.0.4", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/mime": "^7.4.9|^8.0.9", + "symfony/polyfill-intl-icu": "^1.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/security-csrf": "^6.4|^7.0|^8.0", - "symfony/security-http": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/validator": "^6.4|^7.0|^8.0", - "symfony/web-link": "^6.4|^7.0|^8.0", - "symfony/workflow": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/security-http": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0", "twig/cssinliner-extra": "^3", "twig/inky-extra": "^3", "twig/markdown-extra": "^3" @@ -4775,7 +4591,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.4.7" + "source": "https://github.com/symfony/twig-bridge/tree/v8.1.1" }, "funding": [ { @@ -4795,49 +4611,43 @@ "type": "tidelift" } ], - "time": "2026-03-04T15:37:05+00:00" + "time": "2026-06-17T15:04:37+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.4.4", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "e8829e02ff96a391ed0703bac9e7ff0537480b6b" + "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/e8829e02ff96a391ed0703bac9e7ff0537480b6b", - "reference": "e8829e02ff96a391ed0703bac9e7ff0537480b6b", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/b7f4a471a07b8b52174d153e4db12f46954192ed", + "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", - "php": ">=8.2", + "php": ">=8.4.1", "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", - "symfony/twig-bridge": "^7.3|^8.0", - "twig/twig": "^3.12" - }, - "conflict": { - "symfony/framework-bundle": "<6.4", - "symfony/translation": "<6.4" + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0" }, "require-dev": { - "symfony/asset": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/form": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4|^7.0|^8.0", - "symfony/web-link": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/asset": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -4865,7 +4675,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.4.4" + "source": "https://github.com/symfony/twig-bundle/tree/v8.1.0" }, "funding": [ { @@ -4885,26 +4695,25 @@ "type": "tidelift" } ], - "time": "2026-01-06T12:34:24+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/type-info", - "version": "v7.4.7", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "31f1e40cbf7851c7354281c90eb1b352c4cb8269" + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/31f1e40cbf7851c7354281c90eb1b352c4cb8269", - "reference": "31f1e40cbf7851c7354281c90eb1b352c4cb8269", + "url": "https://api.github.com/repos/symfony/type-info/zipball/9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.4.1", + "psr/container": "^1.1|^2.0" }, "conflict": { "phpstan/phpdoc-parser": "<1.30" @@ -4948,7 +4757,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.4.7" + "source": "https://github.com/symfony/type-info/tree/v8.1.0" }, "funding": [ { @@ -4968,62 +4777,55 @@ "type": "tidelift" } ], - "time": "2026-03-04T12:49:16+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/validator", - "version": "v7.4.7", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "3a1a460a9f8c5e5611e15c52c4baa5a62fa3c203" + "reference": "d162b73fa884ce2185033c143172dc12b023051a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/3a1a460a9f8c5e5611e15c52c4baa5a62fa3c203", - "reference": "3a1a460a9f8c5e5611e15c52c4baa5a62fa3c203", + "url": "https://api.github.com/repos/symfony/validator/zipball/d162b73fa884ce2185033c143172dc12b023051a", + "reference": "d162b73fa884ce2185033c143172dc12b023051a", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php83": "^1.27", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0", "symfony/translation-contracts": "^2.5|^3" }, "conflict": { "doctrine/lexer": "<1.1", - "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<7.0", - "symfony/expression-language": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/intl": "<6.4", - "symfony/property-info": "<6.4", - "symfony/translation": "<6.4.3|>=7.0,<7.0.3", - "symfony/var-exporter": "<6.4.25|>=7.0,<7.3.3", - "symfony/yaml": "<6.4" + "symfony/doctrine-bridge": "<7.4", + "symfony/expression-language": "<7.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/translation": "^6.4.3|^7.0.3|^8.0", - "symfony/type-info": "^7.1.8", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/type-info": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5052,7 +4854,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.4.7" + "source": "https://github.com/symfony/validator/tree/v8.1.1" }, "funding": [ { @@ -5072,35 +4874,35 @@ "type": "tidelift" } ], - "time": "2026-03-06T11:10:17+00:00" + "time": "2026-06-27T06:18:14+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.8", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd" + "reference": "40096a2515a979f3125c5c928603995b8664c62a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd", - "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/40096a2515a979f3125c5c928603995b8664c62a", + "reference": "40096a2515a979f3125c5c928603995b8664c62a", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { - "symfony/console": "<6.4" + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", "twig/twig": "^3.12" }, "bin": [ @@ -5139,7 +4941,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.8" + "source": "https://github.com/symfony/var-dumper/tree/v8.1.1" }, "funding": [ { @@ -5159,20 +4961,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:44:50+00:00" + "time": "2026-06-09T10:54:51+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.4.9", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "22e03a49c95ef054a43601cd159b222bfab1c701" + "reference": "0118811b1d59f323bf131250b3fb919febfece28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/22e03a49c95ef054a43601cd159b222bfab1c701", - "reference": "22e03a49c95ef054a43601cd159b222bfab1c701", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0118811b1d59f323bf131250b3fb919febfece28", + "reference": "0118811b1d59f323bf131250b3fb919febfece28", "shasum": "" }, "require": { @@ -5220,7 +5022,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.4.9" + "source": "https://github.com/symfony/var-exporter/tree/v7.4.14" }, "funding": [ { @@ -5240,32 +5042,32 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:18:21+00:00" + "time": "2026-06-27T08:41:53+00:00" }, { "name": "symfony/yaml", - "version": "v7.4.12", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "8b6952b56ca6417f25f7a65758cadd0ce02edc51" + "reference": "8e4cdd4311683516be06944f4b85244063cdb886" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/8b6952b56ca6417f25f7a65758cadd0ce02edc51", - "reference": "8b6952b56ca6417f25f7a65758cadd0ce02edc51", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8e4cdd4311683516be06944f4b85244063cdb886", + "reference": "8e4cdd4311683516be06944f4b85244063cdb886", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<6.4" + "symfony/console": "<7.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0" + "symfony/console": "^7.4|^8.0", + "yaml/yaml-test-suite": "*" }, "bin": [ "Resources/bin/yaml-lint" @@ -5296,7 +5098,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.12" + "source": "https://github.com/symfony/yaml/tree/v8.1.1" }, "funding": [ { @@ -5316,7 +5118,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-06-09T11:06:24+00:00" }, { "name": "twig/twig", @@ -5466,28 +5268,29 @@ }, { "name": "composer/pcre", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.10" + "phpstan/phpstan": "<2.2.2" }, "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { @@ -5525,7 +5328,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { @@ -5535,13 +5338,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2026-06-07T11:47:49+00:00" }, { "name": "composer/semver", @@ -5859,16 +5658,16 @@ }, { "name": "doctrine/data-fixtures", - "version": "2.2.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97" + "reference": "bf7ac3a050b54b261cedfb3d0a44733819062275" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/7a615ba135e45d67674bb623d90f34f6c7b6bd97", - "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/bf7ac3a050b54b261cedfb3d0a44733819062275", + "reference": "bf7ac3a050b54b261cedfb3d0a44733819062275", "shasum": "" }, "require": { @@ -5886,12 +5685,14 @@ "doctrine/dbal": "^3.5 || ^4", "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", "doctrine/orm": "^2.14 || ^3", + "doctrine/phpcr-odm": "^1.8 || ^2.0", "ext-sqlite3": "*", "fig/log-test": "^1", - "phpstan/phpstan": "2.1.31", - "phpunit/phpunit": "10.5.45 || 12.4.0", - "symfony/cache": "^6.4 || ^7", - "symfony/var-exporter": "^6.4 || ^7" + "jackalope/jackalope-fs": "*", + "phpstan/phpstan": "2.1.46", + "phpunit/phpunit": "10.5.63 || 12.5.12", + "symfony/cache": "^6.4 || ^7 || ^8", + "symfony/var-exporter": "^6.4 || ^7 || ^8" }, "suggest": { "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", @@ -5922,7 +5723,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/2.2.0" + "source": "https://github.com/doctrine/data-fixtures/tree/2.2.1" }, "funding": [ { @@ -5938,20 +5739,20 @@ "type": "tidelift" } ], - "time": "2025-10-17T20:06:20+00:00" + "time": "2026-04-01T13:56:01+00:00" }, { "name": "doctrine/mongodb-odm", - "version": "2.16.1", + "version": "2.16.2", "source": { "type": "git", "url": "https://github.com/doctrine/mongodb-odm.git", - "reference": "b34f2dce3ca5b2de8903419a31ad48837d852b74" + "reference": "da149ec2596c8e3732c5332b19f1351f3d5b6f70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/mongodb-odm/zipball/b34f2dce3ca5b2de8903419a31ad48837d852b74", - "reference": "b34f2dce3ca5b2de8903419a31ad48837d852b74", + "url": "https://api.github.com/repos/doctrine/mongodb-odm/zipball/da149ec2596c8e3732c5332b19f1351f3d5b6f70", + "reference": "da149ec2596c8e3732c5332b19f1351f3d5b6f70", "shasum": "" }, "require": { @@ -6041,7 +5842,7 @@ ], "support": { "issues": "https://github.com/doctrine/mongodb-odm/issues", - "source": "https://github.com/doctrine/mongodb-odm/tree/2.16.1" + "source": "https://github.com/doctrine/mongodb-odm/tree/2.16.2" }, "funding": [ { @@ -6057,7 +5858,7 @@ "type": "tidelift" } ], - "time": "2026-01-29T11:33:08+00:00" + "time": "2026-06-19T09:23:48+00:00" }, { "name": "doctrine/mongodb-odm-bundle", @@ -6147,6 +5948,75 @@ }, "time": "2026-01-26T10:21:57+00:00" }, + { + "name": "ergebnis/agent-detector", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/agent-detector.git", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271", + "shasum": "" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.51.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.18.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "^0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.54", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\AgentDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a detector for detecting the presence of an agent.", + "homepage": "https://github.com/ergebnis/agent-detector", + "support": { + "issues": "https://github.com/ergebnis/agent-detector/issues", + "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/agent-detector" + }, + "time": "2026-05-07T08:19:07+00:00" + }, { "name": "evenement/evenement", "version": "v3.0.2", @@ -6257,22 +6127,23 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.94.2", + "version": "v3.95.11", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63" + "reference": "35f98e1293283397824d7f349ce5afb8747c3cd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63", - "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/35f98e1293283397824d7f349ce5afb8747c3cd5", + "reference": "35f98e1293283397824d7f349ce5afb8747c3cd5", "shasum": "" }, "require": { "clue/ndjson-react": "^1.3", "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.5", + "ergebnis/agent-detector": "^1.2", "ext-filter": "*", "ext-hash": "*", "ext-json": "*", @@ -6283,32 +6154,32 @@ "react/event-loop": "^1.5", "react/socket": "^1.16", "react/stream": "^1.4", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.33", - "symfony/polyfill-php80": "^1.33", - "symfony/polyfill-php81": "^1.33", - "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-mbstring": "^1.37", + "symfony/polyfill-php80": "^1.37", + "symfony/polyfill-php81": "^1.37", + "symfony/polyfill-php84": "^1.37", "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.7.1", - "infection/infection": "^0.32.3", - "justinrainbow/json-schema": "^6.6.4", + "facile-it/paraunit": "^1.3.1 || ^2.11.0", + "infection/infection": "^0.32.7", + "justinrainbow/json-schema": "^6.10.0", "keradus/cli-executor": "^2.3", "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.9.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", - "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", - "symfony/polyfill-php85": "^1.33", - "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", - "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55", + "symfony/polyfill-php85": "^1.38", + "symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.0", + "symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -6349,7 +6220,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.11" }, "funding": [ { @@ -6357,7 +6228,7 @@ "type": "github" } ], - "time": "2026-02-20T16:13:53+00:00" + "time": "2026-06-25T14:17:04+00:00" }, { "name": "friendsofphp/proxy-manager-lts", @@ -6931,11 +6802,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.40", + "version": "2.2.4", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", - "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f0fe3fb03bb53ce68cc2416785b260e62226ec27", + "reference": "f0fe3fb03bb53ce68cc2416785b260e62226ec27", "shasum": "" }, "require": { @@ -6958,6 +6829,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -6980,7 +6862,7 @@ "type": "github" } ], - "time": "2026-02-23T15:04:35+00:00" + "time": "2026-07-03T07:00:23+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7940,21 +7822,21 @@ }, { "name": "rector/rector", - "version": "2.3.8", + "version": "2.5.2", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c" + "reference": "49ff6339174bdbdf50b0b35ecbcff14a05ac9e24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/bbd37aedd8df749916cffa2a947cfc4714d1ba2c", - "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/49ff6339174bdbdf50b0b35ecbcff14a05ac9e24", + "reference": "49ff6339174bdbdf50b0b35ecbcff14a05ac9e24", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.38" + "phpstan/phpstan": "^2.2.2" }, "conflict": { "rector/rector-doctrine": "*", @@ -7988,7 +7870,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.3.8" + "source": "https://github.com/rectorphp/rector/tree/2.5.2" }, "funding": [ { @@ -7996,7 +7878,7 @@ "type": "github" } ], - "time": "2026-02-22T09:45:50+00:00" + "time": "2026-06-22T11:39:33+00:00" }, { "name": "sebastian/cli-parser", @@ -9117,20 +8999,20 @@ }, { "name": "symfony/process", - "version": "v7.4.5", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "608476f4604102976d687c483ac63a79ba18cc97" + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", - "reference": "608476f4604102976d687c483ac63a79ba18cc97", + "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -9158,7 +9040,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.5" + "source": "https://github.com/symfony/process/tree/v8.1.0" }, "funding": [ { @@ -9178,7 +9060,7 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "theseer/tokenizer", @@ -9237,14 +9119,13 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.1|^8.2|^8.3|^8.4" + "php": "^8.2|^8.3|^8.4" }, "platform-dev": { "ext-mongodb": "^1.21.0 || ^2.0.0", "ext-pdo": "*" }, "platform-overrides": { - "php": "8.2.0", "ext-mongodb": "2.1.4" }, "plugin-api-version": "2.9.0" diff --git a/docker-compose.yml b/docker-compose.yml index 0cd30454..3ea03ee9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,7 @@ services: retries: 3 mongo: - image: mongo:4.0 + image: mongo:4.4 environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: secret