From 5ec3405432a4d1213ebc1b421746e9dabe3c1ff5 Mon Sep 17 00:00:00 2001 From: Sascha Date: Fri, 17 Jul 2026 10:09:54 +0200 Subject: [PATCH 1/2] feat: update to PHP 8.2, adjust dependencies, and refine workflows --- .github/workflows/ci.yml | 17 +++++++---------- composer.json | 10 ++++++---- mago.toml | 9 ++++----- rector.php | 2 +- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e16e58c..eb26f87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,18 +10,15 @@ jobs: fail-fast: false matrix: env: - - { php: 7.4, phpunit: 9 } - - { php: 8.0, phpunit: 10 } - - { php: 8.1, phpunit: 10 } - - { php: 8.2, phpunit: 10 } - - { php: 8.3, phpunit: 10 } - - { php: 8.4, phpunit: 10, main: true } - - { php: 8.5, phpunit: 10 } + - { php: 8.2, phpunit: 11 } + - { php: 8.3, phpunit: 12 } + - { php: 8.4, phpunit: 13 } + - { php: 8.5, phpunit: 13, main: true } steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Composer cache - uses: actions/cache@v5 + uses: actions/cache@v6 with: path: "vendor" key: ${{ runner.os }}-${{ matrix.env.php }}-composer-${{ hashFiles('composer.json') }} @@ -63,7 +60,7 @@ jobs: thresholds: '60 80' - name: Add Coverage PR Comment if: "matrix.env.main == true && github.event_name == 'pull_request'" - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@v3 with: recreate: true path: code-coverage-results.md diff --git a/composer.json b/composer.json index d774bbc..025b1c9 100644 --- a/composer.json +++ b/composer.json @@ -9,14 +9,14 @@ "docs": "https://github.com/netlogix/XML-Processor/blob/main/README.md" }, "require": { - "php": "^7.4 || ^8.0", - "ext-xmlreader": "*", - "symfony/polyfill-php80": ">=1.33.0" + "php": "^8.2", + "ext-xmlreader": "*" }, "require-dev": { "behat/behat": "^3.12.0", + "carthage-software/mago": "^1.43", "ergebnis/composer-normalize": "^2.50", - "phpunit/phpunit": "^9.6.6", + "phpunit/phpunit": "^11.5.56", "rector/rector": "^2.3" }, "prefer-stable": true, @@ -40,6 +40,7 @@ "scripts": { "apply-coding-standard": [ "@composer:normalize:fix", + "@lint:fix", "@rector:fix", "@format:fix" ], @@ -52,6 +53,7 @@ "format": "mago fmt --dry-run", "format:fix": "mago fmt", "lint": "mago lint", + "lint:fix": "mago lint --fix --unsafe", "rector": "rector process --dry-run", "rector:fix": "rector process", "test": [ diff --git a/mago.toml b/mago.toml index 8656dc6..15dcad2 100644 --- a/mago.toml +++ b/mago.toml @@ -1,12 +1,10 @@ -php-version = "8.4" +php-version = "8.2" [source] workspace = "." paths = [ "src/", - "tests/", - # "importmap.php", - "migrations/", + "tests/" ] includes = ["vendor"] excludes = [] @@ -25,7 +23,8 @@ empty-line-before-return = true integrations = ["phpunit"] [linter.rules] -ambiguous-function-call = { enabled = false } +no-fully-qualified-global-function = { enabled = false, level = "warning" } +ambiguous-function-call = { enabled = true, level = "warning" } literal-named-argument = { enabled = false } halstead = { effort-threshold = 7000 } final-controller = { enabled = false } diff --git a/rector.php b/rector.php index 185fb28..e7bdcae 100644 --- a/rector.php +++ b/rector.php @@ -13,7 +13,7 @@ __DIR__ . '/tests', ]) // uncomment to reach your current PHP version - ->withPhpVersion(PhpVersion::PHP_74) + ->withPhpVersion(PhpVersion::PHP_82) ->withSets([ PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES ]) From 2678d6a24c2b9739da5fd36ad33d3dd4640b435b Mon Sep 17 00:00:00 2001 From: Sascha Date: Fri, 17 Jul 2026 15:45:45 +0200 Subject: [PATCH 2/2] refactor: allow custom conext data and improve context handling --- README.md | 21 +++ phpunit.xml | 28 ++-- src/NodeProcessor/AbstractNodeProcessor.php | 8 +- .../Context/AbstractElementContext.php | 2 + src/XmlProcessor.php | 126 ++++++++++-------- src/XmlProcessorContext.php | 46 +++++-- .../NodePathNodeProcessor.php | 25 +++- .../TestNodeProcessor.php | 2 + .../NodeProcessor/ArrayNodeProcessorTest.php | 10 +- .../NodeProcessor/TextNodeProcessorTest.php | 8 +- .../AbstractNodeProcessorTest.php | 35 ++--- .../Context/AbstractElementContextTest.php | 19 ++- .../Context/CloseContextTest.php | 2 +- .../Context/NodeProcessorContextTest.php | 10 +- .../NodeProcessor/Context/OpenContextTest.php | 8 +- .../NodeProcessor/Context/TextContextTest.php | 8 +- tests/Unit/XmlProcessorContextTest.php | 79 ++++++++--- tests/Unit/XmlProcessorTest.php | 85 +++++++----- 18 files changed, 338 insertions(+), 184 deletions(-) diff --git a/README.md b/README.md index 8dad80c..bb3249e 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,25 @@ array(3) { } ``` +## Passing custom data to `processFile` + +`XmlProcessor::processFile()` accepts an optional associative array of custom data. +It is stored on the [`XmlProcessorContext`], together with the filename currently being processed, +and is available to every node processor via `$context->getXmlProcessorContext()`. + +```php +$processor->processFile('file.xml', ['tenant' => 'acme']); +``` + +```php +public function openElement(OpenContext $context) +{ + $xmlProcessorContext = $context->getXmlProcessorContext(); + $filename = $xmlProcessorContext->getFilename(); + $tenant = $xmlProcessorContext->get('tenant'); +} +``` + [`XmlProcessor`]: src/XmlProcessor.php [`NodeProcessorInterface`]: src/NodeProcessor/NodeProcessorInterface.php @@ -129,6 +148,8 @@ array(3) { [`TextNodeProcessorInterface`]: src/NodeProcessor/TextNodeProcessorInterface.php +[`XmlProcessorContext`]: src/XmlProcessorContext.php + [`NodeProcessorContext`]: src/NodeProcessor/Context/NodeProcessorContext.php [`OpenContext]: src/NodeProcessor/Context/OpenContext.php diff --git a/phpunit.xml b/phpunit.xml index 6cf3137..2296422 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,18 +1,14 @@ - - - - src/ - features/NodeProcessor - - - - - tests/Unit - - + + + + tests/Unit + + + + + src/ + features/NodeProcessor + + diff --git a/src/NodeProcessor/AbstractNodeProcessor.php b/src/NodeProcessor/AbstractNodeProcessor.php index da5f3eb..c63c9cd 100644 --- a/src/NodeProcessor/AbstractNodeProcessor.php +++ b/src/NodeProcessor/AbstractNodeProcessor.php @@ -9,16 +9,20 @@ use Netlogix\XmlProcessor\XmlProcessor; use Netlogix\XmlProcessor\XmlProcessorContext; +use function get_class; +use function defined; +use function constant; + class AbstractNodeProcessor implements NodeProcessorInterface { public function getNodePath(): string { - $constName = \get_class($this) . '::NODE_PATH'; + $constName = get_class($this) . '::NODE_PATH'; if (!defined($constName)) { throw new Exception('NODE_PATH not defined in ' . static::class); } - return \constant($constName); + return constant($constName); } public function getSubscribedEvents(string $nodePath, XmlProcessorContext $context): Iterator diff --git a/src/NodeProcessor/Context/AbstractElementContext.php b/src/NodeProcessor/Context/AbstractElementContext.php index 0071998..ea33b78 100644 --- a/src/NodeProcessor/Context/AbstractElementContext.php +++ b/src/NodeProcessor/Context/AbstractElementContext.php @@ -1,5 +1,7 @@ */ - private iterable $processors; - - /** @var iterable */ - private iterable $parserProperties; + private readonly XMLReader $xml; /** * @var string[] @@ -58,21 +52,19 @@ class XmlProcessor private bool $skipCurrentNode = false; private bool $selfClosing = false; + private string $currentNodePathString = ''; /** * @param iterable $processors * @param iterable $parserProperties */ public function __construct( - iterable $processors, - iterable $parserProperties = [ + private readonly iterable $processors, + private readonly iterable $parserProperties = [ XMLReader::VALIDATE => false ] ) { $this->xml = new XMLReader(); - $this->processors = $processors; - $this->parserProperties = $parserProperties; - $this->context = new XmlProcessorContext($this->xml, $this->processors, fn () => $this->skipCurrentNode = true); } function setSkipNodes(?array $skipNodes = null): void @@ -95,50 +87,68 @@ function getWhitelistEvents(): ?array return $this->whitelistEvents; } - function getProcessor(string $processorName): ?NodeProcessorInterface + function getProcessor(string $processorClass): ?NodeProcessorInterface { - return $this->context->getProcessor($processorName); + foreach ($this->processors as $processor) { + if (\class_exists($processorClass) && $processor instanceof $processorClass) { + return $processor; + } + } + + return null; } - public function processFile(string $filename): void + /** + * @param array $data + */ + public function processFile(string $filename, array $data = []): void { + $this->eventCache = []; + $context = new XmlProcessorContext( + $this->xml, + $this->processors, + fn() => $this->skipCurrentNode = true, + $filename, + $data + ); + $this->xml->open($filename); foreach ($this->parserProperties as $parserProperty => $value) { $this->xml->setParserProperty($parserProperty, $value); } - $this->callProcessorEvents(self::EVENT_OPEN_FILE); + $this->callProcessorEvents(self::EVENT_OPEN_FILE, $context); while ($this->xml->read()) { switch ($this->xml->nodeType) { case XMLReader::END_ELEMENT: - $this->eventCloseElement(); + $this->eventCloseElement($context); break; case XMLReader::ELEMENT: $this->selfClosing = $this->xml->isEmptyElement; - $this->eventOpenElement(); + $this->eventOpenElement($context); $skip = $this->shouldSkipNode(); if ($skip) { $this->xml->next(); } if ($skip || $this->selfClosing) { - $this->eventCloseElement(); + $this->eventCloseElement($context); } break; case XMLReader::TEXT: - $this->eventTextElement(); + $this->eventTextElement($context); break; default: - $this->callProcessorEvents('NodeType_' . $this->xml->nodeType); + $this->callProcessorEvents('NodeType_' . $this->xml->nodeType, $context); break; } } - $this->callProcessorEvents(self::EVENT_END_OF_FILE); + $this->callProcessorEvents(self::EVENT_END_OF_FILE, $context); $this->xml->close(); } - private function skipNode(): bool + private function skipNode($context): bool { $result = $this->xml->next(); - $this->eventCloseElement(); + $this->eventCloseElement($context); return $result; } @@ -153,9 +163,9 @@ private function shouldSkipNode(): bool if ($this->skipNodes === null) { return false; } - $nodePath = \implode('/', $this->nodePath); + foreach ($this->skipNodes as $skipNode) { - if (self::checkNodePath($nodePath, $skipNode)) { + if (self::checkNodePath($this->currentNodePathString, $skipNode)) { return true; } } @@ -163,56 +173,56 @@ private function shouldSkipNode(): bool return false; } - private function eventOpenElement(): void + private function eventOpenElement(XmlProcessorContext $context): void { $this->pushNodePath(); - $this->callProcessorEvents(XmlProcessor::NODE_TYPE_ELEMENT, OpenContext::class); + $this->callProcessorEvents(XmlProcessor::NODE_TYPE_ELEMENT, $context, OpenContext::class); } - private function eventTextElement(): void + private function eventTextElement(XmlProcessorContext $context): void { $this->currentValue = $this->xml->value; - $this->callProcessorEvents(XmlProcessor::NODE_TYPE_TEXT, TextContext::class); + $this->callProcessorEvents(XmlProcessor::NODE_TYPE_TEXT, $context, TextContext::class); } - private function eventCloseElement(): void + private function eventCloseElement(XmlProcessorContext $context): void { - $this->callProcessorEvents(XmlProcessor::NODE_TYPE_END_ELEMENT, CloseContext::class); + $this->callProcessorEvents(XmlProcessor::NODE_TYPE_END_ELEMENT, $context, CloseContext::class); $this->popNodePath(); } - private function callProcessorEvents(string $event, string $contextClass = NodeProcessorContext::class): void - { + private function callProcessorEvents( + string $event, + XmlProcessorContext $context, + string $contextClass = NodeProcessorContext::class + ): void { if ($this->whitelistEvents !== null && !\in_array($event, $this->whitelistEvents, true)) { return; } - $context = null; - foreach ($this->getProcessorForEvent($event) as $action) { - \call_user_func($action, $context ??= $this->createContext($contextClass)); + $nodeContext = null; + foreach ($this->getProcessorForEvent($event, $context) as $action) { + \call_user_func($action, $nodeContext ??= $this->createNodeContext($contextClass, $context)); } - unset($context); + unset($nodeContext); } /** * @return iterable */ - private function getProcessorForEvent(string $event): iterable + private function getProcessorForEvent(string $event, XmlProcessorContext $context): iterable { - $nodePath = \implode('/', $this->nodePath); - - if (!\is_array($this->eventCache[$nodePath][$event] ?? false)) { - $this->eventCache[$nodePath][$event] = []; + $nodePath = $this->currentNodePathString; + if (!isset($this->eventCache[spl_object_hash($context)][$nodePath])) { + $events = []; foreach ($this->processors as $processor) { - foreach ($processor->getSubscribedEvents($nodePath, $this->context) as $e => $action) { - if ($e !== $event) { - continue; - } - $this->eventCache[$nodePath][$event][] = $action; + foreach ($processor->getSubscribedEvents($nodePath, $context) as $e => $action) { + $events[$e][] = $action; } } + $this->eventCache[spl_object_hash($context)][$nodePath] = $events; } - yield from $this->eventCache[$nodePath][$event]; + yield from $this->eventCache[spl_object_hash($context)][$nodePath][$event] ?? []; } /** @@ -231,16 +241,18 @@ private function getAttributes(): array private function pushNodePath(): void { $this->nodePath[] = $this->xml->name; + $this->currentNodePathString = \implode('/', $this->nodePath); } private function popNodePath(): void { \array_pop($this->nodePath); + $this->currentNodePathString = \implode('/', $this->nodePath); } - private function createContext(string $contextClass): NodeProcessorContext + private function createNodeContext(string $contextClass, XmlProcessorContext $context): NodeProcessorContext { - $context = new $contextClass($this->context, $this->nodePath); + $context = new $contextClass($context, $this->nodePath); if ($context instanceof AbstractElementContext) { $context->setSelfClosing($this->selfClosing); } @@ -255,8 +267,6 @@ private function createContext(string $contextClass): NodeProcessorContext static function checkNodePath(string $nodePath, string $expected): bool { - return $nodePath === $expected - || '/' . $nodePath === $expected - || \str_ends_with($nodePath, $expected); + return $nodePath === $expected || '/' . $nodePath === $expected || \str_ends_with($nodePath, $expected); } } diff --git a/src/XmlProcessorContext.php b/src/XmlProcessorContext.php index 4d00d70..1987e69 100644 --- a/src/XmlProcessorContext.php +++ b/src/XmlProcessorContext.php @@ -10,19 +10,16 @@ class XmlProcessorContext { - private XMLReader $xml; /** - * @var iterable + * @param $processors iterable */ - private iterable $processors; - - private Closure $skipNode; - - public function __construct(XMLReader $xml, iterable $processors, Closure $skipNode) - { - $this->xml = $xml; - $this->processors = $processors; - $this->skipNode = $skipNode; + public function __construct( + private XMLReader $xml, + private iterable $processors, + private Closure $skipNode, + private string $filename, + private array $data + ) { } public function skipCurrentNode(): bool @@ -45,4 +42,31 @@ public function getXMLReader(): XMLReader { return $this->xml; } + + public function getFilename(): string + { + return $this->filename; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * @param mixed $default + * @return mixed + */ + public function get(string $key, $default = null): mixed + { + return $this->data[$key] ?? $default; + } + + public function has(string $key): bool + { + return \array_key_exists($key, $this->data); + } } diff --git a/tests/Fixtures/AbstractNodeProcessorTest/NodePathNodeProcessor.php b/tests/Fixtures/AbstractNodeProcessorTest/NodePathNodeProcessor.php index f5ef18e..63d5b61 100644 --- a/tests/Fixtures/AbstractNodeProcessorTest/NodePathNodeProcessor.php +++ b/tests/Fixtures/AbstractNodeProcessorTest/NodePathNodeProcessor.php @@ -1,10 +1,21 @@ getMockBuilder(XmlProcessorContext::class)->disableOriginalConstructor()->getMock(); - self::assertIsIterable($nodeProcessor->getSubscribedEvents('test', $context)); + static::assertIsIterable($nodeProcessor->getSubscribedEvents('test', $context)); $events = iterator_to_array($nodeProcessor->getSubscribedEvents('test', $context)); - self::assertEquals( + static::assertEquals( [ XmlProcessor::NODE_TYPE_ELEMENT => [$nodeProcessor, 'openElement'], XmlProcessor::NODE_TYPE_TEXT => [$nodeProcessor, 'textElement'] @@ -74,7 +76,7 @@ function testOpenElement(): void } } - self::assertEquals( + static::assertEquals( [ [ 'node' => 'foo', diff --git a/tests/Unit/Behat/NodeProcessor/TextNodeProcessorTest.php b/tests/Unit/Behat/NodeProcessor/TextNodeProcessorTest.php index 65e2ebf..41caf1b 100644 --- a/tests/Unit/Behat/NodeProcessor/TextNodeProcessorTest.php +++ b/tests/Unit/Behat/NodeProcessor/TextNodeProcessorTest.php @@ -13,7 +13,7 @@ class TextNodeProcessorTest extends TestCase function test__invoke(): void { $nodeProcessor = new TextNodeProcessor(); - self::assertIsArray($nodeProcessor()); + static::assertIsArray($nodeProcessor()); } function testTextElement(): void @@ -22,9 +22,9 @@ function testTextElement(): void $context = $this->getMockBuilder(TextContext::class)->disableOriginalConstructor()->getMock(); $context->method('getText')->willReturnOnConsecutiveCalls('foo', 'bar'); $nodeProcessor->textElement($context); - self::assertIsArray($nodeProcessor()); - self::assertEquals(['foo'], $nodeProcessor()); + static::assertIsArray($nodeProcessor()); + static::assertEquals(['foo'], $nodeProcessor()); $nodeProcessor->textElement($context); - self::assertEquals(['foo', 'bar'], $nodeProcessor()); + static::assertEquals(['foo', 'bar'], $nodeProcessor()); } } diff --git a/tests/Unit/NodeProcessor/AbstractNodeProcessorTest.php b/tests/Unit/NodeProcessor/AbstractNodeProcessorTest.php index 7839412..a643e97 100644 --- a/tests/Unit/NodeProcessor/AbstractNodeProcessorTest.php +++ b/tests/Unit/NodeProcessor/AbstractNodeProcessorTest.php @@ -12,44 +12,49 @@ use Netlogix\XmlProcessor\Tests\Fixtures\AbstractNodeProcessorTest\TestNodeProcessor; use Netlogix\XmlProcessor\XmlProcessor; use Netlogix\XmlProcessor\XmlProcessorContext; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +#[CoversClass(AbstractNodeProcessor::class)] class AbstractNodeProcessorTest extends TestCase { public function testGetNodePath(): void { $nodeProcessor = new TestNodeProcessor(); - $this->assertEquals(TestNodeProcessor::NODE_PATH, $nodeProcessor->getNodePath()); + static::assertEquals(TestNodeProcessor::NODE_PATH, $nodeProcessor->getNodePath()); + + $nodeProcessor= new class extends AbstractNodeProcessor{}; - $nodeProcessor = $this->getMockForAbstractClass(AbstractNodeProcessor::class); $this->expectException(Exception::class); $nodeProcessor->getNodePath(); } public function testGetSubscribedEvents(): void { - $this->markTestSkipped('ToDo: Implement testGetSubscribedEvents()'); + static::markTestSkipped('ToDo: Implement testGetSubscribedEvents()'); } - /** - * @dataProvider isNodeDataProvider - */ + #[DataProvider('isNodeDataProvider')] public function testIsNode(NodeProcessorInterface $nodeProcessor, string $nodePath, bool $expectedResult): void { - $nodeProcessor = $this->getMockForAbstractClass(TestNodeProcessor::class); + static::assertSame($expectedResult, $nodeProcessor->isNode($nodePath)); + $context = $this->createMock(XmlProcessorContext::class); $events = []; - foreach ($nodeProcessor->getSubscribedEvents('test', $context) as $event => $action) { + foreach ($nodeProcessor->getSubscribedEvents($nodePath, $context) as $event => $action) { $events[] = $event; - self::assertIsCallable($action); + static::assertIsCallable($action); } - self::assertEquals( - [ - XmlProcessor::NODE_TYPE_ELEMENT, - XmlProcessor::NODE_TYPE_END_ELEMENT, - XmlProcessor::NODE_TYPE_TEXT - ], + static::assertEquals( + $expectedResult + ? [ + XmlProcessor::NODE_TYPE_ELEMENT, + XmlProcessor::NODE_TYPE_END_ELEMENT, + XmlProcessor::NODE_TYPE_TEXT + ] + : [], $events ); } diff --git a/tests/Unit/NodeProcessor/Context/AbstractElementContextTest.php b/tests/Unit/NodeProcessor/Context/AbstractElementContextTest.php index e2247a6..4255805 100644 --- a/tests/Unit/NodeProcessor/Context/AbstractElementContextTest.php +++ b/tests/Unit/NodeProcessor/Context/AbstractElementContextTest.php @@ -4,6 +4,7 @@ namespace Netlogix\XmlProcessor\Tests\Unit\NodeProcessor\Context; +use PHPUnit\Framework\Attributes\DataProvider; use Generator; use Netlogix\XmlProcessor\NodeProcessor\Context\AbstractElementContext; use Netlogix\XmlProcessor\XmlProcessorContext; @@ -24,38 +25,34 @@ private function getCloseContext( public function test__construct(): void { $nodeProcessorContext = $this->getCloseContext(); - self::assertInstanceOf(AbstractElementContext::class, $nodeProcessorContext); + static::assertInstanceOf(AbstractElementContext::class, $nodeProcessorContext); } - /** - * @dataProvider setSelfClosingDataProvider - */ + #[DataProvider('setSelfClosingDataProvider')] function testSetSelfClosing($set, $expect): void { $nodeProcessorContext = $this->getCloseContext(); $nodeProcessorContext->setSelfClosing($set); - self::assertEquals($expect, $nodeProcessorContext->getSelfClosing()); + static::assertEquals($expect, $nodeProcessorContext->getSelfClosing()); } - function setSelfClosingDataProvider(): Generator + static function setSelfClosingDataProvider(): Generator { yield [true, true]; yield [false, false]; } - /** - * @dataProvider getSelfClosingDataProvider - */ + #[DataProvider('getSelfClosingDataProvider')] function testGetSelfClosing($set, $expect): void { $nodeProcessorContext = $this->getCloseContext(); if ($set !== null) { $nodeProcessorContext->setSelfClosing($set); } - self::assertEquals($expect, $nodeProcessorContext->getSelfClosing()); + static::assertEquals($expect, $nodeProcessorContext->getSelfClosing()); } - function getSelfClosingDataProvider(): Generator + static function getSelfClosingDataProvider(): Generator { yield [null, false]; yield [true, true]; diff --git a/tests/Unit/NodeProcessor/Context/CloseContextTest.php b/tests/Unit/NodeProcessor/Context/CloseContextTest.php index c6089bc..fe46c65 100644 --- a/tests/Unit/NodeProcessor/Context/CloseContextTest.php +++ b/tests/Unit/NodeProcessor/Context/CloseContextTest.php @@ -23,6 +23,6 @@ private function getCloseContext( public function test__construct(): void { $nodeProcessorContext = $this->getCloseContext(); - self::assertInstanceOf(CloseContext::class, $nodeProcessorContext); + static::assertInstanceOf(CloseContext::class, $nodeProcessorContext); } } diff --git a/tests/Unit/NodeProcessor/Context/NodeProcessorContextTest.php b/tests/Unit/NodeProcessor/Context/NodeProcessorContextTest.php index 2af4539..e55dcac 100644 --- a/tests/Unit/NodeProcessor/Context/NodeProcessorContextTest.php +++ b/tests/Unit/NodeProcessor/Context/NodeProcessorContextTest.php @@ -22,29 +22,29 @@ private function getNodeProcessorContext( public function testGetCurrentNodeName(): void { - self::assertEquals('bar', $this->getNodeProcessorContext()->getCurrentNodeName()); + static::assertSame('bar', $this->getNodeProcessorContext()->getCurrentNodeName()); } public function test__construct(): void { $nodeProcessorContext = $this->getNodeProcessorContext(); - self::assertInstanceOf(NodeProcessorContext::class, $nodeProcessorContext); + static::assertInstanceOf(NodeProcessorContext::class, $nodeProcessorContext); } public function testGetXmlProcessorContext(): void { $context = $this->getMockBuilder(XmlProcessorContext::class)->disableOriginalConstructor()->getMock(); - self::assertEquals($context, $this->getNodeProcessorContext($context)->getXmlProcessorContext()); + static::assertEquals($context, $this->getNodeProcessorContext($context)->getXmlProcessorContext()); } public function testGetNodePath(): void { - self::assertEquals('foo/bar', $this->getNodeProcessorContext()->getNodePath()); + static::assertSame('foo/bar', $this->getNodeProcessorContext()->getNodePath()); } public function testGetNodePathArray(): void { - self::assertEquals(['foo', 'bar'], $this->getNodeProcessorContext()->getNodePathArray()); + static::assertEquals(['foo', 'bar'], $this->getNodeProcessorContext()->getNodePathArray()); } } diff --git a/tests/Unit/NodeProcessor/Context/OpenContextTest.php b/tests/Unit/NodeProcessor/Context/OpenContextTest.php index aa8b8bd..d60c564 100644 --- a/tests/Unit/NodeProcessor/Context/OpenContextTest.php +++ b/tests/Unit/NodeProcessor/Context/OpenContextTest.php @@ -1,5 +1,7 @@ getOpenContext(); - self::assertInstanceOf(OpenContext::class, $nodeProcessorContext); + static::assertInstanceOf(OpenContext::class, $nodeProcessorContext); } public function testSetAttributes() { $context = $this->getOpenContext(); $context->setAttributes(['foo' => 'bar']); - self::assertEquals(['foo' => 'bar'], $context->getAttributes()); + static::assertEquals(['foo' => 'bar'], $context->getAttributes()); } public function testGetAttributes() { $context = $this->getOpenContext(); $context->setAttributes(['foo' => 'bar']); - self::assertEquals(['foo' => 'bar'], $context->getAttributes()); + static::assertEquals(['foo' => 'bar'], $context->getAttributes()); } } diff --git a/tests/Unit/NodeProcessor/Context/TextContextTest.php b/tests/Unit/NodeProcessor/Context/TextContextTest.php index 5a1aa16..7ec45a2 100644 --- a/tests/Unit/NodeProcessor/Context/TextContextTest.php +++ b/tests/Unit/NodeProcessor/Context/TextContextTest.php @@ -1,5 +1,7 @@ getTextContext(); - self::assertInstanceOf(TextContext::class, $nodeProcessorContext); + static::assertInstanceOf(TextContext::class, $nodeProcessorContext); } public function testSetText() { $context = $this->getTextContext(); $context->setText('foo'); - self::assertEquals('foo', $context->getText()); + static::assertSame('foo', $context->getText()); } public function testGetText() { $context = $this->getTextContext(); $context->setText('bar'); - self::assertEquals('bar', $context->getText()); + static::assertSame('bar', $context->getText()); } } diff --git a/tests/Unit/XmlProcessorContextTest.php b/tests/Unit/XmlProcessorContextTest.php index 65e4be6..17e48ed 100644 --- a/tests/Unit/XmlProcessorContextTest.php +++ b/tests/Unit/XmlProcessorContextTest.php @@ -4,8 +4,8 @@ namespace Netlogix\XmlProcessor\Tests\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use Generator; -use stdClass; use XMLReader; use Netlogix\XmlProcessor\NodeProcessor\NodeProcessorInterface; use Netlogix\XmlProcessor\XmlProcessorContext; @@ -24,24 +24,28 @@ function setUp(): void function test__construct(): void { - $context = new XmlProcessorContext($this->getXMLReaderMock(), [], fn() => true); - $this->assertInstanceOf(XmlProcessorContext::class, $context); + $context = new XmlProcessorContext($this->getXMLReaderMock(), [], static fn() => true, 'file.xml', []); + static::assertInstanceOf(XmlProcessorContext::class, $context); } function testGetXMLReader(): void { $xmlReader = $this->getXMLReaderMock(); - $context = new XmlProcessorContext($xmlReader, [], fn() => true); - $this->assertSame($xmlReader, $context->getXMLReader()); + $context = new XmlProcessorContext($xmlReader, [], static fn() => true, 'file.xml', []); + static::assertSame($xmlReader, $context->getXMLReader()); } - /** - * @dataProvider getProcessorDataProvider - */ + #[DataProvider('getProcessorDataProvider')] function testGetProcessor($processor, $expected): void { - $context = new XmlProcessorContext($this->getXMLReaderMock(), [$processor], fn() => true); - $this->assertSame($expected, $context->getProcessor(NodeProcessorInterface::class)); + $context = new XmlProcessorContext( + $this->getXMLReaderMock(), + [$processor], + static fn() => true, + 'file.xml', + [] + ); + static::assertSame($expected, $context->getProcessor(NodeProcessorInterface::class)); } public static function getProcessorDataProvider(): Generator @@ -50,23 +54,62 @@ public static function getProcessorDataProvider(): Generator yield [[], null]; } - /** - * @dataProvider skipCurrentNodeDataProvider - */ + #[DataProvider('skipCurrentNodeDataProvider')] public function testSkipCurrentNode(bool $return): void { - $skipNodeMock = $this->getMockBuilder(stdClass::class)->addMethods(['skipNode'])->getMock(); - $skipNodeMock->expects($this->atLeastOnce())->method('skipNode')->willReturn($return); - $context = new XmlProcessorContext($this->getXMLReaderMock(), [], fn() => $skipNodeMock->skipNode()); - self::assertEquals($context->skipCurrentNode(), $return); + $calls = 0; + $skipNode = static function () use (&$calls, $return): bool { + $calls++; + + return $return; + }; + $context = new XmlProcessorContext($this->getXMLReaderMock(), [], $skipNode, 'file.xml', []); + static::assertEquals($context->skipCurrentNode(), $return); + static::assertGreaterThanOrEqual(1, $calls); } - function skipCurrentNodeDataProvider(): iterable + static function skipCurrentNodeDataProvider(): iterable { yield [true]; yield [false]; } + function testGetFilename(): void + { + $context = new XmlProcessorContext($this->getXMLReaderMock(), [], static fn() => true, 'file.xml', []); + + static::assertSame('file.xml', $context->getFilename()); + } + + function testGetData(): void + { + $context = new XmlProcessorContext($this->getXMLReaderMock(), [], static fn() => true, 'file.xml', [ + 'foo' => 'bar' + ]); + + static::assertSame(['foo' => 'bar'], $context->getData()); + } + + function testGet(): void + { + $context = new XmlProcessorContext($this->getXMLReaderMock(), [], static fn() => true, 'file.xml', [ + 'foo' => 'bar' + ]); + static::assertNull($context->get('baz')); + static::assertSame('default', $context->get('baz', 'default')); + + static::assertSame('bar', $context->get('foo')); + } + + function testHas(): void + { + $context = new XmlProcessorContext($this->getXMLReaderMock(), [], static fn() => true, 'file.xml', [ + 'foo' => 'bar' + ]); + static::assertFalse($context->has('baz')); + static::assertTrue($context->has('foo')); + } + private function getXMLReaderMock(): XMLReader { return $this->getMockBuilder(XMLReader::class)->getMock(); diff --git a/tests/Unit/XmlProcessorTest.php b/tests/Unit/XmlProcessorTest.php index bb01908..c264ba2 100644 --- a/tests/Unit/XmlProcessorTest.php +++ b/tests/Unit/XmlProcessorTest.php @@ -8,6 +8,7 @@ use Generator; use Netlogix\XmlProcessor\NodeProcessor\CloseNodeProcessorInterface; use Netlogix\XmlProcessor\NodeProcessor\Context\CloseContext; +use Netlogix\XmlProcessor\NodeProcessor\Context\NodeProcessorContext; use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext; use Netlogix\XmlProcessor\NodeProcessor\Context\TextContext; use Netlogix\XmlProcessor\NodeProcessor\NodeProcessorInterface; @@ -15,59 +16,62 @@ use Netlogix\XmlProcessor\NodeProcessor\TextNodeProcessorInterface; use Netlogix\XmlProcessor\Tests\Fixtures\AbstractNodeProcessorTest\TestNodeProcessor; use Netlogix\XmlProcessor\XmlProcessor; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use function get_class; + class XmlProcessorTest extends TestCase { public function test__construct() { $xmlProcessor = new XmlProcessor([ - $this->getMockForAbstractClass(NodeProcessorInterface::class) + $this->createMock(NodeProcessorInterface::class) ], [ XMLReader::SUBST_ENTITIES => true ]); - self::assertInstanceOf(XmlProcessor::class, $xmlProcessor); + static::assertInstanceOf(XmlProcessor::class, $xmlProcessor); } public function testGetProcessor(): void { - $nodeProcessor = $this->getMockForAbstractClass(TestNodeProcessor::class); + $nodeProcessor = $this->createMock(TestNodeProcessor::class); $xmlProcessor = new XmlProcessor([$nodeProcessor]); - self::assertInstanceOf(TestNodeProcessor::class, $xmlProcessor->getProcessor(TestNodeProcessor::class)); - self::assertInstanceOf(get_class($nodeProcessor), $xmlProcessor->getProcessor(TestNodeProcessor::class)); - self::assertNull($xmlProcessor->getProcessor(OpenNodeProcessorInterface::class)); + static::assertInstanceOf(TestNodeProcessor::class, $xmlProcessor->getProcessor(TestNodeProcessor::class)); + static::assertInstanceOf(get_class($nodeProcessor), $xmlProcessor->getProcessor(TestNodeProcessor::class)); + static::assertNull($xmlProcessor->getProcessor(OpenNodeProcessorInterface::class)); } public function testProcessFile() { - $nodeProcessor = $this->getMockForAbstractClass(NodeProcessorInterface::class); + $nodeProcessor = $this->createMock(NodeProcessorInterface::class); $openCallableMock = $this->getMockBuilder(OpenNodeProcessorInterface::class)->getMock(); $openCallableMock ->expects($this->atLeastOnce()) ->method('openElement') - ->with($this->isInstanceOf(OpenContext::class)); + ->with(static::isInstanceOf(OpenContext::class)); $textCallableMock = $this->getMockBuilder(TextNodeProcessorInterface::class)->getMock(); $textCallableMock ->expects($this->atLeastOnce()) ->method('textElement') - ->with($this->isInstanceOf(TextContext::class)); + ->with(static::isInstanceOf(TextContext::class)); $closeCallableMock = $this->getMockBuilder(CloseNodeProcessorInterface::class)->getMock(); $closeCallableMock ->expects($this->atLeastOnce()) ->method('closeElement') - ->with($this->isInstanceOf(CloseContext::class)); + ->with(static::isInstanceOf(CloseContext::class)); $nodeProcessor ->method('getSubscribedEvents') - ->will($this->returnCallback( - fn() => yield from [ + ->willReturnCallback( + static fn() => yield from [ XmlProcessor::NODE_TYPE_ELEMENT => [$openCallableMock, 'openElement'], XmlProcessor::NODE_TYPE_END_ELEMENT => [$closeCallableMock, 'closeElement'], XmlProcessor::NODE_TYPE_TEXT => [$textCallableMock, 'textElement'] ] - )); + ); $xmlProcessor = new XmlProcessor([ $nodeProcessor @@ -82,20 +86,39 @@ public function testProcessFile() $xmlProcessor->processFile(__DIR__ . '/../Fixtures/XmlProcessorTest/test.xml'); } - public function testProcessFile_skipCurrentNode() + public function testProcessFile_fillsContextWithFilenameAndData(): void { - $nodeProcessor = $this->getMockForAbstractClass(NodeProcessorInterface::class); + $filename = __DIR__ . '/../Fixtures/XmlProcessorTest/test.xml'; + $nodeProcessor = $this->createMock(NodeProcessorInterface::class); $nodeProcessor ->method('getSubscribedEvents') - ->will($this->returnCallback( - fn() => yield from [ - XmlProcessor::NODE_TYPE_ELEMENT => function (OpenContext $context): void { - $context->getXmlProcessorContext()->skipCurrentNode(); - self::assertNotEquals('bar', $context->getCurrentNodeName()); + ->willReturnCallback( + static fn() => yield XmlProcessor::EVENT_OPEN_FILE => + static function (NodeProcessorContext $context) use ($filename): void { + $xmlProcessorContext = $context->getXmlProcessorContext(); + self::assertSame($filename, $xmlProcessorContext->getFilename()); + self::assertSame(['foo' => 'bar'], $xmlProcessorContext->getData()); + self::assertSame('bar', $xmlProcessorContext->get('foo')); } - ] - )); + ); + + $xmlProcessor = new XmlProcessor([$nodeProcessor]); + $xmlProcessor->processFile($filename, ['foo' => 'bar']); + } + + public function testProcessFile_skipCurrentNode() + { + $nodeProcessor = $this->createMock(NodeProcessorInterface::class); + + $nodeProcessor + ->method('getSubscribedEvents') + ->willReturnCallback( + static fn() => yield XmlProcessor::NODE_TYPE_ELEMENT => static function (OpenContext $context): void { + $context->getXmlProcessorContext()->skipCurrentNode(); + self::assertNotEquals('bar', $context->getCurrentNodeName()); + } + ); $xmlProcessor = new XmlProcessor([ $nodeProcessor @@ -112,12 +135,10 @@ public function testProcessFile_skipCurrentNode() $xmlProcessor->processFile(__DIR__ . '/../Fixtures/XmlProcessorTest/test.xml'); } - /** - * @dataProvider checkNodePathDataProvider - */ + #[DataProvider('checkNodePathDataProvider')] function testCheckNodePath(string $nodePath, string $expected, bool $result): void { - self::assertSame(XmlProcessor::checkNodePath($nodePath, $expected), $result); + static::assertSame(XmlProcessor::checkNodePath($nodePath, $expected), $result); } public static function checkNodePathDataProvider(): Generator @@ -135,21 +156,21 @@ public static function checkNodePathDataProvider(): Generator function testSetSkipNodes(): void { $xmlProcessor = new XmlProcessor([ - $this->getMockForAbstractClass(NodeProcessorInterface::class) + $this->createMock(NodeProcessorInterface::class) ]); $xmlProcessor->setSkipNodes(['foo']); - self::assertSame(['foo'], $xmlProcessor->getSkipNodes()); + static::assertSame(['foo'], $xmlProcessor->getSkipNodes()); } function testGetSkipNodes(): void { $xmlProcessor = new XmlProcessor([ - $this->getMockForAbstractClass(NodeProcessorInterface::class) + $this->createMock(NodeProcessorInterface::class) ]); - self::assertNull($xmlProcessor->getSkipNodes()); + static::assertNull($xmlProcessor->getSkipNodes()); $xmlProcessor->setSkipNodes([]); - self::assertSame([], $xmlProcessor->getSkipNodes()); + static::assertSame([], $xmlProcessor->getSkipNodes()); $xmlProcessor->setSkipNodes(['foo']); - self::assertSame(['foo'], $xmlProcessor->getSkipNodes()); + static::assertSame(['foo'], $xmlProcessor->getSkipNodes()); } }