Skip to content

Commit 65245cc

Browse files
authored
Merge pull request #13 from sitegeist/feature/addMigrationsToTransformExistingData
FEATURE: Add transformations to allow to modify existing node properties via migration
2 parents 751b7f6 + fc89609 commit 65245cc

21 files changed

+1032
-17
lines changed

.github/workflows/build.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ jobs:
4343
run: composer require neos/neos ^${{ matrix.neos-versions }} --no-progress --no-interaction
4444

4545
- name: Run Tests
46-
run: composer test
46+
if: ${{! startsWith(matrix.neos-versions, '9') }}
47+
run: composer test8
48+
49+
- name: Run Tests
50+
if: ${{startsWith(matrix.neos-versions, '9') }}
51+
run: composer test9

Classes/Eel/ValueObjectHelper.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@ public function unwrapProxyCollection(?ImageSourceProxyCollection $proxyCollecti
3636
return $this->imageSourceFactory->createFromProxyCollection($proxyCollection);
3737
}
3838

39+
public function combineCollection(ImageSourceProxyCollection|ImageSourceProxy|null ...$items): ImageSourceProxyCollection
40+
{
41+
/**
42+
* @var ImageSourceProxy[] $proxies
43+
*/
44+
$proxies = [];
45+
foreach ($items as $item) {
46+
if ($item instanceof ImageSourceProxy) {
47+
$proxies[] = $item;
48+
} elseif ($item instanceof ImageSourceProxyCollection) {
49+
$proxies = array_merge($proxies, $item->items);
50+
}
51+
}
52+
return new ImageSourceProxyCollection(...$proxies);
53+
}
54+
55+
/**
56+
* @param ImageSourceProxyCollection|ImageSourceProxy|null ...$items
57+
* @return ImageSourceInterface[]
58+
*/
59+
public function combineAndUnwrapCollection(ImageSourceProxyCollection|ImageSourceProxy|null ...$items): array
60+
{
61+
return $this->unwrapProxyCollection($this->combineCollection(...$items));
62+
}
63+
3964
public function allowsCallOfMethod($methodName)
4065
{
4166
return true;

Classes/ImageAssetProxy.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,61 @@
44

55
namespace Sitegeist\Kaleidoscope\ValueObjects;
66

7+
use Doctrine\Persistence\Proxy;
78
use Neos\Flow\Annotations as Flow;
9+
use Neos\Media\Domain\Model\AssetInterface;
10+
use Neos\Media\Domain\Model\Image;
11+
use Neos\Media\Domain\Model\ImageVariant;
812

13+
/**
14+
* @phpstan-type assetProxyShapeV8 array{__identifier: string, __flow_object_type: class-string}
15+
* @phpstan-type assetProxyShapeV9 array{identifier: string, classname: class-string}
16+
* @phpstan-type assetProxyShape assetProxyShapeV8|assetProxyShapeV9
17+
*/
918
#[Flow\Proxy(false)]
1019
final class ImageAssetProxy implements \JsonSerializable
1120
{
21+
/**
22+
* @param class-string $classname
23+
*/
1224
public function __construct(
1325
public readonly string $identifier,
1426
public readonly string $classname
1527
) {
1628
}
1729

30+
/**
31+
* @param assetProxyShape $data
32+
* @return self
33+
*/
1834
public static function fromArray(array $data): self
1935
{
2036
return new self(
37+
/** @phpstan-ignore offsetAccess.notFound*/
2138
$data['__identifier'] ?? $data['identifier'],
39+
/** @phpstan-ignore offsetAccess.notFound*/
2240
$data['__flow_object_type'] ?? $data['classname'],
2341
);
2442
}
2543

44+
public static function fromAsset(Image|ImageVariant $asset): self
45+
{
46+
if ($asset instanceof Proxy) {
47+
/** @var class-string $className */
48+
$className = get_parent_class($asset);
49+
} else {
50+
/** @var class-string $className */
51+
$className = get_class($asset);
52+
}
53+
return new self(
54+
$asset->getIdentifier(),
55+
$className
56+
);
57+
}
58+
59+
/**
60+
* @return assetProxyShape
61+
*/
2662
public function jsonSerialize(): array
2763
{
2864
return [

Classes/ImageSourceProxy.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
use Neos\Flow\Annotations as Flow;
88

9+
/**
10+
* @phpstan-import-type assetProxyShape from ImageAssetProxy
11+
* @phpstan-type imagesourceProxyShape array{asset:assetProxyShape, alt?:string, title?: string}
12+
*/
913
#[Flow\Proxy(false)]
1014
final class ImageSourceProxy implements \JsonSerializable
1115
{
@@ -16,6 +20,10 @@ public function __construct(
1620
) {
1721
}
1822

23+
/**
24+
* @param imagesourceProxyShape|array{} $data
25+
* @return self|null
26+
*/
1927
public static function fromArray(array $data): ?self
2028
{
2129
// empty arrays are effectively null values and should be persisted as such
@@ -29,6 +37,9 @@ public static function fromArray(array $data): ?self
2937
);
3038
}
3139

40+
/**
41+
* @return imagesourceProxyShape
42+
*/
3243
public function jsonSerialize(): array
3344
{
3445
return [

Classes/ImageSourceProxyCollection.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
use Neos\Flow\Annotations as Flow;
88

9+
/**
10+
* @phpstan-import-type imagesourceProxyShape from ImageSourceProxy
11+
* @phpstan-type imagesourceProxycollectionShape imagesourceProxyShape[]
12+
* @implements \IteratorAggregate<int, ImageSourceProxy>
13+
*/
914
#[Flow\Proxy(false)]
1015
class ImageSourceProxyCollection implements \IteratorAggregate, \Countable, \JsonSerializable
1116
{
@@ -20,6 +25,45 @@ public function __construct(
2025
$this->items = $items;
2126
}
2227

28+
public function isEmpty(): bool
29+
{
30+
return empty($this->items);
31+
}
32+
33+
public function getFirst(): ?ImageSourceProxy
34+
{
35+
if (count($this->items) > 0) {
36+
return $this->items[array_key_first($this->items)];
37+
}
38+
return null;
39+
}
40+
41+
public function getRandom(): ?ImageSourceProxy
42+
{
43+
if (count($this->items) > 0) {
44+
$randomKey = array_rand($this->items);
45+
return $this->items[$randomKey];
46+
}
47+
return null;
48+
}
49+
50+
public function withRandomOrder(): self
51+
{
52+
$items = $this->items;
53+
shuffle($items);
54+
return new self(...$items);
55+
}
56+
57+
public function widthAppendedCollection(ImageSourceProxyCollection $collection): self
58+
{
59+
return new self(...$this->items, ...$collection->items);
60+
}
61+
62+
public function widthAppendedItem(ImageSourceProxy $item): self
63+
{
64+
return new self(...$this->items, ...[$item]);
65+
}
66+
2367
/**
2468
* @return \Traversable<ImageSourceProxy>
2569
*/
@@ -33,6 +77,10 @@ public function count(): int
3377
return count($this->items);
3478
}
3579

80+
/**
81+
* @param imagesourceProxycollectionShape $data
82+
* @return self
83+
*/
3684
public static function fromArray(array $data): self
3785
{
3886
$items = array_map(
@@ -42,6 +90,9 @@ public static function fromArray(array $data): self
4290
return new self(...array_filter($items));
4391
}
4492

93+
/**
94+
* @return imagesourceProxycollectionShape
95+
*/
4596
public function jsonSerialize(): array
4697
{
4798
return array_map(
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sitegeist\Kaleidoscope\ValueObjects\Migration\Transformations;
6+
7+
use Neos\ContentRepository\Domain\Model\NodeData;
8+
use Neos\ContentRepository\Migration\Transformations\AbstractTransformation;
9+
use Neos\Media\Domain\Model\AssetInterface;
10+
use Neos\Media\Domain\Model\Image;
11+
use Neos\Media\Domain\Model\ImageVariant;
12+
use Sitegeist\Kaleidoscope\ValueObjects\ImageAssetProxy;
13+
use Sitegeist\Kaleidoscope\ValueObjects\ImageSourceProxy;
14+
use Sitegeist\Kaleidoscope\ValueObjects\ImageSourceProxyCollection;
15+
16+
class AssetsToImageSourceProxyCollection extends AbstractTransformation
17+
{
18+
/**
19+
* @var string
20+
*/
21+
protected $sourceProperty;
22+
23+
/**
24+
* @var string
25+
*/
26+
protected $targetProperty;
27+
28+
public function setSourceProperty(string $sourceProperty): void
29+
{
30+
$this->sourceProperty = $sourceProperty;
31+
}
32+
33+
public function setTargetProperty(string $targetProperty): void
34+
{
35+
$this->targetProperty = $targetProperty;
36+
}
37+
38+
/**
39+
* If the given node has the property this transformation should work on, this
40+
* returns true.
41+
*/
42+
public function isTransformable(NodeData $node): bool
43+
{
44+
if (
45+
$node->hasProperty($this->sourceProperty)
46+
&& $node->hasProperty($this->targetProperty)
47+
) {
48+
$sourceValue = $node->getProperty($this->sourceProperty);
49+
if (is_array($sourceValue)) {
50+
foreach ($sourceValue as $sourceItem) {
51+
if (!$sourceItem instanceof AssetInterface) {
52+
return false;
53+
}
54+
}
55+
return true;
56+
}
57+
}
58+
return false;
59+
}
60+
61+
62+
/**
63+
* from: Asset[]
64+
* to: Sitegeist\\Kaleidoscope\\ValueObjects\\ImageSourceProxyCollection
65+
**/
66+
public function execute(NodeData $node): NodeData
67+
{
68+
/**
69+
* @var array<Image|ImageVariant> $sourceValue
70+
*/
71+
$sourceValue = $node->getProperty($this->sourceProperty);
72+
$targetValue = new ImageSourceProxyCollection(
73+
...array_map(
74+
fn(Image|ImageVariant $asset) => new ImageSourceProxy(ImageAssetProxy::fromAsset($asset), "", ""),
75+
$sourceValue
76+
)
77+
);
78+
$node->setProperty($this->targetProperty, $targetValue);
79+
return $node;
80+
}
81+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sitegeist\Kaleidoscope\ValueObjects\Migration\Transformations;
6+
7+
use Neos\ContentRepository\Domain\Model\NodeData;
8+
use Neos\ContentRepository\Migration\Transformations\AbstractTransformation;
9+
use Neos\Flow\Annotations as Flow;
10+
use Sitegeist\Kaleidoscope\ValueObjects\Factory\ImageAssetFactory;
11+
use Sitegeist\Kaleidoscope\ValueObjects\ImageSourceProxy;
12+
use Sitegeist\Kaleidoscope\ValueObjects\ImageSourceProxyCollection;
13+
14+
class ImageSourceProxyCollectionToAssets extends AbstractTransformation
15+
{
16+
/**
17+
* @var string
18+
*/
19+
protected $sourceProperty;
20+
21+
/**
22+
* @var string
23+
*/
24+
protected $targetProperty;
25+
26+
#[Flow\Inject]
27+
protected ImageAssetFactory $imageAssetFactory;
28+
29+
public function setSourceProperty(string $sourceProperty): void
30+
{
31+
$this->sourceProperty = $sourceProperty;
32+
}
33+
34+
public function setTargetProperty(string $targetProperty): void
35+
{
36+
$this->targetProperty = $targetProperty;
37+
}
38+
39+
/**
40+
* If the given node has the property this transformation should work on, this
41+
* returns true.
42+
*/
43+
public function isTransformable(NodeData $node): bool
44+
{
45+
if (
46+
$node->hasProperty($this->sourceProperty)
47+
&& $node->hasProperty($this->targetProperty)
48+
) {
49+
$sourceValue = $node->getProperty($this->sourceProperty);
50+
if ($sourceValue instanceof ImageSourceProxyCollection) {
51+
return true;
52+
}
53+
}
54+
return false;
55+
}
56+
57+
/**
58+
* from: Sitegeist\\Kaleidoscope\\ValueObjects\\ImageSourceProxyCollection
59+
* to: Asset[]
60+
*/
61+
public function execute(NodeData $node): NodeData
62+
{
63+
/**
64+
* @var ImageSourceProxyCollection $sourceValue
65+
*/
66+
$sourceValue = $node->getProperty($this->sourceProperty);
67+
$targetValue = array_map(
68+
function (ImageSourceProxy $item) {
69+
return $this->imageAssetFactory->tryCreateFromProxy($item->asset);
70+
},
71+
$sourceValue->items
72+
);
73+
$node->setProperty($this->targetProperty, $targetValue);
74+
return $node;
75+
}
76+
}

0 commit comments

Comments
 (0)