Zend\Form\Fieldset
和Zend\Form\Collections
可以嵌套,并提供一种非常舒适的方式来将复杂的对象结构映射到它们,以便从表单输入中获取一个comlete对象(准备保存)或多或少地自动化。 Form Collections tutorial提供了一个很好的例子。
我目前的情况有点复杂,因为它包含参考反转。这意味着:
我有两个实体 - MyA
和MyB
,而在数据库中,它们之间的关系从FOREIGN KEY
到myb.mya_id
实现为mya.id
,该应用程序使用反向引用:
MyA has MyB
或者使用一些代码:
namespace My\DataObject;
class MyA {
/**
* @var integer
*/
private $id;
/*
* @var text
*/
private $foo;
/**
* @var MyB
*/
private $myB;
}
namespace My\DataObject;
class MyB {
/**
* @var integer
*/
private $id;
/*
* @var text
*/
private $bar;
/*
Actually it's even bidirectional, but it's not crucial for this issue.
For this problem it's not important,
wheter the class MyB has a property of type MyA.
We get the issue already,
when we define a property of type MyB in the class MyA.
Since the direction of the reference MyA.myB->MyB differes
from the direction of the reference my_b.my_a.id->my_a.id.
*/
/**
* @var MyA
*/
// private $myA;
}
我的Mapper
个对象获得DataObject
作为参数传递:MyAMapper#save(MyA $object)
和MyBMapper#save(MyB $object)
。
namespace My\Mapper;
use ...
class MyAMapper
{
...
public fuction save(MyA $object)
{
// save the plain MyA propertis a new entry in the my_a table
...
$myMapperB->save($myA->getMyB());
}
}
namespace My\Mapper;
use ...
class MyBMapper
{
...
public fuction save(MyB $object)
{
// save the plain MyB propertis a new entry in the my_b table
...
}
}
这意味着,MyAMapper#save(...)
需要将MyA
对象保存到my_a
表。但在MyBMapper
中,my_b.my_a_id
的数据将会丢失。
我也无法使用嵌套字段集MyAFieldset
创建字段集MyBFieldset
,然后将字段集MyBFieldset
嵌套到MyAFieldset
中以填充MyA#MyB#MyA
(为了将my_b.my_a_id
的数据传递给MyBMapper#save(...)
):
class MyAFieldset {
$this->add([
'name' => 'my_b',
'type' => 'My\Form\Fieldset\MyBFieldset',
'options' => []
]);
}
class MyBFieldset {
$this->add([
'name' => 'my_a',
'type' => 'My\Form\Fieldset\MyAFieldset',
'options' => []
]);
}
这会导致递归依赖,无法正常工作。
当应用程序级别的参考方向与数据库中的方向不同时,如何处理案例?如何创建一个fieldsets结构,它提供了一个完整的("准备保存")对象?
解决方法1
处理表单后,可以创建另一个MyA
对象并将其添加到从表单中获取的MyB
对象中:
class MyConrtoller {
...
public function myAction() {
$this->myForm->bind($this->myA);
$request = $this->getRequest();
$this->myForm->setData($request->getPost());
// here the hack #start#
$this->myB->setMyA($this->myA);
// here the hack #stop#
$this->myAService->saveMyA($this->myA);
}
}
好吧,也许不在控制器中,映射器可能是更好的地方:
class MyAMapper
{
...
public function save(MyA $myA)
{
$data = [];
$data['foo'] = [$myA->getFoo()];
// common saving stuff #start#
$action = new Insert('my_a');
$action->values($data);
$sql = new Sql($this->dbAdapter);
$statement = $sql->prepareStatementForSqlObject($action);
$result = $statement->execute();
$newId = $result->getGeneratedValue()
// common saving stuff #stop#
...
// hack #start#
if(! $myA->getB->getA()) {
$myA->getB->setA(new MyA());
$myA->getB->getA()->setId($newId);
}
// hack #stop#
// and only after all that we can save the MyB
$myB = $this->myBMapper->save($myB);
$myA->setMyB($myB);
...
}
}
但无论如何它只是一个黑客。
解决方法2
MyB
类获取属性$myAId
。但它也不是一个干净的方式。
解决方法3
MyBFieldset
获取MyAFieldsetFake
作为子字段集。这个字段集类只是一个"浅" MyAFieldset
的副本,其中仅包含ID
数据对象的MyA
:
class MyAFieldset {
...
public function init()
{
$this->add([
'type' => 'text',
'name' => 'id',
'options' => [...],
]);
$this->add([
'type' => 'text',
'name' => 'foo',
'options' => [...],
]);
}
}
class MyAFieldset {
...
public function init()
{
$this->add([
'type' => 'text',
'name' => 'id',
'options' => [...],
]);
$this->add([
'type' => 'text',
'name' => 'bar',
'options' => [...],
]);
$this->add([
'type' => 'text',
'name' => 'foo',
'type' => 'My\Form\Fieldset\MyAFakeFieldset',
'options' => [...],
]);
}
}
class MyAFieldset {
...
public function init()
{
$this->add([
'type' => 'text',
'name' => 'id',
'options' => [...],
]);
}
}
但假物品也有点脏。
答案 0 :(得分:0)
如何创建一个新表来自己处理映射。然后,您可以将这种复杂性与利用它们的对象隔离开来。
所以,你可以有一个新对象AtoBMappings
namespace My\DataObject;
class MyA {
/**
* @var integer
*/
private $id;
/*
* @var text
*/
private $foo;
/**
* @var MyAtoB
*/
private $myAtoB;
}
namespace My\DataObject;
class MyB {
/**
* @var integer
*/
private $id;
/**
* @var AtoBMapperID
*/
private $myAtoB;
}
class MyAtoBMapper {
/**
* @var myB
*/
private $myB
/**
* @var myA
**
private $myA
}
然后,您可以简单地在MyA到MyB创建中进行分配,而不是破解Mapper方法。
class MyAMapper
{
...
public function save(MyA $myA)
{
$myAtoB = new MyAtoBMapper();
//.... instert new myAtoB into DB
$data = [];
$data['foo'] = [$myA->getFoo()];
$data['myAtoB'] = $myAtoB->getId();
// common saving stuff #start#
$action = new Insert('my_a');
$action->values($data);
$sql = new Sql($this->dbAdapter);
$statement = $sql->prepareStatementForSqlObject($action);
$result = $statement->execute();
$newId = $result->getGeneratedValue();
$myA->setMyAtoB($newAtoB);
$myAtoBMapper->myA = $newId;
// common saving stuff #stop#
// and only after all that we can save the MyB
$myB = $this->myBMapper->save($myB);
$myB->setMyAtoB($newAtoB);
$myAtoBMapper->myB = $myB;
...
}
}
你认为这会起作用,还是你觉得这太过分了?