Skip to main content

Adding a media reference to a Sulu entity

This blog post might be outdated!
This blog post was published more than one year ago and might be outdated!
· 4 min read
Stephan Hochdörfer
Head of IT Business Operations

For a current task, I needed to add a media reference to a custom Sulu entity. At first, it seemed like an easy task but I had issues getting the media image to render in the list view. Thanks to the fabulous Sulu core team and their Slack community, I quickly got some pointers on how to deal with the issue.

First, let's add the property to the entity which will hold the media information. We will call this property "item":

#[ORM\ManyToOne(targetEntity: \Sulu\Bundle\MediaBundle\Entity\Media::class)]
#[ORM\JoinColumn(name: 'icon_id', referencedColumnName: 'id', onDelete: 'SET NULL')]
private ?\Sulu\Bundle\MediaBundle\Entity\Media $icon = null;

Some weird little catch: Initially, I used the \Sulu\Bundle\MediaBundle\Entity\MediaInterface class reference but for some odd reason phpstan-doctrine reported a "type mapping mismatch". As it seems, I am not the only one with this issue. After changing the reference to \Sulu\Bundle\MediaBundle\Entity\Media, phpstan was happy.

We also need to add the getter and setter methods to the entity:

public function getIcon(): ?\Sulu\Bundle\MediaBundle\Entity\Media
{
return $this->icon;
}

public function setIcon(\Sulu\Bundle\MediaBundle\Entity\Media $icon): self
{
$this->icon = $icon;

return $this;
}

In the form xml structure, a property block is added to allow the user to select the media item. Since we only want the user to choose from images, passing "image" as a "type" parameter to the property was needed:

<property name="icon" type="single_media_selection" mandatory="true">
<meta>
<title>app.track_icon</title>
</meta>

<params>
<param name="types" value="image"/>
</params>
</property>

To render a thumbnail version of the icon in the list overview, the following configuration will do. Replace App\Entity\MyEntity with your entity class below:

<property name="icon" visibility="always" translation="app.track_icon" sortable="false">
<field-name>id</field-name>
<entity-name>SuluMediaBundle:Media</entity-name>
<joins>
<join>
<entity-name>SuluMediaBundle:Media</entity-name>
<field-name>App\Entity\MyEntity.icon</field-name>
</join>
</joins>

<transformer type="thumbnails"/>
</property>

As the last step, we need to make sure the Admin API responses contain all the needed data so that the Sulu Admin interface is able to properly render everything.

For the list view, it is necessary to merge in the media information into the regular response. A typical getListAction() implementation looks like this:

$listRepresentation = $this->doctrineListRepresentationFactory->createDoctrineListRepresentation(
MyEntity::RESOURCE_KEY,
);

return $this->json($listRepresentation->toArray());

The code for merging the media information looks like the code snipped below. First, we extract all the media Ids from the list response and use those to get the urls from the media manager component. The result is then merged into the original list response to replace the icon id with the respective url. This code snippet is added in the code block above right before the return statement:

$locale = $this->getLocale($request);
$entities = $listRepresentation->getData();
$ids = \array_filter(\array_column($entities, 'icon'));
/** @var \Sulu\Bundle\MediaBundle\Media\Manager\MediaManagerInterface $this->mediaManager */
$icons = $this->mediaManager->getFormatUrls($ids, $locale);
foreach ($entities as $key => $item) {
if (\array_key_exists('icon', $item)
&& $item['icon']
&& \array_key_exists($item['icon'], $icons)
) {
$entities[$key]['icon'] = $icons[$item['icon']];
}
}

$listRepresentation = new CollectionRepresentation($entities, $listRepresentation->getRel());

Additionally, all other routes returning the entity details (e.g. for the form view) also need to return the icon information to properly render it. At first, I tried to return the icon object but that resulted in an error "A circular reference has been detected when serializing..." in the JSON API. Thanks to the help of the Sulu core developers, the change is simple: Instead of returning the icon object, return an array with an "id" field.

Following the code in the sulu-workshop repository, my Admin controller has a getDataForEntity() method which is called by all methods that return the single entity data. That means I can make the change in just one location:

protected function getDataForEntity(MyEntiry $entity): array
{
return [
'id' => $entity->getId(),
'name' => $entity->getName() ?? '',
// fixes issue with the JSON serializer "A circular reference has been detected when serializing..."
'icon' => $entity->getIcon() ? ['id' => $entity->getIcon()->getId()] : null,
];
}

Thanks for following along. If you have a specific Sulu question, join us in the Sulu slack!