Skip to content

Commit 51500c9

Browse files
authored
Merge pull request #5692 from neos/enumTransformationFactory
FEATURE: Add node migration transformation to change properties from scalar to BackedEnum
2 parents 898e07e + feb3a12 commit 51500c9

File tree

7 files changed

+255
-3
lines changed

7 files changed

+255
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
@contentrepository @adapters=DoctrineDBAL
2+
Feature: Change property type from scalar to backed enum
3+
4+
Background:
5+
Given using the following content dimensions:
6+
| Identifier | Values | Generalizations |
7+
| example | general, source, peer, spec | spec->source->general, peer->general |
8+
And using the following node types:
9+
"""yaml
10+
'Neos.ContentRepository:Root':
11+
constraints:
12+
nodeTypes:
13+
'Neos.ContentRepository.Testing:Document': true
14+
'Neos.ContentRepository.Testing:OtherDocument': true
15+
'Neos.ContentRepository.Testing:Document':
16+
properties:
17+
myString:
18+
type: string
19+
myInt:
20+
type: int
21+
"""
22+
And using identifier "default", I define a content repository
23+
And I am in content repository "default"
24+
And the command CreateRootWorkspace is executed with payload:
25+
| Key | Value |
26+
| workspaceName | "live" |
27+
| newContentStreamId | "cs-identifier" |
28+
And I am in workspace "live"
29+
And the command CreateRootNodeAggregateWithNode is executed with payload:
30+
| Key | Value |
31+
| nodeAggregateId | "lady-eleonode-rootford" |
32+
| nodeTypeName | "Neos.ContentRepository:Root" |
33+
When the command CreateNodeAggregateWithNode is executed with payload:
34+
| Key | Value |
35+
| nodeAggregateId | "nody-mc-nodeface" |
36+
| nodeTypeName | "Neos.ContentRepository.Testing:Document" |
37+
| originDimensionSpacePoint | {"example": "general"} |
38+
| parentNodeAggregateId | "lady-eleonode-rootford" |
39+
| initialPropertyValues | {"myString": "https://schema.org/Wednesday", "myInt": 42} |
40+
When the command CreateNodeVariant is executed with payload:
41+
| Key | Value |
42+
| nodeAggregateId | "nody-mc-nodeface" |
43+
| sourceOrigin | {"example":"general"} |
44+
| targetOrigin | {"example":"source"} |
45+
When the command CreateNodeVariant is executed with payload:
46+
| Key | Value |
47+
| nodeAggregateId | "nody-mc-nodeface" |
48+
| sourceOrigin | {"example":"general"} |
49+
| targetOrigin | {"example":"peer"} |
50+
51+
52+
Scenario: Change property type from scalar to backed enum creating a new target workspace
53+
When I change the node types in content repository "default" to:
54+
"""yaml
55+
'Neos.ContentRepository:Root':
56+
constraints:
57+
nodeTypes:
58+
'Neos.ContentRepository.Testing:Document': true
59+
'Neos.ContentRepository.Testing:OtherDocument': true
60+
'Neos.ContentRepository.Testing:Document':
61+
properties:
62+
myString:
63+
type: \Neos\ContentRepository\Core\Tests\Behavior\Fixtures\DayOfWeek
64+
myInt:
65+
type: \Neos\ContentRepository\Core\Tests\Behavior\Fixtures\ArbitraryNumber
66+
"""
67+
And I run the following node migration for workspace "live", creating target workspace "migration-workspace" on contentStreamId "migration-cs", without publishing on success:
68+
"""yaml
69+
migration:
70+
-
71+
filters:
72+
-
73+
type: 'NodeType'
74+
settings:
75+
nodeType: 'Neos.ContentRepository.Testing:Document'
76+
transformations:
77+
-
78+
type: 'ChangePropertyTypeFromScalarToBackedEnum'
79+
settings:
80+
property: 'myString'
81+
-
82+
type: 'ChangePropertyTypeFromScalarToBackedEnum'
83+
settings:
84+
property: 'myInt'
85+
"""
86+
87+
# the original content stream has not been touched
88+
When I am in workspace "live" and dimension space point {"example": "general"}
89+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"}
90+
And I expect this node to have the following properties:
91+
| Key | Value |
92+
| myString | https://schema.org/Wednesday |
93+
| myInt | 42 |
94+
95+
When I am in workspace "live" and dimension space point {"example": "source"}
96+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{"example": "source"}
97+
And I expect this node to have the following properties:
98+
| Key | Value |
99+
| myString | https://schema.org/Wednesday |
100+
| myInt | 42 |
101+
102+
When I am in workspace "live" and dimension space point {"example": "peer"}
103+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{"example": "peer"}
104+
And I expect this node to have the following properties:
105+
| Key | Value |
106+
| myString | https://schema.org/Wednesday |
107+
| myInt | 42 |
108+
109+
When I am in workspace "live" and dimension space point {"example": "spec"}
110+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{"example": "source"}
111+
And I expect this node to have the following properties:
112+
| Key | Value |
113+
| myString | https://schema.org/Wednesday |
114+
| myInt | 42 |
115+
116+
# the node was changed in the new content stream, in all variants
117+
When I am in workspace "migration-workspace" and dimension space point {"example": "general"}
118+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node migration-cs;nody-mc-nodeface;{"example": "general"}
119+
And I expect this node to have the following properties:
120+
| Key | Value |
121+
| myString | DayOfWeek:https://schema.org/Wednesday |
122+
| myInt | ArbitraryNumber:42 |
123+
124+
When I am in workspace "migration-workspace" and dimension space point {"example": "source"}
125+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node migration-cs;nody-mc-nodeface;{"example": "source"}
126+
And I expect this node to have the following properties:
127+
| Key | Value |
128+
| myString | DayOfWeek:https://schema.org/Wednesday |
129+
| myInt | ArbitraryNumber:42 |
130+
131+
When I am in workspace "migration-workspace" and dimension space point {"example": "peer"}
132+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node migration-cs;nody-mc-nodeface;{"example": "peer"}
133+
And I expect this node to have the following properties:
134+
| Key | Value |
135+
| myString | DayOfWeek:https://schema.org/Wednesday |
136+
| myInt | ArbitraryNumber:42 |
137+
138+
When I am in workspace "migration-workspace" and dimension space point {"example": "spec"}
139+
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node migration-cs;nody-mc-nodeface;{"example": "source"}
140+
And I expect this node to have the following properties:
141+
| Key | Value |
142+
| myString | DayOfWeek:https://schema.org/Wednesday |
143+
| myInt | ArbitraryNumber:42 |
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Neos.ContentRepository.Core package.
5+
*
6+
* (c) Contributors of the Neos Project - www.neos.io
7+
*
8+
* This package is Open Source Software. For the full copyright and license
9+
* information, please view the LICENSE file which was distributed with this
10+
* source code.
11+
*/
12+
13+
declare(strict_types=1);
14+
15+
namespace Neos\ContentRepository\Core\Tests\Behavior\Fixtures;
16+
17+
/**
18+
* An arbitrary number enumeration
19+
*/
20+
enum ArbitraryNumber: int
21+
{
22+
case NUMBER_42 = 42;
23+
case NUMBER_8472 = 8472;
24+
}

