Коллекция форм Symfony - поддерживать связь с первичным ключом - PullRequest
0 голосов
/ 30 ноября 2018

У меня есть форма Symfony, содержащая коллекцию, которая определяется следующим образом:

<?php declare(strict_types=1);

namespace App\Form;

use App\Entity\Documents;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class DocumentsType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add(
            'documents',
            CollectionType::class,
            [
                'entry_type' => DocumentType::class,
                'by_reference' => false,
                'entry_options' => [
                    'label' => false,
                ],
                'allow_add' => true,
                'allow_delete' => true,
                'delete_empty' => true,
                'attr' => [
                    'class' => 'documents-collection',
                    'data-min-items' => 1,
                ],
                'required' => true,
            ]
        );

        parent::buildForm($builder, $options);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            [
                'data_class' => Documents::class,
            ]
        );
    }
}

И DocumentType следующим образом:

<?php declare(strict_types=1);

namespace App\Form;

use App\Entity\Document;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class DocumentType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add(
                'description',
                TextType::class,
                [
                    'required' => true,
                    'attr' => [
                        'placeholder' => 'Document description, eg: Ticket, receipt, itinerary, map, etc…',
                    ],
                ]
            )
            ->add(
                'document',
                FileType::class,
                [
                    'mapped' => false,
                    'required' => true,
                ]
            );

        parent::buildForm($builder, $options);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            [
                'data_class' => Document::class,
            ]
        );
    }
}

Сущность Documents является:

<?php declare(strict_types=1);

namespace App\Entity;

use App\Service\Uuid;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Documents
{
    /**
    * @ORM\Column(type="uuid")
    * @ORM\GeneratedValue(strategy="UUID")
    * @ORM\Id
    */
    private $id;

    /**
    * @ORM\ManyToMany(
    *     targetEntity="Document",
    *     cascade={"persist", "remove"},
    *     orphanRemoval=true
    * )
    * @ORM\JoinTable(
    *     name="documents_document",
    *     joinColumns={
    *         @ORM\JoinColumn(name="documents_id", referencedColumnName="id"),
    *     },
    *     inverseJoinColumns={
    *         @ORM\JoinColumn(name="document_id", referencedColumnName="id", unique=true),
    *     }
    * )
    * @var Document[]
    */
    private $documents;


    public function __construct()
    {
        $this->id = Uuid::uuid4();

        $this->documents = new ArrayCollection();
    }


    /**
    * @return mixed
    */
    public function getId()
    {
        return $this->id;
    }

    /**
    * @return Collection
    */
    public function getDocuments(): Collection
    {
        return $this->documents;
    }

    /**
    * @param Document $document
    *
    * @return $this
    */
    public function addDocument(Document $document): Documents
    {
        if (!$this->documents->contains($document)) {
            $this->documents->add($document);
            $document->setDocuments($this);
        }

        return $this;
    }

    /**
    * @param Document $document
    *
    * @return bool
    */
    public function hasDocument(Document $document): bool
    {
        return $this->documents->contains($document);
    }

    /**
    * @param Document $document
    *
    * @return $this
    */
    public function removeDocument(Document $document): Documents
    {
        if ($this->documents->contains($document)) {
            $this->documents->removeElement($document);
        }

        return $this;
    }

    /**
    * @param Collection $documents
    *
    * @return $this
    */
    public function setDocuments(Collection $documents): Documents
    {
        $this->documents = $documents;

        return $this;
    }

    /**
    * @return $this
    */
    public function clearDocuments(): Documents
    {
        $this->documents = new ArrayCollection();

        return $this;
    }
}

И объект документа:

<?php declare(strict_types=1);

namespace App\Entity;

