HashBackfiller
in package
既存メディア(media_hash が NULL の行)へ、実体ファイルからハッシュを遡及計算して埋め戻す
新規登録・更新は Helper::insertMedia()/updateMedia() 経由で常に同期計算されるため対象外。 実体ファイルが欠損している行はハッシュを NULL のまま残し(= 重複検出の対象外として安全)、 スキップしてログに記録する。1回の呼び出しで処理する件数はチャンク(既定100件)に制限し、 呼び出し側(バックグラウンドジョブ・CLI等)が media_id 昇順で繰り返し呼ぶことで全件を処理し切る 設計にしている(アップデート処理内での全件一括処理はタイムアウトリスクが高いため採用しない)。
Why not hash_file(): 実体はストレージ抽象化レイヤー(ローカル/クラウド)配下にあり、常に ローカルファイルシステム上のパスとは限らない。PublicStorage/PrivateStorage 経由で内容を 読み込み、DuplicateDetectorInterface::hashContent() でハッシュ化する。
制約: 新規登録経路(Helper::uploadImage()/createFile())はリサイズ・再エンコード・サニタイズが
走る前のソースバイト列をハッシュ化するのに対し、本クラスはアップロード時の一時ファイルが
既に破棄された後の既存メディアが対象のため、保存済みの実体しか読めない。
type='image' は media_original(large- バリアント。画像編集の「元に戻す」機能でも
オリジナル相当として使われる)を優先して読む。normalSize=0(Media\Helper::storeImage() の
既定設定)かつ EXIF 回転補正不要・WebP自動変換オフ・外部最適化ツール未適用の場合、この
ファイルは無劣化コピーでソースと一致するため、その条件下では将来の再取り込みとも一致する。
ただしこれは条件付きの挙動であり、上記条件を満たさない環境(回転補正やWebP変換ありなど)
では media_original も再エンコードされ一致しない。SVG(type='file' 扱い)はサニタイズ済み
ファイルしか無いためこの改善の対象外、type='file'(無加工で保存される通常ファイル)は
そもそも再エンコードされないため制約を受けない。
Table of Contents
Methods
- run() : array{processedCount: int, skippedCount: int, lastMediaId: int|null}
- 未計算のメディアを最大 $chunkSize 件処理する
- collectTargets() : array<int, array{media_id: int|string, media_path: string, media_type: string, media_status: string, media_original: string}>
- persistHash() : void
- readContent() : string|false
- 実体ファイルの内容を読み込む。存在しない場合は false を返す
Methods
run()
未計算のメディアを最大 $chunkSize 件処理する
public
run([int $chunkSize = 100 ][, int|null $afterMediaId = null ]) : array{processedCount: int, skippedCount: int, lastMediaId: int|null}
Why not media_id だけで再試行を無限に続けないか: 実体欠損行は何度呼んでも media_hash が NULL のままで対象に残り続けるため、$afterMediaId を指定しないと同じ欠損行の手前で チャンクが埋まり続け、後続の未処理行へ永久に進めなくなる(呼び出し側は本メソッドが返す lastMediaId を次回呼び出しの $afterMediaId にそのまま渡すことで、スキップした行も 含めて必ず前進する)。
Parameters
- $chunkSize : int = 100
- $afterMediaId : int|null = null
-
この media_id より大きい行から処理する(前回呼び出しの lastMediaId)
Return values
array{processedCount: int, skippedCount: int, lastMediaId: int|null} —処理件数・スキップ件数・ このチャンクで最後に見た media_id(次回呼び出しの $afterMediaId に渡す。対象0件なら null)
collectTargets()
private
collectTargets(int $chunkSize, int|null $afterMediaId) : array<int, array{media_id: int|string, media_path: string, media_type: string, media_status: string, media_original: string}>
Parameters
- $chunkSize : int
- $afterMediaId : int|null
Return values
array<int, array{media_id: int|string, media_path: string, media_type: string, media_status: string, media_original: string}>persistHash()
private
persistHash(int $mediaId, string $hash) : void
Parameters
- $mediaId : int
- $hash : string
readContent()
実体ファイルの内容を読み込む。存在しない場合は false を返す
private
readContent(array{media_path: string, media_type: string, media_status: string, media_original: string} $row) : string|false
Why not media_status で公開/非公開を判定しないか: media_status は本来ダウンロード時の アクセス権限(open/close/secret/entry)を表す値であり、保存先ストレージの判定用ではない。 実際に registerLocalFile() 経由で登録される SVG は type='file' のまま MEDIA_LIBRARY_DIR (公開領域)に保存されるが、insertMedia() が media_status に既定値('entry' 等、真値)を 補ってしまうため、真偽だけで公開/非公開を判定すると実体と食い違う。両ストレージへの 存在確認は副作用が無いため、DBカラムの値に頼らず実際に探索して確実な方を使う。
Parameters
- $row : array{media_path: string, media_type: string, media_status: string, media_original: string}