【问题标题】:Zend3 Form Filter - ParamConverter could not generate valid formZend3 表单过滤器 - ParamConverter 无法生成有效表单
【发布时间】:2018-11-20 13:59:26
【问题描述】:

我真的对我的表单过滤器感到困惑。

我的测试项目包含 2 个模型。

class Category extends AbstractEntity 
{
    use Nameable; // just property name and getter and setter

    /**
     * @var boolean
     * @ORM\Column(name="issue", type="boolean")
     */
    private $issue;

    /**
     * @var Collection|ArrayCollection|Entry[]
     *
     * @ORM\OneToMany(targetEntity="CashJournal\Model\Entry", mappedBy="category", fetch="EAGER", orphanRemoval=true, cascade={"persist", "remove"})
     */
    private $entries;
}

条目

class Entry extends AbstractEntity
{
    use Nameable;

    /**
     * @var null|float
     *
     * @ORM\Column(name="amount", type="decimal")
     */
    private $amount;

    /**
     * @var null|Category
     *
     * @ORM\ManyToOne(targetEntity="CashJournal\Model\Category", inversedBy="entries", fetch="EAGER")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)
     */
    protected $category;

    /**
     * @var null|DateTime
     *
     * @ORM\Column(name="date_of_entry", type="datetime")
     */
    private $dateOfEntry;
}

如果有人需要 AbstractEntity

abstract class AbstractEntity implements EntityInterface
{
    /**
     * @var int
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;
}

每个类别可以有多个条目。我正在使用Doctrine 来处理这种关系。这很好用。

我有一个基于这个 FieldSet 的表单:

$this->add([
    'name' => 'id',
    'type' => Hidden::class
]);

$this->add([
    'name' => 'name',
    'type' => Text::class,
    'options' => [
        'label' => 'Name'
    ]
]);

$this->add([
    'name' => 'amount',
    'type' => Number::class,
    'options' => [
        'label' => 'Summe'
    ]
]);

$this->add([
    'name' => 'date_of_entry',
    'type' => Date::class,
    'options' => [
        'label' => 'Datum'
    ]
]);

$this->add([
    'name' => 'category',
    'type' => ObjectSelect::class,
    'options' => [
        'target_class' => Category::class,
    ]
]);

所以我的表单会显示一个包含我的类别的下拉列表。好的。

要为我的条目实体加载类别,我使用过滤器。

$this->add([
    'name' => 'category',
    'required' => true,
    'filters' => [
        [
            'name' => Callback::class,
            'options' => [
                'callback' => [$this, 'loadCategory']
            ]
        ]
    ]
]);

还有回调:

public function loadCategory(string $categoryId)
{
    return $this->mapper->find($categoryId);
}

映射器可以很好地加载类别。伟大的。但表格无效,因为:

CashJournal\Model\Category 类的对象无法转换为 int

好的,所以我要删除过滤器,但现在它无法将属性设置为条目实体,因为设置器需要Category。表单错误说:

输入的步骤无效

在 Symfony 中,我可以创建一个ParamConverter,它将category_id 转换为一个有效的Category 实体。

问题 我如何将过滤器用作我的 ParamConver?

更新 此外,当我将 category_id 转换为 int 时,我会从表单中得到错误。

更新 2 我将我的 FieldSet 更改为:

class EntryFieldSet extends Fieldset implements ObjectManagerAwareInterface
{
    use ObjectManagerTrait;

    /**
     * {@inheritDoc}
     */
    public function init()
    {
        $this->add([
            'name' => 'id',
            'type' => Hidden::class
        ]);

        $this->add([
            'name' => 'name',
            'type' => Text::class,
            'options' => [
                'label' => 'Name'
            ]
        ]);

        $this->add([
            'name' => 'amount',
            'type' => Number::class,
            'options' => [
                'label' => 'Summe'
            ]
        ]);

        $this->add([
            'name' => 'date_of_entry',
            'type' => Date::class,
            'options' => [
                'label' => 'Datum'
            ]
        ]);

        $this->add([
            'name' => 'category',
            'required' => false,
            'type' => ObjectSelect::class,
            'options' => [
                'target_class' => Category::class,
                'object_manager' => $this->getObjectManager(),
                'property'       => 'id',
                'display_empty_item' => true,
                'empty_item_label'   => '---',
                'label_generator' => function ($targetEntity) {
                    return $targetEntity->getName();
                },
            ]
        ]);

        parent::init();
    }
} 

但这将退出并显示错误消息:

Entry::setDateOfEntry() 必须是 DateTime 的实例,给定字符串

