Structured data guide
Model
Let's assume we defined following model
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 = [];
}
namespace App\Entity;
class Author
{
public ?string $firstName;
public ?string $lastName;
}
The property blocks
can contain models from different block types.
namespace App\Entity;
class TextBlock
{
public ?string $text;
}
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
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.
use Enhavo\Bundle\ContentBundle\Attribute\StructuredData;
#[StructuredData('blog_posting')]
class Article
{
#[StructuredData('blog_posting', ['property' => 'headline'])]
public ?string $title;
// ...
}
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:
[
"@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.
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;
// ...
}
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:
[
"@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
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;
}
}
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:
[
"@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.
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;
}
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:
[
"@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
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';
}
}
namespace App\Entity;
use Enhavo\Bundle\ContentBundle\Attribute\StructuredData;
#[StructuredData('blog_posting')]
#[StructuredData('corporation_extension')]
class Article
{
// ...
}
enhavo_content:
structured_data:
App\Entity\Article:
class:
corporation_extension:
type: corporation_extension
[
"@context" => "http://schema.org"
"@type" => "BlogPosting",
// ...
],
[
"@context" => "http://schema.org"
"@type" => "Corporation",
"url" => "",
"description" => "",
]
Create custom transformer
tbc.