Neos.ContentRepository.Core/Tests/Behavior/Fixtures/DayOfWeek.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
namespace Neos\ContentRepository\Core\Tests\Behavior\Fixtures;
1616

1717
/**
18-
* A day of week enumeratoion
18+
* A day of week enumeration
1919
*
2020
* @see https://schema.org/DayOfWeek
2121
*/

Neos.ContentRepository.NodeMigration/src/NodeMigrationServiceFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Neos\ContentRepository\NodeMigration\Transformation\AddDimensionShineThroughTransformationFactory;
1717
use Neos\ContentRepository\NodeMigration\Transformation\AddNewPropertyConverterAwareTransformationFactory;
1818
use Neos\ContentRepository\NodeMigration\Transformation\ChangeNodeTypeTransformationFactory;
19+
use Neos\ContentRepository\NodeMigration\Transformation\ChangePropertyTypeFromScalarToBackedEnumTransformationFactory;
1920
use Neos\ContentRepository\NodeMigration\Transformation\ChangePropertyValueConverterAwareTransformationFactory;
2021
use Neos\ContentRepository\NodeMigration\Transformation\MoveDimensionSpacePointTransformationFactory;
2122
use Neos\ContentRepository\NodeMigration\Transformation\PropertyConverterAwareTransformationFactoryInterface;
@@ -58,6 +59,7 @@ public static function createDefault(): self
5859
'AddNewProperty' => AddNewPropertyConverterAwareTransformationFactory::class,
5960
'ChangeNodeType' => ChangeNodeTypeTransformationFactory::class,
6061
'ChangePropertyValue' => ChangePropertyValueConverterAwareTransformationFactory::class,
62+
'ChangePropertyTypeFromScalarToBackedEnum' => ChangePropertyTypeFromScalarToBackedEnumTransformationFactory::class,
6163
'MoveDimensionSpacePoint' => MoveDimensionSpacePointTransformationFactory::class,
6264
'RemoveNode' => RemoveNodeTransformationFactory::class,
6365
'RemoveProperty' => RemovePropertyTransformationFactory::class,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Neos.ContentRepository package.
5+
*
6+
* (c) Contributors of the Neos Project - www.neos.io
7+
*
8+
* This package is Open Source Software. For the full copyright and license
9+
* information, please view the LICENSE file which was distributed with this
10+
* source code.
11+
*/
12+
13+
declare(strict_types=1);
14+
15+
namespace Neos\ContentRepository\NodeMigration\Transformation;
16+
17+
use Neos\ContentRepository\Core\CommandHandler\Commands;
18+
use Neos\ContentRepository\Core\ContentRepository;
19+
use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties;
20+
use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite;
21+
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
22+
use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate;
23+
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
24+
25+
/**
26+
* Change a node property value from a scalar to a backed enum
27+
*/
28+
class ChangePropertyTypeFromScalarToBackedEnumTransformationFactory implements TransformationFactoryInterface
29+
{
30+
/**
31+
* @param array{property: string} $settings
32+
*/
33+
public function build(
34+
array $settings,
35+
ContentRepository $contentRepository,
36+
): GlobalTransformationInterface|NodeAggregateBasedTransformationInterface|NodeBasedTransformationInterface
37+
{
38+
return new class (
39+
$settings['property'],
40+
$contentRepository->getNodeTypeManager(),
41+
) implements NodeAggregateBasedTransformationInterface {
42+
public function __construct(
43+
/**
44+
* Name of the property to be transformed
45+
*/
46+
private readonly string $property,
47+
48+
private readonly NodeTypeManager $nodeTypeManager,
49+
)
50+
{
51+
}
52+
53+
public function execute(
54+
NodeAggregate $nodeAggregate,
55+
WorkspaceName $workspaceNameForWriting
56+
): TransformationStep {
57+
/** @var class-string<\BackedEnum> $newType */
58+
$newType = $this->nodeTypeManager->getNodeType($nodeAggregate->nodeTypeName)
59+
?->getPropertyType($this->property);
60+
$commands = [];
61+
foreach ($nodeAggregate->getNodes() as $node) {
62+
$propertyValue = $node->properties[$this->property];
63+
if ($propertyValue === null) {
64+
continue;
65+
}
66+
$commands[] = SetNodeProperties::create(
67+
$workspaceNameForWriting,
68+
$node->aggregateId,
69+
$node->originDimensionSpacePoint,
70+
PropertyValuesToWrite::fromArray([
71+
$this->property => $newType::from($propertyValue),
72+
]),
73+
);
74+
}
75+
76+
return TransformationStep::fromCommands(Commands::fromArray($commands));
77+
}
78+
};
79+
}
80+
}

