---
title: "Laravel Storage Dedupler: один файл на диску замість десятка копій"
url: https://laravelukraine.com/blog/laravel-storage-dedupler-odin-fail-na-disku-zamist-desiatka-kopii
date: 2026-11-14
---

# Laravel Storage Dedupler: один файл на диску замість десятка копій

[Laravel Storage Dedupler](https://github.com/maxkhim/laravel-storage-dedupler) Максима Хімусова зберігає завантажені файли без дублікатів: файл ідентифікується SHA-1 хешем свого вмісту, і скільки б разів його не завантажили, на диску лежить одна копія, а моделі отримують посилання на неї. Для застосунків, де користувачі вдесяте прикріплюють той самий договір, рахунок чи логотип, це пряма економія місця, часу бекапів і грошей на S3.

Пакет нещодавно [оглянув Laravel News](https://laravel-news.com/laravel-storage-dedupler), а за кілька днів після огляду автор випустив версію 1.0.6, яка зробила налаштовуваною найспірнішу поведінку - видалення файлу після останнього відв'язування.

### Як це працює

Усередині пакета дві таблиці. `dedupler_unique_files` зберігає по одному запису `UniqueFile` на кожен унікальний вміст, і первинний ключ цього запису - сам SHA-1 хеш. Друга - поліморфна проміжна таблиця, що пов'язує файл із будь-якими моделями та пам'ятає оригінальне ім'я файлу для кожного зв'язку.

Перед записом пакет рахує хеш і шукає `UniqueFile` з таким ключем. Знайшов - повертає наявний запис і за потреби лише додає зв'язок із моделлю. Не знайшов - кладе файл на диск і створює запис. Файли до 1 МБ читаються в пам'ять повністю, більші хешуються потоком шматками по 1 МБ. На диску файли розкладаються за першими символами хешу: `da/39/da39a3ee....pdf`.

Встановлення - дві команди. Друга створює міграції й перевіряє конфігурацію:

```bash
composer require maxkhim/laravel-storage-dedupler
php artisan dedupler:install
```

За замовчуванням файли лягають на окремий локальний диск `deduplicated` у `storage/app/deduplicated`. Диск змінюється через `DEDUPLER_DISK`, а таблиці можна винести в окреме з'єднання через `DEDUPLER_CONNECTION`.

### Фасад Dedupler

Найпростіший спосіб - фасад. Три методи для трьох джерел: завантажений файл, файл на сервері за абсолютним шляхом і сирий вміст з іменем:

```php
$uniqueFile = Dedupler::storeFromUploadedFile($file);

$uniqueFile = Dedupler::storeFromPath($absolutePathToFile);

$uniqueFile = Dedupler::storeFromContent($content, 'direct_content_file.ext');
```

Є ще `storeFromStream()` для потоків і `storeBatch()` для масиву файлів.

Важлива деталь: усі ці методи повертають `?UniqueFile`. Якщо джерело некоректне або запис на диск чи в базу впав, пакет пише помилку в лог і повертає `null`, а не кидає виняток. Тож результат треба перевіряти явно, інакше наступне `$uniqueFile->id` впаде вже з незрозумілою помилкою.

### Трейт Deduplable для моделей

Щоб файли належали моделям, додайте трейт:

```php
use Illuminate\Database\Eloquent\Model;
use Maxkhim\Dedupler\Traits\Deduplable;

class Post extends Model
{
  use Deduplable;
}
```

Трейт дає ті самі методи, але вже з прив'язкою до моделі: `storeUploadedFile()`, `storeLocalFile()`, `storeContentFile()`. Повторне завантаження того самого вмісту поверне той самий запис:

```php
$file1 = $post->storeUploadedFile($sameFile);
$file2 = $post->storeUploadedFile($sameFile);

$file1->id === $file2->id; // true
```

Файл, який уже є в системі, можна прикріпити до іншої моделі за хешем через `attachUniqueFile($sha1Hash)`, без повторного завантаження. Перевірити зв'язок допоможе `hasUniqueFile()`, а всі зв'язки моделі доступні через `uniqueFileUploads()`. Модель має бути збережена: без `id` прив'язувати файл нема до чого.

### Що буде з файлом після останнього detach

Окремого лічильника посилань пакет не тримає - він рахує рядки в проміжній таблиці. `$post->detachUniqueFile($sha1Hash)` видаляє зв'язок і перевіряє, чи лишилися інші. Якщо ні, за замовчуванням видаляються і фізичний файл, і запис `UniqueFile`. Поки файл потрібен хоч одній моделі, він лишається на місці.

Саме на цю поведінку поскаржилися в [issue #9](https://github.com/maxkhim/laravel-storage-dedupler/issues/9): для проєктів з аудитом, ревізіями чи архівом замовлень жорстке видалення - ризик. У [версії 1.0.6](https://github.com/maxkhim/laravel-storage-dedupler/blob/master/CHANGELOG.md) його можна вимкнути:

```dotenv
DEDUPLER_REMOVE_ORPHAN_ON_DETACH=false
```

Тоді файли без зв'язків лишаються, а прибирати їх можна вручну командою `dedupler:prune`. Вона ж чистить зв'язки, що вказують на вже видалені моделі: видалення моделі само по собі зв'язки не знімає. Спершу - завжди з `--dry-run`:

```bash
php artisan dedupler:prune --orphan-relations --orphan-files --dry-run
```

### Обмеження, про які варто знати

- **Звичайний `Storage` не змінюється.** Дедуплікація працює лише для файлів, збережених через Dedupler. `Storage::put()` і `$request->file()->store()` пишуть, як і раніше.
- **Старі дублікати лишаються.** Пакет не чіпає того, що вже лежить на диску. Команда `dedupler:analyse-legacy <шлях>` лише рахує дублікати в теці та потенційну економію - це корисно, щоб оцінити, чи варто взагалі заводити пакет.
- **SHA-1.** Для звичайних завантажень випадковий збіг хешів практично неможливий. Навмисні колізії SHA-1 продемонстровані ще у 2017 році, тож теоретично зловмисник може підготувати два різні файли з однаковим хешем, і пакет вважатиме їх одним. Для більшості застосунків це не проблема, але для систем, де вміст файлу є доказом, варто пам'ятати.
- **Паралельні завантаження.** З коду видно, що пошук і створення запису не загорнуті в транзакцію чи блокування. Якщо два запити одночасно вантажать той самий новий файл, другий може впасти на унікальному ключі - і ви отримаєте `null`.
- **Зв'язок `uniqueFiles()`.** У версії 1.0.6 він будується лише за `deduplable_id`, без типу моделі. Якщо трейт мають дві моделі з однаковими `id`, списки файлів можуть перемішатися. Надійніше йти через `uniqueFileUploads()`, де поліморфний зв'язок враховує тип.
- **Пакет молодий.** Близько 54 зірок, версія 1.0.6, ліцензія MIT. Потребує PHP 8.2+ і підтримує Laravel 10, 11, 12 і 13.

### Чи варто брати

Якщо у вас є застосунок, де ті самі документи чи картинки вантажать багато разів - CRM, документообіг, маркетплейс із типовими фото, - Dedupler розв'язує проблему без власної інфраструктури: дві таблиці й окремий диск. Почніть з `dedupler:analyse-legacy` на копії наявного сховища: цифра потенційної економії швидко покаже, чи є сенс.

Перед продакшеном вирішіть, чи підходить вам автовидалення після останнього detach, перевіряйте `null` після кожного збереження і подумайте про паралельні завантаження. Код пакета невеликий, і прочитати його цілком - найкращий спосіб зрозуміти, на що ви погоджуєтеся.
