diff --git a/UPGRADE-3.x.md b/UPGRADE-3.x.md index da352bbe0..b22fb4cd6 100644 --- a/UPGRADE-3.x.md +++ b/UPGRADE-3.x.md @@ -1,6 +1,14 @@ UPGRADE 3.x =========== +UPGRADE FROM 3.x to 3.x +========================= + +### SonataEasyExtends is deprecated + +Registering `SonataEasyExtendsBundle` bundle is deprecated, it SHOULD NOT be registered. +Register `SonataDoctrineBundle` bundle instead. + UPGRADE FROM 3.3 to 3.4 ======================= @@ -13,8 +21,8 @@ UPGRADE FROM 3.1 to 3.2 ### Tests -All files under the ``Tests`` directory are now correctly handled as internal test classes. -You can't extend them anymore, because they are only loaded when running internal tests. +All files under the ``Tests`` directory are now correctly handled as internal test classes. +You can't extend them anymore, because they are only loaded when running internal tests. More information can be found in the [composer docs](https://getcomposer.org/doc/04-schema.md#autoload-dev). ### Deprecations diff --git a/composer.json b/composer.json index 5f68e0ad7..605495883 100644 --- a/composer.json +++ b/composer.json @@ -30,9 +30,8 @@ "sonata-project/cache": "^1.0.3 || ^2.0", "sonata-project/cache-bundle": "^2.4.1", "sonata-project/datagrid-bundle": "^2.5", - "sonata-project/doctrine-extensions": "^1.5", + "sonata-project/doctrine-extensions": "^1.8", "sonata-project/doctrine-orm-admin-bundle": "^3.19", - "sonata-project/easy-extends-bundle": "^2.5", "sonata-project/form-extensions": "^0.1.1 || ^1.4", "sonata-project/notification-bundle": "^3.8", "sonata-project/seo-bundle": "^2.11", diff --git a/docs/reference/advanced_configuration.rst b/docs/reference/advanced_configuration.rst index ca0a5dce3..b6a00a645 100644 --- a/docs/reference/advanced_configuration.rst +++ b/docs/reference/advanced_configuration.rst @@ -114,11 +114,11 @@ Full configuration options: # Prototype id: ~ class: - page: Application\Sonata\PageBundle\Entity\Page - snapshot: Application\Sonata\PageBundle\Entity\Snapshot - block: Application\Sonata\PageBundle\Entity\Block - site: Application\Sonata\PageBundle\Entity\Site - direct_publication: false + page: App\Entity\SonataPagePage + snapshot: App\Entity\SonataPageSnapshot + block: App\Entity\SonataPageBlock + site: App\Entity\SonataPageSite + direct_publication: false .. code-block:: yaml @@ -130,7 +130,6 @@ Full configuration options: entity_managers: default: mappings: - ApplicationSonataPageBundle: ~ SonataPageBundle: ~ .. _`here`: https://sonata-project.org/bundles/page diff --git a/docs/reference/installation.rst b/docs/reference/installation.rst index 271a1c0d0..8dd7c52a8 100644 --- a/docs/reference/installation.rst +++ b/docs/reference/installation.rst @@ -1,19 +1,30 @@ +.. index:: + single: Installation + single: Configuration + Installation ============ Prerequisites ------------- -PHP 7.2 and Symfony >=4.3 are needed to make this bundle work, there are -also some Sonata dependencies that need to be installed and configured beforehand: +PHP ^7.2 and Symfony ^4.4 are needed to make this bundle work, there are +also some Sonata dependencies that need to be installed and configured beforehand. + +Required dependencies: + +* `SonataAdminBundle `_ +* `SonataBlockBundle_ `_ +* `SonataCacheBundle_ `_ +* `SonataSeoBundle_ `_ +* `SonataNotificationBundle_ `_ + +And the persistence bundle: - - SonataAdminBundle_ - - SonataDoctrineORMAdminBundle_ - - SonataBlockBundle_ - - SonataCacheBundle_ - - SonataSeoBundle_ - - SonataEasyExtendsBundle_ - - SonataNotificationBundle_ +* `SonataDoctrineOrmAdminBundle `_ + +Follow also their configuration step; you will find everything you need in +their own installation chapter. .. note:: @@ -23,20 +34,19 @@ also some Sonata dependencies that need to be installed and configured beforehan Enable the Bundle ----------------- -Add the dependant bundles to the vendor/bundles directory: +Add ``SonataPageBundle`` via composer:: + + composer require sonata-project/page-bundle -.. code-block:: bash +.. note:: - composer require sonata-project/page-bundle --no-update - composer require sonata-project/doctrine-orm-admin-bundle --no-update + This will install the SymfonyCmfRoutingBundle_, too. - # optional when using API - composer require friendsofsymfony/rest-bundle --no-update - composer require nelmio/api-doc-bundle --no-update +If you want to use the REST API, you also need ``friendsofsymfony/rest-bundle`` and ``nelmio/api-doc-bundle``:: - composer update + composer require friendsofsymfony/rest-bundle nelmio/api-doc-bundle -Next, be sure to enable the bundles in your ``bundles.php`` file if they +Next, be sure to enable the bundles in your ``config/bundles.php`` file if they are not already enabled:: // config/bundles.php @@ -44,31 +54,13 @@ are not already enabled:: return [ // ... Sonata\PageBundle\SonataPageBundle::class => ['all' => true], - Sonata\EasyExtendsBundle\SonataEasyExtendsBundle::class => ['all' => true], ]; Configuration -------------- - -Doctrine Configuration -~~~~~~~~~~~~~~~~~~~~~~ - -Add these bundles in the config mapping definition (or enable `auto_mapping`_): - -.. code-block:: yaml - - # config/packages/doctrine.yaml - - doctrine: - orm: - entity_managers: - default: - mappings: - ApplicationSonataPageBundle: ~ # only once the ApplicationSonataPageBundle is generated - SonataPageBundle: ~ +============= CMF Routing Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------- ``sonata.page.router`` service must be added to the index of ``cmf_routing.router`` chain router. @@ -99,20 +91,26 @@ Or register ``sonata.page.router`` automatically: priority: 150 SonataPageBundle Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------ .. code-block:: yaml # config/packages/sonata_page.yaml sonata_page: - slugify_service: sonata.core.slugify.cocur # old BC value is sonata.core.slugify.native + slugify_service: sonata.core.slugify.cocur # old BC value is sonata.core.slugify.native multisite: host use_streamed_response: true # set the value to false in debug mode or if the reverse proxy does not handle streamed response ignore_route_patterns: - ^(.*)admin(.*) # ignore admin route, ie route containing 'admin' - ^_(.*) # ignore symfony routes + class: + page: App\Entity\SonataPagePage + snapshot: App\Entity\SonataPageSnapshot + block: App\Entity\SonataPageBlock + site: App\Entity\SonataPageSite + ignore_routes: - sonata_page_cache_esi - sonata_page_cache_ssi @@ -143,7 +141,7 @@ SonataPageBundle Configuration fatal: [500] # so you can use the same page for different http errors or specify specific page for each error SonataAdminBundle Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------- .. code-block:: yaml @@ -157,7 +155,7 @@ SonataAdminBundle Configuration - bundles/sonatapage/sonata-page.back.min.css SonataBlockBundle Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------- .. code-block:: yaml @@ -171,7 +169,7 @@ SonataBlockBundle Configuration Please you need to use the context ``sonata_page_bundle`` in the SonataBlockBundle to add block into a Page. Security Configuration -~~~~~~~~~~~~~~~~~~~~~~ +---------------------- .. code-block:: yaml @@ -201,7 +199,7 @@ this logout handler: handlers: ['sonata.page.cms_manager_selector'] Routing Configuration -~~~~~~~~~~~~~~~~~~~~~ +--------------------- .. code-block:: yaml @@ -215,62 +213,120 @@ Routing Configuration resource: '@SonataPageBundle/Resources/config/routing/cache.xml' prefix: / -Extend the Bundle ------------------ - -At this point, the bundle is usable, but not quite ready yet. You need to -generate the correct entities for the page: +Doctrine ORM Configuration +-------------------------- -.. code-block:: bash +And these in the config mapping definition (or enable `auto_mapping`_):: - bin/console sonata:easy-extends:generate SonataPageBundle --dest=src --namespace_prefix=App - -With provided parameters, the files are generated in ``src/Application/Sonata/PageBundle``. - -.. note:: - - The command will generate domain objects in an ``App\Application`` namespace. - So you can point entities' associations to a global and common namespace. - This will make Entities sharing easier as your models will allow to - point to a global namespace. For instance the page will be - ``App\Application\Sonata\PageBundle\Entity\Page``. - -Now, add the new ``Application`` Bundle into the ``bundles.php``:: - - // config/bundles.php - - return [ - // ... - App\Application\Sonata\PageBundle\ApplicationSonataPageBundle::class => ['all' => true], - ]; - -Configure SonataPageBundle to use the newly generated classes: + # config/packages/doctrine.yaml -.. code-block:: yaml + doctrine: + orm: + entity_managers: + default: + mappings: + SonataPageBundle: ~ - # config/packages/sonata_page.yaml +And then create the corresponding entities, ``src/Entity/SonataPageBlock``:: + + // src/Entity/SonataPageBlock.php + + use Doctrine\ORM\Mapping as ORM; + use Sonata\PageBundle\Entity\BaseBlock; + + /** + * @ORM\Entity + * @ORM\Table(name="page__block") + */ + class SonataPageBlock extends BaseBlock + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + protected $id; + } + +``src/Entity/SonataPagePage``:: + + // src/Entity/SonataPagePage.php + + use Doctrine\ORM\Mapping as ORM; + use Sonata\PageBundle\Entity\BasePage; + + /** + * @ORM\Entity + * @ORM\Table(name="page__page") + */ + class SonataPagePage extends BasePage + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + protected $id; + } + +``src/Entity/SonataPageSite``:: + + // src/Entity/SonataPageSite.php + + use Doctrine\ORM\Mapping as ORM; + use Sonata\PageBundle\Entity\BaseSite; + + /** + * @ORM\Entity + * @ORM\Table(name="page__site") + */ + class SonataPageSite extends BaseSite + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + protected $id; + } + +and ``src/Entity/SonataPageSnapshot``:: + + // src/Entity/SonataPageSnapshot.php + + use Doctrine\ORM\Mapping as ORM; + use Sonata\PageBundle\Entity\BaseSnapshot; + + /** + * @ORM\Entity + * @ORM\Table(name="page__snapshot") + */ + class SonataPageSnapshot extends BaseSnapshot + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + protected $id; + } + +The only thing left is to update your schema:: - sonata_page: - class: - page: App\Application\Sonata\PageBundle\Entity\Page # This is an optional value - snapshot: App\Application\Sonata\PageBundle\Entity\Snapshot - block: App\Application\Sonata\PageBundle\Entity\Block - site: App\Application\Sonata\PageBundle\Entity\Site + bin/console doctrine:schema:update --force -The only thing left is to update your schema: +Next Steps +---------- -.. code-block:: bash +At this point, your Symfony installation should be fully functional, without errors +showing up from SonataPageBundle. If, at this point or during the installation, +you come across any errors, don't panic: - bin/console doctrine:schema:update --force + - Read the error message carefully. Try to find out exactly which bundle is causing the error. + Is it SonataPageBundle or one of the dependencies? + - Make sure you followed all the instructions correctly, for both SonataPageBundle and its dependencies. + - Still no luck? Try checking the project's `open issues on GitHub`_. -.. _SonataAdminBundle: https://sonata-project.org/bundles/admin -.. _SonataDoctrineORMAdminBundle: https://sonata-project.org/bundles/doctrine-orm-admin -.. _SonataBlockBundle: https://sonata-project.org/bundles/block -.. _SonataCacheBundle: https://sonata-project.org/bundles/cache -.. _SonataSeoBundle: https://sonata-project.org/bundles/seo -.. _SonataEasyExtendsBundle: https://sonata-project.org/bundles/easy-extends -.. _SonataNotificationBundle: https://sonata-project.org/bundles/notification -.. _EasyExtendsBundle: https://sonata-project.org/bundles/easy-extends/master/doc/index.html +.. _`open issues on GitHub`: https://github.com/sonata-project/SonataPageBundle/issues .. _SymfonyCmfRoutingBundle: https://github.com/symfony-cmf/RoutingBundle -.. _SymfonyCmfRoutingExtraBundle: https://github.com/symfony-cmf/RoutingExtraBundle .. _auto_mapping: http://symfony.com/doc/2.0/reference/configuration/doctrine.html#configuration-overview diff --git a/src/DependencyInjection/SonataPageExtension.php b/src/DependencyInjection/SonataPageExtension.php index 491b00481..45cd6dfa1 100644 --- a/src/DependencyInjection/SonataPageExtension.php +++ b/src/DependencyInjection/SonataPageExtension.php @@ -13,7 +13,9 @@ namespace Sonata\PageBundle\DependencyInjection; -use Sonata\EasyExtendsBundle\Mapper\DoctrineCollector; +use Sonata\Doctrine\Mapper\Builder\OptionsBuilder; +use Sonata\Doctrine\Mapper\DoctrineCollector; +use Sonata\EasyExtendsBundle\Mapper\DoctrineCollector as DeprecatedDoctrineCollector; use Sonata\PageBundle\Model\Template; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Config\FileLocator; @@ -111,7 +113,13 @@ public function load(array $configs, ContainerBuilder $container) ->replaceArgument(1, $config['ignore_route_patterns']) ->replaceArgument(2, $config['ignore_uri_patterns']); - $this->registerDoctrineMapping($config); + if (isset($bundles['SonataDoctrineBundle'])) { + $this->registerSonataDoctrineMapping($config); + } else { + // NEXT MAJOR: Remove this line and throw error when not registering SonataDoctrineBundle + $this->registerDoctrineMapping($config); + } + $this->registerParameters($container, $config); } @@ -168,15 +176,22 @@ public function registerParameters(ContainerBuilder $container, array $config) } /** + * NEXT_MAJOR: Remove this method. + * * Registers doctrine mapping on concrete page entities. */ public function registerDoctrineMapping(array $config) { + @trigger_error( + 'Using SonataEasyExtendsBundle is deprecated since sonata-project/page-bundle 3.x. Please register SonataDoctrineBundle as a bundle instead.', + E_USER_DEPRECATED + ); + if (!class_exists($config['class']['page'])) { return; } - $collector = DoctrineCollector::getInstance(); + $collector = DeprecatedDoctrineCollector::getInstance(); $collector->addAssociation($config['class']['page'], 'mapOneToMany', [ 'fieldName' => 'children', @@ -517,4 +532,141 @@ public function configurePageServices(ContainerBuilder $container, array $config $definition = $container->getDefinition('sonata.page.page_service_manager'); $definition->addMethodCall('setDefault', [new Reference($config['default_page_service'])]); } + + private function registerSonataDoctrineMapping(array $config): void + { + if (!class_exists($config['class']['page'])) { + return; + } + + $collector = DoctrineCollector::getInstance(); + + $collector->addAssociation( + $config['class']['page'], + 'mapOneToMany', + OptionsBuilder::createOneToMany('children', $config['class']['page']) + ->cascade(['persist']) + ->mappedBy('parent') + ->addOrder('position', 'ASC') + ); + + $collector->addAssociation( + $config['class']['page'], + 'mapOneToMany', + OptionsBuilder::createOneToMany('blocks', $config['class']['block']) + ->cascade(['remove', 'persist', 'refresh', 'merge', 'detach']) + ->mappedBy('page') + ->addOrder('position', 'ASC') + ); + + $collector->addAssociation( + $config['class']['page'], + 'mapManyToOne', + OptionsBuilder::createManyToOne('site', $config['class']['site']) + ->cascade(['persist']) + ->addJoin([ + 'name' => 'site_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + ]) + ); + + $collector->addAssociation( + $config['class']['page'], + 'mapManyToOne', + OptionsBuilder::createManyToOne('parent', $config['class']['page']) + ->cascade(['persist']) + ->inversedBy('children') + ->addJoin([ + 'name' => 'parent_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + ]) + ); + + $collector->addAssociation( + $config['class']['page'], + 'mapOneToMany', + OptionsBuilder::createOneToMany('sources', $config['class']['page']) + ->mappedBy('target') + ); + + $collector->addAssociation( + $config['class']['page'], + 'mapManyToOne', + OptionsBuilder::createManyToOne('target', $config['class']['page']) + ->cascade(['persist']) + ->inversedBy('sources') + ->addJoin([ + 'name' => 'target_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + ]) + ); + + $collector->addAssociation( + $config['class']['block'], + 'mapOneToMany', + OptionsBuilder::createOneToMany('children', $config['class']['block']) + ->cascade(['remove', 'persist']) + ->mappedBy('parent') + ->orphanRemoval() + ->addOrder('position', 'ASC') + ); + + $collector->addAssociation( + $config['class']['block'], + 'mapManyToOne', + OptionsBuilder::createManyToOne('parent', $config['class']['block']) + ->inversedBy('children') + ->addJoin([ + 'name' => 'parent_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + ]) + ); + + $collector->addAssociation( + $config['class']['block'], + 'mapManyToOne', + OptionsBuilder::createManyToOne('page', $config['class']['page']) + ->cascade(['persist']) + ->inversedBy('blocks') + ->addJoin([ + 'name' => 'page_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + ]) + ); + + $collector->addAssociation( + $config['class']['snapshot'], + 'mapManyToOne', + OptionsBuilder::createManyToOne('site', $config['class']['site']) + ->cascade(['persist']) + ->addJoin([ + 'name' => 'site_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + ]) + ); + + $collector->addAssociation( + $config['class']['snapshot'], + 'mapManyToOne', + OptionsBuilder::createManyToOne('page', $config['class']['page']) + ->cascade(['persist']) + ->addJoin([ + 'name' => 'page_id', + 'referencedColumnName' => 'id', + 'onDelete' => 'CASCADE', + ]) + ); + + $collector->addIndex($config['class']['snapshot'], 'idx_snapshot_dates_enabled', [ + 'publication_date_start', + 'publication_date_end', + 'enabled', + ]); + } } diff --git a/tests/App/AppKernel.php b/tests/App/AppKernel.php index 1efbbed53..57e4b2987 100644 --- a/tests/App/AppKernel.php +++ b/tests/App/AppKernel.php @@ -19,7 +19,7 @@ use Sonata\BlockBundle\SonataBlockBundle; use Sonata\CacheBundle\SonataCacheBundle; use Sonata\CoreBundle\SonataCoreBundle; -use Sonata\Doctrine\Bridge\Symfony\Bundle\SonataDoctrineBundle; +use Sonata\Doctrine\Bridge\Symfony\SonataDoctrineBundle; use Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle; use Sonata\Form\Bridge\Symfony\SonataFormBundle; use Sonata\NotificationBundle\SonataNotificationBundle; diff --git a/tests/DependencyInjection/Compiler/CmfRouterAutoRegisterTest.php b/tests/DependencyInjection/Compiler/CmfRouterAutoRegisterTest.php index 77262a665..4dc8ff83b 100644 --- a/tests/DependencyInjection/Compiler/CmfRouterAutoRegisterTest.php +++ b/tests/DependencyInjection/Compiler/CmfRouterAutoRegisterTest.php @@ -38,7 +38,9 @@ public function testRouterAutoRegister($enabled, $priority): void if ('add' !== $method) { continue; } + [$reference, $weight] = $arguments; + if ($reference instanceof Reference && 'sonata.page.router' === $reference->__toString()) { if ($enabled) { $this->assertSame($priority, $weight); @@ -47,6 +49,10 @@ public function testRouterAutoRegister($enabled, $priority): void $this->fail('"sonata.page.router" service should not be auto registered'); } } + + if (0 === \count($router->getMethodCalls())) { + $this->assertFalse($enabled); + } } public function providerRoutedAutoRegister(): array diff --git a/tests/DependencyInjection/SonataPageExtensionTest.php b/tests/DependencyInjection/SonataPageExtensionTest.php index c7b3a749a..86c792749 100644 --- a/tests/DependencyInjection/SonataPageExtensionTest.php +++ b/tests/DependencyInjection/SonataPageExtensionTest.php @@ -24,7 +24,7 @@ class SonataPageExtensionTest extends AbstractExtensionTestCase { public function testRequestContextServiceIsDefined(): void { - $this->container->setParameter('kernel.bundles', []); + $this->container->setParameter('kernel.bundles', ['SonataDoctrineBundle' => true]); $this->load(); $this->assertContainerBuilderHasService('sonata.page.router.request_context'); } @@ -32,8 +32,9 @@ public function testRequestContextServiceIsDefined(): void public function testApiServicesAreDefinedWhenSpecificBundlesArePresent(): void { $this->container->setParameter('kernel.bundles', [ - 'FOSRestBundle' => 42, - 'NelmioApiDocBundle' => 42, + 'FOSRestBundle' => true, + 'NelmioApiDocBundle' => true, + 'SonataDoctrineBundle' => true, ]); $this->load(); $this->assertContainerBuilderHasService('sonata.page.serializer.handler.page'); @@ -42,7 +43,8 @@ public function testApiServicesAreDefinedWhenSpecificBundlesArePresent(): void public function testAdminServicesAreDefinedWhenAdminBundlesIsPresent(): void { $this->container->setParameter('kernel.bundles', [ - 'SonataAdminBundle' => 42, + 'SonataAdminBundle' => true, + 'SonataDoctrineBundle' => true, ]); $this->load(); $this->assertContainerBuilderHasService('sonata.page.admin.page'); @@ -51,7 +53,8 @@ public function testAdminServicesAreDefinedWhenAdminBundlesIsPresent(): void public function testRouterAutoRegister(): void { $this->container->setParameter('kernel.bundles', [ - 'CmfRouterBundle' => 42, + 'CmfRouterBundle' => true, + 'SonataDoctrineBundle' => true, ]); $this->load([ 'router_auto_register' => [ @@ -66,8 +69,9 @@ public function testRouterAutoRegister(): void public function testDatePickerFormThemeFromSonataCore(): void { $this->container->setParameter('kernel.bundles', [ - 'SonataCoreBundle' => 'SonataCoreBundle', - 'SonataFormBundle' => 'SonataFormBundle', + 'SonataCoreBundle' => true, + 'SonataFormBundle' => true, + 'SonataDoctrineBundle' => true, ]); $this->container->setParameter('kernel.bundles_metadata', []); $this->container->setParameter('kernel.project_dir', __DIR__); @@ -84,7 +88,10 @@ public function testDatePickerFormThemeFromSonataCore(): void public function testDatePickerFormThemeFromSonataForm(): void { - $this->container->setParameter('kernel.bundles', ['SonataFormBundle' => 'SonataFormBundle']); + $this->container->setParameter('kernel.bundles', [ + 'SonataFormBundle' => true, + 'SonataDoctrineBundle' => true, + ]); $this->container->setParameter('kernel.bundles_metadata', []); $this->container->setParameter('kernel.project_dir', __DIR__); $this->container->setParameter('kernel.root_dir', __DIR__);