Я создаю приложение Symfony 5 и использую Doctrine Inheritance Mapping, в частности наследование таблиц классов, чтобы разделить определенные свойства с несколькими сущностями и связать их. Symfony версия 5.0.7 и Vich Uploader Bundle 1.14.x-dev.
Следуя руководству Doctrines для наследования таблиц классов, я создал родительский класс ScopeObject, и этот класс использует Vich Uploader Связать, чтобы у всех подклассов было изображение аватара. Помимо проблем с Vich, Doctrine работает нормально. Обратите внимание, что я предполагаю, что это проблема Vich, а не проблема Doctrine.
Просто для справки, подклассы могут варьироваться от пользователя, заказа, продукта, каталога, и хотя у меня нет всех этих подклассов, проблема появилась на классах, которые у меня есть. Идея состоит в том, что каждый из моих различных подклассов может иметь изображение аватара.
Теперь я уже использовал Vich Uploader Bundle в другом месте моего приложения, и это сработало, и все еще работает отлично. Проблема возникла после того, как я реализовал ее в этом родительском классе, и я не уверен, что это проблема или я делаю что-то явно не так.
Я реализовал Vich, и он работал на контент, который у меня уже был в базе данных после миграции, что позволяет мне добавлять и удалять аватары для моих объектов. Однако когда я пытался создать новую сущность (у меня в настоящее время есть только Directory и User), я получаю следующее исключение
PropertyAccessor требуется граф объектов или массивов для работы, но он нашел тип " NULL "при попытке пройти путь" image.name "в свойстве" name ".
Чтобы временно устранить проблему, я изменил свой родительский класс ScopeObject, чтобы аннотация Vich UploadableField сохраняла fileNameProperty под новым свойством в моем родительском классе с именем imageNewName. Это сработало, но теперь у меня есть бесполезное поле в моей базе данных из унаследованного файла Vich.
Я провел некоторый поиск и пока единственное другое обнаружение этой ошибки, которое я обнаружил здесь было простое исправление, где они пропустили метод конструктора из сущности, которая у меня есть. Я пробовал конструктор как в родительском, так и в подклассе, но ни один из них не работал, и я получил то же исключение. Метод конструктора для Vich:
public function __construct()
{
$this->image = new EmbeddedFile();
}
Ниже представлен мой родительский класс, обратите внимание, что у меня уже есть свойство с именем name, что, как я думал, может быть проблемой, но где я использую Vich в другом месте, моя сущность имеет Имя свойства там, и он отлично работает.
<?php
namespace App\Entity;
use App\Entity\Attachments\Attachments;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Vich\UploaderBundle\Entity\File as EmbeddedFile;
/**
* @ORM\Entity
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="entity", type="string")
* @ORM\DiscriminatorMap({
* "scopeObject" = "ScopeObject",
* "directory" = "Directory",
* "user" = "\App\Entity\User\User"
* })
*
* @Vich\Uploadable
*/
class ScopeObject
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Actions", mappedBy="scopeObject", orphanRemoval=true)
*/
private $action;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\Attachments", mappedBy="scopeObject", orphanRemoval=true)
*/
private $attachments;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* @Vich\UploadableField(mapping="attachment", fileNameProperty="imageNewName", size="image.size", mimeType="image.mimeType", originalName="image.originalName", dimensions="image.dimensions")
*
* @var File|null
*/
private $imageFile;
/**
* @ORM\Embedded(class="Vich\UploaderBundle\Entity\File")
*
* @var EmbeddedFile
*/
private $image;
/**
* @ORM\Column(type="string", nullable=true)
*/
private $imageNewName;
/**
* @ORM\Column(type="datetime", nullable=true)
*
* @var \DateTimeInterface|null
*/
private $updatedAt;
public function __construct()
{
$this->image = new EmbeddedFile();
$this->action = new ArrayCollection();
$this->attachments = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return Collection|Actions[]
*/
public function getAction(): Collection
{
return $this->action;
}
public function addAction(Actions $action): self
{
if (!$this->action->contains($action)) {
$this->action[] = $action;
$action->setObject($this);
}
return $this;
}
public function removeAction(Actions $action): self
{
if ($this->action->contains($action)) {
$this->action->removeElement($action);
// set the owning side to null (unless already changed)
if ($action->getObject() === $this) {
$action->setObject(null);
}
}
return $this;
}
/**
* @return Collection|Attachments[]
*/
public function getAttachments(): Collection
{
return $this->attachments;
}
public function addAttachment(Attachments $attachment): self
{
if (!$this->attachments->contains($attachment)) {
$this->attachments[] = $attachment;
$attachment->setScopeObject($this);
}
return $this;
}
public function removeAttachment(Attachments $attachment): self
{
if ($this->attachments->contains($attachment)) {
$this->attachments->removeElement($attachment);
// set the owning side to null (unless already changed)
if ($attachment->getScopeObject() === $this) {
$attachment->setScopeObject(null);
}
}
return $this;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* @param File|UploadedFile|null $imageFile
*/
public function setImageFile(?File $imageFile = null)
{
$this->imageFile = $imageFile;
if (null !== $imageFile) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
}
public function getImageFile(): ?File
{
return $this->imageFile;
}
public function setImage(EmbeddedFile $image): void
{
$this->image = $image;
}
public function getImage(): ?EmbeddedFile
{
return $this->image;
}
public function setImageNewName(EmbeddedFile $imageNewName): void
{
$this->imageNewName = $imageNewName;
}
public function getImageNewName(): ?EmbeddedFile
{
return $this->imageNewName;
}
}
Ниже приведен мой подкласс Справочника. По сути, это похоже на телефонный справочник, в котором хранятся контакты, клиенты, поставщики и т. Д. c, поэтому он довольно многоцелевой, и на него также можно ссылаться на ManyToMany через свойства контактов и каталогов. Обратите внимание, что исключение также генерируется в моем объекте User, который также расширяет ScopeObject
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\DirectoryRepository")
*/
class Directory extends ScopeObject
{
/**
* @ORM\Column(type="array", nullable=true)
*/
private $phone = [];
/**
* @ORM\Column(type="array", nullable=true)
*/
private $email = [];
/**
* @ORM\Column(type="text", nullable=true)
*/
private $street;
/**
* @ORM\Column(type="string", length=100, nullable=true)
*/
private $city;
/**
* @ORM\Column(type="string", length=100, nullable=true)
*/
private $county;
/**
* @ORM\Column(type="string", length=12, nullable=true)
*/
private $postcode;
/**
* @ORM\Column(type="string", length=100, nullable=true)
*/
private $country;
/**
* @ORM\Column(type="array", nullable=true)
*/
private $address = [];
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
private $accountNumber;
/**
* @ORM\Column(type="string", length=25, nullable=true)
*/
private $vat;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $active;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $cashCustomer;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $onStop;
/**
* @ORM\Column(type="string", length=100, nullable=true)
*/
private $priceCategory;
/**
* @ORM\Column(type="string", length=10, nullable=true)
*/
private $discountCategory;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $bookableResource;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $resourceLocation;
/**
* @ORM\Column(type="array", nullable=true)
*/
private $type = [];
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $title;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $department;
/**
* @ORM\Column(type="string", length=100, nullable=true)
*/
private $lawfulBasis;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
private $purchaseTax;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $dayCost;
/**
* @ORM\ManyToMany(targetEntity="App\Entity\Directory", inversedBy="directories")
*/
private $contacts;
/**
* @ORM\ManyToMany(targetEntity="App\Entity\Directory", mappedBy="contacts")
*/
private $directories;
public function __construct()
{
$this->contacts = new ArrayCollection();
$this->directories = new ArrayCollection();
$this->setActive(true);
}
public function getPhone(): ?array
{
return $this->phone;
}
public function setPhone(?array $phone): self
{
$this->phone = $phone;
return $this;
}
public function getEmail(): ?array
{
return $this->email;
}
public function setEmail(?array $email): self
{
$this->email = $email;
return $this;
}
public function getStreet(): ?string
{
return $this->street;
}
public function setStreet(?string $street): self
{
$this->street = $street;
return $this;
}
public function getCity(): ?string
{
return $this->city;
}
public function setCity(?string $city): self
{
$this->city = $city;
return $this;
}
public function getCounty(): ?string
{
return $this->county;
}
public function setCounty(?string $county): self
{
$this->county = $county;
return $this;
}
public function getPostcode(): ?string
{
return $this->postcode;
}
public function setPostcode(?string $postcode): self
{
$this->postcode = $postcode;
return $this;
}
public function getCountry(): ?string
{
return $this->country;
}
public function setCountry(?string $country): self
{
$this->country = $country;
return $this;
}
public function getAddress(): ?array
{
return $this->address;
}
public function setAddress(?array $address): self
{
$this->address = $address;
return $this;
}
public function getAccountNumber(): ?string
{
return $this->accountNumber;
}
public function setAccountNumber(?string $accountNumber): self
{
$this->accountNumber = $accountNumber;
return $this;
}
public function getVat(): ?string
{
return $this->vat;
}
public function setVat(?string $vat): self
{
$this->vat = $vat;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): self
{
$this->active = $active;
return $this;
}
public function getCashCustomer(): ?bool
{
return $this->cashCustomer;
}
public function setCashCustomer(?bool $cashCustomer): self
{
$this->cashCustomer = $cashCustomer;
return $this;
}
public function getOnStop(): ?bool
{
return $this->onStop;
}
public function setOnStop(?bool $onStop): self
{
$this->onStop = $onStop;
return $this;
}
public function getPriceCategory(): ?string
{
return $this->priceCategory;
}
public function setPriceCategory(?string $priceCategory): self
{
$this->priceCategory = $priceCategory;
return $this;
}
public function getDiscountCategory(): ?string
{
return $this->discountCategory;
}
public function setDiscountCategory(string $discountCategory): self
{
$this->discountCategory = $discountCategory;
return $this;
}
public function getBookableResource(): ?bool
{
return $this->bookableResource;
}
public function setBookableResource(?bool $bookableResource): self
{
$this->bookableResource = $bookableResource;
return $this;
}
public function getResourceLocation(): ?int
{
return $this->resourceLocation;
}
public function setResourceLocation(?int $resourceLocation): self
{
$this->resourceLocation = $resourceLocation;
return $this;
}
public function getType(): ?array
{
return $this->type;
}
public function setType(?array $type): self
{
$this->type = $type;
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getDepartment(): ?string
{
return $this->department;
}
public function setDepartment(?string $department): self
{
$this->department = $department;
return $this;
}
public function getLawfulBasis(): ?string
{
return $this->lawfulBasis;
}
public function setLawfulBasis(?string $lawfulBasis): self
{
$this->lawfulBasis = $lawfulBasis;
return $this;
}
public function getPurchaseTax(): ?string
{
return $this->purchaseTax;
}
public function setPurchaseTax(?string $purchaseTax): self
{
$this->purchaseTax = $purchaseTax;
return $this;
}
public function getDayCost(): ?int
{
return $this->dayCost;
}
public function setDayCost(?int $dayCost): self
{
$this->dayCost = $dayCost;
return $this;
}
/**
* @return Collection|self[]
*/
public function getContacts(): Collection
{
return $this->contacts;
}
public function addContact(self $contact): self
{
if (!$this->contacts->contains($contact)) {
$this->contacts[] = $contact;
}
return $this;
}
public function removeContact(self $contact): self
{
if ($this->contacts->contains($contact)) {
$this->contacts->removeElement($contact);
}
return $this;
}
/**
* @return Collection|self[]
*/
public function getDirectories(): Collection
{
return $this->directories;
}
public function addDirectory(self $directory): self
{
if (!$this->directories->contains($directory)) {
$this->directories[] = $directory;
$directory->addContact($this);
}
return $this;
}
public function removeDirectory(self $directory): self
{
if ($this->directories->contains($directory)) {
$this->directories->removeElement($directory);
$directory->removeContact($this);
}
return $this;
}
}
Для моего временного исправления я изменил отображение imageFile в ScopeObject следующим образом, но, как упоминалось ранее, это оставляет меня с избыточное поле в базе данных:
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* @Vich\UploadableField(mapping="attachment", fileNameProperty="imageNewName", size="image.size", mimeType="image.mimeType", originalName="image.originalName", dimensions="image.dimensions")
*
* @var File|null
*/
private $imageFile;
/**
* @ORM\Column(type="string", nullable=true)
*/
private $imageNewName;
public function setImageNewName(EmbeddedFile $imageNewName): void
{
$this->imageNewName = $imageNewName;
}
public function getImageNewName(): ?EmbeddedFile
{
return $this->imageNewName;
}
Дайте мне знать, если вы хотите увидеть больше кода, это мой первый вопрос (хотя переполнение стека было для меня отличным ресурсом), поэтому я надеюсь, что я выполнили необходимые критерии.