Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transformer use Symfony Serializer #1522

Merged
merged 38 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
09a9cd7
Transformer use Symfony Serializer
Hanmac Aug 5, 2022
e24c502
Functional Test for Transformer
Hanmac Aug 9, 2022
98279fa
Functional Tests for different Position types
Hanmac Aug 10, 2022
04ca311
+ add fallback for faulty array parsing for settings
Hanmac Aug 11, 2022
b4a3cea
~ doctrine test case
Hanmac Aug 17, 2022
8af4964
~ metadata needs to be restored
Hanmac Aug 17, 2022
88f7160
~ fix code quality
Hanmac Aug 17, 2022
2d26980
~ fix generator type
Hanmac Aug 17, 2022
b8065ae
~ force Page::blocks to be list array
Hanmac Aug 17, 2022
b822cab
~ allow string for bool as enabled
Hanmac Sep 9, 2022
dcf9b0b
~ fix children and blocks property, using PropertyAccessor, addBlock …
Hanmac Sep 10, 2022
77b5e60
~ rector and psalm
Hanmac Mar 13, 2023
e9eda7a
~ remove obsolete 4.4 fallback
Hanmac Apr 27, 2023
d9d2b2f
Transformer: without Serializer Group
Hanmac Apr 27, 2023
0fe57b5
~ xml-lint
Hanmac Apr 27, 2023
0a10257
~ bump min composer.json for ignore extra get Methods
Hanmac Apr 27, 2023
28dceb3
~ fix new property-access and property-info dependency
Hanmac Apr 27, 2023
cffd4c4
* Use TypeExtractor instead of Denormalizer
Hanmac Apr 28, 2023
05f5005
~ fix block tests
Hanmac May 24, 2023
864539f
~ add mixed types
Hanmac May 24, 2023
30413e9
add extra loadBlock test for Int DateTime
Hanmac May 30, 2023
ec3a194
Apply suggestions from code review
Hanmac Aug 2, 2023
2daa7ef
~ make internal Extractor classes final
Hanmac Aug 2, 2023
fe5d88b
Extra Tests for Transformer with and without Serializer
Hanmac Aug 2, 2023
21bcdb5
~ lint
Hanmac Aug 2, 2023
2341d73
add NEXT_MAJOR
Hanmac Aug 3, 2023
fc6c93d
~ fix trigger error
Hanmac Aug 3, 2023
b4bfbf6
~ add TransformerWithoutSerializerTest to mark as legacy
Hanmac Aug 3, 2023
c58eeb6
~ pass Transformer for setUpTransformer
Hanmac Aug 3, 2023
a3c5f7a
~ rename Functional TransformerTest
Hanmac Aug 3, 2023
ead3d67
~ use class-string for Type Extractors
Hanmac Aug 3, 2023
41cf336
~ lint
Hanmac Aug 3, 2023
e51aea4
~ add interfaces to class-string for extractor
Hanmac Aug 3, 2023
f458a25
~ lint
Hanmac Aug 3, 2023
5b5f29a
make removeChild and removeBlock as NEXT_MAJOR
Hanmac Aug 7, 2023
38090f9
add method exist check for removeChild and removeBlock
Hanmac Aug 7, 2023
a37b415
~ lint
Hanmac Aug 7, 2023
f2159d4
add phpstan-ignore-next-line for method_exists check
Hanmac Aug 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Hanmac marked this conversation as resolved.
Show resolved Hide resolved
// @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