Writing your own contents

  1. Model
  2. Configuration
  3. Writing your first content
  4. Controller
  5. Templates
  6. Referencing your content pages

First of all, you can start by reading the Stenope documentation.

Since this skeleton already showcases writing blog-post like content, using Markdown, let's take another example and use a different format.

Let's say you'd like to expose galleries of images somewhere in your app.

Model

Start by creating your model in src/Model/Gallery.php:

namespace App\Model;

use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;

class Gallery
{
    public function __construct(
        public string $slug,
        public string $title,
        public string $description,
        #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
        public \DateTime $createdAt,
        public bool $published = true,
    ) {
    }
}

Our gallery consists of a title, a description a creation date and a published/unpublished status.
The slug property is automatically injected by Stenope, and consists of the content filename.

Let's add a property holding an array of images:

class Gallery
{
    public function __construct(
        public string $slug,
        public string $title,
        public string $description,
+        /** @var GalleryImage[] */
+        public array $images,
        #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
        public \DateTime $createdAt,
        public bool $published = true,
    ) {
    }
}

and a dedicated model:

namespace App\Model;

class GalleryImage
{
    public function __construct(
        public string $path,
        public ?string $title = null,
        public ?string $description = null,
    ) {
    }
}

Each image will have a mandatory path on the filesystem, and an optional title and description.

Configuration

Let's now indicate to Stenope where our gallery contents will be stored:

# config/packages/stenope.yaml
stenope:
    providers:
        # […]
        App\Model\Gallery: '%kernel.project_dir%/content/galleries'

Writing your first content

For our use-case, since we do not have a main textual content, like we do with our articles in markdown, we'll use YAML as a structured format for our galleries.

Let's create a first gallery in content/galleries/christmas.yaml:

title: Christmas photos
description: A gallery of Christmas photos
createdAt: "2020-12-25"

images:
    - { path: 'images/galleries/christmas/img1.jpg', title: 'Image 1', description: 'Some description' }
    - { path: 'images/galleries/christmas/img2.jpg', title: 'Image 2' }
    - { path: 'images/galleries/christmas/img3.jpg' }

Note

The images paths are place in the public/images directory so that they are accessible from the web directly.
In case you need to resize them, you can use the Glide integration described in the guide.
In such case, the orignals does not need to be in the public directory, unless you want to let your user download it for instance.

Controller

Now, we need to create a controller action for each pages we want to expose our galleries.
We'll start with a simple list of galleries and a detail page for each:

namespace App\Controller;

use App\Model\Gallery;
use Stenope\Bundle\ContentManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

#[Route('/galleries')]
class GalleryController extends AbstractController
{
    public function __construct(private readonly ContentManagerInterface $manager)
    {
    }

    #[Route('/', name: 'gallery_list')]
    public function list(): Response
    {
        $galleries = $this->manager->getContents(
            Gallery::class,
            sortBy: ['createdAt' => false], // Sort by creation date, descending
            filterBy: '_.published', // Filters out unpublished galleries
        );

        return $this->render('galleries/list.html.twig', [
            'galleries' => $galleries,
        ]);
    }

    #[Route('/{gallery}', name: 'gallery_show', requirements: ['gallery' => '.+'])]
    public function show(Gallery $gallery): Response
    {
        return $this->render('galleries/show.html.twig', [
            'gallery' => $gallery,
        ]);
    }
}

This controller makes use of the ContentManagerInterface to fetch, sort and filter contents managed by Stenope.

Learn more on the documentation about:

Templates

Let's create the templates for each controller:

List:

{# templates/galleries/list.html.twig #}
{% extends 'base.html.twig' %}

{% block meta_title 'List of galleries' %}

{% block content %}
    <h1>Galleries</h1>

    <ul>
        {% for gallery in galleries %}
            <li>
                <a href="{{ path('gallery_show', { gallery: gallery.slug }) }}">
                    {{ gallery.title }}
                </a>
            </li>
        {% endfor %}
    </ul>
{% endblock %}

Show:

{# templates/galleries/show.html.twig #}
{% extends 'base.html.twig' %}

{% block meta_title gallery.title %}

{% block content %}
    <h1>{{ gallery.title }}</h1>

    <small>Created on {{ gallery.createdAt|date('Y-m-d') }}</small>

    <p>{{ gallery.description }}</p>

    {% for image in gallery.images %}
        <figure>
            <img src="{{ asset(image.path) }}" alt="{{ image.title }}">
            <figcaption>{{ image.description }}</figcaption>
        </figure>
    {% endfor %}
{% endblock %}

Note

We use the {{ asset() }} function to generate the image path, so that it is correctly prefixed with the app base URL, if any.

Referencing your content pages

Our galleries are now available at /galleries and /galleries/{slug} path, but there is no link pointing to these pages, so it'll be excluded from the static build, since unreachable.

Let's add a link to our galleries listing in the main menu:

# config/site.yaml
twig:
  globals:
    site:
      #[…]

      menu:
        - { path: 'home', label: 'Home' }
        - { path: 'article_list', label: 'Articles', children: ['article_show'] }
        - { path: 'gallery_list', label: 'Galleries', children: ['gallery_show'] }

Of course this is just an exemple using the skeleton layout. Adapt to your own needs!

Published on 03/09/2022 by Ogi