DataMapper 未在 'inherit_data' => true 的子 FormType 上调用 (Symfony)

DataMapper not called on child FormType with 'inherit_data' => true (Symfony)

总是这样吗?我搜索了网络并阅读了文档,但我 none 更明智。我确实读到当 inherit_data 为真时无法应用 DataTransformers,这似乎也很遗憾。 (可能是什么原因?)

我有一个 FormType 'PermissionType' 映射 'Permission'Permission 与其他一些实体一样,具有 creation/lastModification DateTime。阅读 How to Reduce Code Duplication with "inherit_data" 后,我自然而然地继续实施新发现的好建议并创建了 TimeTrackedType.

PermissionType 的此子表单显示两个 DateTimeType 字段,并将 inherit_data 设置为 true。它们已正确呈现给浏览器,但它们仍然是空的,但我尝试向其中输入数据。我首先添加了一个 DataMapper,但没有调用 TimeTrackedType 之一。然而,其父 PermissionType 的 DataMapper 是,它本身就是一个子表单,这似乎是我可以更改 TimeTrackedType 的 DateTimeType 字段值的唯一地方。

我希望是我在这里做错了什么,因为在子表单中创建输入但必须在父表单中映射到它们似乎是错误的 class。谁能详细说明一下?非常感谢任何指点。

这是实体,第一个用户:

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
 */
class User implements AdvancedUserInterface, \Serializable {
    use HasSingleId, TimeTrackedEntityTrait, EntityCreatorTrait;
    //                 ^^^ This trait has two DateTime fields and that's it.
    // (...)
    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\Permission", mappedBy="user")
     * @Assert\Valid()
     */
    private $permissions;
    // (...)
}

然后权限:

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\PermissionRepository")
 */
class Permission {
use TimeTrackedEntityTrait, EntityCreatorTrait;

/**
 * @var User
 * @ORM\Id
 * @ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="permissions")
 */
private $user;

/**
 * @var array
 * @ORM\Id
 * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Role", inversedBy="permissions")
 */
private $role;
// (...getters and setters...)
}

最后class角色:

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\RoleRepository")
 */
class Role implements RoleInterface {
use HasSingleId, TimeTrackedEntityTrait, EntityCreatorTrait;

/**
 * @var type string
 * @ORM\Column(type="string", nullable=false, unique=true);
 */
private $name;

/**
 * @var type ArrayCollection
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Permission", mappedBy="role")
 * @Assert\Valid()
 */
private $permissions;
}

现在是 FormTypes:

class UserType extends AbstractType {

/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder->add('username', TextType::class, [ 'attr' => [ 'size' => 10 ] ] )
            ->add('password', RepeatedType::class, [
                'type' => PasswordType::class, 
                'attr' => ['size' => 10 ], 
                'first_options' => [ 'label' => 'Password' ], 
                'second_options' => [ 'label' => 'Confirm' ] ]);
    $entity = $builder->getData();
    $admin = $entity->hasRole('ROLE_ADMIN');
    if($admin) {
        $builder->add('id', TextType::class, [ 'attr' => [ 'size' => 4 ] ]);
        $builder->add('isEnabled', CheckboxType::class, [ 'required' => false ]);
    }

    $builder->add('permissions', CollectionType::class, [
        'data_class' => 'Doctrine\ORM\PersistentCollection',
        'mapped'=>true, 
        'prototype'=>true,
        'allow_add'=>true,
        'allow_delete'=>true,
        'entry_type' => PermissionType::class]);
    $builder->add('email', EmailType::class);
}

/**
 * @param OptionsResolver $resolver
 */
public function configureOptions(OptionsResolver $resolver) {
    $resolver->setDefaults([
        'required' => true,
        'mapped' => true,
        'data_class' => 'AppBundle\Entity\User'
    ]);
}
}

...和...

class PermissionType extends AbstractType implements DataMapperInterface {

public function mapDataToForms($permission, $forms) {
    $forms = iterator_to_array($forms);
    if($permission instanceof Permission && $permission) {
        $forms['role']->setData($permission->getRole()->getName());
        // These two statements get the job done, but not as was intended.
        $forms['created']->setData($permission->getCreated());
        $forms['lastModified']->setData($permission->getLastModified());
    }

}

public function mapFormsToData($forms, &$permission) {
    $forms = iterator_to_array($forms);
    if($permission instanceof Permission) {
        $permission->setCreated($forms['created']->getData());
        $permission->setLastModified($forms['lastModified']->getData());
    }
}


public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder->setDataMapper($this);
    $builder->add('role', TextType::class, [ 'mapped' => true ]);
    $builder->add('timing', TimeTrackedEntityType::class, [ 
    'data_class' => 'AppBundle\Entity\Permission', 
    'inherit_data' => true, 'mapped'=>true ]);
}

public function configureOptions(OptionsResolver $resolver) {
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\Permission',
        'mapped'=>true,
        'compound'=>true,
    ));
}

public function getParent() {
    return FormType::class;
}

public function getName() { return 'PermissionType'; }

}

...最后:

class TimeTrackedEntityType extends AbstractType implements DataMapperInterface {
// This is the method that doesn't get called
public function mapDataToForms($permission, $forms) {
    $forms = iterator_to_array($forms);
    $forms['created']->setData($permission->getCreated()->format("d/m/Y H:i:s"));
$forms['lastModified']->setData($permission->getLastModified()->format("d/m/Y H:i:s"));
}
public function mapFormsToData($forms, &$data) {
    $forms = iterator_to_array($forms);
    $data->setCreated($forms['created']->getData());
    $data->setLastModified($forms['lastModified']->getData());
}
public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->setDataMapper($this);
    $builder->add('created', DateTimeType::class, [
         'format' => 'd/M/Y H:i:s', 
         'input' => 'datetime', 
         'widget' => 'single_text',
         'attr'=>['size'=>14, 'class'=>'right'],
         'mapped' => true ]);
    $builder->add('lastModified', DateTimeType::class, [ 
        'format' => 'd/M/Y H:i:s',
        'input' => 'datetime', 
        'widget' => 'single_text', 
        'attr'=>['size'=>14, 'class'=>'right'], 
        'mapped' => true ]);
}
public function configureOptions(OptionsResolver $resolver) {
    $resolver->setDefaults(array(
        'mapped'=>true,
        'compound'=>true,
        'inherit_data' => true,
    ));
}

public function getName() { return 'TimeTrackedEntityType'; }
}

这篇文章根本没有使用DataMapper

使用特征意味着属性像往常一样是 PermissionEntity 对象的一部分,因此它们不是像相应的 PermissionType 表单中的其他字段那样保存这些属性,而是嵌套在您的子表单类型 TimeTrackedEntityType.

然后你只需要将 inherit_data 设置为 true 和正确的 data_class 选项,如果你在其他地方需要这个子表单,这就是你在 [=13] 中所做的=], 由于子表单获取其父表单的数据,因此不需要 DataMapper.

如果你想使用一个,它应该只与父窗体一起使用,而不是它的子窗体,它会按预期被忽略。