📜 ⬆️ ⬇️

Symfony - combine GridFS files with ORM entities

In the previous article I wrote about loading files into the GridFS. There, we created a MongoDB document with the $ file property annotated as @MongoDB \ File . Since I use ORM entities more often than ODM documents, I was looking for an easy way to access the document from the entity.

It is impossible to establish a direct link between the entity and the document, and I thought that it would not be bad to make my own field type. By defining a custom field type, we can control how the document reference is stored and, at the same time, return the reference by calling the entity property.

Create a class of your own field type.


Let's start by creating the UploadType class that defines an upload column:

namespace Dennis\UploadBundle\Types; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Platforms\AbstractPlatform; class UploadType extends Type { const UPLOAD = 'upload'; public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($fieldDeclaration); } public function getName() { return self::UPLOAD; } public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } 

To request a link to the Upload document, we will need a Doctrine ODM DocumentManager to create such a link, for this we will add a setter.
')
 use Doctrine\ODM\MongoDB\DocumentManager; // ... private $dm; public function setDocumentManager(DocumentManager $dm) { $this->dm = $dm; } 

To make sure that only the Upload document's id is saved to the database, we will override the convertToDatabaseValue method so that it returns only the document id.

 use Dennis\UploadBundle\Document\Upload; use Doctrine\DBAL\Types\ConversionException; // ... public function convertToDatabaseValue($value, AbstractPlatform $platform) { if (empty($value)) { return null; } if ($value instanceof Upload) { return $value->getId(); } throw ConversionException::conversionFailed($value, self::UPLOAD); } 

To return a reference to the Upload document, after the entity has received it from the database, we override the convertToPHPValue method. As you can see below, creating a link is simply passing the class and id of the document to the DocumentManager class's getReference () method. Since in the convertToDatabaseValue method , we decided to return the Upload document id, then we can use it immediately.

 // ... public function convertToPHPValue($value, AbstractPlatform $platform) { if (empty($value)) { return null; } return $this->dm->getReference('Dennis\UploadBundle\Document\Upload', $value); } 

It is worth noting that the great advantage of creating a link to a document, instead of using the DennisUploadBundle: Upload repository to retrieve a document, is that the document will be received from the database and initialized only when the field with this document is requested. When you use the DennisUploadBundle: Upload repository to search for a document and assign it to an entity property, a document instance will be created for each Image entity returned by the ORM EntityManager. That is, the same number of documents will be created for 100 entities, which, of course, is inefficient. Creating a link guarantees that the document will be created only when you request it.

Register your own field type


Now that our UploadType has the right to convert the Upload document, it's time to use it in our symfony application. According to this article by Matthias Noback , the best place to add a new type to the Doctrine is the constructor of the bundle, where the ODM DocumentManager dependencies will then be loaded into our UploadType

 namespace Dennis\UploadBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; use Doctrine\DBAL\Types\Type; class DennisUploadBundle extends Bundle { public function __construct() { if (!Type::hasType('upload')) { Type::addType('upload', 'Dennis\UploadBundle\Types\UploadType'); } } public function boot() { $dm = $this->container->get('doctrine.odm.mongodb.document_manager'); /* @var $type \Dennis\UploadBundle\Types\UploadType */ $type = Type::getType('upload'); $type->setDocumentManager($dm); } } 

Using UploadType


To demonstrate the use of our new upload field, I'll start by creating an Image entity:

 namespace Acme\DemoBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class Image { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string", length=100) */ protected $name; /** * @ORM\Column(type="upload") */ protected $image; public function getId() { return $this->id; } public function setName($name) { $this->name = $name; } public function getName() { return $this->name; } public function setImage($image) { $this->image = $image; } public function getImage() { return $this->image; } } 

If you look at the @ORM \ Column annotation of the $ image field, you can see that it is enough to add the type name of our type UploadType ( “upload” ) to the type parameter and the Doctrine will use the object of the UploadType class when reading or writing the $ image field.

Form processing


The processing of the form that was created for our Image entity is about the same as any form based on an entity. The only addition is that you have to make sure that the uploaded file is saved in the GridFS and the created Upload document is assigned to the $ image field of the Image entity.

 namespace Acme\DemoBundle\Controller; use Dennis\UploadBundle\Document\Upload; class ImageController extends Controller { public function newAction(Request $request) { // ... $form->bind($request); if ($form->isValid()) { /** @var $upload \Symfony\Component\HttpFoundation\File\UploadedFile */ $upload = $image->getImage(); $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(); $image->setImage($document); $em = $this->getDoctrine()->getManager(); $em->persist($image); $em->flush(); } } } 

If, after successfully saving the form, you look at the image table, you will see that an entry has been created in which the upload document id is recorded in the image field.

Extract image from base


The method described below is almost the same as the showAction method of the UploadController controller from the previous article . The only difference is that you can use the AcmeDemoBundle: Image repository to extract the Image entity, and then retrieve the Upload document by simply calling getImage (). Again, the Upload document will be retrieved from MongoDB and created only when the getImage () method is called.

 /** * @Route("/{id}", name="image_show") */ public function showAction($id) { $image = $this->getDoctrine()->getManager() ->getRepository('AcmeDemoBundle:Image') ->find($id); $response = new Response(); $response->headers->set('Content-Type', $image->getImage()->getMimeType()); $response->setContent($image->getImage()->getFile()->getBytes()); return $response; } 

That's all! Now we have our own UploadType , which handles links to the Upload documents of our Image entity. I am convinced that this approach to creating your own Type class provides an easy way to combine ODM documents and ORM entities.

The only huge drawback is that you need to manually save (persist ()) the document before saving the entity. This is definitely not something that I would like to repeat in each such controller with such a combination. In my next article I will try to overcome this problem.

Source: https://habr.com/ru/post/314966/


All Articles