diff --git a/composer.json b/composer.json index 88e9fd628..960018bc6 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", @@ -89,6 +89,7 @@ "willdurand/hateoas-bundle": "^2.0" }, "conflict": { + "doctrine/doctrine-bundle": "<2.0 || ^3.0", "friendsofsymfony/rest-bundle": "<3.0", "jms/serializer-bundle": "<3.5", "willdurand/hateoas-bundle": "<2.0 || ^2.6", @@ -102,7 +103,8 @@ "allow-plugins": { "symfony/flex": true, "dealerdirect/phpcodesniffer-composer-installer": false - } + }, + "sort-packages": true }, "extra": { "symfony": { diff --git a/phpstan.neon b/phpstan.neon index e2780adb2..46baf608b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -45,6 +45,8 @@ parameters: - '/Call to an undefined method ReflectionClass::getAttributes\(\)./' - '/Call to an undefined method object::getRealClassName\(\)./' - '/Class Bazinga\\Bundle\\HateoasBundle\\BazingaHateoasBundle not found\./' + - '/Class Doctrine\\ODM\\MongoDB\\DocumentManager not found\./' + - '/Class Doctrine\\ODM\\PHPCR\\Document\\Resource not found\./' - '/Class Doctrine\\Bundle\\MongoDBBundle/' - '/Class Doctrine\\Bundle\\PHPCRBundle/' - '/Class Doctrine\\Common\\Persistence\\ObjectManager not found\./' diff --git a/psalm.xml b/psalm.xml index 879f534e7..38d131068 100644 --- a/psalm.xml +++ b/psalm.xml @@ -264,6 +264,8 @@ + + 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..ba3c013c6 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/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(), ];