Unmanaged Files - Part 3

Tutorial

Part 3: Rendering an Unmanaged File in a Block

In Part 2, we built a service that can locate unmanaged files and return one at random. In this part, we’ll expose that functionality as a block plugin. Blocks are a powerful way to make custom logic reusable: site builders can place them anywhere through the Drupal block layout interface, no code changes required.

The Block Plugin Class

We start with the plugin definition itself. The class extends BlockBase and implements ContainerFactoryPluginInterface, which lets us inject our file handler and URL generator services from Drupal’s service container.

<?php

namespace Drupal\unmanaged_files\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\unmanaged_files\Service\FileHandler;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;

/**
 * Provides a block that displays a random unmanaged file.
 *
 * @Block(
 *   id = "unmanaged_files_block",
 *   admin_label = @Translation("Unmanaged Files Block"),
 * )
 */
final class UnmanagedFilesBlock extends BlockBase implements ContainerFactoryPluginInterface {

Figure 1

The Constructor

The constructor takes the usual plugin parameters plus our two injected services: the custom FileHandler and Drupal’s FileUrlGeneratorInterface. These are stored as private properties for use later.

  /**
   * Constructs a new UnmanagedFilesBlock instance.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin ID for the block.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\unmanaged_files\Service\FileHandler $handler
   *   The file handler service for unmanaged files.
   * @param \Drupal\Core\File\FileUrlGeneratorInterface $urlGen
   *   The file URL generator service.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    private FileHandler $handler,
    private FileUrlGeneratorInterface $urlGen,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

Figure 2

The Factory Method

Because the constructor requires services, we also implement create() from ContainerFactoryPluginInterface. This pulls the services from the container and passes them into the constructor. The @var annotations help IDEs understand the types, avoiding false warnings.

  /**
   * Creates an instance of the UnmanagedFilesBlock.
   *
   * This factory method injects the required services from the container.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $c
   *   The service container.
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin ID for the block.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   *
   * @return static
   *   Returns a new UnmanagedFilesBlock instance.
   */
  public static function create(ContainerInterface $c, array $configuration, $plugin_id, $plugin_definition): self {
    /** @var \Drupal\unmanaged_files\Service\FileHandler $handler */
    $handler = $c->get('unmanaged_files.handler');

    /** @var \Drupal\Core\File\FileUrlGeneratorInterface $urlGen */
    $urlGen = $c->get('file_url_generator');

    return new self(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $handler,
      $urlGen,
    );
  }

Figure 3

The Build Method

Finally, the build() method is what Drupal calls to render the block. It asks the file handler for a random unmanaged file. If a file is found, it returns a render array using the image theme. If no file is found, it returns a simple message. The max-age cache metadata is set to 1 second so that refreshing the page quickly rotates images.

  /**
   * Builds the block render array.
   *
   * Uses the file handler to retrieve a random unmanaged file and returns
   * it as an image render array. If no unmanaged files are found, returns
   * a simple message instead.
   *
   * @return array
   *   A render array for the block content.
   */
  public function build(): array {
    $uri = $this->handler->getRandomFile();

    if (!$uri) {
      return [
        '#markup' => '<p>No unmanaged files found.</p>',
      ];
    }

    return [
      '#theme' => 'image',
      '#uri' => $uri,
      '#alt' => $this->t('Random unmanaged file'),
      '#cache' => [
        'max-age' => 1,
      ],
    ];
  }

Figure 4

Enable and Place the Block

Clear caches, then go to Structure → Block layout. You’ll see “Unmanaged Files Block” listed in the Custom category. Place it in a sidebar or footer region, save, and refresh your page. Each reload should display a different unmanaged file image.

In Part 4, we’ll explore how to render unmanaged files directly in Twig templates, giving developers a more flexible way to use the handler inside their theme layer.

  • Drupal Planet