Доктрина сохраняет только последний объект в CollectionType

Я представляю форму в Symfony с набором подформ. Доктрина, похоже, сохраняет только последний объект в этой коллекции. Как мне заставить его сохранять все объекты в коллекции?

Я дважды проверил свой код и последовал за несколькими учебниками, чтобы убедиться, что у меня все правильно.

EnrichmentApplication

/**
* @ORMEntity
*/
class EnrichmentApplication
{
   //...
   /**
    * @var array
    *
    * @ORMOneToMany(targetEntity="EnrichmentActivity", mappedBy="application", cascade={"persist"})
    */
    private $activities;

   /**
    * @var array
    *
    * @ORMOneToMany(targetEntity="EnrichmentActivityCosts", mappedBy="application", cascade={"persist"})
    */
    private $activityCosts;

    **
    * @var string
    *
    * @ORMColumn(name="project_outline", type="text")
    */
    private $projectOutline;
  //...

public function __construct()
{
    $this->activities = new ArrayCollection();
    $this->activityCosts = new ArrayCollection();
}

  /**
   * Set activityTypes
   *
   * @param array $activityTypes
   *
   * @return EnrichmentApplication
   */
  public function setActivityTypes($activityTypes)
  {
      $this->activityTypes = $activityTypes;

      return $this;
  }

  /**
   * Get activityTypes
   *
   * @return array
   */
  public function getActivityTypes()
  {
      return $this->activityTypes;
  }

  /**
   * Set activities
   *
   * @param array $activities
   *
   * @return EnrichmentApplication
   */
  public function setActivities($activities)
  {
      $this->activities = $activities;

      return $this;
  }

  /**
   * Get activities
   *
   * @return array
   */
  public function getActivities()
  {
      return $this->activities;
  }

  /**
   * Add activity
   *
   * @param array $activity
   *
   * @return EnrichmentApplication
   */
  public function addActivities(EnrichmentActivity $activity)
  {
      $activity->setApplication($this);

      if (!$this->activities->contains($activity))
      {
          $this->activities->add($activity);
      }

      return $this;
  }

  /**
   * Set activityCosts
   *
   * @param array $activityCosts
   *
   * @return EnrichmentApplication
   */
  public function setActivityCosts($activityCosts)
  {
      $this->activityCosts = $activityCosts;

      return $this;
  }

  /**
   * Get activityCosts
   *
   * @return array
   */
  public function getActivityCosts()
  {
      return $this->activityCosts;
  }

  /**
   * Add activityCost
   *
   * @param EnrichmentActivityCosts $activityCost
   *
   * @return EnrichmentApplication
   */
  public function addActivityCosts(EnrichmentActivityCosts $activityCost)
  {
      $activityCost->setApplication($this);

      if (!$this->activityCosts->contains($activityCost))
      {
          $this->activityCosts->add($activityCost);
      }

      return $this;
  }

  /**
   * Set projectOutline
   *
   * @param string $projectOutline
   *
   * @return EnrichmentApplication
   */
  public function setProjectOutline($projectOutline)
  {
      $this->projectOutline = $projectOutline;

      return $this;
  }

  /**
   * Get projectOutline
   *
   * @return string
   */
  public function getProjectOutline()
  {
      return $this->projectOutline;
  }
//...
}

EnrichmentActivity

/**
 * @var int
 *
 * @ORMColumn(name="id", type="integer")
 * @ORMId
 * @ORMGeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var array
 * 
 * @ORMManyToOne(targetEntity="EnrichmentApplication", inversedBy="activities")
 * @ORMJoinColumn(name="application_id", referencedColumnName="id")
 */
private $application;

/**
 * @var DateTime
 *
 * @ORMColumn(name="date", type="date")
 */
private $date;

/**
 * @var DateTime
 *
 * @ORMColumn(name="start_time", type="time")
 */
private $startTime;

/**
 * @var DateTime
 *
 * @ORMColumn(name="end_time", type="time")
 */
private $endTime;

