From f90f70364c706da2c9d9473746da9ec7e10f882f Mon Sep 17 00:00:00 2001 From: Kristian Hempel Date: Fri, 6 Feb 2026 18:05:08 +0100 Subject: [PATCH 1/2] docs: add ContentTypeStorageInterface usage example Add documentation showing how to access the current ContentType on any route using ContentTypeStorageInterface. Includes a practical Twig extension example for rendering a global header with context-aware navigation and language switching. Co-Authored-By: Claude Sonnet 4.5 --- README.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/README.md b/README.md index 0f39717..ebc802a 100644 --- a/README.md +++ b/README.md @@ -423,6 +423,103 @@ final readonly class PageController > support ticket to add a response header with the necessary information that would allow changing the first request to > a HEAD request, which would significantly reduce the overhead. +### Accessing the Current Content Type + +The bundle provides `ContentTypeStorageInterface` to access the current `ContentType` anywhere in your application. This is particularly useful when building reusable components like navigation menus, language switchers, breadcrumbs, or shared layouts that need context about the current page. + +#### Why is this useful? + +Without `ContentTypeStorageInterface`, you would need to pass the content type through every controller action and template, creating tight coupling and duplicating logic. With this interface, you can access the current content type in Twig extensions, event listeners, services, or any other part of your application - making your code cleaner and more maintainable. + +#### Example: Global Header with Content Type Context + +Here's a practical example of a Twig extension that renders a global header. It fetches header data from Storyblok and makes the current content type available to the template, enabling context-aware navigation, active menu highlighting, or language switching: + +```php +use Storyblok\Api\ContentApi\StoriesApiInterface; +use Storyblok\Api\Domain\Value\Resolver\ResolveLinks; +use Storyblok\Api\Domain\Value\Resolver\LinkType; +use Storyblok\Api\Request\StoryRequest; +use Storyblok\Bundle\ContentType\ContentTypeStorageInterface; +use Symfony\Component\DependencyInjection\Attribute\AsTwigFunction; +use Twig\Environment; + +final class HeaderExtension +{ + public function __construct( + private readonly StoriesApiInterface $stories, + private readonly ContentTypeStorageInterface $contentTypeStorage, + ) { + } + + /** + * @param array $parameters + */ + #[AsTwigFunction('render_header', needsEnvironment: true, isSafe: ['html'])] + public function renderHeader(Environment $twig, string $locale, array $parameters = []): string + { + $header = new Header($this->stories->bySlug('_global/header', new StoryRequest( + language: $locale, + resolveLinks: new ResolveLinks(LinkType::Link), + ))->story); + + return $twig->render('layouts/_header.html.twig', [ + ...$parameters, + 'header' => $header, + 'content_type' => $this->contentTypeStorage->getContentType(), + ]); + } +} +``` + +In your base template, use the function to render the header: + +```twig +{# templates/base.html.twig #} + + + + {% block title %}Welcome!{% endblock %} + + + {{ render_header(app.request.locale) }} + + {% block body %}{% endblock %} + + +``` + +In your header template, you now have access to the current content type for context-aware rendering: + +```twig +{# templates/layouts/_header.html.twig #} +
+ + + {# Example: Language switcher using content type's full slug #} + {% if content_type %} +
+ {% for locale in ['en', 'de', 'fr'] %} + + {{ locale|upper }} + + {% endfor %} +
+ {% endif %} +
+``` + +The `ContentTypeStorageInterface` provides seamless access to the current content type throughout your application without needing to pass it explicitly through every controller and template. + ### Caching The bundle provides a global caching configuration to enable HTTP caching directives, which From c4ab100738f98cddf1f77cb199a8ab4a716de7ee Mon Sep 17 00:00:00 2001 From: Kristian Hempel Date: Sat, 7 Feb 2026 15:54:53 +0100 Subject: [PATCH 2/2] docs: update README with examples for ContentTypeStorageInterface usage --- README.md | 151 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 87 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index ebc802a..46c6c52 100644 --- a/README.md +++ b/README.md @@ -427,98 +427,121 @@ final readonly class PageController The bundle provides `ContentTypeStorageInterface` to access the current `ContentType` anywhere in your application. This is particularly useful when building reusable components like navigation menus, language switchers, breadcrumbs, or shared layouts that need context about the current page. +> [!NOTE] +> `ContentTypeStorageInterface` only works in HTTP request context (controllers, Twig extensions, event listeners during request handling). It is not available in CLI/command context where no content type is being rendered. + #### Why is this useful? Without `ContentTypeStorageInterface`, you would need to pass the content type through every controller action and template, creating tight coupling and duplicating logic. With this interface, you can access the current content type in Twig extensions, event listeners, services, or any other part of your application - making your code cleaner and more maintainable. -#### Example: Global Header with Content Type Context +#### Example ContentType Implementation -Here's a practical example of a Twig extension that renders a global header. It fetches header data from Storyblok and makes the current content type available to the template, enabling context-aware navigation, active menu highlighting, or language switching: +First, here's a typical `Page` content type with common Storyblok properties: ```php -use Storyblok\Api\ContentApi\StoriesApiInterface; -use Storyblok\Api\Domain\Value\Resolver\ResolveLinks; -use Storyblok\Api\Domain\Value\Resolver\LinkType; -use Storyblok\Api\Request\StoryRequest; +namespace App\ContentType; + +use Storyblok\Bundle\ContentType\ContentType; +use Storyblok\Bundle\Util\ValueObjectTrait; + +final readonly class Page extends ContentType +{ + use ValueObjectTrait; + + public string $uuid; + public string $fullSlug; + public string $title; + private \DateTimeImmutable $publishedAt; + + public function __construct(array $values) + { + // Extract Storyblok story properties + $this->uuid = self::string($values, 'uuid'); + $this->fullSlug = self::string($values, 'full_slug'); + $this->publishedAt = self::DateTimeImmutable($values, 'published_at'); + + // Extract content fields + $content = $values['content']; + $this->title = self::string($content, 'title'); + } + + public function publishedAt(): \DateTimeImmutable + { + return $this->publishedAt; + } +} +``` + +#### Practical Example: Language Switcher + +Now you can access this content type anywhere in your application. Here's a language switcher that preserves the current page context across translations: + +```php +namespace App\Twig; + +use App\ContentType\Page; use Storyblok\Bundle\ContentType\ContentTypeStorageInterface; use Symfony\Component\DependencyInjection\Attribute\AsTwigFunction; -use Twig\Environment; -final class HeaderExtension +final readonly class LanguageSwitcherExtension { public function __construct( - private readonly StoriesApiInterface $stories, - private readonly ContentTypeStorageInterface $contentTypeStorage, + private ContentTypeStorageInterface $contentTypeStorage, ) { } /** - * @param array $parameters + * Returns language-specific URLs for the current page. + * @return array */ - #[AsTwigFunction('render_header', needsEnvironment: true, isSafe: ['html'])] - public function renderHeader(Environment $twig, string $locale, array $parameters = []): string + #[AsTwigFunction('language_urls')] + public function getLanguageUrls(): array { - $header = new Header($this->stories->bySlug('_global/header', new StoryRequest( - language: $locale, - resolveLinks: new ResolveLinks(LinkType::Link), - ))->story); - - return $twig->render('layouts/_header.html.twig', [ - ...$parameters, - 'header' => $header, - 'content_type' => $this->contentTypeStorage->getContentType(), - ]); + /** @var Page|null $page */ + $page = $this->contentTypeStorage->getContentType(); + + if (null !== $page) { + return []; + } + + $urls = []; + foreach (['en', 'de', 'fr'] as $locale) { + // Build localized URL using the page's full slug + $urls[$locale] = '/' . $locale . '/' . $page->fullSlug; + } + + return $urls; } } ``` -In your base template, use the function to render the header: +Usage in Twig: ```twig -{# templates/base.html.twig #} - - - - {% block title %}Welcome!{% endblock %} - - - {{ render_header(app.request.locale) }} - - {% block body %}{% endblock %} - - +{# templates/components/language_switcher.html.twig #} +
+ {% for locale, url in language_urls() %} + + {{ locale|upper }} + + {% endfor %} +
``` -In your header template, you now have access to the current content type for context-aware rendering: +#### Other Common Use Cases -```twig -{# templates/layouts/_header.html.twig #} -
- - - {# Example: Language switcher using content type's full slug #} - {% if content_type %} -
- {% for locale in ['en', 'de', 'fr'] %} - - {{ locale|upper }} - - {% endfor %} -
- {% endif %} -
-``` +- **Breadcrumb Navigation**: Split `fullSlug` (e.g., `products/electronics/laptops`) to build hierarchical breadcrumbs +- **Active Menu Highlighting**: Compare current page's `fullSlug` with menu item slugs to highlight active navigation +- **Canonical URLs**: Use `uuid` and `fullSlug` to generate canonical URLs and alternate language links +- **Page Metadata**: Access `title` and other properties for generating ``, `<meta>` tags, and OpenGraph data + +#### Key Benefits -The `ContentTypeStorageInterface` provides seamless access to the current content type throughout your application without needing to pass it explicitly through every controller and template. +- **No manual passing**: Access content type context anywhere without passing it through controller → template → component chains +- **Type-safe**: Your concrete ContentType class (e.g., `Page`) provides typed properties and IDE autocomplete +- **Flexible**: Works in Twig extensions, event listeners, services, and any part of your application during request handling ### Caching