From 20263b613d57f5e918f1b0f75de9835714259250 Mon Sep 17 00:00:00 2001 From: Ambroise Maupate Date: Thu, 23 Mar 2023 18:19:11 +0100 Subject: [PATCH] feat(EntityGenerator): Entity generator uses DefaultValuesResolverInterface to compute required ENUM fields length acording to their default values. --- lib/EntityGenerator/src/EntityGenerator.php | 40 ++++----- .../src/EntityGeneratorFactory.php | 18 +++-- .../AbstractConfigurableFieldGenerator.php | 9 ++- .../src/Field/AbstractFieldGenerator.php | 33 +++----- .../Field/DefaultValuesResolverInterface.php | 22 +++++ .../src/Field/NodesFieldGenerator.php | 15 ++-- .../src/Field/NonVirtualFieldGenerator.php | 28 ++++--- .../mocks/GeneratedNodesSources/NSMock.php | 47 +++++++++++ .../NSMock.php | 47 +++++++++++ .../JoinedTableDefaultValuesResolver.php | 27 +++++++ .../tests/mocks/NodeTypeAwareTrait.php | 11 +++ .../tests/units/EntityGenerator.php | 5 +- .../tests/units/EntityGeneratorFactory.php | 9 ++- lib/RoadizCoreBundle/config/services.yaml | 4 + .../Console/NodeTypesDefaultValuesCommand.php | 81 +++++++++++++++++++ .../src/NodeType/DefaultValuesResolver.php | 62 ++++++++++++++ src/GeneratedEntity/NSArticle.php | 2 +- src/GeneratedEntity/NSBasicBlock.php | 2 +- src/GeneratedEntity/NSGroupBlock.php | 2 +- src/GeneratedEntity/NSMenu.php | 2 +- src/GeneratedEntity/NSMenuLink.php | 2 +- src/GeneratedEntity/NSNeutral.php | 2 +- src/GeneratedEntity/NSOffer.php | 49 ++++++++++- src/GeneratedEntity/NSPage.php | 49 ++++++++++- src/Resources/node-types/Offer.json | 15 ++++ src/Resources/node-types/Page.json | 15 ++++ 26 files changed, 513 insertions(+), 85 deletions(-) create mode 100644 lib/EntityGenerator/src/Field/DefaultValuesResolverInterface.php create mode 100644 lib/EntityGenerator/tests/mocks/JoinedTableDefaultValuesResolver.php create mode 100644 lib/RoadizCoreBundle/src/Console/NodeTypesDefaultValuesCommand.php create mode 100644 lib/RoadizCoreBundle/src/NodeType/DefaultValuesResolver.php diff --git a/lib/EntityGenerator/src/EntityGenerator.php b/lib/EntityGenerator/src/EntityGenerator.php index 301b443a..808de9b2 100644 --- a/lib/EntityGenerator/src/EntityGenerator.php +++ b/lib/EntityGenerator/src/EntityGenerator.php @@ -12,6 +12,7 @@ use RZ\Roadiz\EntityGenerator\Field\AbstractFieldGenerator; use RZ\Roadiz\EntityGenerator\Field\CollectionFieldGenerator; use RZ\Roadiz\EntityGenerator\Field\CustomFormsFieldGenerator; +use RZ\Roadiz\EntityGenerator\Field\DefaultValuesResolverInterface; use RZ\Roadiz\EntityGenerator\Field\DocumentsFieldGenerator; use RZ\Roadiz\EntityGenerator\Field\ManyToManyFieldGenerator; use RZ\Roadiz\EntityGenerator\Field\ManyToOneFieldGenerator; @@ -25,23 +26,24 @@ class EntityGenerator implements EntityGeneratorInterface { - private NodeTypeInterface $nodeType; - private array $fieldGenerators; - private NodeTypeResolverInterface $nodeTypeResolver; + protected NodeTypeInterface $nodeType; + protected NodeTypeResolverInterface $nodeTypeResolver; + protected DefaultValuesResolverInterface $defaultValuesResolver; + protected array $fieldGenerators; protected array $options; - /** - * @param NodeTypeInterface $nodeType - * @param NodeTypeResolverInterface $nodeTypeResolver - * @param array $options - */ - public function __construct(NodeTypeInterface $nodeType, NodeTypeResolverInterface $nodeTypeResolver, array $options = []) - { + public function __construct( + NodeTypeInterface $nodeType, + NodeTypeResolverInterface $nodeTypeResolver, + DefaultValuesResolverInterface $defaultValuesResolver, + array $options = [] + ) { $resolver = new OptionsResolver(); $this->configureOptions($resolver); $this->nodeType = $nodeType; $this->nodeTypeResolver = $nodeTypeResolver; + $this->defaultValuesResolver = $defaultValuesResolver; $this->fieldGenerators = []; $this->options = $resolver->resolve($options); @@ -109,19 +111,19 @@ public function configureOptions(OptionsResolver $resolver): void protected function getFieldGenerator(NodeTypeFieldInterface $field): ?AbstractFieldGenerator { if ($field->isYaml()) { - return new YamlFieldGenerator($field, $this->options); + return new YamlFieldGenerator($field, $this->defaultValuesResolver, $this->options); } if ($field->isCollection()) { - return new CollectionFieldGenerator($field, $this->options); + return new CollectionFieldGenerator($field, $this->defaultValuesResolver, $this->options); } if ($field->isCustomForms()) { - return new CustomFormsFieldGenerator($field, $this->options); + return new CustomFormsFieldGenerator($field, $this->defaultValuesResolver, $this->options); } if ($field->isDocuments()) { - return new DocumentsFieldGenerator($field, $this->options); + return new DocumentsFieldGenerator($field, $this->defaultValuesResolver, $this->options); } if ($field->isManyToOne()) { - return new ManyToOneFieldGenerator($field, $this->options); + return new ManyToOneFieldGenerator($field, $this->defaultValuesResolver, $this->options); } if ($field->isManyToMany()) { $configuration = Yaml::parse($field->getDefaultValues() ?? ''); @@ -134,15 +136,15 @@ protected function getFieldGenerator(NodeTypeFieldInterface $field): ?AbstractFi * Manually create a Many-to-Many relation using a proxy class * for handling position for example. */ - return new ProxiedManyToManyFieldGenerator($field, $this->options); + return new ProxiedManyToManyFieldGenerator($field, $this->defaultValuesResolver, $this->options); } - return new ManyToManyFieldGenerator($field, $this->options); + return new ManyToManyFieldGenerator($field, $this->defaultValuesResolver, $this->options); } if ($field->isNodes()) { - return new NodesFieldGenerator($field, $this->nodeTypeResolver, $this->options); + return new NodesFieldGenerator($field, $this->nodeTypeResolver, $this->defaultValuesResolver, $this->options); } if (!$field->isVirtual()) { - return new NonVirtualFieldGenerator($field, $this->options); + return new NonVirtualFieldGenerator($field, $this->defaultValuesResolver, $this->options); } return null; diff --git a/lib/EntityGenerator/src/EntityGeneratorFactory.php b/lib/EntityGenerator/src/EntityGeneratorFactory.php index 7a68d0a6..f9846c11 100644 --- a/lib/EntityGenerator/src/EntityGeneratorFactory.php +++ b/lib/EntityGenerator/src/EntityGeneratorFactory.php @@ -6,25 +6,27 @@ use RZ\Roadiz\Contracts\NodeType\NodeTypeInterface; use RZ\Roadiz\Contracts\NodeType\NodeTypeResolverInterface; +use RZ\Roadiz\EntityGenerator\Field\DefaultValuesResolverInterface; final class EntityGeneratorFactory { private NodeTypeResolverInterface $nodeTypeResolverBag; + private DefaultValuesResolverInterface $defaultValuesResolver; private array $options; - /** - * @param NodeTypeResolverInterface $nodeTypeResolverBag - * @param array $options - */ - public function __construct(NodeTypeResolverInterface $nodeTypeResolverBag, array $options) - { + public function __construct( + NodeTypeResolverInterface $nodeTypeResolverBag, + DefaultValuesResolverInterface $defaultValuesResolver, + array $options + ) { $this->nodeTypeResolverBag = $nodeTypeResolverBag; + $this->defaultValuesResolver = $defaultValuesResolver; $this->options = $options; } public function create(NodeTypeInterface $nodeType): EntityGeneratorInterface { - return new EntityGenerator($nodeType, $this->nodeTypeResolverBag, $this->options); + return new EntityGenerator($nodeType, $this->nodeTypeResolverBag, $this->defaultValuesResolver, $this->options); } public function createWithCustomRepository(NodeTypeInterface $nodeType): EntityGeneratorInterface @@ -35,7 +37,7 @@ public function createWithCustomRepository(NodeTypeInterface $nodeType): EntityG '\\Repository\\' . $nodeType->getSourceEntityClassName() . 'Repository'; - return new EntityGenerator($nodeType, $this->nodeTypeResolverBag, $options); + return new EntityGenerator($nodeType, $this->nodeTypeResolverBag, $this->defaultValuesResolver, $options); } public function createCustomRepository(NodeTypeInterface $nodeType): RepositoryGeneratorInterface diff --git a/lib/EntityGenerator/src/Field/AbstractConfigurableFieldGenerator.php b/lib/EntityGenerator/src/Field/AbstractConfigurableFieldGenerator.php index 0b9e5cb8..12b20d2e 100644 --- a/lib/EntityGenerator/src/Field/AbstractConfigurableFieldGenerator.php +++ b/lib/EntityGenerator/src/Field/AbstractConfigurableFieldGenerator.php @@ -11,9 +11,12 @@ abstract class AbstractConfigurableFieldGenerator extends AbstractFieldGenerator { protected array $configuration; - public function __construct(NodeTypeFieldInterface $field, array $options = []) - { - parent::__construct($field, $options); + public function __construct( + NodeTypeFieldInterface $field, + DefaultValuesResolverInterface $defaultValuesResolver, + array $options = [] + ) { + parent::__construct($field, $defaultValuesResolver, $options); if (empty($this->field->getDefaultValues())) { throw new \LogicException('Default values must be a valid YAML for ' . static::class); diff --git a/lib/EntityGenerator/src/Field/AbstractFieldGenerator.php b/lib/EntityGenerator/src/Field/AbstractFieldGenerator.php index 7721207b..e0a689e7 100644 --- a/lib/EntityGenerator/src/Field/AbstractFieldGenerator.php +++ b/lib/EntityGenerator/src/Field/AbstractFieldGenerator.php @@ -12,38 +12,23 @@ abstract class AbstractFieldGenerator { - const USE_NATIVE_JSON = 'use_native_json'; - const TAB = ' '; - const ANNOTATION_PREFIX = AbstractFieldGenerator::TAB . ' *'; + public const TAB = ' '; + public const ANNOTATION_PREFIX = AbstractFieldGenerator::TAB . ' *'; protected NodeTypeFieldInterface $field; + protected DefaultValuesResolverInterface $defaultValuesResolver; protected array $options; - /** - * @param NodeTypeFieldInterface $field - * @param array $options - */ - public function __construct(NodeTypeFieldInterface $field, array $options = []) - { + public function __construct( + NodeTypeFieldInterface $field, + DefaultValuesResolverInterface $defaultValuesResolver, + array $options = [] + ) { $this->field = $field; + $this->defaultValuesResolver = $defaultValuesResolver; $this->options = $options; } - /** - * @param array $ormParams - * - * @return string - */ - public static function flattenORMParameters(array $ormParams): string - { - $flatParams = []; - foreach ($ormParams as $key => $value) { - $flatParams[] = $key . '=' . $value; - } - - return implode(', ', $flatParams); - } - /** * Generate PHP code for current doctrine field. * diff --git a/lib/EntityGenerator/src/Field/DefaultValuesResolverInterface.php b/lib/EntityGenerator/src/Field/DefaultValuesResolverInterface.php new file mode 100644 index 00000000..5ec89188 --- /dev/null +++ b/lib/EntityGenerator/src/Field/DefaultValuesResolverInterface.php @@ -0,0 +1,22 @@ +nodeTypeResolver = $nodeTypeResolver; } diff --git a/lib/EntityGenerator/src/Field/NonVirtualFieldGenerator.php b/lib/EntityGenerator/src/Field/NonVirtualFieldGenerator.php index 46735cf2..dbf1de72 100644 --- a/lib/EntityGenerator/src/Field/NonVirtualFieldGenerator.php +++ b/lib/EntityGenerator/src/Field/NonVirtualFieldGenerator.php @@ -43,17 +43,25 @@ protected function getFieldLength(): ?int if ($this->getDoctrineType() !== 'string') { return null; } - switch (true) { - case $this->field->isColor(): - return 10; - case $this->field->isCountry(): - return 5; - case $this->field->isPassword(): - case $this->field->isGeoTag(): - return 128; - default: - return 250; + return match (true) { + $this->field->isColor() => 10, + $this->field->isCountry() => 5, + $this->field->isPassword(), $this->field->isGeoTag() => 128, + $this->field->isEnum() => $this->defaultValuesResolver->getMaxDefaultValuesLengthAmongAllFields($this->field), + default => 250, + }; + } + + protected function getMaxDefaultValuesLength(): int + { + // get max length of exploded default values + $max = 0; + foreach (explode(',', $this->field->getDefaultValues()) as $value) { + $value = trim($value); + $max = max($max, strlen($value)); } + + return $max > 0 ? $max : 250; } protected function isExcludingFieldFromJmsSerialization(): bool diff --git a/lib/EntityGenerator/tests/mocks/GeneratedNodesSources/NSMock.php b/lib/EntityGenerator/tests/mocks/GeneratedNodesSources/NSMock.php index 6ba62783..e9e4f618 100644 --- a/lib/EntityGenerator/tests/mocks/GeneratedNodesSources/NSMock.php +++ b/lib/EntityGenerator/tests/mocks/GeneratedNodesSources/NSMock.php @@ -29,6 +29,7 @@ ORM\Index(columns: ["fooIndexed"]), ORM\Index(columns: ["boolIndexed"]), ORM\Index(columns: ["foo_decimal_excluded"]), + ORM\Index(columns: ["layout"]), ApiFilter(PropertyFilter::class) ] class NSMock extends \mock\Entity\NodesSources @@ -933,6 +934,52 @@ public function setFooBarTypedSources(?array $fooBarTypedSources): static } + /** + * ForBar layout enum. + * Default values: light, dark, transparent + */ + #[ + SymfonySerializer\SerializedName(serializedName: "layout"), + SymfonySerializer\Groups(["nodes_sources", "nodes_sources_default"]), + SymfonySerializer\MaxDepth(2), + ApiFilter(OrmFilter\SearchFilter::class, strategy: "exact"), + ApiFilter(\RZ\Roadiz\CoreBundle\Api\Filter\NotFilter::class), + Gedmo\Versioned, + ORM\Column( + name: "layout", + type: "string", + nullable: true, + length: 11 + ), + Serializer\Groups(["nodes_sources", "nodes_sources_default"]), + Serializer\MaxDepth(2), + Serializer\Type("string") + ] + private ?string $layout = null; + + /** + * @return string|null + */ + public function getLayout(): ?string + { + return $this->layout; + } + + /** + * @param string|null $layout + * + * @return $this + */ + public function setLayout(?string $layout): static + { + $this->layout = null !== $layout ? + (string) $layout : + null; + + return $this; + } + + /** * For many_to_one field. * Default values: classname: \MyCustomEntity diff --git a/lib/EntityGenerator/tests/mocks/GeneratedNodesSourcesWithRepository/NSMock.php b/lib/EntityGenerator/tests/mocks/GeneratedNodesSourcesWithRepository/NSMock.php index 8e8b5a48..813f497e 100644 --- a/lib/EntityGenerator/tests/mocks/GeneratedNodesSourcesWithRepository/NSMock.php +++ b/lib/EntityGenerator/tests/mocks/GeneratedNodesSourcesWithRepository/NSMock.php @@ -29,6 +29,7 @@ ORM\Index(columns: ["fooIndexed"]), ORM\Index(columns: ["boolIndexed"]), ORM\Index(columns: ["foo_decimal_excluded"]), + ORM\Index(columns: ["layout"]), ApiFilter(PropertyFilter::class) ] class NSMock extends \mock\Entity\NodesSources @@ -933,6 +934,52 @@ public function setFooBarTypedSources(?array $fooBarTypedSources): static } + /** + * ForBar layout enum. + * Default values: light, dark, transparent + */ + #[ + SymfonySerializer\SerializedName(serializedName: "layout"), + SymfonySerializer\Groups(["nodes_sources", "nodes_sources_default"]), + SymfonySerializer\MaxDepth(2), + ApiFilter(OrmFilter\SearchFilter::class, strategy: "exact"), + ApiFilter(\RZ\Roadiz\CoreBundle\Api\Filter\NotFilter::class), + Gedmo\Versioned, + ORM\Column( + name: "layout", + type: "string", + nullable: true, + length: 11 + ), + Serializer\Groups(["nodes_sources", "nodes_sources_default"]), + Serializer\MaxDepth(2), + Serializer\Type("string") + ] + private ?string $layout = null; + + /** + * @return string|null + */ + public function getLayout(): ?string + { + return $this->layout; + } + + /** + * @param string|null $layout + * + * @return $this + */ + public function setLayout(?string $layout): static + { + $this->layout = null !== $layout ? + (string) $layout : + null; + + return $this; + } + + /** * For many_to_one field. * Default values: classname: \MyCustomEntity diff --git a/lib/EntityGenerator/tests/mocks/JoinedTableDefaultValuesResolver.php b/lib/EntityGenerator/tests/mocks/JoinedTableDefaultValuesResolver.php new file mode 100644 index 00000000..f0d65008 --- /dev/null +++ b/lib/EntityGenerator/tests/mocks/JoinedTableDefaultValuesResolver.php @@ -0,0 +1,27 @@ +getDefaultValues())); + } + + public function getMaxDefaultValuesLengthAmongAllFields(NodeTypeFieldInterface $field): int + { + // get max length of exploded default values + $max = 0; + foreach ($this->getDefaultValuesAmongAllFields($field) as $value) { + $max = max($max, strlen($value)); + } + + return $max > 0 ? $max : 250; + } +} diff --git a/lib/EntityGenerator/tests/mocks/NodeTypeAwareTrait.php b/lib/EntityGenerator/tests/mocks/NodeTypeAwareTrait.php index 29b2a9ab..eea532b0 100644 --- a/lib/EntityGenerator/tests/mocks/NodeTypeAwareTrait.php +++ b/lib/EntityGenerator/tests/mocks/NodeTypeAwareTrait.php @@ -149,6 +149,12 @@ protected function getMockNodeType() ->setLabel('ForBar nodes typed field') ->setIndexed(false) ->setDefaultValues('MockTwo'), + (new NodeTypeField()) + ->setName('layout') + ->setTypeName('enum') + ->setLabel('ForBar layout enum') + ->setIndexed(true) + ->setDefaultValues('light, dark, transparent'), (new NodeTypeField()) ->setName('foo_many_to_one') ->setTypeName('many_to_one') @@ -228,4 +234,9 @@ protected function getMockNodeTypeResolver() }; return $mockNodeTypeResolver; } + + protected function getMockDefaultValuesResolver() + { + return $this->newMockInstance(JoinedTableDefaultValuesResolver::class); + } } diff --git a/lib/EntityGenerator/tests/units/EntityGenerator.php b/lib/EntityGenerator/tests/units/EntityGenerator.php index 6a7f836d..3f225b5f 100644 --- a/lib/EntityGenerator/tests/units/EntityGenerator.php +++ b/lib/EntityGenerator/tests/units/EntityGenerator.php @@ -14,6 +14,7 @@ public function testGetClassContent() { $mockNodeType = $this->getMockNodeType(); $mockNodeTypeResolver = $this->getMockNodeTypeResolver(); + $mockDefaultValuesResolver = $this->getMockDefaultValuesResolver(); /* * Uncomment for generating a mock file from tests @@ -38,7 +39,7 @@ public function testGetClassContent() $this // creation of a new instance of the tested class - ->given($this->newTestedInstance($mockNodeType, $mockNodeTypeResolver, [ + ->given($this->newTestedInstance($mockNodeType, $mockNodeTypeResolver, $mockDefaultValuesResolver, [ 'parent_class' => '\mock\Entity\NodesSources', 'node_class' => '\mock\Entity\Node', 'translation_class' => '\mock\Entity\Translation', @@ -61,7 +62,7 @@ public function testGetClassContent() */ $this // creation of a new instance of the tested class - ->given($this->newTestedInstance($mockNodeType, $mockNodeTypeResolver, [ + ->given($this->newTestedInstance($mockNodeType, $mockNodeTypeResolver, $mockDefaultValuesResolver, [ 'parent_class' => 'mock\Entity\NodesSources', 'node_class' => 'mock\Entity\Node', 'translation_class' => 'mock\Entity\Translation', diff --git a/lib/EntityGenerator/tests/units/EntityGeneratorFactory.php b/lib/EntityGenerator/tests/units/EntityGeneratorFactory.php index 8e154418..ba75bd8e 100644 --- a/lib/EntityGenerator/tests/units/EntityGeneratorFactory.php +++ b/lib/EntityGenerator/tests/units/EntityGeneratorFactory.php @@ -14,10 +14,11 @@ public function testCreate() { $mockNodeType = $this->getMockNodeType(); $mockNodeTypeResolver = $this->getMockNodeTypeResolver(); + $mockDefaultValuesResolver = $this->getMockDefaultValuesResolver(); $this // creation of a new instance of the tested class - ->given($this->newTestedInstance($mockNodeTypeResolver, [ + ->given($this->newTestedInstance($mockNodeTypeResolver, $mockDefaultValuesResolver, [ 'parent_class' => '\mock\Entity\NodesSources', 'node_class' => '\mock\Entity\Node', 'translation_class' => '\mock\Entity\Translation', @@ -40,6 +41,7 @@ public function testCreateWithCustomRepository() { $mockNodeType = $this->getMockNodeType(); $mockNodeTypeResolver = $this->getMockNodeTypeResolver(); + $mockDefaultValuesResolver = $this->getMockDefaultValuesResolver(); /* * Uncomment for generating a mock file from tests @@ -64,7 +66,7 @@ public function testCreateWithCustomRepository() $this // creation of a new instance of the tested class - ->given($this->newTestedInstance($mockNodeTypeResolver, [ + ->given($this->newTestedInstance($mockNodeTypeResolver, $mockDefaultValuesResolver, [ 'parent_class' => '\mock\Entity\NodesSources', 'node_class' => '\mock\Entity\Node', 'translation_class' => '\mock\Entity\Translation', @@ -87,6 +89,7 @@ public function testCreateCustomRepository() { $mockNodeType = $this->getMockNodeType(); $mockNodeTypeResolver = $this->getMockNodeTypeResolver(); + $mockDefaultValuesResolver = $this->getMockDefaultValuesResolver(); /* * Uncomment for generating a mock file from tests @@ -111,7 +114,7 @@ public function testCreateCustomRepository() $this // creation of a new instance of the tested class - ->given($this->newTestedInstance($mockNodeTypeResolver, [ + ->given($this->newTestedInstance($mockNodeTypeResolver, $mockDefaultValuesResolver, [ 'parent_class' => '\mock\Entity\NodesSources', 'node_class' => '\mock\Entity\Node', 'translation_class' => '\mock\Entity\Translation', diff --git a/lib/RoadizCoreBundle/config/services.yaml b/lib/RoadizCoreBundle/config/services.yaml index 97914fd1..d61aab40 100644 --- a/lib/RoadizCoreBundle/config/services.yaml +++ b/lib/RoadizCoreBundle/config/services.yaml @@ -357,9 +357,13 @@ services: alias: RZ\Roadiz\CoreBundle\Bag\NodeTypes public: true + RZ\Roadiz\EntityGenerator\Field\DefaultValuesResolverInterface: + class: RZ\Roadiz\CoreBundle\NodeType\DefaultValuesResolver + RZ\Roadiz\EntityGenerator\EntityGeneratorFactory: arguments: - '@RZ\Roadiz\CoreBundle\Bag\NodeTypes' + - '@RZ\Roadiz\CoreBundle\NodeType\DefaultValuesResolver' - '%roadiz_core.entity_generator_factory.options%' Gedmo\Loggable\LoggableListener: diff --git a/lib/RoadizCoreBundle/src/Console/NodeTypesDefaultValuesCommand.php b/lib/RoadizCoreBundle/src/Console/NodeTypesDefaultValuesCommand.php new file mode 100644 index 00000000..bf59585e --- /dev/null +++ b/lib/RoadizCoreBundle/src/Console/NodeTypesDefaultValuesCommand.php @@ -0,0 +1,81 @@ +defaultValuesResolver = $defaultValuesResolver; + $this->managerRegistry = $managerRegistry; + } + + protected function configure(): void + { + $this->setName('nodetypes:default-values') + ->setDescription('Get all default values for a field across all node-types.') + ->addArgument( + 'name', + InputArgument::REQUIRED, + 'Field name' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $name = $input->getArgument('name'); + + if (empty($name)) { + throw new \InvalidArgumentException('Field name must not be empty.'); + } + $enumFields = $this->managerRegistry->getRepository(NodeTypeField::class)->findBy([ + 'type' => AbstractField::ENUM_T, + ]); + $enumFieldsNames = array_unique(array_map(function (NodeTypeField $field) { + return $field->getName(); + }, $enumFields)); + + $oneField = $this->managerRegistry->getRepository(NodeTypeField::class)->findOneBy([ + 'name' => $name, + ]); + + if (!$oneField instanceof NodeTypeField) { + throw new \InvalidArgumentException('Field name must be a valid field name.'); + } + if (!$oneField->isEnum()) { + throw new \InvalidArgumentException('Field name must be an enum field. Valid fields names are: ' . implode(', ', $enumFieldsNames)); + } + + $defaultValues = $this->defaultValuesResolver->getDefaultValuesAmongAllFields($oneField); + $maxDefaultValuesLength = $this->defaultValuesResolver->getMaxDefaultValuesLengthAmongAllFields($oneField); + + $io = new SymfonyStyle($input, $output); + $io->horizontalTable(['Field name', 'Label', 'Description', 'Default values', 'Max length'], [ + [ + $oneField->getName(), + $oneField->getLabel(), + $oneField->getDescription(), + implode(', ', array_unique($defaultValues)), + $maxDefaultValuesLength + ], + ]); + return 0; + } +} diff --git a/lib/RoadizCoreBundle/src/NodeType/DefaultValuesResolver.php b/lib/RoadizCoreBundle/src/NodeType/DefaultValuesResolver.php new file mode 100644 index 00000000..022d30c1 --- /dev/null +++ b/lib/RoadizCoreBundle/src/NodeType/DefaultValuesResolver.php @@ -0,0 +1,62 @@ +managerRegistry = $managerRegistry; + $this->inheritanceType = $inheritanceType; + } + + + public function getDefaultValuesAmongAllFields(NodeTypeFieldInterface $field): array + { + /* + * With joined inheritance, we can use current field default values because + * SQL field won't be shared between all node types. + */ + if ($this->inheritanceType === Configuration::INHERITANCE_TYPE_JOINED) { + return array_map('trim', explode(',', $field->getDefaultValues())); + } else { + /* + * With single table inheritance, we need to get all default values + * from all fields of all node types. + */ + $defaultValues = []; + $nodeTypeFields = $this->managerRegistry->getRepository(NodeTypeField::class)->findBy([ + 'name' => $field->getName(), + 'type' => $field->getType(), + ]); + foreach ($nodeTypeFields as $nodeTypeField) { + $defaultValues = array_merge($defaultValues, array_map('trim', explode(',', $nodeTypeField->getDefaultValues()))); + } + return $defaultValues; + } + } + + public function getMaxDefaultValuesLengthAmongAllFields(NodeTypeFieldInterface $field): int + { + // get max length of exploded default values + $max = 0; + foreach ($this->getDefaultValuesAmongAllFields($field) as $value) { + $max = max($max, mb_strlen($value)); + } + + return $max > 0 ? $max : 250; + } +} diff --git a/src/GeneratedEntity/NSArticle.php b/src/GeneratedEntity/NSArticle.php index 2dc5494c..55c49801 100644 --- a/src/GeneratedEntity/NSArticle.php +++ b/src/GeneratedEntity/NSArticle.php @@ -185,7 +185,7 @@ public function isPublishable(): bool return true; } - public function __toString() + public function __toString(): string { return '[NSArticle] ' . parent::__toString(); } diff --git a/src/GeneratedEntity/NSBasicBlock.php b/src/GeneratedEntity/NSBasicBlock.php index 0f012f21..0e930dd8 100644 --- a/src/GeneratedEntity/NSBasicBlock.php +++ b/src/GeneratedEntity/NSBasicBlock.php @@ -214,7 +214,7 @@ public function isPublishable(): bool return false; } - public function __toString() + public function __toString(): string { return '[NSBasicBlock] ' . parent::__toString(); } diff --git a/src/GeneratedEntity/NSGroupBlock.php b/src/GeneratedEntity/NSGroupBlock.php index 3e9f6e90..17efd482 100644 --- a/src/GeneratedEntity/NSGroupBlock.php +++ b/src/GeneratedEntity/NSGroupBlock.php @@ -61,7 +61,7 @@ public function isPublishable(): bool return false; } - public function __toString() + public function __toString(): string { return '[NSGroupBlock] ' . parent::__toString(); } diff --git a/src/GeneratedEntity/NSMenu.php b/src/GeneratedEntity/NSMenu.php index 3ac4d6fa..74631f49 100644 --- a/src/GeneratedEntity/NSMenu.php +++ b/src/GeneratedEntity/NSMenu.php @@ -61,7 +61,7 @@ public function isPublishable(): bool return false; } - public function __toString() + public function __toString(): string { return '[NSMenu] ' . parent::__toString(); } diff --git a/src/GeneratedEntity/NSMenuLink.php b/src/GeneratedEntity/NSMenuLink.php index 08114549..78c64e33 100644 --- a/src/GeneratedEntity/NSMenuLink.php +++ b/src/GeneratedEntity/NSMenuLink.php @@ -164,7 +164,7 @@ public function isPublishable(): bool return false; } - public function __toString() + public function __toString(): string { return '[NSMenuLink] ' . parent::__toString(); } diff --git a/src/GeneratedEntity/NSNeutral.php b/src/GeneratedEntity/NSNeutral.php index 6994045e..7c688f2f 100644 --- a/src/GeneratedEntity/NSNeutral.php +++ b/src/GeneratedEntity/NSNeutral.php @@ -103,7 +103,7 @@ public function isPublishable(): bool return false; } - public function __toString() + public function __toString(): string { return '[NSNeutral] ' . parent::__toString(); } diff --git a/src/GeneratedEntity/NSOffer.php b/src/GeneratedEntity/NSOffer.php index 2099e3e9..e1c20e33 100644 --- a/src/GeneratedEntity/NSOffer.php +++ b/src/GeneratedEntity/NSOffer.php @@ -26,6 +26,7 @@ ORM\Entity(repositoryClass: \App\GeneratedEntity\Repository\NSOfferRepository::class), ORM\Table(name: "ns_offer"), ORM\Index(columns: ["price"]), + ORM\Index(columns: ["layout"]), ApiFilter(PropertyFilter::class) ] class NSOffer extends \RZ\Roadiz\CoreBundle\Entity\NodesSources @@ -183,6 +184,52 @@ public function setMultiGeolocation($multiGeolocation): static } + /** + * Layout. + * Default values: dark + */ + #[ + SymfonySerializer\SerializedName(serializedName: "layout"), + SymfonySerializer\Groups(["nodes_sources", "nodes_sources_default"]), + SymfonySerializer\MaxDepth(2), + ApiFilter(OrmFilter\SearchFilter::class, strategy: "exact"), + ApiFilter(\RZ\Roadiz\CoreBundle\Api\Filter\NotFilter::class), + Gedmo\Versioned, + ORM\Column( + name: "layout", + type: "string", + nullable: true, + length: 11 + ), + Serializer\Groups(["nodes_sources", "nodes_sources_default"]), + Serializer\MaxDepth(2), + Serializer\Type("string") + ] + private ?string $layout = null; + + /** + * @return string|null + */ + public function getLayout(): ?string + { + return $this->layout; + } + + /** + * @param string|null $layout + * + * @return $this + */ + public function setLayout(?string $layout): static + { + $this->layout = null !== $layout ? + (string) $layout : + null; + + return $this; + } + + #[ Serializer\VirtualProperty, Serializer\Groups(["nodes_sources", "nodes_sources_default"]), @@ -215,7 +262,7 @@ public function isPublishable(): bool return false; } - public function __toString() + public function __toString(): string { return '[NSOffer] ' . parent::__toString(); } diff --git a/src/GeneratedEntity/NSPage.php b/src/GeneratedEntity/NSPage.php index 859ae61d..81ea19e8 100644 --- a/src/GeneratedEntity/NSPage.php +++ b/src/GeneratedEntity/NSPage.php @@ -27,6 +27,7 @@ ORM\Table(name: "ns_page"), ORM\Index(columns: ["sticky"]), ORM\Index(columns: ["stickytest"]), + ORM\Index(columns: ["layout"]), ApiFilter(PropertyFilter::class) ] class NSPage extends \RZ\Roadiz\CoreBundle\Entity\NodesSources @@ -1066,6 +1067,52 @@ public function setMultiGeolocation($multiGeolocation): static } + /** + * Layout. + * Default values: dark, transparent + */ + #[ + SymfonySerializer\SerializedName(serializedName: "layout"), + SymfonySerializer\Groups(["nodes_sources", "nodes_sources_default"]), + SymfonySerializer\MaxDepth(2), + ApiFilter(OrmFilter\SearchFilter::class, strategy: "exact"), + ApiFilter(\RZ\Roadiz\CoreBundle\Api\Filter\NotFilter::class), + Gedmo\Versioned, + ORM\Column( + name: "layout", + type: "string", + nullable: true, + length: 11 + ), + Serializer\Groups(["nodes_sources", "nodes_sources_default"]), + Serializer\MaxDepth(2), + Serializer\Type("string") + ] + private ?string $layout = null; + + /** + * @return string|null + */ + public function getLayout(): ?string + { + return $this->layout; + } + + /** + * @param string|null $layout + * + * @return $this + */ + public function setLayout(?string $layout): static + { + $this->layout = null !== $layout ? + (string) $layout : + null; + + return $this; + } + + public function __construct(\RZ\Roadiz\CoreBundle\Entity\Node $node, \RZ\Roadiz\CoreBundle\Entity\Translation $translation) { parent::__construct($node, $translation); @@ -1120,7 +1167,7 @@ public function __clone() $this->usersProxy = $usersProxyClone; } - public function __toString() + public function __toString(): string { return '[NSPage] ' . parent::__toString(); } diff --git a/src/Resources/node-types/Offer.json b/src/Resources/node-types/Offer.json index eec9ee3c..758681fd 100644 --- a/src/Resources/node-types/Offer.json +++ b/src/Resources/node-types/Offer.json @@ -60,6 +60,21 @@ "excluded_from_serialization": false, "indexed": false, "visible": true + }, + { + "position": 5.0, + "name": "layout", + "label": "Layout", + "placeholder": "light", + "default_values": "dark", + "type": 15, + "expanded": false, + "node_type_name": "Offer", + "universal": true, + "exclude_from_search": true, + "excluded_from_serialization": false, + "indexed": true, + "visible": true } ], "default_ttl": 0, diff --git a/src/Resources/node-types/Page.json b/src/Resources/node-types/Page.json index 9523089d..573ce91e 100644 --- a/src/Resources/node-types/Page.json +++ b/src/Resources/node-types/Page.json @@ -301,6 +301,21 @@ "excluded_from_serialization": false, "indexed": false, "visible": true + }, + { + "position": 22.0, + "name": "layout", + "label": "Layout", + "placeholder": "light", + "default_values": "dark, transparent", + "type": 15, + "expanded": false, + "node_type_name": "Page", + "universal": true, + "exclude_from_search": true, + "excluded_from_serialization": false, + "indexed": true, + "visible": true } ], "default_ttl": 0,