通过 Symfony 的表单生成器将新实体保存在按字段索引的 One-To-Many 关联中

Saving new entities in a One-To-Many association indexed by field through Symfony's form builder

我花了很长时间才把标题弄对,我还没有写问题:/

这里是: 我的菜单是从实体加载的。 为了允许用户将菜单翻译成多种语言,我创建了一个 Menu 实体和一个 LocalizedMenu 实体,通过 ManyToOne 关联与 Menu 关联。

this short guide 之后,我索引了与 LocalizedMenu->locale 字段的关联。这确保数据库中每个区域设置只有一个 LocalizedMenu,并且该原则会覆盖现有的区域设置。

这是它的样子:

/**
 * @ORM\Entity(repositoryClass="App\Repository\MenuRepository")
 */
class Menu
{
    /**
     * @Groups({"menu"})
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    // ...

    /**
     * References translated menus.
     * @Groups({"localized_menus"})
     *
     * @ORM\OneToMany(
     *     targetEntity="LocalizedMenu"
     *     ,mappedBy="parentMenu"
     *     ,indexBy="locale"
     *     ,cascade={"persist", "remove"}
     * )
     * @ORM\OrderBy({"locale" = "ASC"})
     */
    private $localizedMenus;

    // ...

    public function getLocalizedMenu($locale) {
        if (!isset($this->localizedMenus[$locale])) {
            return new LocalizedMenu($locale, $this);
        }
        return $this->localizedMenus[$locale];
    }

    public function addLocalizedMenu($localizedMenu): self
    {
        $this->localizedMenus[$localizedMenu->getLocale()] = $localizedMenu;

        return $this;
    }
}

LocalizedMenu 是一个包含用户翻译菜单字段的实体:

/**
 * @ORM\Entity(repositoryClass="App\Repository\LocalizedMenuRepository")
 */
class LocalizedMenu
{
    public function __construct($locale, $menu) {
        $this->locale = $locale;
        $this->parentMenu = $menu;
        $this->parentMenu->addLocalizedMenu($this);
    }

    // region FIELDS
    /**
     * @Groups({"localized_menu", "localized_menus"})
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @Groups({"localized_menu", "localized_menus"})
     * @var $locale string
     * @ORM\Column(
     *     type                 = "string"
     *     ,unique              = true
     * )
     */
    private $locale = "";
    /**
     * @Groups({"localized_menu", "localized_menus"})
     * @ORM\Column(
     *     type                 = "string",
     *     length               = 75
     * )
     */
    private $title = "";
    /**
     * @Groups({"localized_menu", "localized_menus"})
     * @var $description string Extra description for this menu item
     * @ORM\Column(
     *     type                 = "text",
     *     name                 = "description"
     * )
     */
    private $description = "";
    /**
     * @Groups({"localized_menu", "localized_menus"})
     * @ORM\Column(
     *     type                 = "datetime",
     *     name                 = "creation_date"
     * )
     * @Assert\DateTime()
     */
    private $creationDate;
    /**
     * @Groups({"localized_menu", "localized_menus"})
     * @ORM\Column(
     *     type                 = "datetime",
     *     name                 = "edit_date"
     * )
     * @Assert\DateTime()
     */
    private $editDate;

    /**
     * @Groups({"localized_menu", "localized_menus"})
     * @ORM\Column(type="text")
     * @Assert\NotBlank(
     *     message = "Een menu item is een pagina die inhoud nodig heeft, vergeet dit niet"
     * )
     */
    private $content = "";

    /**
     * @var $parentMenu Menu Parent menu for this localized menu
     *
     * @Groups({"localized_menu", "localized_parent_menu"})
     * @ORM\ManyToOne(
     *     targetEntity="Menu",
     *     inversedBy="localizedMenus"
     * )
     */
    private $parentMenu;
}

为了将这一切呈现给用户进行编辑,我创建了一个 MenyType 表单:

    $builder
        ->add('localizedMenus', CollectionType::class, array(
            'entry_type' => LocalizedMenuType::class,
            "entry_options" => [
                "choice_locale" => $options["choice_locale"]
            ],
            'allow_add' => true,
            'allow_delete' => true,
            'required' => false
        ))

还有一个LocalizedMenuType表格:

    $builder
        ->add('title', TextType::class, array(
            'label'             => 'Titel',
            'trim'              => true
        ))
        ->add('description', TextareaType::class, array(
            'label'             => 'Omschrijving',
            'trim'              => true
        ))
        ->add('content', TextareaType::class, array(
            'label'             => 'Inhoud',
            'trim'              => true,
            'attr'              => array('class' => 'tinymce'),
            'data'              => " "
        ))
        ->add('locale', LocaleType::class, array(
            "choice_translation_locale" => $options["choice_locale"]
        ))
    ;

我"think"这个逻辑是正确的,但是在使用javascript创建新的LocalizedMenu表单后,我得到这个错误:

Too few arguments to function App\Entity\LocalizedMenu::__construct(), 0 passed in /Users/robbievercammen/Projects/web/base/vendor/symfony/form/Extension/Core/Type/FormType.php on line 136 and exactly 2 expected

我怎样才能让我的表单按照我的逻辑优雅地工作?

编辑 - 真正的问题

此处的错误消息并不是真正的错误。正如我之前所说,如果我删除构造函数参数,它会将记录保存到数据库中。即让学说使用关联将新的 LocalizedMenu 记录关联到 Menu 记录。这是它在数据库中的样子:

Menu

|id| //...
| 7| //...

LocalizedMenu

| id | locale | title   | description | creation_date       | edit_date           
| content | parent_menu_id |
----
|  4 | nl     | Contact | Contact     | 2019-02-21 14:02:47 | 2019-02-21 14:02:47 |
 Contact |           NULL |

问题是 LocalizedMenu -> parent_menu_id 为 NULL。 出于某种原因,我的设置不会为 parent 菜单生成 ID。 下次从数据库中获取菜单时,$menu->getLocalizedMenus() returns 一个空数组,因为它们没有正确关联。

在我提到的 guide 之后,这似乎是我告诉学说按 $localizedMenu -> locale

索引的唯一方法

您的 LocalizedMenu 构造函数需要两个参数 - $locale$menu。当 Symfony 为你新提交的数据实例化新的 LocalizedMenu 实例时,它会直接 new LocalizedMenu() 来填充它的数据。

如果您需要自定义如何为表单中的 new/dynamic 内容创建对象(例如,当您有构造函数参数时),您必须在 [=17] 上设置 empty_data 选项=] class.

有关详细信息,请参阅 https://symfony.com/doc/current/form/use_empty_data.html

您的 LocalizedMenu 构造函数参数之一是菜单实例。此菜单实例需要作为必需选项传递给您的 LocalizedMenuType

class LocalizedMenuType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setRequired('menu');
        $resolver->setAllowedTypes('menu', Menu::class);
        $resolver->setDefault('empty_data', function (Options $options) {
            return new LocalizedMenu($options['choice_locale'], $options['menu']);
        });
    }
}