Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -198,6 +200,10 @@
->decorate(PlantumlServerRenderer::class)
->public()

->set(DecoratingPlantumlBinaryRenderer::class)
->decorate(PlantumlRenderer::class)
->public()

->set(ConfvalMenuDirective::class)
->set(DirectoryTreeDirective::class)
->set(GlossaryDirective::class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace T3Docs\Typo3DocsTheme\Renderer;

use phpDocumentor\Guides\Graphs\Renderer\DiagramRenderer;
use phpDocumentor\Guides\RenderContext;

use function is_dir;
use function mkdir;
use function sys_get_temp_dir;

/**
* Decorator for PlantumlRenderer that ensures the temp directory exists.
*
* The upstream PlantumlRenderer uses tempnam() with a subdirectory that may not exist,
* which triggers an E_NOTICE. This decorator creates the directory before rendering.
*
* @see https://github.com/TYPO3-Documentation/render-guides/pull/1099
*/
final class DecoratingPlantumlBinaryRenderer implements DiagramRenderer
{
private const TEMP_SUBDIRECTORY = '/phpdocumentor';

public function __construct(private readonly DiagramRenderer $innerRenderer) {}

public function render(RenderContext $renderContext, string $diagram): string|null
{
$this->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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);

namespace T3Docs\Typo3DocsTheme\Tests\Unit\Renderer;

use phpDocumentor\Guides\Graphs\Renderer\DiagramRenderer;
use phpDocumentor\Guides\RenderContext;
use PHPUnit\Framework\Attributes\Test;
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
{
$tempDir = sys_get_temp_dir() . self::TEMP_SUBDIRECTORY;

// Remove the directory if it exists to test creation
if (is_dir($tempDir)) {
@rmdir($tempDir);
}

// Skip if we can't remove it (contains files from other processes)
if (is_dir($tempDir)) {
self::markTestSkipped('Cannot remove temp directory - it contains files from other processes');
}

$innerRenderer = $this->createMock(DiagramRenderer::class);
$innerRenderer->method('render')->willReturn('<svg></svg>');

$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 = '<svg>diagram</svg>';
$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);
}
}