【问题讨论】:

    标签: php zend-framework zend-form zend-framework3


    【解决方案1】:

    您是否查看过ObjectSelect 的文档?您似乎缺少一些选项,即要使用的水合器 (EntityManager) 和标识属性 (id)。 Have a look here

    例子:

    $this->add([
        'type'    => ObjectSelect::class,
        'name'    => 'category',                           // Name of property, 'category' in your question
        'options' => [
            'object_manager' => $this->getObjectManager(), // Make sure you provided the EntityManager to this Fieldset/Form
            'target_class'   => Category::class,           // Entity to target
            'property'       => 'id',                      // Identifying property
        ],
    ]);
    

    要验证所选元素,请添加您的 InputFilter:

    $this->add([
        'name'     => 'category',
        'required' => true,
    ]);
    

    不再需要 InputFilter。类别已经存在,因此之前已经过验证。因此,您应该能够选择它。

    如果您有特殊要求,您只需要额外的过滤器/验证器,例如:“一个类别只能在条目中使用一次”,因此您需要使用 NoObjectExists 验证器。但这里似乎并非如此。


    根据评论和过去的问题更新

    我认为你在尝试做的事情中把很多事情复杂化了。您似乎只想在客户端加载表单之前简单地填充它。在接收到 POST(来自客户端)时,您希望将接收到的数据放入表单中,对其进行验证并存储它。正确的?

    基于此,请为我的一个项目中的用户找到一个完整的控制器。希望对您有所帮助。提供它是因为更新偏离了您原来的问题,这可能会对您有所帮助。

    我已经删除了一些额外的检查和错误抛出,但在其他方面是完整的工作方式。

    (请注意,我使用的是我自己的抽象控制器,请确保将其替换为您自己的和/或重新创建并匹配要求)

    我还在整个代码中放置了额外的 cmets 来帮助你

    <?php
    
    namespace User\Controller\User;
    
    use Doctrine\Common\Persistence\ObjectManager;
    use Doctrine\ORM\ORMException;
    use Exception;
    use Keet\Mvc\Controller\AbstractDoctrineActionController;
    use User\Entity\User;
    use User\Form\UserForm;
    use Zend\Http\Request;
    use Zend\Http\Response;
    
    class EditController extends AbstractDoctrineActionController
    {
        /**
         * @var UserForm
         */
        protected $userEditForm; // Provide this
    
        public function __construct(ObjectManager $objectManager, UserForm $userEditForm)
        {
            parent::__construct($objectManager); // Require this in this class or your own abstract class
    
            $this->setUserEditForm($userEditForm);
        }
    
        /**
         * @return array|Response
         * @throws ORMException|Exception
         */
        public function editAction()
        {
            $id = $this->params()->fromRoute('id', null);
            // check if id set -> else error/redirect
    
            /** @var User $entity */
            $entity = $this->getObjectManager()->getRepository(User::class)->find($id);
            // check if entity -> else error/redirect
    
    
            /** @var UserForm $form */
            $form = $this->getUserEditForm();  // GET THE FORM
            $form->bind($entity);              // Bind the Entity (object) on the Form
    
            // Only go into the belof if() on POST, else return Form. Above the data is set on the Form, so good to go (pre-filled with existing data)
    
            /** @var Request $request */
            $request = $this->getRequest();
            if ($request->isPost()) {
                $form->setData($request->getPost());       // Set received POST data on Form
    
                if ($form->isValid()) {                    // Validates Form. This also updates the Entity (object) with the received POST data
                    /** @var User $user */
                    $user = $form->getObject();            // Gets updated Entity (User object)
    
                    $this->getObjectManager()->persist($user); // Persist it
    
                    try {
                        $this->getObjectManager()->flush();    // Store in DB
                    } catch (Exception $e) {
    
                        throw new Exception('Could not save. Error was thrown, details: ', $e->getMessage());
                    }
    
                    return $this->redirectToRoute('users/view', ['id' => $user->getId()]);
                }
            }
    
            // Returns the Form with bound Entity (object). 
            // Print magically in view with `<?= $this->form($form) ?>` (prints whole Form!!!)
    
            return [
                'form' => $form,
            ];
        }
    
        /**
         * @return UserForm
         */
        public function getUserEditForm() : UserForm
        {
            return $this->userEditForm;
        }
    
        /**
         * @param UserForm $userEditForm
         *
         * @return EditController
         */
        public function setUserEditForm(UserForm $userEditForm) : EditController
        {
            $this->userEditForm = $userEditForm;
    
            return $this;
        }
    }
    

    希望对您有所帮助...

    【讨论】:

    • 嗨@rkeet,感谢您的大力帮助。是的,我阅读了文档并以这种方式构建,但后来我收到了 Entry::setDateOfEntry() must be an instance of DateTime, string given 的消息,所以我决定将参数转换为对象
    • 我更新了我的问题并添加了带有更改的 FieldSet
    • Update2 涉及不同的问题。接收到的值应该是实例Date,但您将它传递给string。你说你在用 Doctrine,你为什么要手动做这些?
    • 认为您可能没有在您的 Fieldset 上设置正确的 Hydrator。所以,EntryFieldset 应该有 DoctrineObject hydrator 集。当您然后设置客户端的 POST 数据 ($form-&gt;setDate($this-&gt;getRequest()-&gt;getPost())) 时,该 hydrator 会将 id (34) 匹配到正确的类别。
    • 很高兴我能帮上忙 ;-) 请记住,一旦你掌握了窍门,它通常会变得像这样微不足道 ;-) 如果你不得不手动分配值在基于发布数据等的完整实体对象上,然后,通常情况下,有些东西会被遗忘或稍微偏离;-) Gl hf
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多