handlerrequest()之后的表单收集数据正在交换


Form collection data after handleRequest() is swapping

我通过symfony教程嵌入了一个表单集合。一切正常,除了删除现有实体。

初始数据:

+----+-------+
| id | value |
+----+-------+
| 1  | a     |
+----+-------+
| 2  | b     |
+----+-------+
| 3  | c     |
+----+-------+

则在集合表i中去掉a值表。在提交和handlerrequest()之后,数据变成:

+----+-------+
| id | value |
+----+-------+
| 1  | b     |
+----+-------+
| 2  | c     |
+----+-------+

看起来像handleRequest()只是重写数组集合值并删除所有差异。预期的结果应该是:

+----+-------+
| id | value |
+----+-------+
| 2  | b     |
+----+-------+
| 3  | c     |
+----+-------+

为什么会这样呢?

控制器:

public function indexAction(Request $request, Agreement $agreement)
{
    $form = $this
        ->createForm(CostGroupsCollectionType::class, $agreement)
        ->add('submit', SubmitType::class, ['label' => 'button.save']);
    $form->handleRequest($request);
    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->flush();
        return $this->redirectToRoute('team_agreements_cost_groups', ['id' => $agreement->getId()]);
    }
    return array(
        'form' => $form->createView(),
        'agreement' => $agreement
    );
}

CostGroupsCollectionType:

class CostGroupsCollectionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('costGroups', CollectionType::class, [
            'label' => false,
            'entry_type' => CostGroupType::class,
            'cascade_validation' => true,
            'allow_add'    => true,
            'allow_delete' => true,
            'prototype'    => true,
            'by_reference' => false,
            'attr'         => array(
                'class' => 'form-collection',
            ),
        ]);
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'Nfq'TeamBundle'Entity'Agreement',
            'cascade_validation' => true
        ]);
    }
    public function getName()
    {
        return 'nfq_team_bundle_cost_groups_collection_type';
    }
}

CostGroupType:

class CostGroupType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        /** @var EmployeeRepository $employeeRepo */
        $employeeRepo = $options['employeesRepo'];
        $builder
            ->add('costGroupPosition', EntityType::class, [
                'label' => 'teams.cost_group.title',
                'class' => 'Nfq'TeamBundle'Entity'CostGroupPosition',
                'attr' => ['class' => 'autocomplete'],
                'choice_label' => 'title',
                'multiple' => false,
                'required' => true
            ])
            ->add('price', NumberType::class, [
                'label' => 'teams.cost_group.price',
                'required' => true
            ])
            ->add('employees', EntityType::class, [
                'label' => 'teams.cost_group.employees',
                'class'=> 'Nfq'ResourcesBundle'Entity'Employee',
                'attr' => ['class' => 'autocomplete-unique'],
                'choice_label' => function ($employee) {
                    /** @var Employee $employee */
                    return $employee->getFullName();
                },
                'multiple'  => true,
                'required' => false
            ])
            ->add('currency', EntityType::class, [
                'label' => 'currency.single_title',
                'class' => Currency::class,
                'choice_label' => function (Currency $c) {
                    return $c->getName();
                },
                'multiple' => false,
                'expanded' => false,
                'empty_data' => 'null',
                'placeholder' => 'misc.selectAValue',
                'required' => true
            ])
            ->add('exchangeRate', NumberType::class, [
                'label' => 'teams.dedicated.invoices.details.exchange_rate',
                'required' => true,
            ]);
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'Nfq'TeamBundle'Entity'CostGroup'
        ]);
    }
    public function getName()
    {
        return 'nfq_team_bundle_cost_group_type';
    }
}

协议

