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 = 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. Планируется перевод второй части.
ссылка на оригинал статьи https://habrahabr.ru/post/314840/
Добавить комментарий