Skip to content

Structured data guide

Model

Let's assume we defined following model

php
namespace App\Entity;

use Doctrine\Common\Collections\Collection;
use Enhavo\Bundle\MediaBundle\Model\FileInterface;

class Article 
{
    public ?string $title;
    public ?FileInterface $image;
    public ?Author $author;
    public array $blocks = [];
}
php
namespace App\Entity;

class Author 
{
    public ?string $firstName;
    public ?string $lastName;
}

The property blocks can contain models from different block types.

php
namespace App\Entity;

class TextBlock 
{
    public ?string $text;
}
php
namespace App\Entity;

class ImageBlock 
{
    public ?FileInterface $image;
    public ?string $caption;
}

Manager

To create structured data, we just need the StructuredDataManager. You can easily inject it as a service. Here we have an Endpoint

php
namespace App\Endpoint;

use Enhavo\Bundle\ApiBundle\Endpoint\AbstractEndpointType;
use Enhavo\Bundle\ArticleBundle\Repository\ArticleRepository;
use Enhavo\Bundle\ContentBundle\StructuredData\StructuredDataManager;
use Enhavo\Bundle\ApiBundle\Data\Data;
use Enhavo\Bundle\ApiBundle\Endpoint\Context;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

#[Route(path: '/show/article')]
class ArticleEndpoint extends AbstractEndpointType
{
    public function __construct(
        private StructuredDataManager $structuredDataManager,
    ) {}

    public function handleRequest($options, Request $request, Data $data, Context $context): void
    {
        $article = $this->getArticle($request);
        $data->set('structuredData', $this->structuredDataManager->getData($article));
    }
    
    public function getArticle(Request $request) 
    {
        // receive article from request, e.g. use a repository
    }
}

Markup

Let's have a look at some markup examples.

Mapping

The easiest case is just to map properties. You can use the StructuredData Attribute or yaml. You have to apply a type to the class first. This tells the manager where a type begins.

Then you mark up the properties or functions and use the property option to map it to the type.

php
use Enhavo\Bundle\ContentBundle\Attribute\StructuredData; 

#[StructuredData('blog_posting')] 
class Article 
{
    #[StructuredData('blog_posting', ['property' => 'headline'])] 
    public ?string $title;
    // ...
}
yaml
enhavo_content:
    structured_data:
        App\Entity\Article:
            class:
                blog_posting:
                    type: blog_posting
            properties:
                title:
                    blog_posting:
                        type: blog_posting
                        property: headline

Structured Data output:

php
[
    "@context": "http://schema.org"
    "@type" => "BlogPosting",
    "headline" => "Lorem ipsum",
]

Transform

Some Objects need to transformed into plain string. So you can apply a transformer. Let's apply a media_url transformer on the image property to convert it into an absolute url. But we don't want a format and not the original file, so we need to pass some options to the transformer as well.

php
use Enhavo\Bundle\ContentBundle\Attribute\StructuredData; 

#[StructuredData('blog_posting')] 
class Article 
{
    #[StructuredData('blog_posting', [ 
        'property' => 'headline', 
        'transform' => 'media_url', 
        'transform_options' => [ 'format' => 'article_wide' ] 
    ])] 
    public FileInterface $image;
    // ...
}
yaml
enhavo_content:
    structured_data:
        App\Entity\Article:
            class:
                blog_posting:
                    type: blog_posting
            properties:
                image:
                    blog_posting:
                        type: blog_posting
                        property: image
                        transform: media_url
                        transform_options:
                            format: article_wide

Structured Data output:

php
[
        "@context" => "http://schema.org"
        "@type" => "BlogPosting",
        "image" => "https://domain.tld/media/format/article_wide/0f2396b/image.png",
]

Nested mapping

In schema.org you can nest types. Here is a simple example on how you can add an author to a BlogPosting type

php
use Enhavo\Bundle\ContentBundle\Attribute\StructuredData; 