/**
 * @var int
 *
 * @ORMColumn(name="total_students", type="integer")
 */
private $totalStudents;

    /**
     * @var DateTime
     *
     * @ORMColumn(name="date", type="date")
     */

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

public function __clone()
{
    $this->id = null;
    $this->application = null;
}

/**
 * Set application
 *
 * @param EnrichmentApplication $application
 *
 * @return EnrichmentActivity
 */
public function setApplication($application)
{
    $this->application = $application;

    return $this;
}

/**
 * Get application
 *
 * @return EnrichmentApplication
 */
public function getApplication()
{
    return $this->application;
}

/**
 * Set date
 *
 * @param DateTime $date
 *
 * @return EnrichmentActivity
 */
public function setDate($date)
{
    $this->date = $date;

    return $this;
}

/**
 * Get date
 *
 * @return DateTime
 */
public function getDate()
{
    return $this->date;
}

/**
 * Set startTime
 *
 * @param DateTime $startTime
 *
 * @return EnrichmentActivity
 */
public function setStartTime($startTime)
{
    $this->startTime = $startTime;

    return $this;
}

/**
 * Get startTime
 *
 * @return DateTime
 */
public function getStartTime()
{
    return $this->startTime;
}

/**
 * Set endTime
 *
 * @param DateTime $endTime
 *
 * @return EnrichmentActivity
 */
public function setEndTime($endTime)
{
    $this->endTime = $endTime;

    return $this;
}

/**
 * Get endTime
 *
 * @return DateTime
 */
public function getEndTime()
{
    return $this->endTime;
}

/**
 * Set totalStudents
 *
 * @param integer $totalStudents
 *
 * @return EnrichmentActivity
 */
public function setTotalStudents($totalStudents)
{
    $this->totalStudents = $totalStudents;

    return $this;
}

/**
 * Get totalStudents
 *
 * @return int
 */
public function getTotalStudents()
{
    return $this->totalStudents;
}
}

EnrichmentApplicationType (форма)

namespace AppBundleForm;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
use SymfonyComponentFormExtensionCoreType as Types;
use LifoTypeaheadBundleFormTypeTypeaheadType;

class EnrichmentApplicationType extends AbstractType
{
  /**
   * {@inheritdoc}
   */
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $uniform_col_label              = 'col-xs-12 col-sm-5 col-md-4 col-lg-3'
    $uniform_col_element            = 'col-xs-12 col-sm-7 col-md-8 col-lg-8'
    $uniform_col_label_smaller      = 'col-xs-12 col-sm-5 col-md-4 col-lg-2'
    $uniform_col_element_smaller    = 'col-xs-6 col-sm-2 col-md-2 col-lg-2'
    $uniform_col_element_offset     = ' col-sm-offset-5 col-md-offset-4 col-lg-offset-3'
    $uniform_col_fullwidth          = 'col-xs-12 col-sm-12'

