Zend3 表单过滤器 - ParamConverter 无法生成有效表单
Zend3 Form Filter - ParamConverter could not generate valid form
我对我的表单过滤器感到很困惑。
我的测试项目包含 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);
}
映射器可以很好地加载类别。伟大的。但表格无效,因为:
Object of class CashJournal\Model\Category could not be converted to int
好的,所以我要删除过滤器,但现在无法将属性设置为条目实体,因为 setter 需要 Category
。表单错误显示:
The input is not a valid step
在 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() must be an instance of DateTime, string given
您检查过 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
],
]);
要验证 selected 元素,请在您的 InputFilter 中添加:
$this->add([
'name' => 'category',
'required' => true,
]);
InputFilter 不再需要。一个类别已经存在,因此之前已经过验证。所以,你应该能够 select 它。
如果您有特殊要求,您只需要额外的 filters/validators,例如:"A Category may only be used once in Entries",因此您需要使用 NoObjectExists
验证器。但这里似乎并非如此。
根据评论和过去的问题更新
我认为您在尝试做的事情中把很多事情复杂化了。似乎您想在加载客户端之前简单地填充一个表单。在收到 POST(来自客户端)时,您希望将收到的数据放入表单中,验证并存储它。正确的?
基于此,请为我的一个项目中的用户找到一个完整的控制器。希望你觉得它有帮助。提供它是因为更新偏离了您最初的问题,这可能会对您有所帮助。
我已经删除了一些额外的检查和错误抛出,但在其他方面是完整的工作方式。
(请注意,我使用的是自己的抽象控制器,请确保将其替换为您自己的 and/or 重新创建并匹配要求)
我还在这段代码中添加了额外的注释来帮助你解决问题
<?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;
}
}
希望对您有所帮助...
我对我的表单过滤器感到很困惑。
我的测试项目包含 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);
}
映射器可以很好地加载类别。伟大的。但表格无效,因为:
Object of class CashJournal\Model\Category could not be converted to int
好的,所以我要删除过滤器,但现在无法将属性设置为条目实体,因为 setter 需要 Category
。表单错误显示:
The input is not a valid step
在 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() must be an instance of DateTime, string given
您检查过 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
],
]);
要验证 selected 元素,请在您的 InputFilter 中添加:
$this->add([
'name' => 'category',
'required' => true,
]);
InputFilter 不再需要。一个类别已经存在,因此之前已经过验证。所以,你应该能够 select 它。
如果您有特殊要求,您只需要额外的 filters/validators,例如:"A Category may only be used once in Entries",因此您需要使用 NoObjectExists
验证器。但这里似乎并非如此。
根据评论和过去的问题更新
我认为您在尝试做的事情中把很多事情复杂化了。似乎您想在加载客户端之前简单地填充一个表单。在收到 POST(来自客户端)时,您希望将收到的数据放入表单中,验证并存储它。正确的?
基于此,请为我的一个项目中的用户找到一个完整的控制器。希望你觉得它有帮助。提供它是因为更新偏离了您最初的问题,这可能会对您有所帮助。
我已经删除了一些额外的检查和错误抛出,但在其他方面是完整的工作方式。
(请注意,我使用的是自己的抽象控制器,请确保将其替换为您自己的 and/or 重新创建并匹配要求)
我还在这段代码中添加了额外的注释来帮助你解决问题
<?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;
}
}
希望对您有所帮助...