Symfony — завантаження файлів в MongoDB GridFS

GridFS — це специфікація MongoDB для зберігання великих файлів. У цій статті я розповім як можна легко завантажувати файли в GridFS, а потім витягати їх з бази даних і відображати в браузері.

Але перед стартом, ось коротке пояснення того, як працює GridFS від Kristina Chodorow:
GridFS розбиває великі файли на маленькі шматочки. Шматочки зберігаються в одну колекцію (fs.chunks), а метадані про файл у іншу колекцію (fs.files). Коли ви робите запит до файлу, GridFS робить запит в колекцію з шматочками і повертає файл цілком.
Звичайно ж драйвер MongoDB для PHP поставляється з парочкою класів, які можна використовувати для зберігання і видобування файлів з GridFS.

Кілька переваг GridFS, описаних в цієї статті:

  • Якщо ви використовуєте реплікацію або сегментування (шардінг), GridFS зробить все за вас.
  • MongoDB дробить файли на шматки по 2Гб, так що у вашої ОС точно не буде проблем з маніпулюванням файлами.
  • Вам не потрібно турбуватися про обмеження ОС на імена файлів або кількість файлів в одній директорії.
  • MongoDB автоматично генерує і зберігає MD5 хеш вашого файлу. Це зручно для порівняння завантажених файлів по MD5 хешу і виявлення дублікатів або валідації успішного завантаження.

Створення документа GridFS

Почнемо з простого документа Upload:

namespace Dennis\UploadBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
* @MongoDB\Document
*/
class Upload
{
/** @MongoDB\Id */
private $id;

/** @MongoDB\File */
private $file;

/** @MongoDB\String */
private $filename;

/** @MongoDB\String */
private $mimeType;

/** @MongoDB\Date */
private $uploadDate;

/** @MongoDB\Int */
private $length;

/** @MongoDB\Int */
private $chunkSize;

/** @MongoDB\String */
private $md5;

public function getFile()
{
return $this->file;
}

public function setFile($file)
{
$this->file = $file;
}

public function getFilename()
{
return $this->filename;
}

public function setFilename($filename)
{
$this->filename = $filename;
}

public function getMimeType()
{
return $this->mimeType;
}

public function setMimeType($mimeType)
{
$this->mimeType = $mimeType;
}

public function getChunkSize()
{
return $this->chunkSize;
}

public function getLength()
{
return $this->length;
}

public function getMd5()
{
return $this->md5;
}

public function getUploadDate()
{
return $this->uploadDate;
}
}

Важлива частина цього лістингу — анотація @MongoDB\File. Вона говорить Doctrine MongoDB ODM, що документ повинен бути збережений з використанням GridFS, та екземпляр класу MongoGridFSFile міститься у властивості $file.

Властивості $chunkSize, $length, $md5 $uploadDate не потребують сеттерах, тому що вони будуть заповнені автоматично драйвером MongoDB.

Обробка завантаження файлу

В якості прикладу я буду використовувати простий контролер, який використовує form builder для створення форми з полем типу file:

namespace Dennis\UploadBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class UploadController extends Controller
{
public function newAction(Request $request)
{
$form = $this->createFormBuilder(array())
->add('upload', 'file')
->getForm();

if ($request->isMethod('POST')) {
$form->bind($request);

// ...
}

return array('form' => $form->createView());
}
}

Моя мета — зберегти файл в базу даних прямо з папки /tmp, куди він поміщається після завантаження, щоб уникнути багаторазового переміщення файлу у файловій системі. Для цього я извлеку надіслані дані з форми за допомогою $form->getData(), щоб отримати об'єкт UploadedFile. При використанні сутностей, об'єкт UploadedFile можна отримати значення властивості вашій сутності, яке в form builder'e зазначено як поле типу file.

Об'єкт UploadedFile містить всю необхідну нам інформацію, щоб додати файл в базу даних прямо з тимчасової папки, тому що вона заснована на даних глобальної PHP змінної $_FILES.

use Dennis\UploadBundle\Document\Upload;

public function newAction(Request $request)
{
// ...

$data = $form->getData();

/** @var $upload \Symfony\Component\HttpFoundation\File\UploadedFile */
$upload = $data['upload'];

$document = Upload new();
$document->setFile($upload->getPathname());
$document->setFilename($upload->getClientOriginalName());
$document->setMimeType($upload->getClientMimeType());

$dm = $this- > get('doctrine.odm.mongodb.document_manager');
$dm->persist($document);
$dm->flush();
}

Тепер, коли вся необхідна інформація у нас на руках, ми можемо створити об'єкт Upload, в який ми можемо передати шлях до тимчасового файлу властивість $file. Об'єкт UploadedFile так само надає нам додаткову інформацію про файлі, частина з якої ми можемо додати до документа Upload, наприклад, MIME-тип та ім'я файлу. На даному етапі документ готовий до збереження в базу даних і, як і очікується від ODM, робиться це за допомогою persist() flush().

Витяг завантажених файлів з GridFS

Тепер, коли файл вже у нас в базі, подивимося як його можна звідти дістати відображення в браузері.

В контролер, який я описував вище, додамо ще один метод:

/** 
* @Route("/{id}", name="upload_show") 
*/
public function showAction($id)
{
$upload = $this- > get('doctrine.odm.mongodb.document_manager')
->getRepository('DennisUploadBundle:Upload')
->find($id);

if (null === $upload) {
throw $this->createNotFoundException(sprintf('Upload with id "%s" could not be found', $id));
}

$response = new Response();
$response->headers->set('Content-Type', $upload->getMimeType());

$response->setContent($upload->getFile()->getBytes());

return $response;
}

Досить прямолінійно, як бачите. Параметр id, згенерований MongoDB повинен вказуватися в URL і буде використовуватися для отримання документа Upload з бази. Для виводу файлу створимо об'єкт класу Response з зазначенням Content-Type, який ми візьмемо властивості $mimeType документу Upload. А контент для виведення беремо властивості $file, за допомогою методу getBytes().

Потоковий ресурс і StreamedResponse

Починаючи з версії 1.3.0-beta1 драйвер MongoDB підтримує метод getResource(), який повертає потоковий ресурс файлу. Це дозволяє вам використовувати об'єкт StreamedResponse замість звичайного Response. StreamedResponse дозволяє стримить контент клієнту (браузеру — прим. пер.) з допомогою callback. Виглядає це наступним чином:

public function showAction($id)
{
// ...

$response = new StreamedResponse();
$response->headers->set('Content-Type', $upload->getMimeType());

$stream = $upload->getFile()->getResource();

$response->setCallback(function () use ($stream) {
fpassthru($stream);
});

return $response;
}

Поки що все. У наступній статті я напишу про те, як скомбінувати документ Upload c сутністю (Entity).

— Це був вільний переклад статті Uploading files to MongoDB GridFS. Планується переклад другої частини.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.