Skip to content
Open
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
79 changes: 79 additions & 0 deletions docs/developers/caching.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
=======
Caching
=======

The guides library supports optional caching to improve performance when rendering
documentation repeatedly. This is particularly useful for development workflows
and CI/CD pipelines where the same templates are rendered multiple times.

Template Caching
================

Twig templates can be compiled and cached to avoid re-parsing them on each render.
This significantly improves performance when rendering large documentation sets.

To enable template caching, pass a cache directory to the ``EnvironmentBuilder``:

.. code-block:: php

use phpDocumentor\Guides\Twig\EnvironmentBuilder;
use phpDocumentor\Guides\Twig\Theme\ThemeManager;

$cacheDir = '/path/to/cache/twig';

$environmentBuilder = new EnvironmentBuilder(
themeManager: $themeManager,
extensions: $extensions,
cacheDir: $cacheDir,
);

When caching is enabled:

- Compiled templates are stored in the specified directory
- Subsequent renders use the cached compiled templates
- Templates are automatically recompiled when the source changes (``auto_reload: true``)

To disable caching (default behavior), pass ``false`` or omit the parameter:

.. code-block:: php

// Caching disabled (default)
$environmentBuilder = new EnvironmentBuilder(
themeManager: $themeManager,
extensions: $extensions,
);

Cache Directory Permissions
---------------------------

Ensure the cache directory:

- Exists and is writable by the PHP process
- Is excluded from version control (add to ``.gitignore``)
- Is cleared when deploying new template versions in production

Symfony Integration
-------------------

When using the guides library with Symfony's dependency injection, configure
the cache directory in your services configuration:

.. code-block:: yaml

# config/services.yaml
services:
phpDocumentor\Guides\Twig\EnvironmentBuilder:
arguments:
$cacheDir: '%kernel.cache_dir%/guides/twig'

Performance Impact
------------------

Template caching provides the most benefit when:

- Rendering large documentation sets (100+ files)
- Running repeated builds during development
- Using complex templates with many includes

For small documentation sets or one-time builds, the overhead of cache management
may not provide significant benefits.
1 change: 1 addition & 0 deletions docs/developers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ it in some other way that is not possible with the ``guides`` command line tool.
extensions/index
compiler
directive
caching
18 changes: 14 additions & 4 deletions packages/guides/src/Twig/EnvironmentBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,22 @@ final class EnvironmentBuilder
{
private Environment $environment;

/** @param ExtensionInterface[] $extensions */
public function __construct(ThemeManager $themeManager, iterable $extensions = [])
{
/**
* @param ExtensionInterface[] $extensions
* @param string|false $cacheDir Directory for compiled Twig templates, or false to disable caching
*/
public function __construct(
ThemeManager $themeManager,
iterable $extensions = [],
string|false $cacheDir = false,
) {
$this->environment = new Environment(
$themeManager->getFilesystemLoader(),
['debug' => true],
[
'debug' => true,
'cache' => $cacheDir,
'auto_reload' => true,
],
);
$this->environment->addExtension(new DebugExtension());

Expand Down
132 changes: 132 additions & 0 deletions packages/guides/tests/unit/Twig/EnvironmentBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

declare(strict_types=1);

/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link https://phpdoc.org
*/

namespace phpDocumentor\Guides\Twig;

use phpDocumentor\Guides\RenderContext;
use phpDocumentor\Guides\Twig\Theme\ThemeManager;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Extension\DebugExtension;
use Twig\Extension\ExtensionInterface;
use Twig\Loader\FilesystemLoader;

use function glob;
use function is_dir;
use function rmdir;
use function sys_get_temp_dir;
use function uniqid;
use function unlink;

final class EnvironmentBuilderTest extends TestCase
{
private ThemeManager $themeManager;
private string $cacheDir;
private string $fixtureDir;

protected function setUp(): void
{
$this->fixtureDir = __DIR__ . '/Theme/fixtures';
$loader = new FilesystemLoader([], $this->fixtureDir);
$this->themeManager = new ThemeManager($loader, []);
$this->cacheDir = sys_get_temp_dir() . '/phpDocumentor-guides-twig-test-' . uniqid();
}

protected function tearDown(): void
{
// Clean up cache directory
if (!is_dir($this->cacheDir)) {
return;
}

$files = glob($this->cacheDir . '/*');
if ($files !== false) {
foreach ($files as $file) {
unlink($file);
}
}

rmdir($this->cacheDir);
}

public function testEnvironmentBuilderWithoutCaching(): void
{
$builder = new EnvironmentBuilder($this->themeManager);
$environment = $builder->getTwigEnvironment();

self::assertFalse($environment->getCache());
self::assertTrue($environment->isDebug());
}

public function testEnvironmentBuilderWithCaching(): void
{
$builder = new EnvironmentBuilder($this->themeManager, [], $this->cacheDir);
$environment = $builder->getTwigEnvironment();

self::assertSame($this->cacheDir, $environment->getCache());
self::assertTrue($environment->isDebug());
}

public function testDebugExtensionIsAlwaysAdded(): void
{
$builder = new EnvironmentBuilder($this->themeManager);
$environment = $builder->getTwigEnvironment();

self::assertTrue($environment->hasExtension(DebugExtension::class));
}

public function testCustomExtensionsAreAdded(): void
{
$extension = $this->createMock(ExtensionInterface::class);
$extensionClass = $extension::class;

$builder = new EnvironmentBuilder($this->themeManager, [$extension]);
$environment = $builder->getTwigEnvironment();

self::assertTrue($environment->hasExtension($extensionClass));
}

public function testSetContextAddsGlobal(): void
{
$builder = new EnvironmentBuilder($this->themeManager);
$context = $this->createMock(RenderContext::class);

$builder->setContext($context);
$environment = $builder->getTwigEnvironment();

$globals = $environment->getGlobals();
self::assertArrayHasKey('env', $globals);
self::assertSame($context, $globals['env']);
}

public function testAutoReloadIsEnabled(): void
{
$builder = new EnvironmentBuilder($this->themeManager, [], $this->cacheDir);
$environment = $builder->getTwigEnvironment();

self::assertTrue($environment->isAutoReload());
}

public function testSetEnvironmentFactory(): void
{
$builder = new EnvironmentBuilder($this->themeManager);
$originalEnvironment = $builder->getTwigEnvironment();

$loader = new FilesystemLoader();
$builder->setEnvironmentFactory(static fn () => new Environment($loader));

$newEnvironment = $builder->getTwigEnvironment();

self::assertNotSame($originalEnvironment, $newEnvironment);
}
}