Here is some code of my Type Class. For one of the property I use a ModelTransform to display what I want :
$builder->add('myProperty', 'text', array(
'label' => 'MyLabel',
'required' => true,
'disabled' => true
));
$builder->get('myProperty')
->addModelTransformer(new CallbackTransformer(
function($propertyFrom) {
return (round($propertyFrom/ 100, 3)) + 1;
},
function($propertyTo) {
return round((($propertyTo- 1) * 100), 3);
}
));
it works good when I display my form in the twig.
I would like to get the entity from my form. I guessed, assuming I have my Form object, I would do like this :
$entity = $form->getViewData()
I wanted to have the entity with the value of the fields passed through the ModelTransform. Though I have the value of field with no transformation.
Finally I wonder what is the difference between $form->getData() and $form->getViewData().
How can I get the entity with the values transformed by my ModelTransformer ?
It is a Symfony bug that does not apply transformer via the getViewData on the object. But it does it on the field.
getViewData vs. getNormData and DataTransformers - different results depending on context
This is a kind of limitation because compound forms having an object or array as underlying data, don't keep their view data synchronized with their children's view data. https://github.com/symfony/symfony/issues/18683#issuecomment-249676768
You can get the difference thus:
// if myProperty value is equal to 23
$form->get('myProperty')->getData(); //output: 23
$form->get('myProperty')->getNormData(); //output: 1.23
$form->get('myProperty')->getViewData(); //output: "1.23"
Related
// buildForm
...
->add('book', 'entity', [
'class' => 'MyBundle\Entity\Book',
'choices' => [],
])
->addEventSubscriber(new MySubscriber());
The field book gets filled through javascript and gets the title of the book.
What I need to do is check if the book already exists in my db, otherwise I create it. I created a subscriber for that works well.
The problem is that I couldn't get rid of the error emitted by $form->handleRequest($request)->isValid(), Which is weird because I edited data in the request this way in my subscriber:
public function preSetData(FormEvent $event)
{
...
$author = $event->getData();
$requestForm = $this->request->request->get('mybundle_author');
$bookTitle = $requestForm['book'];
// if this book title doesn't exist -> create it
...
$requestForm['book] = (string) $book->getId();
$this->request->request->set('mybundle_author', $requestForm);
}
No matter what FormEvents I used, it emits the error that book value is not valid
I crossed a similar problem with the entity type.
The problem is that the new Entity is not marked as managed, and the entity type is focused on selecting existing entities. You could either pass the ObjectManager to the subscriber and set the entity as managed (with persist), or get rid of the validation error yourself. The latter is cleaner, but may require more work.
Removing the option choices fixed the problem.
My subscriber is correct but in my form I had to edit the field
// buildForm
...
->add('book', 'entity', [
'class' => 'MyBundle\Entity\Book',
//'choices' => [], // removing this fixed the problem
])
->addEventSubscriber(new MySubscriber());
I cannot find the correct way to pass an array of 'models' to the choice type. It expects the 'norm' data, but that might be code duplication is many cases imho.
Duplicates code of model transformer
$vehicles = $repo->findAll();
$vehicleChoice = $builder
->create('vehicle', 'choice', [
'choices' => \array_map(
function(Vehicle $v) {
return $v->getId(); // same as VehicleModelTransformer::transform
}, $vehicles),
])
->addModelTransformer(
new VehicleModelTransformer($this->em)
)
;
vs.
Form should use model transformer to initialize with norm data
$vehicleChoice = $builder
->create('vehicle', 'choice', [
'choices_as_model' => $repo->findAll(), // anything like that
])
->addModelTransformer(
new VehicleModelTransformer($this->em)
)
;
Anyone knows how to write it the second way, or to convert the array of models to norm data? In the symfony components modelToNorm is a private method. That code is also not reusable.
Thank you!
As I see you already has own dataTransformer which make
everything for your purpose.
So I think that the better solution for this case is create custom
form type like VehicleChoice which will extend built-in choice type
and will contain required logic.
My Product entity has the following structure:
private $id;
private $title;
/**
* #ManyToOne(targetEntity="Category")
* #JoinColumn(name="cat_id", referencedColumnName="id")
*/
private $category;
Category have nested structure. And each level of nesting is shown in 5 separate fields:
In class form code, I solve it in this way:
$builder
->add('cat_1', 'entity', array(
...
'query_builder' => function() { return someSelectLogic1(); }
))
->add('cat_2', 'entity', array(
...
'query_builder' => function() { return someSelectLogic2(); }
))
->add('cat_3', 'entity', array(
...
'query_builder' => function() { return someSelectLogic3(); }
))
->add('cat_4', 'entity', array(
...
'query_builder' => function() { return someSelectLogic4(); }
))
->add('cat_5', 'entity', array(
...
'query_builder' => function() { return someSelectLogic5(); }
))
Now I need to know which field is filled in the last turn and pass the value of that field in the entity property.
In all that I do not like:
complex logic to determine which field with category was filled at the end
each of these fields is not tied to the entity 'mapped' => false
1) What the right way to organize code of my form?
2) And is there a way to bring these fields into a separate class which will deal with the logic of determining which category was chosen in the end?
I would suggest the following:
1) Create a new custom form field type and put all those entity in there.
This process is not much different from ordinary creation of form type. Just enclose those fields in it's own buildForm() and that should do the trick. Docs.
2) Mark all those entity fields with property "property_path => false".
Clearly you wont be storing these values inside your model.
3) Add two more fields: chosen and lastOne.
Now, this might be tricky: I would either set the chosen to text type (basically, generic type) or would use entity as well. If you go for entity you would need to include all possible answers from all entity fields. As for the lastOne set it to text as it will reflect which field (by name) was selected last.
Either way, those two fields will be invisible. Don't forget to set property_path to false for lastOne field.
4) Finally, add ValueTransformer (docs) which will contain logic to "see" which field was selected last.
Now, I dealt with it only once and don't understand it just quite yet, so your best bet would be trial and error with examples from official docs, unfortunately.
What basically you should do is to, within value-transformer, read the value of field lastOne. This will give you the name of field which was selected last. Then, using that value, read the actual last value selected. Last, set that value (object, if you've went for entity type, or it's ID otherwise) to chosen field.
That should basically do the thing.
As for the JS, I don't know if you're using any framework but I will assume jQuery. You will need to set lastOne field as your selecting items in your form.
$(function(){
$('#myform').find('select').on('change', function(){
var $this = $(this);
$this.closest('form').find('#__ID_OF_YOUR_LASTONE_FIELD').val($this.attr('name'));
});
});
I'm sorry I cannot provide you with code samples for PHP right now. It's a bit late here and will do my best to further update this answer tomorrow.
Suppose to have an entity in Symfony2 that has a field bestfriend, which is a User entity selected from a list of User entities that satisfy a complex requirement.
You can render this field in a form by specifying that it is an entity field type, i.e.:
$builder->add('bestfriend', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'property' => 'username',
));
This form field is rendered as a <select>, where each one of the displayed values is in the form:
<option value="user_id">user_username</option>
So, one would render the field by using the <optgroup> tags to highlight such special feature of the friends.
Following this principle, I created a field type, namely FriendType, that creates the array of choices as in this answer, which is rendered as follows:
$builder->add('bestfriend', new FriendType(...));
The FriendType class creates a <select> organized with the same <option>s but organized under <optgroup>s.
Here I come to the problem! When submitting the form, the framework recognize that the user field is not an instance of User, but it is an integer. How can I let Symfony2 understand that the passed int is the id of an entity of type User?
Here follows my solution.
Notice that it is not mentioned in the Symfony2 official docs, but it works! I exploited the fact that the entity field type is child of choice.
Hence, you can just pass the array of choices as a param.
$builder->add('bestfriend', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'choices' => $this->getArrayOfEntities()
));
where the function getArrayOfEntities() is a function that fills the choice list with the friends of my friends, organized by my friends:
private function getArrayOfEntities(){
$repo = $this->em->getRepository('AcmeHelloBundle:User');
$friends = $repo->findAllFriendByComplexCriteria(...);
$list = array();
foreach($friends as $friend){
$name = $friend->getUsername();
if(count($friend->getFriends())>0){
$list[$name] = array();
foreach($friend->getFriends() as $ff){
$list[$name][$ff->getUsername()] = $ff;
}
}
}
return $list;
}
I know the example could be meaningless, but it works...
PS: You need to pass the entity manager to let it working...
I have a simple entity which is a table holding my user data
and I want to fetch all columns of a specific user as an array and then json_encode them but what I get is an entity object which I will have to use get method for every value. I just want an associative array of my user table values.
The codes I tried and didn't work (returned entity object) are as follows:
1.
$qb = $this->em->createQueryBuilder();
$qb->add('select', 'a')
->add('from', 'Entities\Adminprofile a')
->add('where', 'a.userid = 3333');
$accounts = $qb->getQuery()->getResult();
2.
$account = $this->em->getRepository('Entities\Adminprofile')->findOneBy(
array('userid' => '3333'));
PS: im using z2d2 Project,which is doctrine2 integration into Zend framework.
When you do $accounts = $qb->getQuery()->getResult(); the argument you pass to getResult tells it how to hydrate the result set which is will return.
Array Hydration
If you want arrays, than you should pass the CONSTANT for array hydrations Doctrine\ORM\Query::HYDRATE_ARRAY.
$accounts = $qb->getQuery()->getResult( Doctrine\ORM\Query::HYDRATE_ARRAY );
If you are using findOneBy() then it will always return an entity. Due to the internals of how find works, you cannot tell it to hydrate by any other means other than to return entities.
In this scenario, what you need to do is create a getValues() method inside of your entity which returns an array of your entity, like this:
public function getSimpleValues(){
return array(
'id' => $this->getId(),
'lft' => $this->getLft(),
'rgt' => $this->getRgt(),
'name' => $this->getName(),
'md5Name' => $this->getMd5Name(),
'owner' => $this->getOwner()->getId(),
'etag' => $this->getEtag()
);
}
Hydration API Docs: http://www.doctrine-project.org/api/orm/2.1/namespace-Doctrine.ORM.Internal.Hydration.html
You can also use getArrayResult() as a shortcut to passing in the constant to get an array back:
$accounts = $qb->getQuery()->getArrayResult();
You should use constant containing value 2 and it is inbuilt, you can do it like this at the end part of your query
$qb->getQuery()->getResult( Doctrine\ORM\Query::HYDRATE_ARRAY );
$data = $this->entity->findOneBy(array('key' => $value));
$hydrator = new \DoctrineModule\Stdlib\Hydrator\DoctrineObject($this->_em, $entity_name);
$array = $hydrator->extract($data);