[Drupal]パブリックファイルとプライベートファイル・ファイルエンティティの使い方

今回は開発者がDrupalで画像、テキスト、音声など、コンテツに添付されるファイルを扱う上で知っておきたいポイントをまとめてみました。

## パブリックファイルとプライベートファイル

### 両者の違い
Drupalのファイルは閲覧制限のスタイルによってパブリックファイルとプライベートファイルに分けられます。

- パブリック → パスを直打ちすれば誰でもアクセスできる
- プライベート → **ユーザーがファイルが添付されているエンティティにアクセスできない場合、パスを直打ちしても403（アクセス禁止）エラーになる**

というわけで関係者にしか閲覧してほしくないファイルなどはプライベートファイルに保存する必要があります。

### 配置場所
パブリックファイルとプライベートファイルはそれぞれ特定のディレクトリ下にまとめて置かれます。

- パブリック → sites/default/files などが通例
- プライベート → **Webルートの外側**に置くのが通例

パブリックディレクトリとプライベートディレクトリの場所はそれぞれ`settings.php`から設定することができます。プライベートファイルを使用する場合はこの設定が必須になります。

```
// /sites/default/settings.phpに追加
$settings['file_private_path'] = '../private/files';
```
上記の設定が読み込まれていれば`/admin/config/media/file-system`の「プライベートファイルシステムパス」に表示されるので、`drush cr`して確認してみてください。

プライベートディレクトリを設定したら、sshなどでプライベートディレクトリ下に適当なファイルを置いてみて、ブラウザからアクセスできたら設定ミスってるとのことです（Drupalを介してじゃないとアクセスできてはいけない）。
[Are your private Drupal files secure? Check now!](https://www.mydropwizard.com/blog/are-your-private-drupal-files-secure-check-now)

### プライベートファイルフィールドの設定
デフォルトでは全てのファイルフィールドにアップロードされたファイルはパブリックファイルとして保存されます。特定のフィールドでプライベートファイルとしてファイルを保存したい場合は、そのフィールドの「フィールドの設定」から「プライベートファイル」を選択します。
（例えば記事ノードの「Myファイル」というフィールドをプライベートファイルにしたい場合は、サイト構築 > コンテンツタイプ > 記事の「フィールドの管理」> Myフィールドの「編集」>「フィールドの設定」タブから設定します）
これでこのフィールドに保存したファイルはプライベートディレクトリに保存される & 閲覧権限が親エンティティの閲覧権限と連動するようになります。

![private_file_setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/3d58c0fd-dd75-45b6-a568-12174eb51792.png)

## ファイルエンティティの基本的な使い方&ファイルパスなどの取得方法
DrupalではファイルはFileというエンティティで管理されています。
ノードなど他のエンティティにファイルを添付するフィールドを作った場合はノードエンティティ→ファイルエンティティという参照関係になっています。基本的な使い方は以下です。

```php
// FID(ファイルID)からファイルエンティティをロードする.
$file = \Drupal::entityTypeManager()->getStorage('file')->load($fid);

// ファイル名を取得する('my_file.jpg')
$file_name = $file->getFileName();

// URIを取得する('public://my_file.jpg'や'private://my_file.jpg').
$uri = $file->getFileUri();

// ファイルパスを取得する('/var/www/mysite/sites/default/files/my_file.jpg')
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager')->getViaUri($uri);
$file_path = $stream_wrapper_manager->realpath();
// もしくは
\Drupal::service('file_system')->realpath($uri);

// 絶対URLと相対URLを取得する
// Drupal 9.3以降の場合. 
$file_url_generator = \Drupal::service('file_url_generator');
$file_url_generator->generateAbsoluteString($uri); // http://mysite.com/sites/default/files/my_file.jpg
$file_url_generator->generateString($uri); // /sites/default/files/my_file.jpg
// 9.2までの場合.
$absolute_url = file_create_url($uri); // http://mysite.com/sites/default/files/my_file.jpg
$url = parse_url($absolute_url)['path']; // /sites/default/files/my_file.jpg

// ノードからファイルエンティティを取得する.
$file = current($node->get('field_my_file')->referencedEntities());

// ノードにファイルエンティティをセットする.
$node->set('field_my_file', $file->id())->save();
```

## ファイルエンティティの作成方法
通常Drupalのエンティティはこんな感じで作ることが多いのですが、

```php
$node = Node::create([
  'type'        => 'article',
  'title'       => 'test',
]);
$node->save();
```
これをファイルエンティティでやるとプライベートファイルの場合にDrupalがファイルにアクセスできませんでした。`file_save_data()`をいう関数を利用した以下の方法が正しいようです。

```php
// 保存先のディレクトリを用意.
\Drupal::service('file_system')->prepareDirectory(
  '/path/to/directory',
  FileSystemInterface::CREATE_DIRECTORY // ディレクトリが無ければ作成する
);

// ファイルをDrupalに保存 & ファイルエンティティを取得
$handle = fopen('/path/to/file', 'r');
$file = file_save_data(
  $handle,
  '/path/to/destination',
   FILE_EXISTS_REPLACE // 既存ファイルがあれば上書きする
);
```

[file_save_data()](https://api.drupal.org/api/drupal/core%21modules%21file%21file.module/function/file_save_data/8.2.x)