Neos.ContentRepository.NodeMigration/src/Transformation/RenamePropertyTransformationFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
2323

2424
/**
25-
* Remove the property
25+
* Rename a node property
2626
*/
2727
class RenamePropertyTransformationFactory implements TransformationFactoryInterface
2828
{

Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
3333
use Neos\ContentRepository\Core\SharedModel\Node\PropertyName;
3434
use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace;
35+
use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\ArbitraryNumber;
3536
use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\DayOfWeek;
3637
use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PostalAddress;
3738
use Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PriceSpecification;
@@ -379,7 +380,7 @@ public function iExpectThisNodeToHaveTheFollowingProperties(TableNode $expectedP
379380
});
380381
}
381382

382-
private function resolvePropertyValue(string $serializedPropertyValue)
383+
private function resolvePropertyValue(string $serializedPropertyValue): mixed
383384
{
384385
switch ($serializedPropertyValue) {
385386
case 'PostalAddress:dummy':
@@ -399,6 +400,8 @@ private function resolvePropertyValue(string $serializedPropertyValue)
399400
return new Uri(\mb_substr($serializedPropertyValue, 4));
400401
} elseif (\str_starts_with($serializedPropertyValue, 'DayOfWeek:')) {
401402
return DayOfWeek::from(\mb_substr($serializedPropertyValue, 10));
403+
} elseif (\str_starts_with($serializedPropertyValue, 'ArbitraryNumber:')) {
404+
return ArbitraryNumber::from((int)\mb_substr($serializedPropertyValue, 16));
402405
}
403406
}
404407

0 commit comments

Comments
 (0)