use App\Service\Uuid;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Document
{
    /**
    * @var Uuid|string
    * @ORM\Column(type="uuid")
    * @ORM\GeneratedValue(strategy="UUID")
    * @ORM\Id
    */
    private $id;

    /**
    * @var Documents
    * @ORM\ManyToOne(targetEntity="Documents")
    */
    private $documents;

    /**
    * @var string
    * @ORM\Column(type="string", length=1024, nullable=false)
    */
    private $description;


    public function __construct()
    {
        $this->id = Uuid::uuid4();
    }


    /**
    * @return Uuid|string
    */
    public function getId()
    {
        return $this->id;
    }

    /**
    * @return Documents
    */
    public function getDocuments(): Documents
    {
        return $this->documents;
    }

    /**
    * @param Documents $documents
    *
    * @return $this
    */
    public function setDocuments(Documents $documents): Document
    {
        $this->documents = $documents;

        return $this;
    }

    /**
    * @return string
    */
    public function getDescription(): ?string
    {
        return $this->description;
    }

    /**
    * @param string $description
    *
    * @return $this
    */
    public function setDescription(string $description): Document
    {
        $this->description = $description;

        return $this;
    }
}

Я создаю форму в своем контроллере следующим образом:

$repo = $entityManager->getRepository(Documents::class);
$documents = $repo->findOneBy(['id' => $id]);

$form = $this->formFactory->create(
    DocumentsType::class,
    $documents
);

Когда я добавляю новые записи документа в коллекцию в визуализированном видезатем сохраните форму, они правильно сохранятся в базе данных и будут связаны с сущностью «Документы».

Если я удаляю последнюю запись в коллекции, она корректно удаляется из коллекции $ Documents, а затем удаляется изтаблица документов, поскольку на нее больше нет ссылок.

Однако, если я удалю запись в середине коллекции, Doctrine сохранит данные из оставшихся записей над удаленной и ее последователями,и затем удаляет последнюю сущность в списке, изменяя идентификаторы для всех сущностей.

Я сохраняю файл, загруженный в поле document в DocumentType с использованием UUID в качестве нового имени файла, поэтому идентификаторы должны оставаться неизменными при удалении записей из коллекции.Я попытался добавить в коллекцию как сопоставленное, так и несопоставленное поле идентификатора, однако несопоставленное поле полностью игнорируется, и сопоставленное поле позволит пользователям изменять данные в столбце идентификатора и, таким образом, здесь не подходит для использования.

Что мне нужно сделать, чтобы изменить эту форму, чтобы Doctrine поддерживала связь между данными в коллекции и сущностью, которую она представляет в базе данных?

1 Ответ

0 голосов
/ 21 декабря 2018

Таким образом, после обнаружения этой проблемы в системе отслеживания ошибок репозитория, имеющей аналогичное поведение, последняя связанная проблема указала мне на эту часть прочитанного мной:

Не изменяйте имена полей

Symfony использует имена полей для упорядочивания коллекции, а не положение каждого элемента в dom.Таким образом, по умолчанию, если вы удаляете элемент посередине, индекс всех последующих элементов будет уменьшен до 1 (field[3] станет field[2] и т. Д.), И если вы добавите некоторые элементы посередине, все последующие элементы будутпосмотрите на увеличение их индекса, чтобы оставить место для нового.

В этой реализации вы обязательно сохраните правильные позиции, нажимая «двигаться вверх» и «двигаться вниз», например.Но в некоторых ситуациях вы можете не захотеть перезаписывать индексы, скорее всего, для поддержания отношений Doctrine.

Установите для параметра preserve_names значение true, чтобы никогда не трогать имена полей.Но имейте в виду, что эта опция отключит опции allow_up, allow_down, drag_drop и приведет к значению add_at_the_end в true.

Значение по умолчанию:

$('.collection').collection({
   preserve_names: false
});

source: https://github.com/ninsuo/symfony-collection/blob/d5e6cbc7c7dc1f0509631c9bb6094fead0f6c8f0/README.md#options

Таким образом, решение должно состоять в том, чтобы инициализировать коллекцию с параметром preserve_names, установленным на true, а не значением по умолчанию, которое является false.

$('.collection').collection({
    preserve_names: true // this is our fix
});
...