Zend\Form\Fieldset
s and Zend\Form\Collections
s can be nested and provide a very comfortable way to map complex object structures to them, in order to get a comlete object (ready to be saved) from the form input more or less automatically. The Form Collections tutorial provides a very good example.
The case I'm currently having is a bit more complex, since it contains a reference inversion. That means:
I have two entities -- MyA
and MyB
and while in the database the relationship between them is implemented as FOREIGN KEY
from myb.mya_id
to mya.id
, the application is using an inverted referencing:
MyA has MyB
Or with some code:
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; }
My Mapper
objects get DataObject
s passed as argument: MyAMapper#save(MyA $object)
and 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 ... } }
That means, the MyAMapper#save(...)
has evrything needed to save the MyA
object to the my_a
table. But in the MyBMapper
the data for my_b.my_a_id
will be missing.
And I also cannot create a fieldset MyAFieldset
with a nested fieldset MyBFieldset
and then nest the fieldset MyBFieldset
into MyAFieldset
in order to fill MyA#MyB#MyA
(in order to pass the data for my_b.my_a_id
to 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' => [] ]); }
This would cause a recursive dependency and cannot work.
How to handle a case, when the reference direction on the application level differs from it's direction in the database? How to create though a fieldsets structure, that provides a complete ("ready to be saved") object?
Workaround 1
When the form is processed, a further MyA
object can be created and added to the MyB
object got from the form:
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); } }
Well, maybe not in the controller, the mapper might be a better place for that:
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); ... } }
But anyway it's just a hack.
Workaround 2
The MyB
class gets a property $myAId
. But it's also not a clean way.
Workaround 3
The MyBFieldset
gets a MyAFieldsetFake
as sub-fieldset. This fieldset class is then just a "shallow" copy of the MyAFieldset
, that contains only the ID
for the MyA
data object:
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' => [...], ]); } }
But fake objects are a bit dirty as well.
1 Answers
Answers 1
How about creating a new table to handle the mappings on their own. Then you can isolate that complexity away from the objects that take advantage of them.
So, you could have a new object 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 }
Then, instead of hacking your Mapper method, you can simply make an assignment in MyA to MyB creation.
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; ... } }
Do you think this would work, or do you think this is too much of a hack?
0 comments:
Post a Comment