Skip to content

Commit 994bdb0

Browse files
committed
TASK: Create nodeInterfaces in a first loop and the nodeObjects in a afterwards
The decision which interfaces are rendered for a NodeObject is made by checking the existing of a NodeInterface for each Supertype. That way Interfaces from foreign packages are used directly. Also a NodeObjectFactory is introduced and the NodeObjectInterface is moved to the root of the package classes folder
1 parent c2ffcb1 commit 994bdb0

11 files changed

+356
-194
lines changed

Classes/Command/NodeObjectsCommandController.php

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
namespace PackageFactory\NodeTypeObjects\Command;
66

7+
use Neos\ContentRepository\Core\NodeType\NodeType;
78
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
89
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
910
use Neos\Flow\Cli\CommandController;
1011
use Neos\Flow\Package\FlowPackageInterface;
1112
use Neos\Flow\Package\GenericPackage;
1213
use Neos\Flow\Package\PackageManager;
1314
use Neos\Utility\Files;
15+
use PackageFactory\NodeTypeObjects\Domain\NodeInterfaceNameSpecification;
16+
use PackageFactory\NodeTypeObjects\Domain\NodeInterfaceSpecification;
1417
use PackageFactory\NodeTypeObjects\Domain\NodeObjectNameSpecification;
1518
use PackageFactory\NodeTypeObjects\Domain\NodeObjectNameSpecificationCollection;
1619
use PackageFactory\NodeTypeObjects\Domain\NodeObjectSpecification;
@@ -39,14 +42,9 @@ public function injectContentRepositoryRegistry(ContentRepositoryRegistry $conte
3942
*/
4043
public function cleanCommand(string $packageKey): void
4144
{
42-
$package = $this->findFlowPackageByPackageKey($packageKey);
45+
$package = $this->getPackage($packageKey);
4346

44-
if ($package === null) {
45-
$this->output->outputLine('No packages found for packageKeys <error>"%s"</error>:', [$packageKey]);
46-
$this->quit(1);
47-
} else {
48-
$this->output->outputLine('Removing NodeObjects and NodeInterfaces from packages <info>"%s"</info>:', [$package->getPackageKey()]);
49-
}
47+
$this->output->outputLine('Removing NodeObjects and NodeInterfaces from package <info>"%s"</info>:', [$packageKey]);
5048

5149
$packagePath = $package->getPackagePath();
5250
if (!file_exists($packagePath . DIRECTORY_SEPARATOR . 'NodeTypes')) {
@@ -80,55 +78,53 @@ public function cleanCommand(string $packageKey): void
8078
*/
8179
public function buildCommand(string $packageKey, string $crId = 'default'): void
8280
{
83-
$package = $this->findFlowPackageByPackageKey($packageKey);
81+
$package = $this->getPackage($packageKey);
8482

85-
if ($package === null) {
86-
$this->output->outputLine('No packages found for packageKeys <error>"%s"</error>:', [$packageKey]);
87-
$this->quit(1);
88-
} else {
89-
$this->output->outputLine('Building NodeObjects and NodeInterfaces for package <info>"%s"</info>:', [$package->getPackageKey()]);
90-
}
83+
$this->output->outputLine('Building NodeObjects and NodeInterfaces for package <info>"%s"</info>:', [$packageKey]);
9184

9285
$contentRepository = $this->contentRepositoryRegistry->get(ContentRepositoryId::fromString($crId));
9386
$nodeTypeManager = $contentRepository->getNodeTypeManager();
94-
$nodeTypes = $nodeTypeManager->getNodeTypes(true);
95-
$nameSpecifications = [];
96-
foreach ($nodeTypes as $nodeType) {
97-
if (!str_starts_with($nodeType->name->value, $package->getPackageKey() . ':')) {
98-
continue;
99-
}
100-
$nameSpecifications[$nodeType->name->value] = NodeObjectNameSpecification::createFromNodeType($nodeType);
101-
}
102-
$nameSpecificationsCollection = new NodeObjectNameSpecificationCollection(...$nameSpecifications);
10387

104-
// loop 1 build interfaces
105-
// loop 2 build objects
106-
foreach ($nodeTypes as $nodeType) {
107-
if (!str_starts_with($nodeType->name->value, $package->getPackageKey() . ':')) {
108-
continue;
109-
}
88+
// loop 1 build interfaces for all nodetypes in package, this is done first as in the next step
89+
// the node objects will create implements statements for all existing interfaces even those from other packages
11090

111-
$specification = NodeObjectSpecification::createFromPackageAndNodeType($package, $nodeType, $nameSpecificationsCollection);
91+
$this->output->outputLine();
92+
$this->output->outputLine('Creating NodeInterfaces');
93+
$this->output->outputLine();
11294

113-
Files::createDirectoryRecursively($specification->directory);
95+
$nodeTypes = array_filter(
96+
$nodeTypeManager->getNodeTypes(true),
97+
fn (NodeType $nodeType) => str_starts_with($nodeType->name->value, $packageKey . ':')
98+
);
11499

115-
$generatedFiles = [];
116-
if ($specification->classFilename) {
117-
file_put_contents(
118-
$specification->classFilename,
119-
$specification->toPhpClassString()
120-
);
121-
$generatedFiles[] = $specification->names->fullyQualifiedClassName;
122-
}
123-
if ($specification->interfaceFilename) {
124-
file_put_contents(
125-
$specification->interfaceFilename,
126-
$specification->toPhpInterfaceString()
127-
);
128-
$generatedFiles[] = $specification->names->fullyQualifiedInterfaceName;
129-
}
100+
foreach ($nodeTypes as $nodeType) {
101+
$interfaceSpecification = NodeInterfaceSpecification::createFromPackageAndNodeType($package, $nodeType);
102+
Files::createDirectoryRecursively($interfaceSpecification->directory);
103+
file_put_contents(
104+
$interfaceSpecification->interfaceFilename,
105+
$interfaceSpecification->toPhpString()
106+
);
107+
$this->outputLine(' - ' . $interfaceSpecification->interfaceName->nodeTypeName . ' -> <info>' . $interfaceSpecification->interfaceFilename . '</info>');
108+
}
130109

131-
$this->outputLine(' - ' . $specification->names->nodeTypeName . ' -> <info>' . implode(', ', $generatedFiles) . '</info>');
110+
// loop 2 build objects for all non abstract nodetypes in package
111+
$this->output->outputLine();
112+
$this->output->outputLine('Creating NodeObjects');
113+
$this->output->outputLine();
114+
115+
$nonAbstractNodeTypes = array_filter(
116+
$nodeTypeManager->getNodeTypes(false),
117+
fn (NodeType $nodeType) => str_starts_with($nodeType->name->value, $packageKey . ':')
118+
);
119+
120+
foreach ($nonAbstractNodeTypes as $nodeType) {
121+
$objectSpecification = NodeObjectSpecification::createFromPackageAndNodeType($package, $nodeType);
122+
Files::createDirectoryRecursively($objectSpecification->directory);
123+
file_put_contents(
124+
$objectSpecification->classFilename,
125+
$objectSpecification->toPhpString()
126+
);
127+
$this->outputLine(' - ' . $objectSpecification->objectName->nodeTypeName . ' -> <info>' . $objectSpecification->objectName->fullyQualifiedClassName . '</info>');
132128
}
133129
}
134130

@@ -179,16 +175,4 @@ protected function getPackage(string $packageKey): FlowPackageInterface & Generi
179175
}
180176
return $package;
181177
}
182-
183-
protected function findFlowPackageByPackageKey(string $packageKey): ?FlowPackageInterface
184-
{
185-
if ($this->packageManager->isPackageAvailable($packageKey) === false) {
186-
return null;
187-
}
188-
$package = $this->packageManager->getPackage($packageKey);
189-
if ($package instanceof FlowPackageInterface) {
190-
return $package;
191-
}
192-
return null;
193-
}
194178
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PackageFactory\NodeTypeObjects\Domain;
6+
7+
use Neos\ContentRepository\Core\NodeType\NodeType;
8+
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
9+
use Neos\Flow\Annotations as Flow;
10+
11+
#[Flow\Proxy(false)]
12+
readonly class NodeInterfaceNameSpecification
13+
{
14+
public function __construct(
15+
public string $nodeTypeName,
16+
public string $phpNamespace,
17+
public string $interfaceName,
18+
public string $fullyQualifiedInterfaceName,
19+
) {
20+
}
21+
22+
public static function createFromNodeTypeName(
23+
NodeTypeName $nodeTypeName
24+
): self {
25+
26+
list($packageKey, $nodeName) = explode(':', $nodeTypeName->value, 2);
27+
28+
$localNameParts = explode('.', $nodeName);
29+
$localName = array_pop($localNameParts);
30+
31+
$phpNamespace = str_replace(['.', ':'], ['\\', '\\NodeTypes\\'], $nodeTypeName->value);
32+
$interfaceName = str_replace('.', '\\', $localName) . 'NodeInterface';
33+
34+
return new self(
35+
$nodeTypeName->value,
36+
$phpNamespace,
37+
$interfaceName,
38+
$phpNamespace . '\\' . $interfaceName
39+
);
40+
}
41+
42+
public static function createFromNodeType(
43+
NodeType $nodeType
44+
): self {
45+
return self::createFromNodeTypeName($nodeType->name);
46+
}
47+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PackageFactory\NodeTypeObjects\Domain;
6+
7+
use Behat\Gherkin\Node\NodeInterface;
8+
use Neos\ContentRepository\Core\NodeType\NodeType;
9+
use Neos\Flow\Annotations as Flow;
10+
11+
#[Flow\Proxy(false)]
12+
readonly class NodeInterfaceNameSpecificationCollection
13+
{
14+
/**
15+
* @var array<string,NodeInterfaceNameSpecification>
16+
*/
17+
public array $items;
18+
19+
public function __construct(
20+
NodeInterfaceNameSpecification ...$items
21+
) {
22+
$itemsIndexedByName = [];
23+
foreach ($items as $nodeTypeObjectNameSpecification) {
24+
$itemsIndexedByName[ $nodeTypeObjectNameSpecification->nodeTypeName ] = $nodeTypeObjectNameSpecification;
25+
}
26+
$this->items = $itemsIndexedByName;
27+
}
28+
29+
public function findByNodeTypeName(string $nodeTypeName): ?NodeInterfaceNameSpecification
30+
{
31+
if (array_key_exists($nodeTypeName, $this->items)) {
32+
return $this->items[$nodeTypeName];
33+
}
34+
return null;
35+
}
36+
37+
public function combine(self $other): self
38+
{
39+
return new self(...$this->items, ...$other->items);
40+
}
41+
42+
public static function createFromNodeType(NodeType $nodeType, bool $checkForExistence = false): self
43+
{
44+
$interfaces = [
45+
NodeInterfaceNameSpecification::createFromNodeType($nodeType)
46+
];
47+
foreach ($nodeType->getDeclaredSuperTypes() as $superType) {
48+
$interface = NodeInterfaceNameSpecification::createFromNodeType($superType);
49+
if ($checkForExistence && interface_exists($interface->fullyQualifiedInterfaceName)) {
50+
$interfaces[] = $interface;
51+
} else {
52+
$interfaces[] = $interface;
53+
}
54+
}
55+
return new self(...$interfaces);
56+
}
57+
58+
public function asImplementsStatement(): string
59+
{
60+
if (empty($this->items)) {
61+
return '';
62+
} else {
63+
return 'implements ' . implode(', ', array_map(fn(NodeInterfaceNameSpecification $item)=> $item->fullyQualifiedInterfaceName, $this->items));
64+
}
65+
}
66+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PackageFactory\NodeTypeObjects\Domain;
6+
7+
use Neos\ContentRepository\Core\NodeType\NodeType;
8+
use Neos\Flow\Annotations as Flow;
9+
use Neos\Flow\Package\FlowPackageInterface;
10+
11+
#[Flow\Proxy(false)]
12+
readonly class NodeInterfaceSpecification
13+
{
14+
public function __construct(
15+
public NodeInterfaceNameSpecification $interfaceName,
16+
public NodeInterfaceNameSpecificationCollection $interfaceNames,
17+
public NodePropertySpecificationCollection $properties,
18+
public string $directory,
19+
public string $interfaceFilename,
20+
) {
21+
}
22+
23+
public static function createFromPackageAndNodeType(
24+
FlowPackageInterface $package,
25+
NodeType $nodeType,
26+
): self {
27+
28+
if (!str_starts_with($nodeType->name->value, $package->getPackageKey() . ':')) {
29+
throw new \Exception("Only nodetypes from the given package are allowed");
30+
}
31+
32+
$nameSpecification = NodeInterfaceNameSpecification::createFromNodeType($nodeType);
33+
34+
$localNameParts = explode('.', str_replace($package->getPackageKey() . ':', '', $nodeType->name->value));
35+
$localName = array_pop($localNameParts);
36+
$localNamespace = implode('.', $localNameParts);
37+
38+
$directory = $package->getPackagePath()
39+
. 'NodeTypes' . DIRECTORY_SEPARATOR
40+
. ($localNamespace ? str_replace('.', DIRECTORY_SEPARATOR, $localNamespace) . DIRECTORY_SEPARATOR : '')
41+
. $localName;
42+
43+
$interfaceFileName = $directory . DIRECTORY_SEPARATOR . $nameSpecification->interfaceName . '.php';
44+
45+
return new self(
46+
$nameSpecification,
47+
NodeInterfaceNameSpecificationCollection::createFromNodeType($nodeType),
48+
NodePropertySpecificationCollection::createFromNodeType($nodeType),
49+
$directory,
50+
$interfaceFileName
51+
);
52+
}
53+
54+
public function toPhpString(): ?string
55+
{
56+
57+
$propertyAccessors = '';
58+
$internalPropertyAccessors = '';
59+
60+
foreach ($this->properties as $property) {
61+
$propertyIsInternal = str_starts_with($property->propertyName, '_');
62+
if ($propertyIsInternal) {
63+
$internalPropertyAccessors .= $property->toPhpInterfaceMethodString();
64+
} else {
65+
$propertyAccessors .= $property->toPhpInterfaceMethodString();
66+
}
67+
}
68+
69+
$class = <<<EOL
70+
<?php
71+
72+
declare(strict_types=1);
73+
74+
namespace {$this->interfaceName->phpNamespace};
75+
76+
use Neos\ContentRepository\Domain\Model\NodeInterface;
77+
use Neos\Flow\Annotations as Flow;
78+
79+
/**
80+
* AUTOGENERATED CODE ... DO NOT MODIFY !!!
81+
*
82+
* run `./flow nodetypeobjects:build` to regenerate this
83+
*/
84+
interface {$this->interfaceName->interfaceName}
85+
{
86+
// property accessors
87+
$propertyAccessors
88+
89+
// internal property accessors
90+
$internalPropertyAccessors
91+
}
92+
93+
EOL;
94+
95+
return $class;
96+
}
97+
}

0 commit comments

Comments
 (0)