class Agreement
{
    /**
     * @var int
     *
     * @ORM'Column(name="id", type="integer")
     * @ORM'Id
     * @ORM'GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @var string
     *
     * @ORM'Column(name="title", type="string", length=255)
     */
    private $title;
    /**
     * @ORM'ManyToOne(targetEntity="Nfq'ClientsBundle'Entity'Company", inversedBy="agreements")
     * @ORM'JoinColumn(name="Company_id", referencedColumnName="id")
     */
    private $client;
    /**
     * @ORM'ManyToOne(targetEntity="Nfq'ResourcesBundle'Entity'Team", inversedBy="agreements")
     * @ORM'JoinColumn(name="Team_id", referencedColumnName="id")
     */
    private $team;
    /**
     * @ORM'ManyToOne(targetEntity="Nfq'UserBundle'Entity'User", inversedBy="agreements")
     * @ORM'JoinColumn(name="User_id", referencedColumnName="id")
     */
    private $assignee;
    /**
     * @var 'DateTime
     *
     * @ORM'Column(name="start_date", type="date")
     */
    private $startDate;
    /**
     * @var 'DateTime
     *
     * @ORM'Column(name="end_date", type="date", nullable=true)
     */
    private $endDate;
    /**
     * @var bool
     *
     * @ORM'Column(name="ended", type="boolean")
     */
    private $ended;
    /**
     * @ORM'OneToOne(targetEntity="Nfq'TeamBundle'Entity'Requisites", mappedBy="agreement")
     */
    private $requisites;
    /**
     * @ORM'OneToMany(targetEntity="Nfq'TeamBundle'Entity'CostGroup", mappedBy="agreement",
     *     cascade={"all"}, orphanRemoval=true)
     */
    private $costGroups;
    /**
     * @ORM'OneToMany(targetEntity="Nfq'TeamBundle'Entity'DedicatedInvoice", mappedBy="agreement")
     */
    private $dedicatedInvoices;
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->ended = false;
        $this->costGroups = new 'Doctrine'Common'Collections'ArrayCollection();
        $this->dedicatedInvoices = new 'Doctrine'Common'Collections'ArrayCollection();
    }
    /**
     * Get id.
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }
    /**
     * Set title.
     *
     * @param string $title
     *
     * @return Agreement
     */
    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }
    /**
     * Get title.
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }
    /**
     * Set startDate.
     *
     * @param 'DateTime $startDate
     *
     * @return Agreement
     */
    public function setStartDate($startDate)
    {
        $this->startDate = $startDate;
        return $this;
    }
    /**
     * Get startDate.
     *
     * @return 'DateTime
     */
    public function getStartDate()
    {
        return $this->startDate;
    }
    /**
     * Set endDate.
     *
     * @param 'DateTime $endDate
     *
     * @return Agreement
     */
    public function setEndDate($endDate)
    {
        $this->endDate = $endDate;
        return $this;
    }
    /**
     * Get endDate.
     *
     * @return 'DateTime
     */
    public function getEndDate()
    {
        return $this->endDate;
    }
    /**
     * Set ended.
     *
     * @param boolean $ended
     *
     * @return Agreement
     */
    public function setEnded($ended)
    {
        $this->ended = $ended;
        return $this;
    }
    /**
     * Get ended.
     *
     * @return boolean
     */
    public function getEnded()
    {
        return $this->ended;
    }
    /**
     * Set client.
     *
     * @param 'Nfq'ClientsBundle'Entity'Company $client
     *
     * @return Agreement
     */
    public function setClient('Nfq'ClientsBundle'Entity'Company $client = null)
    {
        $this->client = $client;
        return $this;
    }
    /**
     * Get client.
     *
     * @return 'Nfq'ClientsBundle'Entity'Company
     */
    public function getClient()
    {
        return $this->client;
    }
    /**
     * Set team.
     *
     * @param 'Nfq'ResourcesBundle'Entity'Team $team
     *
     * @return Agreement
     */
    public function setTeam('Nfq'ResourcesBundle'Entity'Team $team = null)
    {
        $this->team = $team;
        return $this;
    }
    /**
     * Get team.
     *
     * @return 'Nfq'ResourcesBundle'Entity'Team
     */
    public function getTeam()
    {
        return $this->team;
    }
    /**
     * Set assignee.
     *
     * @param User $assignee
     *
     * @return Agreement
     */
    public function setAssignee(User $assignee = null)
    {
        $this->assignee = $assignee;
        return $this;
    }
    /**
     * Get assignee.
     *
     * @return User
     */
    public function getAssignee()
    {
        return $this->assignee;
    }
    /**
     * Set requisites.
     *
     * @param 'Nfq'TeamBundle'Entity'Requisites $requisites
     *
     * @return Agreement
     */
    public function setRequisites('Nfq'TeamBundle'Entity'Requisites $requisites = null)
    {
        $this->requisites = $requisites;
        return $this;
    }
    /**
     * Get requisites.
     *
     * @return 'Nfq'TeamBundle'Entity'Requisites
     */
    public function getRequisites()
    {
        return $this->requisites;
    }
    /**
     * Add costGroup.
     *
     * @param 'Nfq'TeamBundle'Entity'CostGroup $costGroup
     *
     * @return Agreement
     */
    public function addCostGroup('Nfq'TeamBundle'Entity'CostGroup $costGroup)
    {
        if (!$this->costGroups->contains($costGroup)) {
            $this->costGroups->add($costGroup);
        }
        $costGroup->setAgreement($this);
        return $this;
    }
    /**
     * Remove costGroup.
     *
     * @param 'Nfq'TeamBundle'Entity'CostGroup $costGroup
     */
    public function removeCostGroup('Nfq'TeamBundle'Entity'CostGroup $costGroup)
    {
        if ($this->costGroups->contains($costGroup)) {
            $this->costGroups->removeElement($costGroup);
        }
        $costGroup->setAgreement(null);
    }
    /**
     * Get costGroups.
     *
     * @return 'Doctrine'Common'Collections'Collection
     */
    public function getCostGroups()
    {
        return $this->costGroups;
    }
    /**
     * Add dedicatedInvoice.
     *
     * @param 'Nfq'TeamBundle'Entity'DedicatedInvoice $dedicatedInvoice
     *
     * @return Agreement
     */
    public function addDedicatedInvoice('Nfq'TeamBundle'Entity'DedicatedInvoice $dedicatedInvoice)
    {
        $this->dedicatedInvoices[] = $dedicatedInvoice;
        return $this;
    }
    /**
     * Remove dedicatedInvoice.
     *
     * @param 'Nfq'TeamBundle'Entity'DedicatedInvoice $dedicatedInvoice
     */
    public function removeDedicatedInvoice('Nfq'TeamBundle'Entity'DedicatedInvoice $dedicatedInvoice)
    {
        $this->dedicatedInvoices->removeElement($dedicatedInvoice);
    }
    /**
     * Get dedicatedInvoices.
     *
     * @return 'Doctrine'Common'Collections'Collection
     */
    public function getDedicatedInvoices()
    {
        return $this->dedicatedInvoices;
    }
}

