Генерация уникального имени файла в PHP проектах
C самых первых проектов, поддерживающих загрузку файлов на сервер, любой программист сталкивается с необходимостью генерации уникальных случайных имён для загруженных файлов. Рассмотрим несколько вариантов решения данной проблемы.
Предположим, мы должны сохранять все файлы в одну папку. Чтобы файлы не повторялись и чтобы не возникало проблем с кириллицей нам лучше давать им уникальные имена вида
Самый лёгкий вариант – это использование встроенной в PHP функции uniqid():
Эта функция вернёт случайную 13-символьную строку. Если же нужно делать имена длиннее, то можно использовать функции вычисления хэша от случайной строки:
Функция md5() генерирует 32-символьную строку. Можно, конечно использовать любую другую функцю. При желании можно установить любую длину от 1 до 32 обрезав md5-хэш функцией substr() :
Если нужно хранить файл с расширением, то его расширение можно легко приписывать к идентификатору:
Это простые способы, но у них есть один недостаток: уникальность имени не контролируется, а следовательно имеется вероятность перезаписи старого файла при случайной генерации такого же имени для нового файла. И эта вероятность тем выше, чем больше файлов сохранено.
Чтобы избежать перезаписи нам необходимо проверять папку на отсутствие такого же файла. Напишем функцию, избавленную от этого недостатка:
Здесь в постусловии цикла осуществляется проверка на существование файла. Если файл с таким именем уже есть, то генерируется следующее имя. Вместо md5(microtime() . rand(0, 9999)) для генерации идентификатора можно использовать любой вариант из разобранных выше.
Рассмотрим пример загрузки изображения с использованием данной функции:
В Yii для получения расширения удобно пользоваться объектом $file класса CUploadedFile :
С помощью функции DFileHelper::getRandomFileName() мы генерируем уникальное имя файла для папки upload/images и используем это имя для загрузки. Теперь файлы никогда не перезапишутся, так как имена никогда не совпадут.
Не пропускайте новые статьи, бонусы и мастер-классы:
Недавно на Deworker провели большой стрим с планами по записи и перезаписи скринкастов на новый рабочий сезон этого и следующего года. Спасибо всем за ваши предложения по контенту! С вами мы сделали наши видео лучше, чем изначально планировали.
Все видеозаписи уроков обработаны, тайм-коды проставлены, вопросы отвечены. Наш самый крупный четырёхмесячный мастер-класс по Symfony завершён. Вот что у нас с вами получилось.
В комментариях к записи о HTML Purifier был задан вопрос о том, каким образом можно переделать внешние адреса на переход через промежуточную страницу на своём сайте. Сделаем функционал, обрабатывающий внешние ссылки в тексте и переадресующий их на страницу подтверждения перехода на другие сайты.
Довольно часто возникает необходимость проследить время исполнения некоторых фрагментов программного кода и отследить особо медленные участки для их последующей оптимизации. Имеющиеся отладочные расширения не проникают внутрь ваших файлов, а чаще выводят только общее время выполнения скрипта. Для локального рефакторинга и оптимизации они не подходят.
С микротаймом и мд5, коллизии никогда не будет по 1 простой причине :) В одной папке есть лимит количества файлов, после которого просто система заглохнет читать папку, а удалять файлы в такой папке будет пыткой даже для СИшных приложений и будет очень дорогостоящей операцией для системы.
Увы, но это не причина.
Не знаю, правильно, или нет, но так в одной папке много не скопится :)
И такой вопрос: а если надо сохранить имена файлов (ещё и на русском языке)? Сервер корёжит имена при загрузке.
Да, кстати, лучше создавать папки по дате. Так многие CMS делают (тот же WordPress).
И касательно имён: можно хранить имя в ещё одной ячейке, а файлы отдавать через x-sendfile в Nginx с передачей оригинального имени.
Я делал проще с использованием TIME():
Статья вводит бедных юниоров в заблуждение. Для действительного случайного имени нужно использовать tempnam, а не указанный код.
Увы, эта функция не припишет к имени файла расширение. После конкатенации расширения её также придётся оборачивать в этот же цикл do-while.
И зачем ему расширение?
Очень прошу, поменяйте хотя бы для первых блоков (с uniqid и md5(microtime)) описание, допишите, что этот способ использовать _нельзя_. Ну открывают же и копируют, не глядя.
А Вы изображения без расширений загружаете?
В Yii2 есть методы:
Для тех кто не любит MD5.
Посмотрел на код нашего юниора, который использует эту замечательную статью, вскрылся очередной косяк:
// Генерируем уникальное имя файла с этим расширением $filename = DFileHelper::getRandomFileName($path, $extension);
// ВОТ ТУТ ДРУГОЙ ПРОЦЕСС СОЗДАЕТ ФАЙЛ С ТАКИМ ЖЕ ИМЕНЕМ
// Собираем адрес файла назначения $target = $path . '/' . $filename . '.' . $extension;
// Загружаем move_uploaded_file($_FILES['image']['tmp_name'], $target);
// ОПА - А ТАКОЙ ФАЙЛ УЖЕ ЕСТЬ
Слушайте, ну уберите статью, ну пожалуйста. Она не имеет абсолютно никакого смысла и только запутывает людей. Пожалуйста, посмотрите в исходниках PHP, как работает tempnam (подсказка: через mkstemp), посмотрите, что такое mkstemp и как она отличается от mktemp. В качестве бонуса попробуйте осознать понятия "атомарность" и "параллельное выполнение". Ваша статья в гугле висит на первых местах и несёт страдание в этот мир.
Да запросто. Если подскажете, как сгенерировать случайное имя с расширением – удалю.
Да хотя бы переработайте свой же код, чтобы он не просто пытался сгенерировать случайное имя файла, а пытался _создать_ файл со случайным именем до тех пор, пока у него это не получится, будет значительно лучше. В простой генерации случайного несуществующего имени практического смысла мало - кроме гарантии, что _на момент_ генерации имени такого файла не существовало, больше никаких гарантий нет. А это достаточно бестолково :)
Переработал ещё позавчера.
Иначе с цикла мы выйдем когда найдем файл с таким же именем? что нам как раз не нужно
Нет. Наоборот. Это цикл while, а не until.
Да, ошибся, сорри
С интересом читаю все ваши статьи, но относительного правильности способа генерации уникального имени файла, который изложен в этом материале, возникают сомнения. Все, что делает статический метод DFileHelper::getRandomFileName() - это генерирует уникальное (на момент генерации) имя файла в рамках заданной директории. Однако, представим, что с приложением параллельно работают несколько клиентов (клиент1 и клиент2): Клиент1 вызывает метод getRandomFileName() - генерируется уникальное имя; Клиент2 вызывает этот же метод с некоторым запозданием, но еще до вызова Клиентом1 метода, который запишет файл в директорию.
В этой ситуации может произойти такое, что и для Клиента1, и для Клиента2 будет создано одно и тоже "уникальное" имя, но Клиент2 не узнает об этом, поскольку Клиент1 еще не произвел запись файла в каталог, а ведь лишь после этого метод file_exists() сможет определить, что файл с таким именем уже существует в директории.
Считаю, что после генерации уникального имени необходимо сразу же записывать файл в директорию (пустой) и после этого возвращать, действительно уникальное имя клиенту.
Сейчаc от этого спасает только rand(0, 9999) в имени, что даёт крайне малую верятность этого события:
Можно повысить верхний предел до миллиона, что снизит возможность совпадения до одной миллионной.
Но всё равно создание пустого файла не спасёт, так как это «сразу же» тоже не будет мгновенным.
А разве так не надежнее будет?
чтобы исключить возможность ошибки надо просто содать отдельную дирректорию для файлов пользователя с именем ID
Я пользуюсь алгоритмом генерации файлов как у автора, только не проверяю директорию на наличие такого файла уже, какова вероятность что два файла записанные в разное время будут иметь одинаковый хеш?, плюс ко всему у меня у каждого юзера своя папка с файлами, название которой соответствуют id юзера, который уникальный в системе.
Вопрос, какова вероятность перезаписи файла в моем случаи, я хеш не обрезаю вообще
Значит в Вашем случае способ, предложенный автором, используется просто как генератор имени файла. Если Вы не проверяете директорию на наличие в ней файла с именем, которое было сгенерировано для записи нового файла - вероятность перезаписи существует, как если бы файлы записывались "в разное время", так и одновременно, т.к. в md5 могут быть коллизии.