Skip to content

Commit 5ef798e

Browse files
committed
WIP [FEATURE] Add iterator for TypoScript files
1 parent a1b20f4 commit 5ef798e

5 files changed

+220
-0
lines changed
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Symfony\Component\DependencyInjection\ContainerBuilder;
6+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
7+
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
8+
9+
return static function (ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void {
10+
$services = $containerConfigurator->services();
11+
$services->defaults()
12+
->autowire()
13+
->autoconfigure();
14+
15+
$services->load('a9f\\FractorTypoScript\\', __DIR__ . '/../src/');
16+
17+
$services->set(Helmich\TypoScriptParser\Tokenizer\Tokenizer::class);
18+
$services->set(Helmich\TypoScriptParser\Parser\Parser::class)
19+
->arg('$tokenizer', service(Helmich\TypoScriptParser\Tokenizer\Tokenizer::class));
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorTypoScript\Contract;
6+
7+
use a9f\FractorTypoScript\TypoScriptFileProcessor;
8+
use a9f\FractorTypoScript\TypoScriptStatementsIterator;
9+
use Helmich\TypoScriptParser\Parser\AST\Statement;
10+
11+
/**
12+
* Interface for node visitors. Will be called for each node in the tree.
13+
*/
14+
interface TypoScriptNodeVisitor
15+
{
16+
public function beforeTraversal(array $statements): void;
17+
/**
18+
* @return Statement|list<Statement>|TypoScriptStatementsIterator::*
19+
*/
20+
public function enterNode(Statement $node): Statement|array|int;
21+
22+
public function leaveNode(Statement $node): void;
23+
24+
public function afterTraversal(array $statements): void;
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorTypoScript;
6+
7+
use a9f\FractorTypoScript\Contract\TypoScriptNodeVisitor;
8+
use Helmich\TypoScriptParser\Parser\AST\Statement;
9+
use Webmozart\Assert\Assert;
10+
11+
final readonly class TypoScriptStatementsIterator
12+
{
13+
/**
14+
* @var int
15+
*/
16+
public const REMOVE_NODE = 3;
17+
18+
/**
19+
* @var array<TypoScriptNodeVisitor>
20+
*/
21+
private iterable $visitors;
22+
23+
/**
24+
* @param list<TypoScriptNodeVisitor> $visitors
25+
*/
26+
public function __construct(
27+
iterable $visitors
28+
) {
29+
$visitors = iterator_to_array($visitors);
30+
Assert::allIsInstanceOf($visitors, TypoScriptNodeVisitor::class);
31+
$this->visitors = $visitors;
32+
}
33+
34+
public function traverseDocument(array $statements): void
35+
{
36+
foreach ($this->visitors as $visitor) {
37+
$visitor->beforeTraversal($statements);
38+
}
39+
40+
foreach ($statements as $statement) {
41+
$this->traverseNode($statement);
42+
}
43+
44+
foreach ($this->visitors as $visitor) {
45+
$visitor->afterTraversal($statements);
46+
}
47+
}
48+
49+
private function traverseNode(Statement $node): void
50+
{
51+
$nodeRemoved = false;
52+
foreach ($this->visitors as $visitor) {
53+
$result = $visitor->enterNode($node);
54+
}
55+
56+
foreach ($this->visitors as $visitor) {
57+
$visitor->leaveNode($node);
58+
}
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorTypoScript\Tests\Fixture;
6+
7+
use a9f\FractorTypoScript\Contract\TypoScriptNodeVisitor;
8+
use Helmich\TypoScriptParser\Parser\AST\Statement;
9+
10+
final class StatementCollectingVisitor implements TypoScriptNodeVisitor {
11+
/**
12+
* @param list<non-empty-string> $calls
13+
*/
14+
public function __construct(
15+
private readonly string $visitorName,
16+
public array &$calls // only public to please PHPStan
17+
) {
18+
}
19+
20+
/**
21+
* @param list<Statement> $statements
22+
*/
23+
public function beforeTraversal(array $statements): void
24+
{
25+
$this->calls[] = sprintf('%s:beforeTraversal:%s', $this->visitorName, count($statements));
26+
}
27+
28+
public function enterNode(Statement $node): Statement|array|int
29+
{
30+
$this->calls[] = sprintf('%s:enterNode:%s:l-%d', $this->visitorName, get_class($node), $node->sourceLine);
31+
return $node;
32+
}
33+
34+
public function leaveNode(Statement $node): void
35+
{
36+
$this->calls[] = sprintf('%s:leaveNode:%s:l-%d', $this->visitorName, get_class($node), $node->sourceLine);
37+
}
38+
39+
/**
40+
* @param list<Statement> $statements
41+
*/
42+
public function afterTraversal(array $statements): void
43+
{
44+
$this->calls[] = sprintf('%s:afterTraversal:%s', $this->visitorName, count($statements));
45+
}
46+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorTypoScript;
6+
7+
use a9f\Fractor\DependencyInjection\ContainerContainerBuilder;
8+
use a9f\Fractor\Exception\ShouldNotHappenException;
9+
use a9f\FractorTypoScript\Tests\Fixture\StatementCollectingVisitor;
10+
use Helmich\TypoScriptParser\Parser\Parser;
11+
use PHPUnit\Framework\Attributes\Test;
12+
use PHPUnit\Framework\TestCase;
13+
use Psr\Container\ContainerInterface;
14+
15+
final class TypoScriptStatementsIteratorTest extends TestCase
16+
{
17+
private ?ContainerInterface $currentContainer = null;
18+
19+
protected function setUp(): void
20+
{
21+
parent::setUp();
22+
23+
$this->currentContainer = (new ContainerContainerBuilder())
24+
->createDependencyInjectionContainer(__DIR__ . '/config/fractor.php', [
25+
__DIR__ . '/config/config.php',
26+
]);
27+
}
28+
29+
#[Test]
30+
public function visitorsAreCalledForAllStatements(): void
31+
{
32+
$parser = $this->getService(Parser::class);
33+
$nodes = $parser->parseString(<<<TS
34+
page = PAGE
35+
page.10 = TEXT
36+
page.10.value = Hello World!
37+
TS);
38+
39+
$calls = [];
40+
$subject = new TypoScriptStatementsIterator([new StatementCollectingVisitor('statements', $calls)]);
41+
$subject->traverseDocument($nodes);
42+
43+
self::assertSame([
44+
'statements:beforeTraversal:3',
45+
'statements:enterNode:Helmich\TypoScriptParser\Parser\AST\Operator\Assignment:l-1',
46+
'statements:leaveNode:Helmich\TypoScriptParser\Parser\AST\Operator\Assignment:l-1',
47+
'statements:enterNode:Helmich\TypoScriptParser\Parser\AST\Operator\ObjectCreation:l-2',
48+
'statements:leaveNode:Helmich\TypoScriptParser\Parser\AST\Operator\ObjectCreation:l-2',
49+
'statements:enterNode:Helmich\TypoScriptParser\Parser\AST\Operator\Assignment:l-3',
50+
'statements:leaveNode:Helmich\TypoScriptParser\Parser\AST\Operator\Assignment:l-3',
51+
'statements:afterTraversal:3',
52+
], $calls);
53+
}
54+
55+
/**
56+
* @template T of object
57+
* @phpstan-param class-string<T> $type
58+
* @phpstan-return T
59+
*/
60+
protected function getService(string $type): object
61+
{
62+
if ($this->currentContainer === null) {
63+
throw new ShouldNotHappenException('Container is not initalized');
64+
}
65+
66+
return $this->currentContainer->get($type)
67+
?? throw new ShouldNotHappenException(sprintf('Service "%s" was not found', $type));
68+
}
69+
}

0 commit comments

Comments
 (0)