From e229c44352bb210d414c1e6868c7274f963c071e Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Wed, 6 Oct 2021 18:19:41 +0200 Subject: [PATCH] Refractor dimension content collection behaviour --- .../ContentDataMapper/ContentDataMapper.php | 11 +-- .../DataMapper/RoutableDataMapper.php | 1 - .../ContentManager/ContentManager.php | 5 +- .../ContentManagerInterface.php | 5 +- .../ContentPersister/ContentPersister.php | 29 +++--- .../ContentPersisterInterface.php | 4 +- .../DimensionContentCollectionFactory.php | 98 ++++--------------- ...nsionContentCollectionFactoryInterface.php | 3 +- .../Model/ContentRichEntityInterface.php | 1 + .../Model/DimensionContentCollection.php | 94 +++++++++++++++--- .../DimensionContentCollectionInterface.php | 7 ++ Resources/config/services.xml | 4 +- UPGRADE.md | 16 +++ 13 files changed, 148 insertions(+), 130 deletions(-) diff --git a/Content/Application/ContentDataMapper/ContentDataMapper.php b/Content/Application/ContentDataMapper/ContentDataMapper.php index d654912b..7ce53d2a 100644 --- a/Content/Application/ContentDataMapper/ContentDataMapper.php +++ b/Content/Application/ContentDataMapper/ContentDataMapper.php @@ -39,13 +39,10 @@ public function map( $localizedDimensionAttributes = $dimensionAttributes; $unlocalizedDimensionAttributes = $dimensionAttributes; $unlocalizedDimensionAttributes['locale'] = null; - $unlocalizedDimensionContent = $dimensionContentCollection->getDimensionContent($unlocalizedDimensionAttributes); - $localizedDimensionContent = $dimensionContentCollection->getDimensionContent($localizedDimensionAttributes); - - if (!$unlocalizedDimensionContent || !$localizedDimensionContent) { - // TODO see https://github.com/sulu/SuluContentBundle/pull/204 - throw new \RuntimeException('Create unlocalized and localized dimension content.'); - } + $unlocalizedDimensionContent = $dimensionContentCollection->getDimensionContent($unlocalizedDimensionAttributes) + ?: $dimensionContentCollection->createDimensionContent($unlocalizedDimensionAttributes); + $localizedDimensionContent = $dimensionContentCollection->getDimensionContent($localizedDimensionAttributes) + ?: $dimensionContentCollection->createDimensionContent($localizedDimensionAttributes); foreach ($this->dataMappers as $mapper) { $mapper->map($unlocalizedDimensionContent, $localizedDimensionContent, $data); diff --git a/Content/Application/ContentDataMapper/DataMapper/RoutableDataMapper.php b/Content/Application/ContentDataMapper/DataMapper/RoutableDataMapper.php index fc656d20..08f43524 100644 --- a/Content/Application/ContentDataMapper/DataMapper/RoutableDataMapper.php +++ b/Content/Application/ContentDataMapper/DataMapper/RoutableDataMapper.php @@ -117,7 +117,6 @@ public function map( $entityClass = null; $routeSchema = null; $resourceKey = $localizedDimensionContent::getResourceKey(); - foreach ($this->routeMappings as $key => $mapping) { if ($resourceKey === $mapping['resource_key']) { $entityClass = $mapping['entityClass'] ?? $key; diff --git a/Content/Application/ContentManager/ContentManager.php b/Content/Application/ContentManager/ContentManager.php index b78c6051..77a400c8 100644 --- a/Content/Application/ContentManager/ContentManager.php +++ b/Content/Application/ContentManager/ContentManager.php @@ -20,6 +20,7 @@ use Sulu\Bundle\ContentBundle\Content\Application\ContentResolver\ContentResolverInterface; use Sulu\Bundle\ContentBundle\Content\Application\ContentWorkflow\ContentWorkflowInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentCollectionInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface; class ContentManager implements ContentManagerInterface @@ -75,9 +76,9 @@ public function resolve(ContentRichEntityInterface $contentRichEntity, array $di return $this->contentResolver->resolve($contentRichEntity, $dimensionAttributes); } - public function persist(ContentRichEntityInterface $contentRichEntity, array $data, array $dimensionAttributes): DimensionContentInterface + public function persist(ContentRichEntityInterface $contentRichEntity, array $dimensionAttributes, array $data): DimensionContentCollectionInterface { - return $this->contentPersister->persist($contentRichEntity, $data, $dimensionAttributes); + return $this->contentPersister->persist($contentRichEntity, $dimensionAttributes, $data); } public function normalize(DimensionContentInterface $dimensionContent): array diff --git a/Content/Application/ContentManager/ContentManagerInterface.php b/Content/Application/ContentManager/ContentManagerInterface.php index f40bb01b..df6e744d 100644 --- a/Content/Application/ContentManager/ContentManagerInterface.php +++ b/Content/Application/ContentManager/ContentManagerInterface.php @@ -14,6 +14,7 @@ namespace Sulu\Bundle\ContentBundle\Content\Application\ContentManager; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentCollectionInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface; interface ContentManagerInterface @@ -24,10 +25,10 @@ interface ContentManagerInterface public function resolve(ContentRichEntityInterface $contentRichEntity, array $dimensionAttributes): DimensionContentInterface; /** - * @param mixed[] $data * @param mixed[] $dimensionAttributes + * @param mixed[] $data */ - public function persist(ContentRichEntityInterface $contentRichEntity, array $data, array $dimensionAttributes): DimensionContentInterface; + public function persist(ContentRichEntityInterface $contentRichEntity, array $dimensionAttributes, array $data): DimensionContentCollectionInterface; /** * @return mixed[] diff --git a/Content/Application/ContentPersister/ContentPersister.php b/Content/Application/ContentPersister/ContentPersister.php index c06317aa..ca98d733 100644 --- a/Content/Application/ContentPersister/ContentPersister.php +++ b/Content/Application/ContentPersister/ContentPersister.php @@ -13,10 +13,10 @@ namespace Sulu\Bundle\ContentBundle\Content\Application\ContentPersister; -use Sulu\Bundle\ContentBundle\Content\Application\ContentMerger\ContentMergerInterface; +use Sulu\Bundle\ContentBundle\Content\Application\ContentDataMapper\ContentDataMapperInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Factory\DimensionContentCollectionFactoryInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface; -use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentCollectionInterface; class ContentPersister implements ContentPersisterInterface { @@ -26,34 +26,27 @@ class ContentPersister implements ContentPersisterInterface private $dimensionContentCollectionFactory; /** - * @var ContentMergerInterface + * @var ContentDataMapperInterface */ - private $contentMerger; + private $contentDataMapper; public function __construct( DimensionContentCollectionFactoryInterface $dimensionContentCollectionFactory, - ContentMergerInterface $contentMerger + ContentDataMapperInterface $contentDataMapper ) { $this->dimensionContentCollectionFactory = $dimensionContentCollectionFactory; - $this->contentMerger = $contentMerger; + $this->contentDataMapper = $contentDataMapper; } - public function persist(ContentRichEntityInterface $contentRichEntity, array $data, array $dimensionAttributes): DimensionContentInterface + public function persist(ContentRichEntityInterface $contentRichEntity, array $dimensionAttributes, array $data): DimensionContentCollectionInterface { - /* - * Data should always be persisted to the STAGE_DRAFT content-dimension of the given $dimensionAttributes. - * Modifying data of other content-dimensions (eg. STAGE_LIVE) should only be possible by applying transitions - * of the ContentWorkflow. - * - * TODO: maybe throw an exception here if the $dimensionAttributes contain another stage than 'STAGE_DRAFT' - */ - $dimensionContentCollection = $this->dimensionContentCollectionFactory->create( $contentRichEntity, - $dimensionAttributes, - $data + $dimensionAttributes ); - return $this->contentMerger->merge($dimensionContentCollection); + $this->contentDataMapper->map($dimensionContentCollection, $dimensionAttributes, $data); + + return $dimensionContentCollection; } } diff --git a/Content/Application/ContentPersister/ContentPersisterInterface.php b/Content/Application/ContentPersister/ContentPersisterInterface.php index 0cfea457..8793b21d 100644 --- a/Content/Application/ContentPersister/ContentPersisterInterface.php +++ b/Content/Application/ContentPersister/ContentPersisterInterface.php @@ -14,7 +14,7 @@ namespace Sulu\Bundle\ContentBundle\Content\Application\ContentPersister; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface; -use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface; +use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentCollectionInterface; interface ContentPersisterInterface { @@ -22,5 +22,5 @@ interface ContentPersisterInterface * @param mixed[] $data * @param mixed[] $dimensionAttributes */ - public function persist(ContentRichEntityInterface $contentRichEntity, array $data, array $dimensionAttributes): DimensionContentInterface; + public function persist(ContentRichEntityInterface $contentRichEntity, array $dimensionAttributes, array $data): DimensionContentCollectionInterface; } diff --git a/Content/Application/DimensionContentCollectionFactory/DimensionContentCollectionFactory.php b/Content/Application/DimensionContentCollectionFactory/DimensionContentCollectionFactory.php index 18964c14..980e6ee4 100644 --- a/Content/Application/DimensionContentCollectionFactory/DimensionContentCollectionFactory.php +++ b/Content/Application/DimensionContentCollectionFactory/DimensionContentCollectionFactory.php @@ -13,115 +13,51 @@ namespace Sulu\Bundle\ContentBundle\Content\Application\DimensionContentCollectionFactory; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; -use Sulu\Bundle\ContentBundle\Content\Application\ContentDataMapper\ContentDataMapperInterface; +use Sulu\Bundle\ContentBundle\Content\Application\ContentMerger\Merger\MergerInterface; +use Sulu\Bundle\ContentBundle\Content\Application\ContentMetadataInspector\ContentMetadataInspectorInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Factory\DimensionContentCollectionFactoryInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface; use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentCollection; use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentCollectionInterface; -use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface; -use Sulu\Bundle\ContentBundle\Content\Domain\Repository\DimensionContentRepositoryInterface; use Symfony\Component\PropertyAccess\PropertyAccessor; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; class DimensionContentCollectionFactory implements DimensionContentCollectionFactoryInterface { /** - * @var DimensionContentRepositoryInterface + * @var ContentMetadataInspectorInterface */ - private $dimensionContentRepository; + private $contentMetadataInspector; /** - * @var ContentDataMapperInterface + * @var iterable */ - private $contentDataMapper; + private $mergers; /** - * @var PropertyAccessorInterface + * @var PropertyAccessor */ private $propertyAccessor; public function __construct( - DimensionContentRepositoryInterface $dimensionContentRepository, - ContentDataMapperInterface $contentDataMapper, + iterable $mergers, + ContentMetadataInspectorInterface $contentMetadataInspector, PropertyAccessor $propertyAccessor ) { - $this->dimensionContentRepository = $dimensionContentRepository; - $this->contentDataMapper = $contentDataMapper; + $this->mergers = $mergers; + $this->contentMetadataInspector = $contentMetadataInspector; $this->propertyAccessor = $propertyAccessor; } public function create( ContentRichEntityInterface $contentRichEntity, - array $dimensionAttributes, - array $data + array $dimensionAttributes ): DimensionContentCollectionInterface { - $dimensionContentCollection = $this->dimensionContentRepository->load($contentRichEntity, $dimensionAttributes); - $dimensionAttributes = $dimensionContentCollection->getDimensionAttributes(); - - $orderedContentDimensions = \iterator_to_array($dimensionContentCollection); - $dimensionContents = new ArrayCollection($orderedContentDimensions); - - $unlocalizedAttributes = $dimensionAttributes; - $unlocalizedAttributes['locale'] = null; - - // get or create unlocalized dimension content - $unlocalizedDimensionContent = $dimensionContentCollection->getDimensionContent($unlocalizedAttributes); - - if (!$unlocalizedDimensionContent) { - $unlocalizedDimensionContent = $this->createContentDimension( - $contentRichEntity, - $dimensionContents, - $unlocalizedAttributes - ); - $orderedContentDimensions[] = $unlocalizedDimensionContent; - } - - $localizedDimensionContent = null; - if (isset($dimensionAttributes['locale'])) { - // get or create localized dimension content - $localizedDimensionContent = $dimensionContentCollection->getDimensionContent($dimensionAttributes); - - if (!$localizedDimensionContent) { - $localizedDimensionContent = $this->createContentDimension( - $contentRichEntity, - $dimensionContents, - $dimensionAttributes - ); - $orderedContentDimensions[] = $localizedDimensionContent; - } - } - - $dimensionContentCollection = new DimensionContentCollection( - $orderedContentDimensions, + return new DimensionContentCollection( + $contentRichEntity, $dimensionAttributes, - $dimensionContentCollection->getDimensionContentClass() + $this->contentMetadataInspector->getDimensionContentClass(\get_class($this)), + $this->mergers, + $this->propertyAccessor ); - - $this->contentDataMapper->map($dimensionContentCollection, $dimensionAttributes, $data); - - return $dimensionContentCollection; - } - - /** - * @param Collection $dimensionContents - * @param mixed[] $attributes - */ - private function createContentDimension( - ContentRichEntityInterface $contentRichEntity, - Collection $dimensionContents, - array $attributes - ): DimensionContentInterface { - $dimensionContent = $contentRichEntity->createDimensionContent(); - - foreach ($attributes as $attributeName => $attributeValue) { - $this->propertyAccessor->setValue($dimensionContent, $attributeName, $attributeValue); - } - - $contentRichEntity->addDimensionContent($dimensionContent); - $dimensionContents->add($dimensionContent); - - return $dimensionContent; } } diff --git a/Content/Domain/Factory/DimensionContentCollectionFactoryInterface.php b/Content/Domain/Factory/DimensionContentCollectionFactoryInterface.php index 91b52620..24386fc2 100644 --- a/Content/Domain/Factory/DimensionContentCollectionFactoryInterface.php +++ b/Content/Domain/Factory/DimensionContentCollectionFactoryInterface.php @@ -24,7 +24,6 @@ interface DimensionContentCollectionFactoryInterface */ public function create( ContentRichEntityInterface $contentRichEntity, - array $dimensionAttributes, - array $data + array $dimensionAttributes ): DimensionContentCollectionInterface; } diff --git a/Content/Domain/Model/ContentRichEntityInterface.php b/Content/Domain/Model/ContentRichEntityInterface.php index ba95aabb..1a961fb2 100644 --- a/Content/Domain/Model/ContentRichEntityInterface.php +++ b/Content/Domain/Model/ContentRichEntityInterface.php @@ -14,6 +14,7 @@ namespace Sulu\Bundle\ContentBundle\Content\Domain\Model; use Doctrine\Common\Collections\Collection; +use Sulu\Bundle\ContentBundle\Content\Domain\Factory\DimensionContentCollectionFactoryInterface; interface ContentRichEntityInterface { diff --git a/Content/Domain/Model/DimensionContentCollection.php b/Content/Domain/Model/DimensionContentCollection.php index 5deea355..a48ebc2f 100644 --- a/Content/Domain/Model/DimensionContentCollection.php +++ b/Content/Domain/Model/DimensionContentCollection.php @@ -15,7 +15,9 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; +use Sulu\Bundle\ContentBundle\Content\Application\ContentMerger\Merger\MergerInterface; use Sulu\Component\Util\SortUtils; +use Symfony\Component\PropertyAccess\PropertyAccessor; /** * @implements \IteratorAggregate @@ -23,9 +25,9 @@ class DimensionContentCollection implements \IteratorAggregate, DimensionContentCollectionInterface { /** - * @var ArrayCollection + * @var ContentRichEntityInterface */ - private $dimensionContents; + private $contentRichEntity; /** * @var mixed[] @@ -42,26 +44,37 @@ class DimensionContentCollection implements \IteratorAggregate, DimensionContent */ private $defaultDimensionAttributes; + /** + * @var iterable + */ + private $mergers; + + /** + * @var PropertyAccessor + */ + private $propertyAccessor; + /** * DimensionContentCollection constructor. * - * @param DimensionContentInterface[] $dimensionContents + * @param ContentRichEntityInterface $contentRichEntity * @param mixed[] $dimensionAttributes * @param class-string $dimensionContentClass + * @param iterable */ public function __construct( - array $dimensionContents, + ContentRichEntityInterface $contentRichEntity, array $dimensionAttributes, - string $dimensionContentClass + string $dimensionContentClass, + iterable $mergers, + PropertyAccessor $propertyAccessor ) { + $this->contentRichEntity = $contentRichEntity; $this->dimensionContentClass = $dimensionContentClass; $this->defaultDimensionAttributes = $dimensionContentClass::getDefaultDimensionAttributes(); $this->dimensionAttributes = $dimensionContentClass::getEffectiveDimensionAttributes($dimensionAttributes); - - $this->dimensionContents = new ArrayCollection( - // dimension contents need to be sorted from most specific to least specific when they are merged - SortUtils::multisort($dimensionContents, \array_keys($this->dimensionAttributes), 'asc') - ); + $this->mergers = $mergers; + $this->propertyAccessor = $propertyAccessor; } public function getDimensionContentClass(): string @@ -84,7 +97,20 @@ public function getDimensionContent(array $dimensionAttributes): ?DimensionConte $criteria->andWhere($expr); } - return $this->dimensionContents->matching($criteria)->first() ?: null; + return $this->contentRichEntity->getDimensionContents()->matching($criteria)->first() ?: null; + } + + public function createDimensionContent(array $dimensionAttributes): DimensionContentInterface + { + $dimensionContent = $this->contentRichEntity->createDimensionContent(); + + foreach ($dimensionAttributes as $attributeName => $attributeValue) { + $this->propertyAccessor->setValue($dimensionContent, $attributeName, $attributeValue); + } + + $this->contentRichEntity->addDimensionContent($dimensionContent); + + return $dimensionContent; } public function getDimensionAttributes(): array @@ -94,11 +120,53 @@ public function getDimensionAttributes(): array public function getIterator() { - return $this->dimensionContents; + return new ArrayCollection(SortUtils::multisort( + $this->contentRichEntity->getDimensionContents()->toArray(), + \array_keys($this->dimensionAttributes), 'asc' + )); } public function count(): int { - return \count($this->dimensionContents); + return $this->contentRichEntity->getDimensionContents()->count(); + } + + public function getMergedDimensionContent(): DimensionContentInterface + { + $unlocalizedDimensionAttributes = $this->dimensionAttributes; + $unlocalizedDimensionAttributes['locale'] = null; + + $dimensionContents = \array_filter([ + $this->getDimensionContent($unlocalizedDimensionAttributes), + $this->getDimensionContent($this->dimensionAttributes), + ]); + + $mergedDimensionContent = null; + + foreach ($dimensionContents as $dimensionContent) { + if (!$mergedDimensionContent) { + $contentRichEntity = $dimensionContent->getResource(); + $mergedDimensionContent = $contentRichEntity->createDimensionContent(); + $mergedDimensionContent->markAsMerged(); + } + + foreach ($this->mergers as $merger) { + $merger->merge($mergedDimensionContent, $dimensionContent); + } + + foreach ($this->dimensionAttributes as $key => $value) { + $this->propertyAccessor->setValue( + $mergedDimensionContent, + $key, + $this->propertyAccessor->getValue($dimensionContent, $key) + ); + } + } + + if (!$mergedDimensionContent) { + throw new \RuntimeException('Expected at least one dimensionContent given.'); + } + + return $mergedDimensionContent; } } diff --git a/Content/Domain/Model/DimensionContentCollectionInterface.php b/Content/Domain/Model/DimensionContentCollectionInterface.php index 7ed53e14..5caef7a7 100644 --- a/Content/Domain/Model/DimensionContentCollectionInterface.php +++ b/Content/Domain/Model/DimensionContentCollectionInterface.php @@ -23,6 +23,11 @@ interface DimensionContentCollectionInterface extends \Traversable, \Countable */ public function getDimensionContent(array $dimensionAttributes): ?DimensionContentInterface; + /** + * @param mixed[] $dimensionAttributes + */ + public function createDimensionContent(array $dimensionAttributes): DimensionContentInterface; + /** * @return class-string */ @@ -32,4 +37,6 @@ public function getDimensionContentClass(): string; * @return mixed[] */ public function getDimensionAttributes(): array; + + public function getMergedDimensionContent(): DimensionContentInterface; } diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 362934e1..b7daefce 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -54,7 +54,7 @@ - + @@ -108,7 +108,7 @@ - + diff --git a/UPGRADE.md b/UPGRADE.md index 62a49bec..52fb4b20 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -2,6 +2,22 @@ ## 0.7.0 +### ContentPersisterInterface and ContentManagerInterface changed + +The `ContentPersisterInterface` and `ContentManagerInterface` was changed to be similar to the ContentMapper: + +**Before** + +```php + public function persist(ContentRichEntityInterface $contentRichEntity, array $data, array $dimensionAttributes): DimensionContentInterface; +``` + +**After** + +```php + public function persist(ContentRichEntityInterface $contentRichEntity, array $dimensionAttributes, array $data): DimensionContentCollectionInterface; +``` + ### ContentMapperInterface changed The `ContentMapperInterface` was changed as a preparation for refactoring the `DimensionContentCollection`: