Skip to content

Commit

Permalink
Transformer use Symfony Serializer (#1522)
Browse files Browse the repository at this point in the history
Co-authored-by: Vincent Langlet <VincentLanglet@users.noreply.github.com>
  • Loading branch information
Hanmac and VincentLanglet authored Aug 10, 2023
1 parent d2a8460 commit bb9a92c
Show file tree
Hide file tree
Showing 20 changed files with 1,122 additions and 131 deletions.
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@
"symfony/intl": "^5.4 || ^6.2",
"symfony/options-resolver": "^5.4 || ^6.2",
"symfony/process": "^5.4 || ^6.2",
"symfony/property-access": "^5.4 || ^6.2",
"symfony/property-info": "^5.4 || ^6.2",
"symfony/routing": "^5.4 || ^6.2",
"symfony/security-core": "^5.4 || ^6.2",
"symfony/security-http": "^5.4 || ^6.2",
"symfony/serializer": "^5.4 || ^6.2",
"symfony/serializer": "^5.4.24 || ^6.2.11",
"symfony/validator": "^5.4 || ^6.2",
"twig/string-extra": "^3.0",
"twig/twig": "^3.0"
Expand Down
248 changes: 180 additions & 68 deletions src/Entity/Transformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
use Sonata\PageBundle\Model\SnapshotInterface;
use Sonata\PageBundle\Model\SnapshotManagerInterface;
use Sonata\PageBundle\Model\TransformerInterface;
use Sonata\PageBundle\Serializer\BlockTypeExtractor;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;

/**
* This class transform a SnapshotInterface into PageInterface.
Expand All @@ -40,14 +47,23 @@ final class Transformer implements TransformerInterface
private array $children = [];

/**
* @param ManagerInterface<PageBlockInterface> $blockManager
* @param ManagerInterface<PageBlockInterface> $blockManager
* @param SerializerInterface&NormalizerInterface&DenormalizerInterface $serializer
*/
public function __construct(
private SnapshotManagerInterface $snapshotManager,
private PageManagerInterface $pageManager,
private ManagerInterface $blockManager,
private ManagerRegistry $registry
private ManagerRegistry $registry,
private ?SerializerInterface $serializer = null
) {
// NEXT_MAJOR: Remove null support
if (null === $this->serializer) {
@trigger_error(sprintf(
'Not passing an instance of %s as 5th parameter is deprecated since version 4.x and will be removed in 5.0.',
SerializerInterface::class
), \E_USER_DEPRECATED);
}
}

public function create(PageInterface $page, ?SnapshotInterface $snapshot = null): SnapshotInterface
Expand Down Expand Up @@ -75,36 +91,69 @@ public function create(PageInterface $page, ?SnapshotInterface $snapshot = null)
$snapshot->setParentId($parent->getId());
}

$blocks = [];
foreach ($page->getBlocks() as $block) {
if (null !== $block->getParent()) { // ignore block with a parent => must be a child of a main
continue;
// NEXT_MAJOR: Remove null support and method check
// @phpstan-ignore-next-line
if (null !== $this->serializer && method_exists($page, 'removeChild') && method_exists($page, 'removeBlock')) {
/**
* @var PageContent $content
*/
$content = $this->serializer->normalize($page, null, [
DateTimeNormalizer::FORMAT_KEY => 'U',
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
AbstractNormalizer::CALLBACKS => [
'blocks' => static fn (Collection $collection, PageInterface $object, string $attribute, ?string $format = null, array $context = []) => $collection->filter(static fn (BlockInterface $block) => !$block->hasParent())->getValues(),
'parent' => static fn (?PageInterface $page, PageInterface $object, string $attribute, ?string $format = null, array $context = []) => $page?->getId(),
],
]);
} else {
// @phpstan-ignore-next-line
if (!method_exists($page, 'removeChild')) {
@trigger_error('Not implementing a `PageInterface::removeChild` method is deprecated since 4.x and will throw an error in 5.0.', \E_USER_DEPRECATED);
}
// @phpstan-ignore-next-line
if (!method_exists($page, 'removeBlock')) {
@trigger_error('Not implementing a `PageInterface::removeBlock` method is deprecated since 4.x and will throw an error in 5.0.', \E_USER_DEPRECATED);
}

$blocks[] = $this->createBlock($block);
}
$blocks = [];
foreach ($page->getBlocks() as $block) {
if (null !== $block->getParent()) { // ignore block with a parent => must be a child of a main
continue;
}

$createdAt = $page->getCreatedAt();
$updatedAt = $page->getUpdatedAt();
$parent = $page->getParent();
$blocks[] = $this->createBlock($block);
}

$createdAt = $page->getCreatedAt();
$updatedAt = $page->getUpdatedAt();
$parent = $page->getParent();

$data = [
'id' => $page->getId(),
'name' => $page->getName(),
'javascript' => $page->getJavascript(),
'stylesheet' => $page->getStylesheet(),
'raw_headers' => $page->getRawHeaders(),
'title' => $page->getTitle(),
'meta_description' => $page->getMetaDescription(),
'meta_keyword' => $page->getMetaKeyword(),
'template_code' => $page->getTemplateCode(),
'request_method' => $page->getRequestMethod(),
'created_at' => $createdAt?->format('U'),
'updated_at' => $updatedAt?->format('U'),
'slug' => $page->getSlug(),
'parent_id' => $parent?->getId(),
'blocks' => $blocks,
];
// need to filter out null values

/**
* @var PageContent $content
*/
$content = array_filter($data, static fn ($v) => null !== $v);
}

$snapshot->setContent([
'id' => $page->getId(),
'name' => $page->getName(),
'javascript' => $page->getJavascript(),
'stylesheet' => $page->getStylesheet(),
'raw_headers' => $page->getRawHeaders(),
'title' => $page->getTitle(),
'meta_description' => $page->getMetaDescription(),
'meta_keyword' => $page->getMetaKeyword(),
'template_code' => $page->getTemplateCode(),
'request_method' => $page->getRequestMethod(),
'created_at' => null !== $createdAt ? (int) $createdAt->format('U') : null,
'updated_at' => null !== $updatedAt ? (int) $updatedAt->format('U') : null,
'slug' => $page->getSlug(),
'parent_id' => null !== $parent ? $parent->getId() : null,
'blocks' => $blocks,
]);
$snapshot->setContent($content);

return $snapshot;
}
Expand All @@ -124,18 +173,37 @@ public function load(SnapshotInterface $snapshot): PageInterface

