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\Model\BlockInterface;
use Sonata\BlockBundle\Block\BlockContextInterface;

use Sonata\AdminBundle\Form\FormMapper;
use Sonata\CoreBundle\Validator\ErrorElement;
use Sonata\BlockBundle\Block\Service\AbstractBlockService;

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)
{
    $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

In order to allow editing forms, the BlockBundle relies on the AdminBundle:

public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
    $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)
{
    $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)
{
    // 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{% 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:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # config/services.yaml
    
    services:
        sonata.block.service.rss:
            class: Sonata\BlockBundle\Block\Service\RssBlockService
            arguments:
                - sonata.block.service.rss
                - "@templating"
            tags:
                - { name: sonata.block }
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    <!-- config/services.xml -->
    
    <service id="sonata.block.service.rss" class="Sonata\BlockBundle\Block\Service\RssBlockService">
        <tag name="sonata.block"/>
        <argument/>
        <argument type="service" id="sonata.templating"/>
    </service>
    

Then, add the service to Sonata configuration:

  • YAML
    1
    2
    3
    4
    5
    # config/packages/sonata_block.yaml
    
    sonata_block:
        blocks:
            sonata.block.service.rss: ~
    

If you want to set up caching, take a look at the SonataCacheBundle support documentation: Cache.