4. Your first block
This quick tutorial explains how to create an RSS reader block.
A block service is just a service which must implement the BlockServiceInterface
interface. There is only one instance of a block service, however there are many block instances.
4.1. First namespaces
The AbstractBlockService
implements some basic methods defined by the interface.
The current RSS block will extend this base class. The other use statements are required by the interface’s remaining methods:
namespace Sonata\BlockBundle\Block;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Block\Service\AbstractBlockService;
use Sonata\BlockBundle\Mapper\FormMapper;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\Form\Validator\ErrorElement;
4.2. Default settings
A block service needs settings to work properly, so to ensure consistency, the service should define a configureOptions
method:
public function configureSettings(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'url' => false,
'title' => 'Insert the rss title',
'template' => '@SonataBlock/Block/block_core_rss.html.twig',
]);
}
In the current tutorial, the default settings are:
URL: the feed url,
title: the block title,
template: the template to render the block.
4.3. Form Editing
You can define an editing config the following way:
public function buildEditForm(FormMapper $formMapper, BlockInterface $block): void
{
$formMapper
->add('settings', 'sonata_type_immutable_array', [
'keys' => [
['url', 'url', ['required' => false]],
['title', 'text', ['required' => false]],
]
])
;
}
The validation is done at runtime through a validateBlock
method. You can call any Symfony assertions, like:
public function validateBlock(ErrorElement $errorElement, BlockInterface $block): void
{
$errorElement
->with('settings.url')
->assertNotNull([])
->assertNotBlank()
->end()
->with('settings.title')
->assertNotNull([])
->assertNotBlank()
->assertMaxLength(['limit' => 50])
->end()
;
}
The sonata_type_immutable_array
type is a specific form type which allows to edit an array.
4.4. Execute
The next step is to implement the execute method. This method must return a Response
object, which is used to render the block:
public function execute(BlockContextInterface $blockContext, Response $response = null): Reponse
{
// merge settings
$settings = $blockContext->getSettings();
$feeds = false;
if ($settings['url']) {
$options = [
'http' => [
'user_agent' => 'Sonata/RSS Reader',
'timeout' => 2,
]
];
// retrieve contents with a specific stream context to avoid php errors
$content = @file_get_contents($settings['url'], false, stream_context_create($options));
if ($content) {
// generate a simple xml element
try {
$feeds = new \SimpleXMLElement($content);
$feeds = $feeds->channel->item;
} catch (\Exception $e) {
// silently fail error
}
}
}
return $this->renderResponse($blockContext->getTemplate(), [
'feeds' => $feeds,
'block' => $blockContext->getBlock(),
'settings' => $settings
], $response);
}
4.5. Template
In this tutorial, the block template is very simple. We loop through feeds, or if none are available, an error message is displayed.
{% extends sonata_block.templates.block_base %}
{% block block %}
<h3 class="sonata-feed-title">{{ settings.title }}</h3>
<div class="sonata-feeds-container">
{% for feed in feeds %}
<div>
<strong><a href="{{ feed.link}}" rel="nofollow" title="{{ feed.title }}">{{ feed.title }}</a></strong>
<div>{{ feed.description|raw }}</div>
</div>
{% else %}
No feeds available.
{% endfor %}
</div>
{% endblock %}
4.6. Service
We are almost done! Now, just declare the block as a service:
# config/services.yaml
services:
sonata.block.service.rss:
class: Sonata\BlockBundle\Block\Service\RssBlockService
arguments:
- ~
- '@twig'
tags:
- { name: sonata.block }
Then, add the service to Sonata configuration:
# config/packages/sonata_block.yaml
sonata_block:
blocks:
sonata.block.service.rss: ~