$content = $snapshot->getContent();

if (null !== $content) {
$pageClass = $this->pageManager->getClass();

// NEXT_MAJOR: Remove null support and method check
// @phpstan-ignore-next-line
if (null !== $this->serializer && method_exists($page, 'removeChild') && method_exists($page, 'removeBlock')) {
$this->serializer->denormalize($content, $pageClass, null, [
DateTimeNormalizer::FORMAT_KEY => 'U',
AbstractNormalizer::OBJECT_TO_POPULATE => $page,
AbstractNormalizer::CALLBACKS => $this->getDenormalizeCallbacks(),
]);
} elseif (null !== $content) {
// @phpstan-ignore-next-line
if (!method_exists($page, 'removeChild')) {
@trigger_error('Not implementing a `PageInterface::removeChild` method is deprecated since 4.x and will throw an error in 5.0.', \E_USER_DEPRECATED);
}
// @phpstan-ignore-next-line
if (!method_exists($page, 'removeBlock')) {
@trigger_error('Not implementing a `PageInterface::removeBlock` method is deprecated since 4.x and will throw an error in 5.0.', \E_USER_DEPRECATED);
}
$page->setId($content['id']);
$page->setJavascript($content['javascript']);
$page->setStylesheet($content['stylesheet']);
$page->setRawHeaders($content['raw_headers']);
$page->setJavascript($content['javascript'] ?? null);
$page->setStylesheet($content['stylesheet'] ?? null);
$page->setRawHeaders($content['raw_headers'] ?? null);
$page->setTitle($content['title'] ?? null);
$page->setMetaDescription($content['meta_description']);
$page->setMetaKeyword($content['meta_keyword']);
$page->setName($content['name']);
$page->setSlug($content['slug']);
$page->setTemplateCode($content['template_code']);
$page->setRequestMethod($content['request_method']);
$page->setMetaDescription($content['meta_description'] ?? null);
$page->setMetaKeyword($content['meta_keyword'] ?? null);

$page->setName($content['name'] ?? null);
$page->setSlug($content['slug'] ?? null);
$page->setTemplateCode($content['template_code'] ?? null);
$page->setRequestMethod($content['request_method'] ?? null);

$createdAt = new \DateTime();
$createdAt->setTimestamp((int) $content['created_at']);
Expand All @@ -155,40 +223,60 @@ public function loadBlock(array $content, PageInterface $page): PageBlockInterfa

$block->setPage($page);

if (isset($content['id'])) {
$block->setId($content['id']);
}
$blockClass = $this->blockManager->getClass();

// NEXT_MAJOR: Remove null support and method check
// @phpstan-ignore-next-line
if (null !== $this->serializer && method_exists($page, 'removeChild') && method_exists($page, 'removeBlock')) {
$this->serializer->denormalize($content, $blockClass, null, [
DateTimeNormalizer::FORMAT_KEY => 'U',
AbstractNormalizer::OBJECT_TO_POPULATE => $block,
AbstractNormalizer::CALLBACKS => $this->getDenormalizeCallbacks(),
]);
} else {
// @phpstan-ignore-next-line
if (!method_exists($page, 'removeChild')) {
@trigger_error('Not implementing a `PageInterface::removeChild` method is deprecated since 4.x and will throw an error in 5.0.', \E_USER_DEPRECATED);
}
// @phpstan-ignore-next-line
if (!method_exists($page, 'removeBlock')) {
@trigger_error('Not implementing a `PageInterface::removeBlock` method is deprecated since 4.x and will throw an error in 5.0.', \E_USER_DEPRECATED);
}
if (isset($content['id'])) {
$block->setId($content['id']);
}

if (isset($content['name'])) {
$block->setName($content['name']);
}
if (isset($content['name'])) {
$block->setName($content['name']);
}

// NEXT_MAJOR: Simplify this code by removing the in_array function and assign directly.
$block->setEnabled(\in_array($content['enabled'], ['1', true], true));
// NEXT_MAJOR: Simplify this code by removing the in_array function and assign directly.
$block->setEnabled(\in_array($content['enabled'], ['1', true], true));

if (isset($content['position']) && is_numeric($content['position'])) {
$block->setPosition((int) $content['position']);
}
if (isset($content['position']) && is_numeric($content['position'])) {
$block->setPosition((int) $content['position']);
}

$block->setSettings($content['settings']);
$block->setSettings($content['settings']);

if (isset($content['type'])) {
$block->setType($content['type']);
}
if (isset($content['type'])) {
$block->setType($content['type']);
}

$createdAt = new \DateTime();
$createdAt->setTimestamp((int) $content['created_at']);
$block->setCreatedAt($createdAt);
$createdAt = new \DateTime();
$createdAt->setTimestamp((int) $content['created_at']);
$block->setCreatedAt($createdAt);

$updatedAt = new \DateTime();
$updatedAt->setTimestamp((int) $content['updated_at']);
$block->setUpdatedAt($updatedAt);
$updatedAt = new \DateTime();
$updatedAt->setTimestamp((int) $content['updated_at']);
$block->setUpdatedAt($updatedAt);

/**
* @phpstan-var BlockContent $child
*/
foreach ($content['blocks'] as $child) {
$block->addChild($this->loadBlock($child, $page));
/**
* @phpstan-var BlockContent $child
*/
foreach ($content['blocks'] as $child) {
$block->addChild($this->loadBlock($child, $page));
}
}

return $block;
Expand Down Expand Up @@ -238,6 +326,24 @@ public function getChildren(PageInterface $page): Collection
return $this->children[$id];
}

/**
* @return \Closure[]
*/
private function getDenormalizeCallbacks(): array
{
$result = [
'position' => static fn (string|int|null $value, string $object, string $attribute, ?string $format = null, array $context = []): int => null === $value ? 0 : (int) $value,
];

$nullableStringCallback = static fn (?string $value, string $object, string $attribute, ?string $format = null, array $context = []): string => (string) $value;

foreach (BlockTypeExtractor::NULLABLE_STRINGS as $key) {
$result[$key] = $nullableStringCallback;
}

return $result;
}

/**
* @return array<string, mixed>
*
Expand All @@ -251,8 +357,14 @@ private function createBlock(BlockInterface $block): array
$childBlocks[] = $this->createBlock($child);
}

$createdAt = $block->getCreatedAt();
$updatedAt = $block->getUpdatedAt();
/**
* @var numeric-string|null $createdAt
*/
$createdAt = $block->getCreatedAt()?->format('U');
/**
* @var numeric-string|null $updatedAt
*/
$updatedAt = $block->getUpdatedAt()?->format('U');

return [
'id' => $block->getId(),
Expand All @@ -261,8 +373,8 @@ private function createBlock(BlockInterface $block): array
'position' => $block->getPosition(),
'settings' => $block->getSettings(),
'type' => $block->getType(),
'created_at' => null !== $createdAt ? (int) $createdAt->format('U') : null,
'updated_at' => null !== $updatedAt ? (int) $updatedAt->format('U') : null,
'created_at' => $createdAt,
'updated_at' => $updatedAt,
'blocks' => $childBlocks,
];
}
Expand Down
4 changes: 1 addition & 3 deletions src/Model/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ public function setPage(?PageInterface $page = null): void

public function addChild(BlockInterface $child): void
{
$this->children[] = $child;

$child->setParent($this);
parent::addChild($child);

if ($child instanceof PageBlockInterface) {
$child->setPage($this->getPage());
Expand Down
11 changes: 11 additions & 0 deletions src/Model/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ public function addChild(PageInterface $child): void
$child->setParent($this);
}

public function removeChild(PageInterface $child): void
{
$this->children->removeElement($child);
}

public function getBlocks(): Collection
{
return $this->blocks;
Expand All @@ -301,6 +306,12 @@ public function addBlock(PageBlockInterface $block): void
$this->blocks[] = $block;
}

public function removeBlock(PageBlockInterface $block): void
{
$this->blocks->removeElement($block);
$block->setPage();
}

public function getContainerByCode(string $code): ?PageBlockInterface
{
foreach ($this->getBlocks() as $block) {
Expand Down
Loading

0 comments on commit bb9a92c

Please sign in to comment.