Was this page helpful?

Using Rich Text in the PHP Delivery SDK

Sometimes you find yourself in a situation where the editor must have great control over how content is structured. For such cases, the rich text field type is exactly what you need. The previous link will discuss how this field type is handled in the web app, whereas this tutorial will teach how you can use it in your applications with the PHP Delivery SDK.

The SDK automatically handles rich text field types, using the rich-text.php library. However, it's very likely that you might want to customize its behavior. You can do this in two ways: through the NodeRenderer and through the NodeParser.

Customizing the rendering

Often you might want to customize the rendering of a certain node type, most commonly those with entries or assets. In order to do so, you need to work with a main renderer, and multiple node renderers. Let's make an example with the following assumption: we have a content type with ID blogPost, which has a slug and a title (simple strings) and a body (a rich text field). We want to allow the editor to link to other blog posts in the body, and in our templates use the slug property to generate the appropriate link:

use Contentful\RichText\NodeRenderer\NodeRendererInterface;
use Contentful\RichText\Node\EntryHyperlink;
use Contentful\RichText\Node\NodeInterface;
use Contentful\RichText\RendererInterface;

class BlogPostEntryHyperlink implements NodeRendererInterface
{
    private $urlGenerator;

    /**
     * As this class must only implement an interface,
     * the constructor can be anything you need, so it's dependency injection-friendly.
     * In this example we inject an object which will handle
     * the generation of a URL in the rendering phase.
     */
    public function __construct($urlGenerator)
    {
        $this->urlGenerator = $urlGenerator;
    }

    /**
     * This method must return whether the current node renderer supports rendering
     * for the given node.
     *
     * In this example, we check first if the node is of type EntryHyperlink,
     * and second whether the entry is of content type "blogPost".
     */
    public function supports(NodeInterface $node): bool
    {
        if (!$node instanceof EntryHyperlink) {
            return false;
        }

        /** @var \Contentful\Delivery\Resource\Entry $entry */
        $entry = $node->getEntry();

        return $entry->getContentType()->getId() === 'blogPost';
    }

    /**
     * This method is supposed to transform a node object into a string
     */
    public function render(RendererInterface $renderer, NodeInterface $node, array $context = []): string
    {
        /** @var \Contentful\Delivery\Resource\Entry $entry */
        $entry = $node->getEntry();

        return \sprintf(
            '<a href="%s">%s</a>',
            $this->urlGenerator->generate('blogPost', ['slug' => $entry->get('slug')]),
            $entry->get('title')
        );
    }
}

// ...
$renderer = new \Contentful\RichText\Renderer();

// $urlGenerator is something specific to your application,
// it can be for instance of Symfony\Component\Routing\Generator\UrlGeneratorInterface
// if you're in a Symfony application
$blogPostEntryHyperlink = new BlogPostEntryHyperlink($urlGenerator);
$renderer->pushNodeRenderer($blogPostEntryHyperlink);


$entry = $client->getEntry($entryId);
// Optionally, you can pass a context array as second parameter of the render method,
// which will be passed down to all node renderers in their "render" methods.
$output = $renderer->render($entry->get('body'));

For more about extending the renderer, and how to integrate it with Twig or Plates, take a look at the library's README.

Customizing the parsing

A less common yet still useful use case is to customize the parsing of rich text, that is, the process of converting the raw API response structured as a nested PHP array into a graph of Contentful\RichText\Node\NodeInterface objects. The Delivery SDK automatically creates an instance of the Contentful\RichText\NodeParser class, and you can access it using $client->getNodeParser(); the most common use case is to define custom node mappers.

use Contentful\Core\Api\LinkResolverInterface;
use Contentful\RichText\ParserInterface;
use Contentful\RichText\Node\NodeInterface;
use Contentful\RichText\NodeMapper\NodeMapperInterface;

class CustomNodeMapper implements NodeMapperInterface
{
    public function map(ParserInterface $parser, LinkResolverInterface $linkResolver, array $data): NodeInterface
    {
        // CustomNode can be any class which implements NodeInterface
        return new CustomNode($data);
    }
}

// ...

$parser = $client->getNodeParser();
$parser->setNodeMapper('text', new CustomNodeMapper());

In the example above, all nodes with "nodeType": "text" will be forwarded to the custom node mapper instead of the default one.