Symfony 4, Object и SubObjects, отсутствующие внешние ключи - PullRequest
0 голосов
/ 02 мая 2019

Я не могу найти хитрость, чтобы получить следующее.

Скажем, у меня есть два Entity: Main и Minor, Main один-ко-многим Minor,mainId - поле внешнего ключа.

Я хотел бы иметь (Незначительную) форму для создания объекта Minor, чтобы пользователи могли выбирать его объект Main из списка уже доступных Main объектов, и (Основной)Форма для создания Main объекта и, возможно, множества различных Minor (под) объектов одновременно.

Проблема в том, что в последнем случае я не могу сохранить внешний ключ.

Для младшей формы я определяю:

$builder ->add('minorTitle')
         ->add('Main', EntityType::class, array(
               'class' => Main::class,
               'choice_label' => 'mainTtile',
               'label' => 'main'))

have 'data_class' => Minor::class, и работает нормально.

Для основной формы я попытался:

$builder
        ->add('mainTitle')
        ->add('Minors', CollectionType::class, array(
              'entry_type' => MinorType::class,
              'allow_add' => true,
              'label' => 'Minor'
              ))   

              'data_class' => Main::class`

Таким образом, малая форма действительно встроена как подчиненная форма в основную форму.Чтобы добавить больше подчиненных форм, у меня есть несколько JS, как предложено в CollectionType .Чтобы не отображать поле Main в второстепенных подчиненных формах, я немного взломал prototype чем-то вроде:

newWidget = newWidget.replace(newWidget.match(/\B<div class="form-group"><label class="required" for="main_Minors___name_Main">Main<\/label><select id="main_Minors___name_Main" name="main\[Minors\]\[__name__\]\[Main\]" class="form-control">.*<\/select>\B/g),""); 

Пользователь может создать главный объект и множество второстепенныхтоже, но id первого не сохраняется как внешние ключи последних.Я пытался исправить вещи в главном контроллере с помощью чего-то вроде (или вариантов):

public function new(Request $request): Response {
    $em = $this->getDoctrine()->getManager();
    $main = new Main();
    $form = $this->createForm(MainType::class, $main);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $postData = $request->request->get('main');

        $minors = array();
        foreach($postData['Minors'] as $key => $obj){
            $minors[$key]= new Minor();
            $minors[$key]->setMain($main);
            $minors[$key]->setMinorTitle($obj['minorTitle']);
            $em->persist($minors[$key]);
        }

        $em->persist($main);
        $em->flush();
   }

, но либо он не работает, либо дважды сохраняет один и тот же подобъект (только один раз с правильным внешним ключом),

(Возможно, я мог бы исправить два разных класса MinorType, но я бы хотел этого избежать)

Спасибо

1 Ответ

2 голосов
/ 03 мая 2019

Всего несколько подсказок.

  1. для типов ваших типов должна быть указана опция data_class для соответствующего класса.
  2. имена полей вашей формы должны соответствовать имени свойства объекта.(и по умолчанию все имена свойств находятся в camelCase, в нижнем регистре первый символ ... в symfony)
  3. после 1. и 2. вы получаете правильные объекты, просто вызывая $form->getData() (или, как вы могли бы иметьзаметил, что когда вы даете createForm вызову сущность, она будет изменена компонентом формы - это не всегда может быть предназначено. рассмотрите объекты передачи данных (DTO), когда это не предназначено.)
  4. вашВ поле CollectionType должна быть опция byReference, установленная на false, чтобы сеттеры могли использовать поле сбора (в данном случае Main::setMinors).
  5. обычно сторона «один ко многим» (то есть класс Main) может сойти с рук:

    public function setMinors(array $minors) {
        foreach($minors as $minor) {
            $minor->setMain($this); // set the main, just to be safe
        }
        $this->minors = $minors;    // set the property Main.minors
    }
    

    , но вы должны не сделать это в setMain и наоборот (это тоже не так тривиально. Альтернативы setMinors являются addMinor и removeMinor, есть преимущества и затраты для любого решения, но когда дело доходит до форм, они вполне эквивалентны, я бы сказал,)

  6. на Main, если вы установите опцию cascade={"PERSIST"} на OneToMany (то есть @ORM\OneToMany(targetEntity="App\Entity\Minor", cascade={"PERSIST"})), вам не нужно явно вызывать постоянный вызов для всех несовершеннолетних, они будут сохранены, как только вы сохраните (и очистите) объект / экземпляр Main.

  7. Наконец, либодобавьте параметр к второстепенному типу, чтобы пропустить поле формы main, или добавьте новый тип формы MainMinorType (или любой другой), который не имеет поля формы main (расширьте MinorType и удалите main поле).Это устраняет необходимость в грязных взломах; o)

Однако в целом, если вы не установите несовершеннолетних на основной в двунаправленном отношении, результаты не будут четко определены.(просто предположим, что A имеет ссылку на B, но B не имеет ссылки на A, но должна иметь, потому что это двунаправленное отношение. Это может означать, что связь должна быть установлена.Это также может означать, что ссылка должна быть удалена. Таким образом, чтобы быть в безопасности и четко сообщать, что предназначено, установите обе стороны!) И, в конечном счете, это может быть причиной того, что она не работает как задумано.

обновление

Для уточнения по пункту 7. Ваш MinorType может быть изменен следующим образом:

class MinorType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        // ... other fields before
        if(empty($options['remove_main_field'])) {
            // field is the same, but isn't added always, due to 'if'
            $builder->add('main', EntityType::class, [
               'class' => Main::class,
               'choice_label' => 'mainTtile',
               'label' => 'main'
            ]);
        }
        // ... rest of form
    }

    public function configureOptions(OptionsResolver $resolver) {
        // maybe parent call ...
        $resolver->setDefaults([
            // your other defaults
            'remove_main_field' => false, // add new option, self-explanatory
        ]);
    }
}

в вашем MainType у вас было следующее, чтобыкоторый я добавил новую опцию

       ->add('Minor', EntityType::class, array(
               'class' => Minor::class,
               'remove_main_field' => true, // <-- this is new
       ))

, теперь это удалит главное поле из ваших младших форм, когда оно встроено в вашу основную форму.однако по умолчанию не удаляется основное поле, поэтому при самостоятельном редактировании вспомогательного поля основное поле будет отображаться, как это было раньше ... если только я не допустил ошибку в своем коде; o)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...