    $builder->add('manager', TypeaheadType::class, array(
                'label' => 'Head of Department',
                'route' => 'ajax_name_search',
                'minLength' => 3,
                'render'    => 'fullName',
                'attr'      => array(
                    'data-second-glyph' => 'user',
                    'data-label-col'    => 'col-xs-12 col-sm-5 col-md-4 col-lg-4 ',
                    'data-group-col'    => 'col-xs-12 col-sm-7 col-md-8 col-lg-6'
                )
            ))
            ->add('personResponsible', TypeaheadType::class, array(
                'label' => 'Person responsible for activity delivery',
                'route' => 'ajax_name_search',
                'minLength' => 3,
                'render'    => 'fullName',
                'attr'      => array(
                    'data-second-glyph' => 'user',
                    'data-label-col'    => 'col-xs-12 col-sm-5 col-md-5 col-lg-4',
                    'data-group-col'    => 'col-xs-12 col-sm-7 col-md-7 col-lg-6'
                )
            ))
            //->add('author') //populated by LDAP
            ->add('activityTitle', TypesTextType::class, array(
                'label'     => 'Title of proposed activity',
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('activityTypes', TypesChoiceType::class, array(
                'label'     => 'Activity type',
                'choices'   => $options['activityTypes'],
                'expanded'  => true,
                'help'      => 'Please select all that apply.',
                'multiple'  => true,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('activities', TypesCollectionType::class, array(
                'entry_type'    => EnrichmentActivityType::class,
                'allow_add'     => true,
                'by_reference'  => false,
                'entry_options' => array(
                    'empty_data'    => new AppBundleEntityEnrichmentActivity(),
                ),
                'attr'      => array(
                    'data-label-col'    => 'col-sm-12',
                    'data-group-col'    => 'col-sm-12'
                )
            ))
            ->add('activityCosts', TypesCollectionType::class, array(
                'entry_type'    => EnrichmentActivityCostsType::class,
                'allow_add'     => true,
                'entry_options' => array(
                    'empty_data'    => new AppBundleEntityEnrichmentActivityCosts(),
                ),
                'attr'      => array(
                    'data-label-col'    => 'col-xs-12 col-sm-3 col-md-4 col-lg-3',
                    'data-group-col'    => 'col-xs-12 col-sm-9 col-md-8 col-lg-9'
                )
            ))
            ->add('projectOutline', TypesTextareaType::class, array(
                'label'     => 'Activity outline and rationale',
                'help'      => 'Provide background information of your activity, what you intend to do and how it will impact on students' life skills development.',
                #   
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('projectObjectives', TypesCollectionType::class, array(
                'entry_type'    => TypesTextType::class,
                'label'         => 'Activity objectives & outputs',
                'help'      => 'Please describe what you intend to achieve. Will there be any specific outputs (eg. deliver a workshop/fundraiser)?',
                'allow_add'     => true,
                'prototype'     => true,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('studentConsulted', TypesTextareaType::class, array(
                'label'     => 'How have the students been consulted/involved with the design of this activity?',
                'help'      => 'For example, have students requested this activity? Have they been involved in the planning?',
                'attr'      => array(
                    'data-label-col'    => $uniform_col_fullwidth,
                    'data-group-col'    => $uniform_col_fullwidth
                )

            ))
            ->add('studentInvolvement', TypesTextareaType::class, array(
                'label'     => 'Will the students be involved in the delivery of the activity?',
                'help'      => 'Will students be participating only or will they also be volunteering to run this activity?',
                'attr'      => array(
                    'data-label-col'    => $uniform_col_fullwidth,
                    'data-group-col'    => $uniform_col_fullwidth
                )
            ))
            ->add('communityContribution', TypesChoiceType::class, array(
                'choices'   => $options['communityContrib'],
                'expanded'  => true,
                'multiple'  => true,
                'required'  => false,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('studySuccess', TypesChoiceType::class, array(
                'label'     => 'Study and Work Success',
                'choices'   => $options['studySuccess'],
                'expanded'  => true,
                'multiple'  => true,
                'required'  => false,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('lifestyles', TypesChoiceType::class, array(
                'label'     => 'Healthy & Happy Lifestyles',
                'choices'   => $options['lifestyles'],
                'expanded'  => true,
                'multiple'  => true,
                'required'  => false,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('totalStudents', TypesIntegerType::class, array(
                'label'     => 'Total number of students:',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label_smaller,
                    'data-group-col'    => $uniform_col_element_smaller
                )
            ))
            ->add('studentsUnder16', TypesIntegerType::class, array(
                'label'     => 'Aged under 16 years:',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label_smaller,
                    'data-group-col'    => $uniform_col_element_smaller
                )
            ))
            ->add('students16To18', TypesIntegerType::class, array(
                'label'     => 'Aged 16 to 18 years:',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label_smaller,
                    'data-group-col'    => $uniform_col_element_smaller
                )
            ))
            ->add('studentsOver18', TypesIntegerType::class, array(
                'label'     => 'Aged over 18 years:',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label_smaller,
                    'data-group-col'    => $uniform_col_element_smaller
                )
            ))
            ->add('alsStudents', TypesIntegerType::class, array(
                'label'     => 'Students accessing Additional Learning Support',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => 'col-xs-12 col-sm-6 col-md-5 col-lg-4',
                    'data-group-col'    => $uniform_col_element_smaller
                )
            ))
            ->add('availableAccrossCollege', TypesChoiceType::class, array(
                'label'     => 'Is the activity only open to students from across college?',
                'choices'   => array(
                    'Yes'   => true,
                    'No'    => false
                ),
                'expanded'  => true,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_fullwidth,
                    'data-group-col'    => $uniform_col_element_offset
                )
            ))
            ->add('availableOutside', TypesChoiceType::class, array(
                'label'     => 'Is the activity open to students from outside college?',
                'choices'   => array(
                    'Yes'   => true,
                    'No'    => false
                ),
                'expanded'  => true,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_fullwidth,
                    'data-group-col'    => $uniform_col_element_offset
                )
            ))
            ->add('availableOnlyDepartment', TypesChoiceType::class, array(
                'label'     => 'Is the activity only open to students from a specific department?',
                'choices'   => array(
                    'Yes'   => true,
                    'No'    => false
                ),
                'expanded'  => true,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_fullwidth,
                    'data-group-col'    => $uniform_col_element_offset
                )
            ))
            ->add('availableDepartment', TypesTextType::class, array(
                'label'     => 'If yes, please specify:',
                'required'  => false,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                )
            ))
            ->add('availableOnlyCurriculumArea', TypesChoiceType::class, array(
                'label'     => 'Is the activity only open to students from a specific curriculum area?',
                'choices'   => array(
                    'Yes'   => true,
                    'No'    => false
                ),
                'expanded'  => true,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_fullwidth,
                    'data-group-col'    => $uniform_col_element_offset
                )
            ))
            ->add('availableCurriculumArea', TypesTextType::class, array(
                'label'     => 'If yes, please specify:',
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                ),
                'required'  => false,
            ))
            ->add('availableOnlyCourse', TypesChoiceType::class, array(
                'label'     => 'Is the activity open to students from a specific course?',
                'choices'   => array(
                    'Yes'   => true,
                    'No'    => false
                ),
                'expanded'  => true,
                'attr'      => array(
                    'data-label-col'    => $uniform_col_fullwidth,
                    'data-group-col'    => $uniform_col_element_offset
                )
            ))
            ->add('availableCourse', TypesTextType::class, array(
                'label'     => 'If yes, please specify:',
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                ),
                'required'  => false,
            ))
            ->add('availableOutsideDetail', TypesTextType::class, array(
                'label'     => 'If yes, please specify:',
                'attr'      => array(
                    'data-label-col'    => $uniform_col_label,
                    'data-group-col'    => $uniform_col_element
                ),
                'required'  => false,
            ))
            ->add('behaviouralEngagement', TypesCheckboxType::class, array(
                'required'  => false,
            ))
            ->add('emotionalEngagement', TypesCheckboxType::class, array(
                'required'  => false,
            ))
            ->add('cognitiveEngagement', TypesCheckboxType::class, array(
                'required'  => false,
            ))
            ->add('noStudentsBronze', TypesIntegerType::class, array(
                'label'     => 'Bronze',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => 'col-xs-5 col-sm-2 col-md-2 col-lg-1',
                    'data-group-col'    => 'col-xs-6 col-sm-2 col-md-2 col-lg-2'
                )
            ))
            ->add('noStudentsSilver', TypesIntegerType::class, array(
                'label'     => 'Silver',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => 'col-xs-5 col-sm-2 col-md-2 col-lg-1',
                    'data-group-col'    => 'col-xs-6 col-sm-2 col-md-2 col-lg-2'
                )
            ))
            ->add('noStudentsGold', TypesIntegerType::class, array(
                'label'     => 'Gold',
                'scale'     => 0,
                'attr'      => array(
                    'data-label-col'    => 'col-xs-5 col-sm-2 col-md-2 col-lg-1',
                    'data-group-col'    => 'col-xs-6 col-sm-2 col-md-2 col-lg-2'
                )
            ))
            ;
  }

 /**
  * {@inheritdoc}
  */
  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults(array(
        'data_class'    => 'AppBundleEntityEnrichmentApplication',
        'activityTypes' => null,
        'communityContrib'  => null,
        'studySuccess'  => null,
        'lifestyles'    => null,
    ));
  }

 /**
  * {@inheritdoc}
  */
  public function getBlockPrefix()
  {
    return 'appbundle_enrichmentapplication'
  }
}

