Replicating Metadata in Entities
This chapter describes how to replicate file metadata in your entities.
Objective
This framework gives you the convenience of requiring only a single property to associate a file with an entity. However, there are cases where it can be useful to have the file's metadata stored in the entity. For example:
- To optimize performance together with our lazy-loading feature, especially when you are dealing with a lot of entities and/or files.
- You need to use the database to index, search, or sort the files based on their metadata.
Using the method described in this chapter, you will be able to accomplish that by replicating the files' metadata in your entities and it does not require you to change the way you work with files.
Execution
You need to install the package rekalogika/file-association-entity
to use this
feature:
composer require rekalogika/file-association-entity
In short, you need to:
- Add a property with
EmbeddedMetadata
type. This is a Doctrine embeddable that implementsRawMetadataInterface
we will be using to store the file's metadata. - Modify the getter of the file property so that it returns a decorated
version of the
FileInterface
that will use ourEmbeddedMetadata
in #1. - Modify the setter of the file property so it will copy the metadata of a new
file to our
EmbeddedMetadata
in #1.
If your original entity looks like this:
use Doctrine\ORM\Mapping\Entity;
use Rekalogika\Contracts\File\FileInterface;
use Rekalogika\File\Association\Attribute\WithFileAssociation;
use Rekalogika\File\Association\Attribute\AsFileAssociation;
#[Entity]
#[WithFileAssociation]
class Product
{
#[AsFileAssociation]
private ?FileInterface $image = null;
public function getImage(): ?FileInterface
{
return $this->image;
}
public function setImage(?FileInterface $image): self
{
$this->image = $image;
return $this;
}
}
You need to modify it to look like this:
use Doctrine\ORM\Mapping\Embedded;
use Doctrine\ORM\Mapping\Entity;
use Rekalogika\Contracts\File\FileInterface;
use Rekalogika\Domain\File\Association\Entity\EmbeddedMetadata;
use Rekalogika\Domain\File\Association\Entity\FileDecorator;
use Rekalogika\File\Association\Attribute\WithFileAssociation;
use Rekalogika\File\Association\Attribute\AsFileAssociation;
#[Entity]
#[WithFileAssociation]
class Product
{
#[AsFileAssociation]
private ?FileInterface $image = null;
#[Embedded]
private EmbeddedMetadata $imageMetadata;
public function __construct()
{
$this->imageMetadata = new EmbeddedMetadata();
}
public function getImage(): ?FileInterface
{
return FileDecorator::getFile($this->image, $this->imageMetadata);
}
public function setImage(?FileInterface $image): self
{
FileDecorator::setFile($image, $this->image, $this->imageMetadata);
return $this;
}
}
After the change, calling the setter will still give you a FileInterface
that
you can use like before. But behind the scenes, any reads to the metadata will
be done from the data stored in the entity. And any writes to the metadata are
saved to both the file and the entity.
Because the metadata is now saved in the entity, after any changes to the
metadata, you need to call flush()
on the entity manager to save the metadata
to the database.
Using The Metadata Fields for Querying and Indexing
EmbeddedMetadata
is a Doctrine embeddable that contains the following fields:
name
: The file name.size
: The file size in bytes.type
: The file MIME type.modificationTime
: The file modification time.width
: The width, if the file is an image.height
: The height, if the file is an image.other
: Other metadata that is not covered by the above fields.
You can use these fields (other than the other
) to query and index the files
in your database.
Mandatory File Properties
If your file property does not accept a null value, you need to modify the setter like the following.
use Doctrine\ORM\Mapping\Entity;
use Rekalogika\Contracts\File\FileInterface;
use Rekalogika\Domain\File\Association\Entity\FileDecorator;
use Rekalogika\Domain\File\Null\NullFile;
use Rekalogika\File\Association\Attribute\WithFileAssociation;
use Rekalogika\File\Association\Attribute\AsFileAssociation;
#[Entity]
#[WithFileAssociation]
class Product
{
// $image is not nullable
#[AsFileAssociation]
private FileInterface $image;
// ...
public function setImage(FileInterface $image): self
{
// make sure the image is not unset, otherwise the next line won't work
$this->image = new NullFile();
// setFileMandatory is identical to setFile, except it does not accept
// null value and works with properties that does not accept null
FileDecorator::setFileMandatory($file, $this->file, $this->fileMetadata);
return $this;
}
}