Unmanaged Files in Drupal (Part 6): Category-Aware Random Selection

Smart Segmentation: Fetch Unique Images from Distinct Subfolders with a Custom Block
Category-Aware Random Selection

Editor’s note: This article is the sixth and final part of Jeff Greenberg’s “Unmanaged Files in Drupal” tutorial series. Each entry has explored practical, database-free file handling techniques in Drupal. In this concluding part, Jeff introduces category-aware randomization, allowing developers to fetch unique images from distinct subfolders. The full series showcases a progression from concept to custom block output using services and Twig templates.


This tutorial concludes the Unmanaged Files in Drupal series. In Part 1 we explored what unmanaged files are and when to use them. Part 2 built the foundation for our custom module and introduced the first file handler. Part 3 rendered unmanaged files dynamically within a custom block. Part 4 extended that output to Twig templates, and Part 5 introduced random selection logic. In this final installment, Part 6, we make that randomness category-aware—selecting three distinct images from separate subfolders within public://segregated_maps, ensuring each category is represented only once.

Folder Structure Example

  • public://segregated_maps/
    • africa
    • antarctica
    • asia
    • australia
    • caribbean
    • central america
    • europe
    • mideast
    • north america
    • pacific islands
    • south america

Each folder represents a region category. The handler guarantees that each image in the output comes from a unique category, producing a balanced, randomized trio.

RandomCategoryFileHandler.php

<?php
namespace Drupal\unmanaged_files\Service;
use Drupal\Core\File\FileSystemInterface;
/**
* Provides category-aware random selection of unmanaged files.
*
* Picks three random files from distinct subfolders under public://segregated_maps.
*/
class RandomCategoryFileHandler {
protected FileSystemInterface $fileSystem;
 protected string $basePath;
public function __construct(FileSystemInterface $file_system) {
   $this->fileSystem = $file_system;
   $this->basePath = 'public://segregated_maps';
 }
/**
  * Selects random files from distinct category subfolders.
  */
 public function getCategoryConstrainedFiles(int $limit = 3): array {
   $selected = [];
   $base = $this->fileSystem->realpath($this->basePath);
  if (!$base || !is_dir($base)) {
     return $selected;
   }
  $dirs = glob($base . '/*', GLOB_ONLYDIR);
   if (empty($dirs)) {
     return $selected;
   }
  shuffle($dirs);
  foreach ($dirs as $dir) {
     $files = glob($dir . '/*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE);
     if (!empty($files)) {
       $selected[] = $files[array_rand($files)];
     }
     if (count($selected) >= $limit) {
       break;
     }
   }
  shuffle($selected);
   return $selected;
 }
/**
  * Returns renderable image arrays for the random selections.
  */
 public function getRenderableCategoryConstrainedFiles(int $limit = 3): array {
   $real_public = $this->fileSystem->realpath('public://');
   $base_url = '/sites/default/files';
   $files = $this->getCategoryConstrainedFiles($limit);
   $renderable = [];
  foreach ($files as $file) {
     $url = str_replace($real_public, $base_url, $file);
     $renderable[] = [
       '#theme' => 'image',
       '#uri' => $url,
       '#alt' => 'Random map image',
       '#attributes' => ['loading' => 'lazy'],
     ];
   }
  return $renderable;
 }
}
RandomCategoryFileHandler.php

RandomCategoryFilesBlock.php


<?php
namespace Drupal\unmanaged_files\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Displays three random files from distinct segregated-map categories.
*
* @Block(
*   id = "random_category_files_block",
*   admin_label = @Translation("Random Category Files Block")
* )
*/
class RandomCategoryFilesBlock extends BlockBase implements ContainerFactoryPluginInterface {
protected $randomCategoryHandler;
public function __construct(array $configuration, $plugin_id, $plugin_definition, $random_category_handler) {
   parent::__construct($configuration, $plugin_id, $plugin_definition);
   $this->randomCategoryHandler = $random_category_handler;
 }
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
   return new static(
     $configuration,
     $plugin_id,
     $plugin_definition,
     $container->get('unmanaged_files.random_category_file_handler')
   );
 }
public function build() {
   $images = $this->randomCategoryHandler->getRenderableCategoryConstrainedFiles(3);
   return [
     '#theme' => 'unmanaged_files_category_block',
     '#images' => $images,
   ];
 }
}
RandomCategoryFilesBlock.php

unmanaged_files.services.yml

services:
 unmanaged_files.handler:
   class: Drupal\unmanaged_files\Service\FileHandler
   arguments: ['@file_system','@stream_wrapper_manager']
unmanaged_files.twig_extension:
   class: Drupal\unmanaged_files\Twig\UnmanagedFilesExtension
   arguments: ['@unmanaged_files.handler','@file_url_generator']
   tags:
     - { name: twig.extension }
unmanaged_files.random_category_file_handler:
   class: Drupal\unmanaged_files\Service\RandomCategoryFileHandler
   arguments: ['@file_system']
unmanaged_files.services.yml

unmanaged_files.module


<?php
/**
* Implements hook_theme().
*/
function unmanaged_files_theme() {
 return [
   'unmanaged_files_test' => [
     'variables' => [
       'image_url' => NULL,
       'uri' => NULL,
       'message' => NULL,
     ],
     'template' => 'unmanaged-files-test',
   ],
  'unmanaged_files_category_block' => [
     'variables' => [
       'images' => [],
     ],
     'template' => 'unmanaged-files-category-block',
   ],
 ];
}
unmanaged_files.module

unmanaged-files-category-block.html.twig


{# 
/** 
 * @file 
 * Template for Random Category Files Block. 
 */ 
#} 
<div class="unmanaged-files-category-block"> 
  {% if images is not empty %} 
    <div class="random-category-images"> 
      {% for image in images %} 
        <div class="random-category-image"> 
          {{ image }} 
        </div> 
      {% endfor %} 
    </div> 
  {% else %} 
    <p>{{ 'No images found in segregated map folders.'|t }}</p> 
  {% endif %} 
</div>
unmanaged-files-category-block.html.twig

Result

When you place the “Random Category Files Block” in any region or custom layout, the block displays three unique images—each from a different regional subfolder under public://segregated_maps. Every page load produces a new mix while avoiding duplicates.

That concludes the six-part Unmanaged Files in Drupal series: from raw file discovery to fully themed, category-aware randomization—no managed-file overhead, pure performance and flexibility.

Jeff Greenberg, The Accidental Coder

Note: The vision of this web portal is to help promote news and stories around the Drupal community and promote and celebrate the people and organizations in the community. We strive to create and distribute our content based on these content policy. If you see any omission/variation on this please reach out to us at #thedroptimes channel on Drupal Slack and we will try to address the issue as best we can.

Related Organizations

Upcoming Events

Latest Opportunities