CostGroup

class CostGroup
{
    /**
     * @var int
     *
     * @ORM'Column(name="id", type="integer")
     * @ORM'Id
     * @ORM'GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @var float
     *
     * @ORM'Column(name="price", type="decimal", precision=10, scale=2)
     */
    private $price;
    /**
     * @ORM'ManyToOne(targetEntity="Nfq'TeamBundle'Entity'Agreement", inversedBy="costGroups")
     * @ORM'JoinColumn(name="Agreement_id", referencedColumnName="id", onDelete="CASCADE")
     */
    private $agreement;
    /**
     * @ORM'ManyToOne(targetEntity="Nfq'TeamBundle'Entity'CostGroupPosition", inversedBy="costGroups")
     * @ORM'JoinColumn(name="cost_group_position_id", referencedColumnName="id", onDelete="CASCADE")
     */
    private $costGroupPosition;
    /**
     * @ORM'ManyToMany(targetEntity="Nfq'ResourcesBundle'Entity'Employee", inversedBy="costGroups")
     * @ORM'JoinTable(name="cost_group_employee")
     */
    private $employees;
    /**
     * @ORM'OneToMany(targetEntity="Nfq'TeamBundle'Entity'DedicatedEmployees", mappedBy="costGroup")
     */
    private $dedicatedEmployees;
    /**
     * @var float
     * @ORM'Column(name="exchange_rate", type="float", nullable=true)
     */
    private $exchangeRate;
    /**
     * @var Currency
     * @ORM'ManyToOne(targetEntity="Nfq'SettingsBundle'Entity'Currency", inversedBy="costGroups")
     * @ORM'JoinColumn(name="Currency_id", referencedColumnName="id")
     */
    private $currency;
    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->employees = new 'Doctrine'Common'Collections'ArrayCollection();
        $this->dedicatedEmployees = new 'Doctrine'Common'Collections'ArrayCollection();
        $this->exchangeRate = 1;
    }
    /**
     * Get id.
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }
    /**
     * Set price.
     *
     * @param string $price
     *
     * @return CostGroup
     */
    public function setPrice($price)
    {
        $this->price = $price;
        return $this;
    }
    /**
     * Get price.
     *
     * @return string
     */
    public function getPrice()
    {
        return $this->price;
    }
    /**
     * Set agreement.
     *
     * @param 'Nfq'TeamBundle'Entity'Agreement $agreement
     *
     * @return CostGroup
     */
    public function setAgreement('Nfq'TeamBundle'Entity'Agreement $agreement = null)
    {
        $this->agreement = $agreement;
        return $this;
    }
    /**
     * Get agreement.
     *
     * @return 'Nfq'TeamBundle'Entity'Agreement
     */
    public function getAgreement()
    {
        return $this->agreement;
    }
    /**
     * Set costGroupPosition.
     *
     * @param 'Nfq'TeamBundle'Entity'CostGroupPosition $costGroupPosition
     *
     * @return CostGroup
     */
    public function setCostGroupPosition('Nfq'TeamBundle'Entity'CostGroupPosition $costGroupPosition = null)
    {
        $this->costGroupPosition = $costGroupPosition;
        return $this;
    }
    /**
     * Get costGroupPosition.
     *
     * @return 'Nfq'TeamBundle'Entity'CostGroupPosition
     */
    public function getCostGroupPosition()
    {
        return $this->costGroupPosition;
    }
    /**
     * Add employee.
     *
     * @param 'Nfq'ResourcesBundle'Entity'Employee $employee
     *
     * @return CostGroup
     */
    public function addEmployee('Nfq'ResourcesBundle'Entity'Employee $employee)
    {
        $this->employees[] = $employee;
        return $this;
    }
    /**
     * Remove employee.
     *
     * @param 'Nfq'ResourcesBundle'Entity'Employee $employee
     */
    public function removeEmployee('Nfq'ResourcesBundle'Entity'Employee $employee)
    {
        $this->employees->removeElement($employee);
    }
    /**
     * Get employees.
     *
     * @return 'Doctrine'Common'Collections'Collection
     */
    public function getEmployees()
    {
        return $this->employees;
    }
    /**
     * Add dedicatedEmployee.
     *
     * @param 'Nfq'TeamBundle'Entity'DedicatedEmployees $dedicatedEmployee
     *
     * @return CostGroup
     */
    public function addDedicatedEmployee('Nfq'TeamBundle'Entity'DedicatedEmployees $dedicatedEmployee)
    {
        $this->dedicatedEmployees[] = $dedicatedEmployee;
        return $this;
    }
    /**
     * Remove dedicatedEmployee.
     *
     * @param 'Nfq'TeamBundle'Entity'DedicatedEmployees $dedicatedEmployee
     */
    public function removeDedicatedEmployee('Nfq'TeamBundle'Entity'DedicatedEmployees $dedicatedEmployee)
    {
        $this->dedicatedEmployees->removeElement($dedicatedEmployee);
    }
    /**
     * Get dedicatedEmployees.
     *
     * @return 'Doctrine'Common'Collections'Collection
     */
    public function getDedicatedEmployees()
    {
        return $this->dedicatedEmployees;
    }
    /**
     * Set exchangeRate.
     *
     * @param float $exchangeRate
     *
     * @return CostGroup
     */
    public function setExchangeRate($exchangeRate)
    {
        $this->exchangeRate = $exchangeRate;
        return $this;
    }
    /**
     * Get exchangeRate.
     *
     * @return float
     */
    public function getExchangeRate()
    {
        return $this->exchangeRate;
    }
    /**
     * Set currency.
     *
     * @param Currency $currency
     *
     * @return CostGroup
     */
    public function setCurrency(Currency $currency = null)
    {
        $this->currency = $currency;
        return $this;
    }
    /**
     * Get currency.
     *
     * @return Currency
     */
    public function getCurrency()
    {
        return $this->currency;
    }
}

请参阅文档如何正确处理使用Doctrine持久化ArrayCollection