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 = 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *