From ac32eab0ebd524149749893a2555f6d00bf3c18a Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 9 Dec 2025 07:33:48 +0100 Subject: [PATCH 1/4] [BUGFIX] Fix tempnam() E_NOTICE when using PlantUML binary renderer When using the local PlantUML binary renderer (renderer="plantuml"), the upstream PlantumlRenderer calls tempnam() with a subdirectory that may not exist, triggering a PHP E_NOTICE that appears in the rendered documentation output. This adds a DecoratingPlantumlBinaryRenderer that ensures the temp directory exists before delegating to the inner renderer, fixing the issue without requiring upstream changes or global E_NOTICE suppression. Resolves: #1099 --- .../resources/config/typo3-docs-theme.php | 6 +++ .../DecoratingPlantumlBinaryRenderer.php | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php diff --git a/packages/typo3-docs-theme/resources/config/typo3-docs-theme.php b/packages/typo3-docs-theme/resources/config/typo3-docs-theme.php index 380cdbe76..2a70d407d 100644 --- a/packages/typo3-docs-theme/resources/config/typo3-docs-theme.php +++ b/packages/typo3-docs-theme/resources/config/typo3-docs-theme.php @@ -8,6 +8,7 @@ use phpDocumentor\Guides\Event\PostProjectNodeCreated; use phpDocumentor\Guides\Event\PostRenderProcess; use phpDocumentor\Guides\Event\PreParseProcess; +use phpDocumentor\Guides\Graphs\Renderer\PlantumlRenderer; use phpDocumentor\Guides\Graphs\Renderer\PlantumlServerRenderer; use phpDocumentor\Guides\ReferenceResolvers\DelegatingReferenceResolver; use phpDocumentor\Guides\ReferenceResolvers\Interlink\InventoryRepository; @@ -56,6 +57,7 @@ use T3Docs\Typo3DocsTheme\Parser\Productions\FieldList\TemplateFieldListItemRule; use T3Docs\Typo3DocsTheme\ReferenceResolvers\FileReferenceResolver; use T3Docs\Typo3DocsTheme\ReferenceResolvers\ObjectsInventory\ObjectInventory; +use T3Docs\Typo3DocsTheme\Renderer\DecoratingPlantumlBinaryRenderer; use T3Docs\Typo3DocsTheme\Renderer\DecoratingPlantumlRenderer; use T3Docs\Typo3DocsTheme\Renderer\MainMenuJsonRenderer; use T3Docs\Typo3DocsTheme\Renderer\NodeRenderer\MainMenuJsonDocumentRenderer; @@ -198,6 +200,10 @@ ->decorate(PlantumlServerRenderer::class) ->public() + ->set(DecoratingPlantumlBinaryRenderer::class) + ->decorate(PlantumlRenderer::class) + ->public() + ->set(ConfvalMenuDirective::class) ->set(DirectoryTreeDirective::class) ->set(GlossaryDirective::class) diff --git a/packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php b/packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php new file mode 100644 index 000000000..4246c26a2 --- /dev/null +++ b/packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php @@ -0,0 +1,44 @@ +ensureTempDirectoryExists(); + + return $this->innerRenderer->render($renderContext, $diagram); + } + + private function ensureTempDirectoryExists(): void + { + $tempDir = sys_get_temp_dir() . self::TEMP_SUBDIRECTORY; + + if (!is_dir($tempDir)) { + mkdir($tempDir, 0o777, true); + } + } +} From 1840c2694e12572c51f674fde01a07e1af8e6696 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 9 Dec 2025 08:08:22 +0100 Subject: [PATCH 2/4] [TASK] Add regression test for DecoratingPlantumlBinaryRenderer Add unit tests to verify the decorator: - Creates temp directory when missing before rendering - Properly delegates to the inner renderer Also changes constructor to accept DiagramRenderer interface instead of concrete PlantumlRenderer to enable mocking in tests. Relates: #1099 --- .../DecoratingPlantumlBinaryRendererTest.php | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php diff --git a/packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php b/packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php new file mode 100644 index 000000000..c1180e136 --- /dev/null +++ b/packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php @@ -0,0 +1,69 @@ +createMock(DiagramRenderer::class); + $innerRenderer->method('render')->willReturn(''); + + $renderContext = $this->createMock(RenderContext::class); + + $decorator = new DecoratingPlantumlBinaryRenderer($innerRenderer); + $decorator->render($renderContext, 'A -> B'); + + self::assertDirectoryExists($tempDir); + + // Clean up + @rmdir($tempDir); + } + + #[Test] + public function renderDelegatesToInnerRenderer(): void + { + $expectedResult = 'diagram'; + $diagram = 'A -> B'; + + $renderContext = $this->createMock(RenderContext::class); + + $innerRenderer = $this->createMock(DiagramRenderer::class); + $innerRenderer->expects(self::once()) + ->method('render') + ->with($renderContext, $diagram) + ->willReturn($expectedResult); + + $decorator = new DecoratingPlantumlBinaryRenderer($innerRenderer); + $result = $decorator->render($renderContext, $diagram); + + self::assertSame($expectedResult, $result); + } +} From b916a0c4b049105859bfe7eead4a7795ef90f8d1 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 9 Dec 2025 08:08:30 +0100 Subject: [PATCH 3/4] [TASK] Use DiagramRenderer interface for decorator dependency Change DecoratingPlantumlBinaryRenderer constructor to accept DiagramRenderer interface instead of concrete PlantumlRenderer. This improves testability and follows dependency inversion principle. The decorator only needs the interface contract, not the concrete implementation. Relates: #1099 --- .../src/Renderer/DecoratingPlantumlBinaryRenderer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php b/packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php index 4246c26a2..5333e6f3d 100644 --- a/packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php +++ b/packages/typo3-docs-theme/src/Renderer/DecoratingPlantumlBinaryRenderer.php @@ -5,7 +5,6 @@ namespace T3Docs\Typo3DocsTheme\Renderer; use phpDocumentor\Guides\Graphs\Renderer\DiagramRenderer; -use phpDocumentor\Guides\Graphs\Renderer\PlantumlRenderer; use phpDocumentor\Guides\RenderContext; use function is_dir; @@ -24,7 +23,7 @@ final class DecoratingPlantumlBinaryRenderer implements DiagramRenderer { private const TEMP_SUBDIRECTORY = '/phpdocumentor'; - public function __construct(private readonly PlantumlRenderer $innerRenderer) {} + public function __construct(private readonly DiagramRenderer $innerRenderer) {} public function render(RenderContext $renderContext, string $diagram): string|null { From 1d448232312121fd49a4576bd50e26e52e733456 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 9 Dec 2025 08:11:11 +0100 Subject: [PATCH 4/4] [TASK] Add canary test to detect upstream fix Add a test that expects tempnam() to trigger E_NOTICE when the directory doesn't exist. When this test FAILS, it indicates that PHP's behavior has changed or upstream has fixed the issue, and the DecoratingPlantumlBinaryRenderer workaround may no longer be needed. This helps track when we can safely remove this workaround. Relates: #1099 --- .../DecoratingPlantumlBinaryRendererTest.php | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php b/packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php index c1180e136..689d90e5b 100644 --- a/packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php +++ b/packages/typo3-docs-theme/tests/unit/Renderer/DecoratingPlantumlBinaryRendererTest.php @@ -10,14 +10,61 @@ use PHPUnit\Framework\TestCase; use T3Docs\Typo3DocsTheme\Renderer\DecoratingPlantumlBinaryRenderer; +use function file_exists; use function is_dir; use function rmdir; +use function set_error_handler; use function sys_get_temp_dir; +use function tempnam; +use function unlink; final class DecoratingPlantumlBinaryRendererTest extends TestCase { private const TEMP_SUBDIRECTORY = '/phpdocumentor'; + /** + * Canary test: Verifies that tempnam() still triggers E_NOTICE when directory doesn't exist. + * + * This test documents the upstream bug that DecoratingPlantumlBinaryRenderer works around. + * When this test FAILS, the upstream library (phpDocumentor/guides-graphs) has likely + * fixed the issue, and this decorator may no longer be needed. + * + * @see https://github.com/phpDocumentor/guides-graphs/issues/1 + * @see https://github.com/TYPO3-Documentation/render-guides/pull/1099 + */ + #[Test] + public function tempnamStillTriggersNoticeWhenDirectoryMissing(): void + { + // Use a unique non-existent directory to avoid interference + $nonExistentDir = sys_get_temp_dir() . '/phpdocumentor_canary_' . uniqid(); + + $noticeTriggered = false; + $previousHandler = set_error_handler(static function (int $errno, string $errstr) use (&$noticeTriggered): bool { + if ($errno === E_NOTICE && str_contains($errstr, 'tempnam()')) { + $noticeTriggered = true; + } + return false; // Let PHP handle it normally + }); + + try { + $tempFile = @tempnam($nonExistentDir, 'canary_'); + + // Clean up the temp file if it was created (in system temp dir) + if ($tempFile !== false && file_exists($tempFile)) { + unlink($tempFile); + } + } finally { + set_error_handler($previousHandler); + } + + self::assertTrue( + $noticeTriggered, + 'Expected tempnam() to trigger E_NOTICE when directory does not exist. ' + . 'If this test fails, the upstream issue may be fixed and DecoratingPlantumlBinaryRenderer ' + . 'might no longer be needed. Check: https://github.com/phpDocumentor/guides-graphs/issues/1' + ); + } + #[Test] public function renderCreatesTempDirectoryWhenMissing(): void {