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 DennisUploadBundleDocument;
use DoctrineODMMongoDBMappingAnnotations as MongoDB;
/**
* @MongoDBDocument
*/
class Upload
{
/** @MongoDBId */
private $id;
/** @MongoDBFile */
private $file;
/** @MongoDBString */
private $filename;
/** @MongoDBString */
private $mimeType;
/** @MongoDBDate */
private $uploadDate;
/** @MongoDBInt */
private $length;
/** @MongoDBInt */
private $chunkSize;
/** @MongoDBString */
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;
}
}
Важная часть в этом листинге — аннотация @MongoDBFile. Она говорит Doctrine MongoDB ODM, что документ должен быть сохранен с использованием GridFS, и экземпляр класса MongoGridFSFile содержится в свойстве $file.
Свойства $chunkSize, $length, $md5 и $uploadDate не нуждаются в сеттерах, потому что они будут заполнены автоматически драйвером MongoDB.
Обработка загрузки файла
В качестве примера я буду использовать простой контроллер, который использует form builder для создания формы с полем типа file:
namespace DennisUploadBundleController;
use SymfonyBundleFrameworkBundleControllerController;
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 DennisUploadBundleDocumentUpload;
public function newAction(Request $request)
{
// ...
$data = $form->getData();
/** @var $upload SymfonyComponentHttpFoundationFileUploadedFile */
$upload = $data['upload'];
$document = new Upload();
$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. Планируется перевод второй части.
Автор: Reshat