Pull to refresh

Хранение, обработка и отдача статики

Reading time 4 min
Views 7.5K
У начинающих (и не очень) разработчиков часто возникают вопросы по поводу того как правильно работать с пользовательским контентом, а конкретно с картинками. У данной темы множество аспектов и не один вариант решения. Здесь будет рассматриваться всего лишь один из возможных вариантов имеющий свои плюсы и свои минусы. Так же будем считать что статика и код хранятся на одном единственном сервере, а файлы загружаются по одному.

Задачи решаемые системой:
— удобная загрузка файлов;
— возможность асинхронной обработки картинок;
— легкая работа с превью;
— отделение конфигурирования от выполнения.

Поехали



Для начала создадим в базе таблицу File со следующими полями: id, name, size, width, height, is_deleted, is_ready, updated, created.

При загрузке файла, первым делом, будет создаваться запись в таблице File. Далее на основе id будет вычисляться путь по которому его нужно сохранить.

$number = sprintf('%08d', $id);
$uri = '/' .
substr($number, 0, 2) . '/' .
substr($number, 2, 2) . '/' .
substr($number, 4, 2) . '/' .
$number;


На выходе получим примерно такое: /00/47/31/00473161

upd1 Arkadiy_Kulev указал на то что глубину каталогов лучше ограничить двумя уровнями.

upd2 Действительно, правильнее составлять путь в обратном порядке для лучшего распределения картинок по папкам. Тоесть урл получим такой /161/473/00473161. Спасибо atd

Для чего это делается?
  • file_id будет полем в таблицах сущностей которым нужны картинки ($user->avatar_file_id). Такой подход позволяет на основе одного file_id определить полный путь до картинки без дополнительных запросов к базе.
  • Мы получаем унифицированный способ работы со всеми картинками, как пользовательскими, так и загружаемыми операторами через админку.
  • На диске создается структура папок таким образом что в одной папке не будет больше 100 подпапок или файлов. Хотя это число можно увеличить до 1000 папок, но при большем количестве могут возникнуть трудности в работе с ФС.
  • Таблица file помогает при асинхронной работе с файлами. Если нам вдруг понадобится провести конвертацию всех картинок в другой формат или провести любые другие манипуляции, то мы можем воспользоваться полем is_ready (или добавить свое поле) для определения картинок которые мы уже обработали и которые осталось обработать.
  • Благодаря полю is_deleted удалением картинок можно заниматься асинхронно. Код должен будет просто поставить true (удобнее реализовать на триггерах), а удалять будет специальный сборщик (его конечно нужно написать). Это можно реализовать на триггерах.


Как видно из примера «00/47/31/00473161», количество возможных загрузок картинок, при таком подходе, ограничено миллиардом. Естественно это несложно изменить на этапе внедрения.

Для реализации описанного выше функционала нужно создать класс Image_Manager с методом receive. Этот класс как раз и будет заниматься созданием записи в базе и перемещением файла в соответствующую директорию в ФС, а так же создавать отсутствующие директории.

...
if ($form->isValid()) {
// Название поля в форме не важно, Image_Manager просто берет из массива
// $_FILE один элемент считая что это он и есть.
...
$im = new Image_Manager($options); // Лучше оформит в виде ресурса
// 'avatar' это название секции конфига, о котором чуть ниже
$file_id = $im->receive('avatar'); // Может выкидывать исключения
$user->avatar_file_id = $file_id;
$user->save()
}


Мы сохранили исходную картинку и пользователю передали ее id. Но ведь ее еще нужно обработать, а возможно создать несколько размеров превью для показа на сайте. Дальше можно пойти несколькими путями, создавать картинки сразу или использовать lazy load. Второй способ тоже имеет несколько вариантов развития и выходит за рамки данной статьи (В конце статьи указан список ссылок, где приводится пример возможной реализации данного способа). Мы пойдем первым путем. И именно для этого в конструктор image_manager передается массив $options, а методу recive строка «avatar».

Для того чтобы создавать превьюшки, нужен конфигурационый файл с описанием типов загружаемых на сайт картинок и их параметры. Например (используется формат Zend_Config_Ini):

...
[avatar] ;Названия для превью big, medium и small выбраны произвольно
resize.big.OutputFileFormat = jpg
resize.big.keepFrame = true
resize.big.backgroundColor = 240.240.240
resize.big.width = 236
resize.big.height = 177
resize.medium.keepFrame = true
resize.medium.backgroundColor = 240.240.240
resize.medium.width = 144
resize.medium.height = 108
resize.small.keepFrame = true
resize.small.width = 72
resize.small.height = 54
resize.small.roundCorners = false

[user_album_photo]
...


Именно этот конфигурационный файл передается в конструктор Image_Manager. Вызывая метод recive, мы указываем секцию конфигурационного файла и фактически определяем как будет обрабатываться эта картинка и сколько будет создано превью. Для обработки картинок желательно иметь отдельную библиотеку с которой взаимодействует Image_Manager внутри себя.

Для сохранения превью используется тот же самый формат хранения исходного файла «00/47/31/00473161», но в конце добавляется специальный хеш, вычисляемый на основе параметров данного превью, в итоге путь будет примерно таким "/00/47/31/00473161X2280688952.jpg" (опять же это всего лишь пример, можно сделать по другому). Этот хеш помогает определить существование превью для картинок у которых изменились параметры и в случае отсутствия сгенерировать картинку по запросу.

Осталось разобраться с выводом. Самым простым вариантом будет написать специальный хелпер image, который будет работать подобно url хелперу:

// Возвращает url картинки
$this->image($user->avatar_file_id, 'small', 'avatar')


Хелпер image обращается к объекту (возможно Image_Manager), который знает как построить путь до текущей картинки с учетом ее параметров, секции и названия превью. Если картинки находятся на отдельном хосте, то подставляет и его. В случае отсутствия необходимой превью, хелпер может его генерировать (спорный момент, при большом количестве отсутствующих картинок может закончится память или таймаут) либо отдавать путь на специальную заглушку для этого типа файлов.

В итоге мы получили удобный конфигурационный файл, загрузку файла одной строкой и унифицированный способ работы со всеми картинками сайта, в том числе и пользовательскими.

Ссылки по теме:
http://habrahabr.ru/blogs/nginx/77873/ — здесь очень ценные комментарии
http://habrahabr.ru/blogs/nginx/94435/
http://ru.wikipedia.org/wiki/WebDAV — при выносе статики на отдельный сервер(а)
Tags:
Hubs:
+29
Comments 49
Comments Comments 49

Articles