理論からコードへ:ランダム画像を取得するシンプルなサービス
Drupalの非管理ファイルを扱うカスタムサービスの実装方法を解説します。ファイルハンドラーの構築からテストルートの作成まで、実際のコード例を通じて学べます。
このシリーズのパート1では、非管理ファイル(Drupalがエンティティとして追跡しないアセット)を使用することで、特定のユースケースをシンプルに保つための準備を行いました。パート2では、画像のフォルダーツリーをスキャンし、ランダムに1つのファイルを返す小さなDrupalサービスを作成します。カテゴリや制約はまだありません(それは後ほど追加します)。今回は、public://、private://、Webルート外のディレクトリなどに配置されたファイルを、Drupalが管理せずに「認識」して使用できることを実証します。
今回実装するモジュールの構成
- カスタムモジュール:unmanaged_files
- サービス:
unmanaged_files.handler(単一のメソッドgetRandomFile()を持つ) - (テスト用)選択した画像を表示するルート:
/unmanaged-files/test
画像ファイルの準備と配置方法
使用する画像は、筆者のブログのリソースセクションまたはGitHubで入手できます。リンクは両方とも、この記事の最後に記載されています。
サーバーを使用している場合は、Filezillaのようなアプリケーションを使用してファイルを配置するか、scpやrsyncなどのユーティリティを使用できます。rsyncを使用する場合、構文の例はパート1に記載されています。
代わりに独自の画像を使用することもできます。その場合は、public://配下のフォルダー内にカテゴリフォルダーを作成して整理してください。独自の画像でも、サンプル画像でも、どのような配置方法であっても、画像は以下のような場所に配置する必要があります。
public://segregated_maps/<category>/<your-images>
# 実際のパスの例(環境により異なります): web/sites/default/files/segregated_maps/africa/algeria.pngDrushを使ったモジュールの雛形作成
モジュールは手動でスキャフォールディングできます。私は以下に示すようにdrush generateコマンドを使用することを好みます。各行の最後でEnterキーを押します。
drush generate module
Module name:
➤ Unmanaged Files
Module machine name [unmanaged_files]:
➤
Module description:
➤ Unmanaged files example
Package [Custom]:
➤
Dependencies (comma separated):
➤
Would you like to create module file? [No]:
➤
Would you like to create install file? [No]:
➤
Would you like to create README.md file? [No]:
➤ Drupalサービスコンテナへの登録
Drupalでは、ビジネスロジックは通常サービスとして実装されます。サービスは、コンテナに登録された再利用可能なPHPクラスであり、\Drupal::service()で直接呼び出すのではなく、コントローラー、プラグイン、または他のクラスに注入できます。
ここでは、unmanaged_files.handlerという新しいサービスを定義します。ここでは、Drupalが管理外のファイルを処理するFileHandlerクラスを見つけてインスタンス化する方法を提供するだけです。コアのfile_systemとstream_wrapper_managerサービスを依存性として注入することで、クラスがpublic://パスを実際のファイルシステムの場所に解決できるようにします。
以下に示すように、web/modules/custom/unmanaged_files/unmanaged_files.services.ymlファイルを作成します。
services:
unmanaged_files.handler:
class: Drupal\unmanaged_files\Service\FileHandler
arguments:
- '@file_system'
- '@stream_wrapper_manager'FileHandlerクラスの実装(基本機能)
ハンドラーは、このチュートリアルパートの中核です。これは、1つのパブリックメソッドgetRandomFile()を持つシンプルなPHPクラスです。このメソッドは、public://segregated_maps配下のすべてをスキャンし、見つかったファイルを収集して、ランダムに1つを返します。
キーポイント:
- PHPの
RecursiveDirectoryIteratorを使用して、すべてのフォルダーとサブフォルダーを走査します。 - 各絶対ファイルシステムパスをDrupalストリームラッパーURI(
public://...)に変換し直すことで、結果をDrupalのFile APIで使用できるようにします。 - ファイルが見つからない場合、メソッドは
NULLを返します。
まだ高度なことをすることではありません。Drupalが非管理ファイルを「認識」し、ランダムに1つを渡すことができることを実証するだけです。
以下に示すように、web/modules/custom/unmanaged_files/src/Service/FileHandler.phpファイルを作成します。
<?php
namespace Drupal\unmanaged_files\Service;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
final class FileHandler {
public function __construct(
private FileSystemInterface $fs,
private StreamWrapperManagerInterface $swm,
) {}
/**
* public://segregated_maps配下のランダムなファイルURIを1つ返します。
* 見つからない場合はNULLを返します。
*
* @return string|null 例: 'public://segregated_maps/africa/algeria.png'
*/
public function getRandomFile(): ?string {
$baseUri = 'public://segregated_maps';
$basePath = $this->fs->realpath($baseUri);
if (!$basePath || !is_dir($basePath)) {
return NULL;
}
$files = [];
$iter = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($basePath, \FilesystemIterator::SKIP_DOTS)
);
foreach ($iter as $f) {
if ($f->isFile()) {
$abs = $f->getPathname(); // ディスク上の絶対パス
// 絶対パスをpublic:// URIに変換し直します。
// $basePathは$baseUriにマップされます。
$rel = ltrim(substr($abs, strlen($basePath)), DIRECTORY_SEPARATOR);
$files[] = $baseUri . '/' . str_replace(DIRECTORY_SEPARATOR, '/', $rel);
}
}
if (!$files) {
return NULL;
}
return $files[array_rand($files)];
}
}動作確認用のテストルート作成
ブラウザで実際の画像を確認できるように、小さなコントローラーとルートを追加します。以下に示すように、web/modules/custom/unmanaged_files/unmanaged_files.routing.ymlファイルを作成します。
unmanaged_files.test:
path: '/unmanaged-files/test'
defaults:
_controller: '\Drupal\unmanaged_files\Controller\TestController::view'
_title: 'Unmanaged files test'
requirements:
_permission: 'access content'そして、以下に示すように、web/modules/custom/unmanaged_files/src/Controller/TestController.phpファイルを作成します。
<?php
namespace Drupal\unmanaged_files\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\unmanaged_files\Service\FileHandler;
use Drupal\Core\File\FileUrlGeneratorInterface;
final class TestController extends ControllerBase {
public function __construct(
private FileHandler $handler,
private FileUrlGeneratorInterface $urlGen,
) {}
public static function create(ContainerInterface $c): self {
return new self(
$c->get('unmanaged_files.handler'),
$c->get('file_url_generator'),
);
}
public function view(): array {
$uri = $this->handler->getRandomFile();
if (!$uri) {
return [
'#markup' => '<p>public://segregated_maps配下にファイルが見つかりません。</p>',
];
}
$url = $this->urlGen->generateAbsoluteString($uri);
return [
'#type' => 'container',
'#attributes' => ['class' => ['unmanaged-files-test']],
'info' => ['#markup' => '<p>選択: <code class="inline">' . $uri . '</code></p>'],
'img' => [
'#type' => 'html_tag',
'#tag' => 'img',
'#attributes' => ['src' => $url, 'alt' => 'ランダムな非管理ファイル'],
],
'#cache' => [
'max-age' => 1,
],
];
}
}デモ目的で、リフレッシュ時に画像がローテーションするように、非常に短いキャッシュ有効期間を設定しています。
モジュールの有効化と動作確認
drush en unmanaged_files -y
drush cr次に、ブラウザでサイトを開き、以下のURLにアクセスします:
https://yoursite.example/unmanaged-files/test画像の1つと、それが由来するURI(例:public://segregated_maps/africa/algeria.png)が表示されるはずです。ページを数回リフレッシュして、ハンドラーがファイルをローテーションしていることを確認してください。
コードと画像ファイルの入手
モジュールコードとサンプルマップ画像は、GitHubのパート2リリースページからダウンロードできます。テストを実行する前に、画像をweb/sites/default/files/segregated_mapsに解凍してください。
次のステップ
動作するハンドラーができたので、パート3~5では、サイトでこれをレンダリングする3つの方法(プリプロセス変数、ブロックプラグイン、Twig関数)を示します。パート6では、ファイルハンドラーに「カテゴリごとに1つ以下」の選択ルールを追加します。
この記事は 「Unmanaged Files in Drupal (Part 2): Building a Random File Handler」の翻訳記事です。
カテゴリ