Create your own Blocks
Create your own Blocks¶
Follow these steps to create a block:
- define a block document class;
- if needed, create a block service and declare it (optional);
- instantiate a data object representing your block in the repository, see Block Document;
- render the block, see Block rendering;
Lets say you are working on a project where you have to integrate data
received from several RSS feeds. You could create an ActionBlock for each of
these feeds, but all those actions would look similar: Receive data from a
feed, sanitize it and pass the data to a template. This would be code
duplication, so instead you decide to create your own block, the RssBlock
.
Tip
In this example, you create an RssBlock
. An RSS block already exists in
the CmfBlockBundle, but this one is more powerful as it allows to define a
specific feed URL per block instance.
Create a block document¶
The first thing you need is a document that contains the options and indicates
the location where the RSS feed should be shown. The easiest way is to extend
Symfony\Cmf\Bundle\BlockBundle\Doctrine\Phpcr\AbstractBlock
, but you are
free to do create your own document. At least, you have to implement
Sonata\BlockBundle\Model\BlockInterface
. In your document, you
need to define the getType
method which returns the type name of your block,
for instance acme_main.block.rss
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | // src/Acme/MainBundle/Document/RssBlock.php
namespace Acme\MainBundle\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCR;
use Symfony\Cmf\Bundle\BlockBundle\Doctrine\Phpcr\AbstractBlock;
/**
* @PHPCR\Document(referenceable=true)
*/
class RssBlock extends AbstractBlock
{
/**
* @PHPCR\Field(type="string", nullable=true)
*/
private $feedUrl;
/**
* @PHPCR\Field(type="string")
*/
private $title;
public function getType()
{
return 'acme_main.block.rss';
}
public function getOptions()
{
$options = array(
'title' => $this->title,
);
if ($this->feedUrl) {
$options['url'] = $this->feedUrl;
}
return $options;
}
// Getters and setters for title and feedUrl...
}
|
Create a Block Service¶
The block service is responsible for rendering the blocks of a specific type,
as returned by the getType
method.
A block service contains:
- The
load
method to initialize a block before rendering; - The
execute
method to render the block; - The
setDefaultSettings
to define default settings; - Cache configuration;
- Form configuration;
- JavaScript and Stylesheet files to be loaded.
If the code of an existing service class satisfies your needs, you can simply
create a new service (see next section) with that existing class. The block
services provided by the CmfBlockBundle are in the namespace
Symfony\Cmf\Bundle\BlockBundle\Block
.
For your RSS block, you need a custom service
that knows how to fetch the feed data of an RssBlock
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | // src/Acme/MainBundle/Block/RssBlockService.php
namespace Acme\MainBundle\Block;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Block\BaseBlockService;
class RssBlockService extends BaseBlockService
{
public function getName()
{
return 'Rss Reader';
}
/**
* Define valid options for a block of this type.
*/
public function setDefaultSettings(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'url' => false,
'title' => 'Feed items',
'template' => 'AcmeMainBundle:Block:rss.html.twig',
));
}
/**
* The block context knows the default settings, but they can be
* overwritten in the call to render the block.
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
$block = $blockContext->getBlock();
if (!$block->getEnabled()) {
return new Response();
}
// merge settings with those of the concrete block being rendered
$settings = $blockContext->getSettings();
$resolver = new OptionsResolver();
$resolver->setDefaults($settings);
$settings = $resolver->resolve($block->getSettings());
$feeds = false;
if ($settings['url']) {
$options = array(
'http' => array(
'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(), array(
'feeds' => $feeds,
'block' => $blockContext->getBlock(),
'settings' => $settings
), $response);
}
// These methods are required by the sonata block service interface.
// They are not used in the CMF. To edit, create a symfony form or
// a sonata admin.
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
throw new \Exception();
}
public function validateBlock(ErrorElement $errorElement, BlockInterface $block)
{
throw new \Exception();
}
}
|
The Execute Method¶
This method of the block service contains controller logic. It is called by the block framework to render a block. In this example, it checks if the block is enabled and if so renders RSS items with a template.
Note
If you need complex logic to handle a block, it is recommended to move that
logic into a dedicated service and inject that service into the block
service and defer execution in the execute
method, passing along
arguments determined from the block.
Tip
When you do a block that will be slow to render, like this example where we read an RSS feed, you should activate block caching.
Default Settings¶
The method setDefaultSettings
allows your service to provide default
configuration options for a block. Settings can be altered in multiple
places afterwards, cascading as follows:
- Default settings from the block service;
- If you use a 3rd party bundle you might want to change them in the bundle configuration for your application see Usage;
- Settings can be altered through template helpers (see example below);
- And settings can also be altered in a block document. Do this only for settings that are individual to the specific block instance rather than all blocks of a type. These settings will be stored in the database.
Example of how settings can be overwritten through a template helper:
- Twig
1 2 3 4
{{ sonata_block_render({'name': 'rssBlock'}, { 'title': 'Symfony2 CMF news', 'url': 'http://cmf.symfony.com/news.rss' }) }}
- PHP
1 2 3 4
<?php $view['blocks']->render(array('name' => 'rssBlock'), array( 'title' => 'Symfony2 CMF news', 'url' => 'http://cmf.symfony.com/news.rss', )) ?>
The Load Method¶
The method load
can be used to load additional data into a block. It is
called each time a block is rendered before the execute
method is called.
Form Configuration¶
The methods buildEditForm
and buildCreateForm
specify how to build the
the forms for editing using a front-end or backend UI. The method
validateBlock
contains the validation configuration. This is not used in
the CMF and it is recommended to instead build forms or Sonata admin classes
that can handle the block documents.
Cache Configuration¶
The method getCacheKeys
contains cache keys to be used for caching the
block. See the section block cache for more on caching.
JavaScripts and Stylesheets¶
The methods getJavaScripts
and getStylesheets
of the service class
define the JavaScript and Stylesheet files needed by a block. There is a
Twig function and a templating helper to render all links for all blocks used
on the current page:
- Twig
1 2
{{ sonata_block_include_javascripts("all") }} {{ sonata_block_include_stylesheets("all") }}
- PHP
1 2
<?php $view['blocks']->includeJavaScripts('all') ?> <?php $view['blocks']->includeStylesheets('all') ?>
Note
This mechanism is not recommended. For optimal load times, it is better
to have a central assets definition for your project and aggregate them
into a single Stylesheet and a single JavaScript file, e.g. with Assetic,
rather than having individual <link>
and <script>
tags for each
single file.
Register the Block Service¶
To make the block work, the last step is to define the service. Do not forget
to tag your service with sonata.block
to make it known to the
SonataBlockBundle. The first argument is the name of the block this service
handles, as per the getType
method of the block. The second argument is the
templating
service, in order to be able to render this block.
- YAML
1 2 3 4 5 6 7
sandbox_main.block.rss: class: Acme\MainBundle\Block\RssBlockService arguments: - "acme_main.block.rss" - "@templating" tags: - {name: "sonata.block"}
- XML
1 2 3 4 5 6
<service id="sandbox_main.block.rss" class="Acme\MainBundle\Block\RssBlockService"> <tag name="sonata.block" /> <argument>acme_main.block.rss</argument> <argument type="service" id="templating" /> </service>
- PHP
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; $container ->addDefinition('sandbox_main.block.rss', new Definition( 'Acme\MainBundle\Block\RssBlockService', array( 'acme_main.block.rss', new Reference('templating'), ) )) ->addTag('sonata.block') ;
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.