Skip to content

Content Modeling in Symfony

Learn how to handle different content types and nestable blocks, render rich text, and use story references to manage content globally.

Alternatively, in the existing space, create the following blocks:

  • An article content type block with the following fields:
    • title: Text
    • content: Rich text
  • An article-overview content type block with the following field:
    • body: Blocks
  • A featured-articles nestable block with the following field:
    • articles: References

Next, create an Articles folder, open it, and create the follwoing stories:

  • A few stories that use the article content type.
  • An article overview story with a article-overview content type. Select the option Define as root for the folder.

Finally, add the featured-articles block to the body field of both the Home and Article overview stories, and select articles to feature.

In the Symfony project, create a new controller to get all the stories from the article-overview content type.

src/Controller/ArticleOverviewController.php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\ContentType\ArticleOverview;
use Storyblok\Api\StoriesApiInterface;
use Storyblok\Api\Request\StoriesRequest;
use Storyblok\Bundle\ContentType\Attribute\AsContentTypeController;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
#[AsContentTypeController(contentType: ArticleOverview::class)]
final class ArticleOverviewController extends AbstractController
{
public function __construct(
private readonly StoriesApiInterface $storiesApi
) {
}
public function __invoke(Request $request, ArticleOverview $articleOverview): Response
{
$response = $this->storiesApi->allByContentType('article');
return $this->render('article_overview.html.twig', [
'articleOverview' => $articleOverview,
'articles' => $response->stories,
]);
}
}

Next, create a new content type and a matching template.

src/ContentType/ArticleOverview.php
<?php
declare(strict_types=1);
namespace App\ContentType;
use Storyblok\Bundle\ContentType\ContentType;
final readonly class ArticleOverview extends ContentType
{
public string $uuid;
public string $name;
public string $slug;
public \DateTimeImmutable $publishedAt;
public function __construct(array $values)
{
$this->uuid = $values['uuid'];
$this->name = $values['name'];
$this->slug = $values['full_slug'];
$this->publishedAt = new \DateTimeImmutable($values['published_at']);
}
public function publishedAt(): \DateTimeImmutable
{
return $this->publishedAt;
}
public static function type(): string
{
return 'article-overview';
}
}
templates/article-overview.html.twig
{% extends 'base.html.twig' %}
{% block title %}Articles - {{ parent() }}{% endblock %}
{% block body %}
<h1>Articles</h1>
<ul>
{% for article in articles %}
{% if article is iterable and article.full_slug is defined %}
<li>
<a href="/{{ article.full_slug }}">{{ article.name }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% endblock %}

Run the Symfony development server and open http://localhost:8000/articles in the browser. The article overview page now shows a list of links to the selected articles.

Add a new controller and content type class to render individual article-type stories.

src/Controller/ArticleController.php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\ContentType\Article;
use Storyblok\Bundle\ContentType\Attribute\AsContentTypeController;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
#[AsContentTypeController(contentType: Article::class)]
final class ArticleController extends AbstractController
{
public function __invoke(Request $request, Article $article): Response
{
return $this->render('article.html.twig', [
'article' => $article,
]);
}
}
src/ContentType/Article.php
<?php
declare(strict_types=1);
namespace App\ContentType;
use Storyblok\Bundle\ContentType\ContentType;
final readonly class Article extends ContentType
{
public string $uuid;
public string $name;
public string $slug;
public string $title;
public array $content;
public \DateTimeImmutable $publishedAt;
public function __construct(array $values)
{
$this->uuid = $values['uuid'];
$this->name = $values['name'];
$this->slug = $values['full_slug'];
$this->title = $values['content']['title'] ?? '';
$this->content = $values['content']['content'] ?? [];
$this->publishedAt = new \DateTimeImmutable($values['published_at']);
}
public function publishedAt(): \DateTimeImmutable
{
return $this->publishedAt;
}
}

Finally, create the article template.

templates/article.html.twig
{% extends 'base.html.twig' %}
{% block body %}
<article>
<h1>{{ article.title }}</h1>
<div class="content">
{{ article.content|rich_text }}
</div>
</article>
{% endblock %}

Use the rich_text Twig filter to propertly render the content in the rich text field.

The last step is to render the featured stories referenced in the home story. Update PageController.php to use StoriesApiInterface and RelationCollection.


src/Controller/PageController.php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\ContentType\Page;
use Storyblok\Api\StoriesApiInterface;
use Storyblok\Api\Request\StoryRequest;
use Storyblok\Api\Domain\Value\Resolver\RelationCollection;
use Storyblok\Bundle\ContentType\Attribute\AsContentTypeController;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
#[AsContentTypeController(contentType: Page::class)]
final class PageController extends AbstractController
{
public function __construct(
private readonly StoriesApiInterface $storiesApi
) {
}
public function __invoke(Request $request, Page $page): Response
{
$response = $this->storiesApi->bySlug(
$page->slug,
new StoryRequest(
withRelations: new RelationCollection(['featured-articles.articles'])
)
);
$resolvedPage = new Page($response->story);
return $this->render('page.html.twig', [
'page' => $page,
'page' => $resolvedPage,
]);
}
}

In config/packages/storyblok.yaml, uncomment auto_resolve_relations. Ensure it's set to true to get the full object response of referenced stories.

Next, create a block class to handle the featured articles.

src/Block/FeaturedArticles.php
<?php
declare(strict_types=1);
namespace App\Block;
use Storyblok\Bundle\Block\Attribute\AsBlock;
#[AsBlock(name: 'featured-articles', template: 'blocks/featured-articles.html.twig')]
final readonly class FeaturedArticles
{
public array $articles;
public function __construct(array $values)
{
$this->articles = $values['articles'] ?? [];
}
}

Then, create the corresponding Twig template.

templates/blocks/featured-articles.html.twig
<section class="featured-articles">
<h2>Featured Articles</h2>
<ul>
{% for article in block.articles %}
{% if article is iterable and article.full_slug is defined %}
<li>
<a href="/{{ article.full_slug }}">{{ article.name }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</section>

In the browser, navigate to the homepage and view the links to the featured articles.

Was this page helpful?

What went wrong?

This site uses reCAPTCHA and Google's Privacy Policy. Terms of Service apply.