Среда

  • CentOS 7
  • PHP 7.1
  • MariaDB 10

Всего 2 ответа


Я вхожу в activities в вашем домене, поэтому не стесняйтесь принимать его в соответствии с вашими потребностями.

EnrichmentApplication

use DoctrineCommonCollectionsArrayCollection;
use DoctrineCommonCollectionsCollection;
use DoctrineORMMapping as ORM;

/**
* @ORMEntity
*/
class EnrichmentApplication
{
   //...

   /**
    * @var Collection
    *
    * @ORMOneToMany(targetEntity="EnrichmentActivity", mappedBy="application", cascade={"persist"})
    */
    private $activities;

    public function __construct()
    {
        $this->activities = new ArrayCollection();
    }

  /**
   * @return Collection
   */
  public function getActivities()
  {
      return $this->activities;
  }

  public function addActivity(EnrichmentActivity $activity)
  {
      if (!$this->getActivities()->contains($activity))
      {
          $this->getActivities()->add($activity);
          $activity->setApplication($this);
      }
  }

  public function removeActivity(EnrichmentActivity $activity)
  {
      if ($this->getActivities()->contains($activity))
      {
          $this->getActivities()->removeElement($activity);
          $activity->setApplication(null);
      }
  }
}

EnrichmentActivity