#[StructuredData('blog_posting')] 
class Article 
{
    #[StructuredData('model')] 
    public Author $image;
    // ...
}

// Author model:

#[StructuredData('author', [ 
    'append_type' => 'BlogPosting',  
    'append_property' => 'author'
])], 
class Author 
{
    #[StructuredData('author', ['property' => 'name'])] 
    public function getName() { 
        return $this->firstName.' '.$this->lastName; 
    } 
}
yaml
enhavo_content:
    structured_data:
        App\Entity\Article:
            class:
                blog_posting:
                    type: blog_posting
            properties:
                author:
                    model:
                        type: model
        App\Entity\Author:
            class:
                author:
                    type: author
                    append_type: BlogPosting
                    append_property: author
            methods:
                name:
                    author:
                        type: author
                        property: name

Structured Data output:

php
[
    "@context": "http://schema.org"
    "@type" => "BlogPosting",
    "author" => [
        "@type" => "Author"
        "name" => "Peter Pan"
    ],
]

Nested root types

Some types can be found inside a model, but are not nested to the origin type. If you want to make it as a root type you can check out following example.

php
use Enhavo\Bundle\ContentBundle\Attribute\StructuredData; 

#[StructuredData('blog_posting')] 
class Article 
{
    #[StructuredData('model')] 
    public array $blocks = [];
    // ...
}

// ImageBlock model:

#[StructuredData('image')] 
class ImageBlock 
{
    #[StructuredData('image', [ 
        'property' => 'image', 
        'transform' => 'media_url', 
    ])] 
    public FileInterface $image;
}
yaml
enhavo_content:
    structured_data:
        App\Entity\Article:
            class:
                blog_posting:
                    type: blog_posting
            properties:
                author:
                    model:
                        type: model
        App\Entity\ImageBlock:
            class:
                image:
                    type: image
            property:
                image:
                    image:
                        type: image
                        property: url
                        transform: media_url

Structured Data output:

php
[
    "@context": "http://schema.org"
    "@type" => "BlogPosting",
    // ...
],
[
    "@context": "http://schema.org"
    "@type" => "Image",
    "url" => "https://domain.tld/media/file/0f2396b/image.png"
]

Groups

tbc.

Create custom type

php
namespace App\StructuredData\Type;

use Enhavo\Bundle\ContentBundle\StructuredData\AbstractStructuredDataType;
use Enhavo\Bundle\ContentBundle\StructuredData\Context;
use Enhavo\Bundle\ContentBundle\StructuredData\StructuredData;
use Enhavo\Bundle\ContentBundle\StructuredData\StructuredDataBag;
use Enhavo\Bundle\MediaBundle\Routing\UrlGeneratorInterface;
use Enhavo\Bundle\ResourceBundle\ExpressionLanguage\ResourceExpressionLanguage;
use Enhavo\Bundle\SettingBundle\Setting\SettingManager;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CorporationExtensionStructuredDataType extends AbstractStructuredDataType
{
    public function buildData(array $options, object $model, StructuredDataBag $bag, Context $context): void
    {
        $data = $bag->createType('Corporation', true);
        $data->set('url', '');
        $data->set('description', '');
    }

    public static function getName(): ?string
    {
        return 'corporation_extension';
    }
}
php
namespace App\Entity;

use Enhavo\Bundle\ContentBundle\Attribute\StructuredData;

#[StructuredData('blog_posting')] 
#[StructuredData('corporation_extension')]  
class Article 
{
    // ...
}
yaml
enhavo_content:
    structured_data:
        App\Entity\Article:
            class:
                corporation_extension:
                    type: corporation_extension
php
[
    "@context" => "http://schema.org"
    "@type" => "BlogPosting",
   // ...
],
[
    "@context" => "http://schema.org"
    "@type" => "Corporation",
    "url" => "",
    "description" => "",
]

Create custom transformer

tbc.