Creating a custom block ======================= Similar to layout types, when creating a custom block, you need a bit of configuration and some templates, but since blocks almost always need some custom logic, you will also need to create a PHP class that will handle custom functionalities of a block. In the following examples, we will show creating a custom block that can render a Markdown document. When creating a custom block, you will often run into two entities mentioned in code and configuration: a block definition, and a block type. Before you actually create a custom block, it is important to understand a difference between a block definition and a block type. Difference between block definition & block type ------------------------------------------------ Block definition is the central entity you will be creating when creating a custom block. As the name implies, block definition **defines** how your custom block behaves. This includes specifying what parameters will the block have and what type are they of and if the block has a collection or not. It also gives you a possibility to write your own custom behaviour for a block, based on block parameters. In case of container blocks, it specifies which placeholders the container block has. Each block definition can have multiple block types. Block type is nothing more than a starting configuration used when creating a block in a layout. In layout editing app, block types are what is shown on the left side and what you drag and drop to a zone in a layout. Creating block types for a certain block definition requires only a couple of lines of configuration where you would specify starting values for block label, block view, block item view and block parameters. Once you create a block in a layout, it doesn't store the information from which block type it was created, it only stores the block definition. When you think about it, this makes sense. Since block type is a starting configuration for a block you're adding to a layout, and that configuration can change in the lifecycle of a block, there is no benefit in storing the information which block type was used to create the block. On the other hand, block definition needs to be stored because it defines how block parameters will be validated, what custom behaviour the block has and so on. Configuring a new block definition ---------------------------------- To register a new block definition in Netgen Layouts, you will need the following configuration: .. code-block:: yaml netgen_layouts: block_definitions: my_markdown: name: 'My markdown block' icon: '/path/to/icon.svg' view_types: my_markdown: name: 'My markdown block' This configuration example adds a new block definition with ``my_markdown`` identifier, which as a human readable name ``My markdown block`` and has one view type, also called ``my_markdown``. It also specifies the full path to the icon of the block. View type is nothing more than an identifier of a template which will be used to render the block. Every block definition needs at least one view type. .. note:: By convention, in built in blocks, if a block definition has only one view type, like above, that view type will have the same identifier as the block definition itself. Creating a PHP service for a block definition --------------------------------------------- Every block definition needs a single PHP class that specifies the entire behaviour of a block. This class needs to implement ``Netgen\Layouts\Block\BlockDefinition\BlockDefinitionHandlerInterface`` interface which specifies a number of methods for you to implement. To simplify implementing new block definitions, an abstract class exists (``Netgen\Layouts\Block\BlockDefinition\BlockDefinitionHandler``) which has all of those methods implemented with default and empty implementations, reducing the need for writing boilerplate code. Let's create a basic block definition handler class: .. code-block:: php add('content', ParameterType\TextType::class); } Notice that we didn't specify the human readable labels for the parameters. That's because they are generated automatically via translation system. To create the correct labels for your block parameters, you need to add one string to ``nglayouts`` translation catalog for every parameter in your block with the format ``block..`` where ``block_definition`` and ``parameter_name`` are placeholders that need to be replaced with correct values. So, for our custom Markdown block definition, the translation file would look something like this: .. code-block:: yaml block.my_markdown.content: 'Content' Custom block behaviour ~~~~~~~~~~~~~~~~~~~~~~ Second method in our handler example above is called ``getDynamicParameters``. This method is used for your own custom logic. Anything goes in this method. You can inject dependencies into your block definition handler, use them here, do some processing based on provided instance of a block or some other parameters you provide when rendering a block manually and so on. After all processing is done, this method needs to set the parameters which will be injected into template when block is rendered. The parameters are set to an instance of ``Netgen\Layouts\Block\DynamicParameters`` object. This object implements ``ArrayAccess`` interface, so you can use array notation to add the parameters. Each of the values can either be a regular scalar, array, object and so on, or it can be a closure, which will transparently be called to calculate the value at the moment the parameter is used inside the block template. In case of our Markdown handler, we will need to inject a Markdown parser into our handler, and use it in this method to parse the raw Markdown into HTML. We will be using ``Michelf\MarkdownInterface``: .. code-block:: php use Michelf\MarkdownInterface; private MarkdownInterface $markdownParser; public function __construct(MarkdownInterface $markdownParser) { $this->markdownParser = $markdownParser; } public function getDynamicParameters(DynamicParameters $params, Block $block): void { $rawContent = $block->getParameter('content')->getValue(); $params['html'] = $this->markdownParser->transform($rawContent); } Contextual blocks ~~~~~~~~~~~~~~~~~ A contextual block is a block which needs the current context (i.e. current request) to function. For example, a block that needs a currently displayed location or content from Ibexa CMS is a contextual block. In order for the system to work properly with contextual blocks, ``isContextual`` method needs to be implemented, which signals to the system if the block is contextual or not. You can use any property of the provided block to decide if it contextual or not, but in our case, we will simply return ``false``: .. code-block:: php public function isContextual(Block $block): false { return false; } Defining the Symfony service for our handler -------------------------------------------- To connect the created handler with block definition configuration, we need to register the handler in Symfony DIC. We also need to specify a service for Markdown parser we used in the handler: .. code-block:: yaml services: app.markdown: class: Michelf\MarkdownExtra app.block.block_definition.handler.markdown: class: App\Block\BlockDefinition\Handler\MyMarkdownHandler arguments: - "@app.markdown" tags: - { name: netgen_layouts.block_definition_handler, identifier: my_markdown } This configuration is a fairly regular specification of services in Symfony, however, to correctly recognize our PHP class as a block definition handler, we need to tag it with ``netgen_layouts.block_definition_handler`` tag and attach to it an ``identifier`` key with a value which equals to the identifier of block definition we configured at the beginning (in this case ``my_markdown``). .. note:: If you are using autoconfiguration in your Symfony project on PHP 8.1, you don't have to manually create a service configuration in your config. Instead, you can use a PHP 8 attribute to mark the block definition handler class as such: .. code-block:: php