use DoctrineCommonCollectionsArrayCollection;
use DoctrineCommonCollectionsCollection;
use DoctrineORMMapping as ORM;

/**
* @ORMEntity
*/
class EnrichmentActivity
{
    //...

    /**
     * @var EnrichmentApplication|null
     * @ORMManyToOne(targetEntity="EnrichmentApplication", inversedBy="activities")
     */
    private $application;

    /**
     * @param EnrichmentApplication|null $application
     */
    public function setApplication(?EnrichmentApplication $application)
    {
        $this->application = $application;
    }

    /**
     * @return EnrichmentApplication|null
     */
    public function getApplication()
    {
        return $this->application;
    }
}

EnrichmentApplicationType

use AppBundleEntityEnrichmentApplication;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreType as Types;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;

class EnrichmentApplicationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('activities', TypesCollectionType::class, array(
                'entry_type'    => EnrichmentActivityType::class,
                'allow_add'     => true,
                'by_reference'  => false
            ))
         ;

  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults(array(
        'data_class'        => EnrichmentApplication::class,
        'activityTypes'     => null,
        'communityContrib'  => null,
        'studySuccess'      => null,
        'lifestyles'        => null,
    ));
  }
}

После большой отладки это оказалось свойство empty_data поля деятельности, которое переписывало данные, поступающие из формы. Я удалил это, и теперь он отлично работает

namespace AppBundleForm;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreType as Types;

class EnrichmentApplicationType extends AbstractType
{
  /**
   * {@inheritdoc}
   */
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
        //...
        ->add('activities', TypesCollectionType::class, array(
            'entry_type'    => EnrichmentActivityType::class,
            'allow_add'     => true,
            'by_reference'  => false,
            'entry_options' => array(
                'empty_data'    => new AppBundleEntityEnrichmentActivity(), //<<this option
            ),
            'attr'      => array(
                'data-label-col'    => 'col-sm-12',
                'data-group-col'    => 'col-sm-12'
            )
        ))
      //...
        ;
  }

Есть идеи?

10000