From f490265eec335fab85e8b91286d2ea60bc3a1819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 23 Sep 2024 14:12:25 +0200 Subject: [PATCH] Doctrine requirement is now optional --- composer.json | 5 +- .../DependencyInjection/Configuration.php | 2 +- .../SyliusResourceExtension.php | 111 +++++++++++++++--- tests/Application/composer.lock | 18 +++ .../config/packages/test/doctrine.yaml | 26 ++++ .../sylius_resource.yaml | 2 + tests/Application/config/services_test.yaml | 95 +++++++++++++++ .../Configuration/ConfigurationTest.php | 75 +++++++----- .../SyliusResourceExtensionTest.php | 52 ++++---- 9 files changed, 306 insertions(+), 80 deletions(-) create mode 100644 tests/Application/composer.lock create mode 100644 tests/Application/config/packages/test/doctrine.yaml create mode 100644 tests/Application/config/packages/test_without_doctrine_orm/sylius_resource.yaml create mode 100644 tests/Application/config/services_test.yaml diff --git a/composer.json b/composer.json index 88e9fd628..03583a04d 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,6 @@ "babdev/pagerfanta-bundle": "^3.7 || ^4.0", "doctrine/annotations": "^2.0", "doctrine/collections": "^1.8 || ^2.0", - "doctrine/doctrine-bundle": "^2.0", "doctrine/event-manager": "^1.1 || ^2.0", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/persistence": "^2.0 || ^3.0", @@ -57,6 +56,7 @@ "sylius/resource": "self.version" }, "require-dev": { + "doctrine/doctrine-bundle": "^2.0", "doctrine/orm": "^2.5", "friendsofsymfony/rest-bundle": "^3.0", "jms/serializer-bundle": "^3.5 || ^4.0 || ^5.0", @@ -102,7 +102,8 @@ "allow-plugins": { "symfony/flex": true, "dealerdirect/phpcodesniffer-composer-installer": false - } + }, + "sort-packages": true }, "extra": { "symfony": { diff --git a/src/Bundle/DependencyInjection/Configuration.php b/src/Bundle/DependencyInjection/Configuration.php index b963cfb5e..2ba50fe5c 100644 --- a/src/Bundle/DependencyInjection/Configuration.php +++ b/src/Bundle/DependencyInjection/Configuration.php @@ -146,7 +146,7 @@ private function addDriversSection(ArrayNodeDefinition $node): void $node ->children() ->arrayNode('drivers') - ->defaultValue([SyliusResourceBundle::DRIVER_DOCTRINE_ORM]) + ->defaultValue([]) ->enumPrototype()->values(SyliusResourceBundle::getAvailableDrivers())->end() ->end() ->end() diff --git a/src/Bundle/DependencyInjection/SyliusResourceExtension.php b/src/Bundle/DependencyInjection/SyliusResourceExtension.php index 7d05dcad1..18b0abff6 100644 --- a/src/Bundle/DependencyInjection/SyliusResourceExtension.php +++ b/src/Bundle/DependencyInjection/SyliusResourceExtension.php @@ -50,7 +50,7 @@ public function load(array $configs, ContainerBuilder $container): void $loader->load('services.xml'); - /** @var array $bundles */ + /** @var array $bundles */ $bundles = $container->getParameter('kernel.bundles'); if (array_key_exists('SyliusGridBundle', $bundles)) { $loader->load('services/integrations/grid.xml'); @@ -68,7 +68,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->autoRegisterResources($config, $container); - $this->loadPersistence($config['drivers'], $config['resources'], $loader); + $this->loadPersistence($config['drivers'], $config['resources'], $loader, $container); $this->loadResources($config['resources'], $container); $container->registerForAutoconfiguration(ProviderInterface::class) @@ -175,8 +175,20 @@ private function getResourceAlias(ResourceMetadata $resource, string $className) return 'app.' . u($shortName)->snake()->toString(); } - private function loadPersistence(array $drivers, array $resources, LoaderInterface $loader): void + /** + * @param array $resources + */ + private function loadPersistence(array $drivers, array $resources, LoaderInterface $loader, ContainerBuilder $container): void { + $availableDrivers = $this->getAvailableDrivers($container); + + // Enable all available drivers if there is no configured drivers + $drivers = [] !== $drivers ? $drivers : $availableDrivers; + + $resourceDrivers = $this->getResourceDrivers($resources); + + $this->checkConfiguredDrivers($drivers, $availableDrivers, $resourceDrivers); + $integrateDoctrine = array_reduce($drivers, function (bool $result, string $driver): bool { return $result || in_array($driver, [SyliusResourceBundle::DRIVER_DOCTRINE_ORM, SyliusResourceBundle::DRIVER_DOCTRINE_PHPCR_ODM, SyliusResourceBundle::DRIVER_DOCTRINE_MONGODB_ODM], true); }, false); @@ -185,20 +197,6 @@ private function loadPersistence(array $drivers, array $resources, LoaderInterfa $loader->load('services/integrations/doctrine.xml'); } - foreach ($resources as $alias => $resource) { - if (false === $resource['driver']) { - break; - } - - if (!in_array($resource['driver'], $drivers, true)) { - throw new InvalidArgumentException(sprintf( - 'Resource "%s" uses driver "%s", but this driver has not been enabled.', - $alias, - $resource['driver'], - )); - } - } - foreach ($drivers as $driver) { if (in_array($driver, [SyliusResourceBundle::DRIVER_DOCTRINE_PHPCR_ODM, SyliusResourceBundle::DRIVER_DOCTRINE_MONGODB_ODM], true)) { trigger_deprecation( @@ -209,10 +207,89 @@ private function loadPersistence(array $drivers, array $resources, LoaderInterfa ); } + // Only Doctrine drivers need service integration file + if (!str_starts_with($driver, 'doctrine')) { + continue; + } + $loader->load(sprintf('services/integrations/%s.xml', $driver)); } } + /** + * @param array $resources + * + * @return array + */ + private function getResourceDrivers(array $resources): array + { + $resourceDrivers = array_map(function (array $resource): string|false { + return $resource['driver'] ?? false; + }, $resources); + + // Remove resources with disabled driver + return array_filter($resourceDrivers, function (string|false $driver): bool { + return false !== $driver; + }); + } + + private function getAvailableDrivers(ContainerBuilder $container): array + { + $availableDrivers = []; + + if ($container::willBeAvailable(SyliusResourceBundle::DRIVER_DOCTRINE_ORM, \Doctrine\ORM\EntityManagerInterface::class, ['doctrine/doctrine-bundle'])) { + $availableDrivers[] = SyliusResourceBundle::DRIVER_DOCTRINE_ORM; + } + + if ($container::willBeAvailable(SyliusResourceBundle::DRIVER_DOCTRINE_PHPCR_ODM, \Doctrine\ODM\PHPCR\Document\Resource::class, ['doctrine/doctrine-bundle'])) { + $availableDrivers[] = SyliusResourceBundle::DRIVER_DOCTRINE_PHPCR_ODM; + } + + if ($container::willBeAvailable(SyliusResourceBundle::DRIVER_DOCTRINE_MONGODB_ODM, \Doctrine\ODM\MongoDB\DocumentManager::class, ['doctrine/doctrine-bundle'])) { + $availableDrivers[] = SyliusResourceBundle::DRIVER_DOCTRINE_MONGODB_ODM; + } + + return $availableDrivers; + } + + /** + * @param string[] $configuredDrivers + * @param string[] $availableDrivers + * @param array $resourceDrivers + */ + private function checkConfiguredDrivers(array $configuredDrivers, array $availableDrivers, array $resourceDrivers): void + { + foreach ($configuredDrivers as $driver) { + if (!in_array($driver, $availableDrivers, true)) { + throw new InvalidArgumentException(sprintf( + 'Driver "%s" is configured, but this driver is not available. Try running "composer require %s"', + $driver, + $driver, + )); + } + } + + foreach ($resourceDrivers as $resource => $driver) { + if (!in_array($driver, $availableDrivers, true)) { + throw new InvalidArgumentException(sprintf( + 'Resource "%s" uses drivers "%s", but this driver is not available. Try running "composer require %s"', + $resource, + $driver, + $driver, + )); + } + + if (!in_array($driver, $configuredDrivers, true)) { + throw new InvalidArgumentException(sprintf( + 'Resource "%s" uses drivers "%s", but this driver is not enabled. Try adding "%s" in sylius_resource.drivers option', + $resource, + $driver, + $driver, + )); + } + } + } + private function loadResources(array $loadedResources, ContainerBuilder $container): void { /** @var array $resources */ diff --git a/tests/Application/composer.lock b/tests/Application/composer.lock new file mode 100644 index 000000000..9e5c7683d --- /dev/null +++ b/tests/Application/composer.lock @@ -0,0 +1,18 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6c791f9c59877e1ca29945be657bcbaa", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/tests/Application/config/packages/test/doctrine.yaml b/tests/Application/config/packages/test/doctrine.yaml new file mode 100644 index 000000000..5c208b673 --- /dev/null +++ b/tests/Application/config/packages/test/doctrine.yaml @@ -0,0 +1,26 @@ +doctrine: + dbal: + driver: "%database_driver%" + path: "%database_path%" + charset: UTF8 + orm: + auto_generate_proxy_classes: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true + mappings: + App: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + BoardGameBlog: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/src/BoardGameBlog/Domain' + prefix: 'App\BoardGameBlog\Domain' + Subscription: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/src/Subscription/Entity' + prefix: 'App\Subscription\Entity' diff --git a/tests/Application/config/packages/test_without_doctrine_orm/sylius_resource.yaml b/tests/Application/config/packages/test_without_doctrine_orm/sylius_resource.yaml new file mode 100644 index 000000000..64821fdc3 --- /dev/null +++ b/tests/Application/config/packages/test_without_doctrine_orm/sylius_resource.yaml @@ -0,0 +1,2 @@ +sylius_resource: + drivers: [] diff --git a/tests/Application/config/services_test.yaml b/tests/Application/config/services_test.yaml new file mode 100644 index 000000000..8639937d1 --- /dev/null +++ b/tests/Application/config/services_test.yaml @@ -0,0 +1,95 @@ +parameters: + database_driver: pdo_sqlite + database_path: "%kernel.project_dir%/config/db.sql" + + locale: en_US + secret: "Three can keep a secret, if two of them are dead." + +services: + # Gets rid of "[error] Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\NotFoundHttpException" + # errors in PHPUnit output when testing 404 errors. + Psr\Log\NullLogger: ~ + logger: '@Psr\Log\NullLogger' + + # Default configuration for services in *this* file + _defaults: + # Automatically injects dependencies in your services + autowire: true + + # Automatically registers your services as commands, event subscribers, etc. + autoconfigure: true + + # Allows optimizing the container by removing unused services; this also means + # fetching services directly from the container via $container->get() won't work + public: false + + # Controllers are imported separately to make sure services can be injected + # as action arguments even if you don't extend any base controller class + App\Controller\: + resource: '../src/Controller' + tags: ['controller.service_arguments'] + + test.translation_locale_provider: + class: Sylius\Component\Resource\Translation\Provider\ImmutableTranslationLocaleProvider + arguments: + - ["pl_PL", "en_US", "de_DE"] + - "en_US" + + test.custom_book_factory: + class: App\Factory\CustomBookFactory + public: true + arguments: + - "%app.model.book.class%" + - "@test.translation_locale_provider" + + test.custom_book_repository: + class: App\Repository\CustomBookRepository + public: true + arguments: + - "@app.repository.book" + + app.form.type.book: + class: App\Form\Type\BookType + arguments: + - '%app.model.book.class%' + tags: ['form.type'] + + app.form.type.science_book: + class: App\Form\Type\ScienceBookType + arguments: + - '%app.model.science_book.class%' + - ['sylius'] + tags: [ 'form.type' ] + + app.form.type.book_translation: + class: App\Form\Type\BookTranslationType + arguments: + - '%app.model.book_translation.class%' + tags: ['form.type'] + + App\Repository\ComicBookRepository: null + App\Repository\LegacyBookRepository: null + + App\Shared\: + resource: '../src/Shared' + + App\BoardGameBlog\: + resource: '../src/BoardGameBlog' + + App\Subscription\: + resource: '../src/Subscription' + + App\Subscription\Factory\SubscriptionFactory: + decorates: 'app.factory.subscription' + + app.service.legacy_autowired_repository: + class: App\Service\LegacyAutowiredRepositoryService + autowire: true + + app.service.legacy_autowired_factory: + class: App\Service\LegacyAutowiredFactoryService + autowire: true + + app.service.legacy_autowired_translatable_factory: + class: App\Service\LegacyAutowiredTranslatableFactoryService + autowire: true diff --git a/tests/Bundle/Configuration/ConfigurationTest.php b/tests/Bundle/Configuration/ConfigurationTest.php index 0eadc817f..54865c590 100644 --- a/tests/Bundle/Configuration/ConfigurationTest.php +++ b/tests/Bundle/Configuration/ConfigurationTest.php @@ -16,15 +16,14 @@ use Matthias\SymfonyConfigTest\PhpUnit\ConfigurationTestCaseTrait; use PHPUnit\Framework\TestCase; use Sylius\Bundle\ResourceBundle\DependencyInjection\Configuration; +use Symfony\Component\Config\Definition\ConfigurationInterface; class ConfigurationTest extends TestCase { use ConfigurationTestCaseTrait; - /** - * @test - */ - public function it_does_not_break_if_not_customized() + /** @test */ + public function it_does_not_break_if_not_customized(): void { $this->assertConfigurationIsValid( [ @@ -33,10 +32,38 @@ public function it_does_not_break_if_not_customized() ); } - /** - * @test - */ - public function it_has_no_default_mapping_paths() + /** @test */ + public function it_has_no_default_drivers(): void + { + $this->assertProcessedConfigurationEquals( + [], + [ + 'drivers' => [], + ], + 'drivers', + ); + } + + /** @test */ + public function its_drivers_can_be_customized(): void + { + $this->assertProcessedConfigurationEquals( + [ + ['drivers' => [ + 'doctrine/orm', + ]], + ], + [ + 'drivers' => [ + 'doctrine/orm', + ], + ], + 'drivers', + ); + } + + /** @test */ + public function it_has_no_default_mapping_paths(): void { $this->assertProcessedConfigurationEquals( [ @@ -51,10 +78,8 @@ public function it_has_no_default_mapping_paths() ); } - /** - * @test - */ - public function its_mapping_paths_can_be_customized() + /** @test */ + public function its_mapping_paths_can_be_customized(): void { $this->assertProcessedConfigurationEquals( [ @@ -73,10 +98,8 @@ public function its_mapping_paths_can_be_customized() ); } - /** - * @test - */ - public function its_default_templates_dir_can_be_customized() + /** @test */ + public function its_default_templates_dir_can_be_customized(): void { $this->assertProcessedConfigurationEquals( [ @@ -93,10 +116,8 @@ public function its_default_templates_dir_can_be_customized() ); } - /** - * @test - */ - public function it_has_default_authorization_checker() + /** @test */ + public function it_has_default_authorization_checker(): void { $this->assertProcessedConfigurationEquals( [ @@ -107,10 +128,8 @@ public function it_has_default_authorization_checker() ); } - /** - * @test - */ - public function its_authorization_checker_can_be_customized() + /** @test */ + public function its_authorization_checker_can_be_customized(): void { $this->assertProcessedConfigurationEquals( [ @@ -121,10 +140,8 @@ public function its_authorization_checker_can_be_customized() ); } - /** - * @test - */ - public function its_authorization_checker_cannot_be_empty() + /** @test */ + public function its_authorization_checker_cannot_be_empty(): void { $this->assertPartialConfigurationIsInvalid( [ @@ -134,7 +151,7 @@ public function its_authorization_checker_cannot_be_empty() ); } - protected function getConfiguration() + protected function getConfiguration(): ConfigurationInterface { return new Configuration(); } diff --git a/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php b/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php index a41af29ce..11a4e2c50 100644 --- a/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php +++ b/tests/Bundle/DependencyInjection/SyliusResourceExtensionTest.php @@ -21,22 +21,20 @@ use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; use Sylius\Bundle\ResourceBundle\Controller\ResourceController; use Sylius\Bundle\ResourceBundle\DependencyInjection\SyliusResourceExtension; +use Sylius\Bundle\ResourceBundle\Doctrine\ResourceMappingDriverChain; use Sylius\Bundle\ResourceBundle\Form\Type\DefaultResourceType; use Sylius\Bundle\ResourceBundle\Tests\DependencyInjection\Dummy\BookWithAliasResource; use Sylius\Bundle\ResourceBundle\Tests\DependencyInjection\Dummy\DummyResource; use Sylius\Bundle\ResourceBundle\Tests\DependencyInjection\Dummy\NoDriverResource; +use Sylius\Resource\Doctrine\Common\State\PersistProcessor; +use Sylius\Resource\Doctrine\Common\State\RemoveProcessor; use Sylius\Resource\Factory\Factory; class SyliusResourceExtensionTest extends AbstractExtensionTestCase { - /** - * @test - */ + /** @test */ public function it_registers_services_and_parameters_for_resources(): void { - // TODO: Move Resource-Grid integration to a dedicated compiler pass - $this->setParameter('kernel.bundles', []); - $this->load([ 'resources' => [ 'app.book' => [ @@ -65,14 +63,9 @@ public function it_registers_services_and_parameters_for_resources(): void $this->assertContainerBuilderHasParameter('app.form.book.class', BookType::class); } - /** - * @test - */ + /** @test */ public function it_aliases_authorization_checker_with_the_one_given_in_configuration(): void { - // TODO: Move Resource-Grid integration to a dedicated compiler pass - $this->setParameter('kernel.bundles', []); - $this->load([ 'authorization_checker' => 'custom_service', ]); @@ -80,14 +73,9 @@ public function it_aliases_authorization_checker_with_the_one_given_in_configura $this->assertContainerBuilderHasAlias('sylius.resource_controller.authorization_checker', 'custom_service'); } - /** - * @test - */ + /** @test */ public function it_registers_default_translation_parameters(): void { - // TODO: Move ResourceGrid integration to a dedicated compiler pass - $this->setParameter('kernel.bundles', []); - $this->load([ 'translation' => [ 'locale_provider' => 'test.custom_locale_provider', @@ -97,13 +85,9 @@ public function it_registers_default_translation_parameters(): void $this->assertContainerBuilderHasAlias('sylius.translation_locale_provider', 'test.custom_locale_provider'); } - /** - * @test - */ + /** @test */ public function it_does_not_break_when_aliasing_two_resources_use_same_factory_class(): void { - // TODO: Move Resource-Grid integration to a dedicated compiler pass - $this->setParameter('kernel.bundles', []); $this->load([ 'resources' => [ 'app.book' => [ @@ -127,12 +111,9 @@ public function it_does_not_break_when_aliasing_two_resources_use_same_factory_c $this->assertContainerBuilderHasAlias(sprintf('%s $comicBookFactory', BookFactory::class), 'app.factory.comic_book'); } - /** - * @test - */ + /** @test */ public function it_registers_parameter_for_paths(): void { - $this->setParameter('kernel.bundles', []); $this->load([ 'mapping' => [ 'paths' => [ @@ -148,12 +129,9 @@ public function it_registers_parameter_for_paths(): void ]); } - /** - * @test - */ + /** @test */ public function it_auto_registers_resources(): void { - $this->setParameter('kernel.bundles', []); $this->load([ 'mapping' => [ 'paths' => [ @@ -193,8 +171,20 @@ public function it_auto_registers_resources(): void ]); } + /** @test */ + public function it_registers_doctrine_related_services_when_doctrine_is_available(): void + { + $this->load(); + + $this->assertContainerBuilderHasService(ResourceMappingDriverChain::class); + $this->assertContainerBuilderHasService(PersistProcessor::class); + $this->assertContainerBuilderHasService(RemoveProcessor::class); + } + protected function getContainerExtensions(): array { + $this->setParameter('kernel.bundles', []); + return [ new SyliusResourceExtension(), ];