I built a form with createFormBuilder
$form = $this->createFormBuilder($post)
->add("name", TextType::class, array("label"=>"Article Title"))
->add("content", TextareaType::class, array("label"=>"Article Content"))
->add("categories",
EntityType::class,
array(
"class" => Category::class,
"choice_label" => "name",
"multiple" => true,
"label" => "Article Category",
"required" => false
)
)
->add("attachments", TextType::class, array("required"=>false))
->add("submit", SubmitType::class, array("label"=>"Add new article"))
->getForm();
the "attachments" variable is an entity variable, I want to get a json string from the form and search the database by myself, like this:
$em = $this->getDoctrine()->getManager();
$attachmentsRepository = $em->getRepository(Attachment::class);
$attachments = $post->getAttachments();
$json = json_decode($attachments);
$dataSize = sizeof($json);
for ($i = 0; $i < $dataSize; $i ++) {
$attachment = $attachmentsRepository->find($json[$i]->getId());
$post->addAttachments($attachment);
}
$em->persist($post);
$em->flush();
However, there is the error hint said that:
Could not determine access type for property "attachments" in class
"App\Entity\Post".
I don't know how to solve this problem, if I add a #ORM\Column(type="string") in my Entity the json string will also be stored, I think this is not a good solution.
How do I modify my code?
First of all to get rid of the error you need to add mapped => false to the field options of your attachments field.
If the attachments fields is configured as a mapped field the form component will look for a setAttachments() method or a public property attachments in your Post Entity to set the value - which do not exist in your Post entity. That's why you get the error message:
Could not determine access type for property "attachments" in class "App\Entity\Post".
In order to transform the submitted JSON string to Attachment entities yourself you need a custom data transformer!
A good example how create and use a data transformer can be found in the documentation chapter How to Use Data Transformers.
A clean solution would involve the attachments field being a CollectionType of EntityType, sending the whole form as a JSON POST request and using i.e FOSRestBundle's fos_rest.decoder.jsontoform body listener to decode the JSON request to a form.
The documentation for FOSRestBundle's body listener support can be found here.
Related
I have created a search form in which I use to search my database to produce some result.
using the below in a my forms directory as a form class I generate the form.
$builder->add(
'startDate',
DateType::class,
[
'label' => 'start date',
'format' => 'yyyy-MM-dd',
'required' => true,
'constraints' => [
new Constraints\NotBlank(),
new Constraints\DateTime(),
],
]
);
in my controller I already have retrieved the data using the getData() method
$form = $this->createForm(testForm::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$start = date_format($form->get('start')->getData(), 'Y-m-d');
At the moment I want to get this data using a class object
e.g like this
$form = $this->createForm(testForm::class, $classobject);
$form->handleRequest($request);
where I would use the class object to retrieve the posted data from the form class "testForm"
how have I tried to solve this on my own?
I tried reading tutorials on this concept e.g as below
https://blog.martinhujer.cz/symfony-forms-with-request-objects/
note : this is a learning curve for me, I do not really grasp this concept
Please, constructive responses would be well appreciated.
Thanks
$form->getData() will return the object populated with the submited data. By default the Form Component returns an array, but if you pass an object as the second arg to createForm or set data_class option then you will get the object back.
Long story short, in Symfony 2.8 I've got Movie entity with actors field, which is ArrayCollection of entity Actor (ManyToMany) and I wanted the field to be ajax-loaded Select2.
When I don't use Ajax, the form is:
->add('actors', EntityType::class, array(
'class' => Actor::class,
'label' => "Actors of the work",
'multiple' => true,
'attr' => array(
'class' => "select2-select",
),
))
And it works.
I tried to put there an empty Select field:
->add('actors', ChoiceType::class, array(
'mapped' => false,
'multiple' => true,
'attr'=>array(
'class' => "select2-ajax",
'data-entity'=>"actor"
)
))
The Select2 Ajax works, everything in DOM looks the same as in previous example, but on form submit I get errors in the profiler: This value is not valid.:
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[actors] = [0 => 20, 1 => 21]
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "actors": Could not find all matching choices for the given values
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Could not find all matching choices for the given values
The funny part is the data received is the same as they were when it was an EntityType: [0 => 20, 1 => 21]
I marked field as not mapped, I even changed field name to other than Movie entity's field name. I tried adding empty choices, I tried to leave it as EntityType but with custom query_builder, returning empty collection. Now I'm out of ideas.
How should I do it?
EDIT after Raymond's answer:
I added DataTransformer:
use Doctrine\Common\Persistence\ObjectManager;
use CompanyName\Common\CommonBundle\Entity\Actor;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ActorToNumberTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $objectManager)
{
$this->manager = $objectManager;
}
public function transform($actors)
{
if(null === $actors)
return array();
$actorIds = array();
foreach($actors as $actor)
$actorIds[] = $actor->getId();
return $actorIds;
}
public function reverseTransform($actorIds)
{
if($actorIds === null)
return array();
foreach($actorIds as $actorId)
{
$actor = $this->manager->getRepository('CommonBundle:Actor')->find($actorId);
if(null === $actor)
throw new TransformationFailedException(sprintf('An actor with id "%s" does not exist!', $actorId));
$actors[] = $actor;
}
return $actors;
}
}
Added it at the end of the MovieType buildForm():
$builder->get('actors')
->addModelTransformer(new ActorToNumberTransformer($this->manager));
$builder->get('actors')
->addViewTransformer(new ActorToNumberTransformer($this->manager));
And added service:
common.form.type.work:
class: CompanyName\Common\CommonBundle\Form\Type\MovieType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
Nothing changed. On form submit, reverseTransform() gets the proper data, but profiler shows the same error. That's a big mistery for me now...
You'll need to add a DTO (Data Transformer ) to transform the value received from your form and return the appropriate object .
Since you're calling the value from Ajax it doesn't recognized it anymore as a an object but a text value.
Examples :
Symfony2 -Use of DTO
Form with jQuery autocomplete
The correct way isn't Data Transformer but Form Events, look here:
http://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-submitted-data
In the example you have the field sport (an entity, like your Movie) and the field position (another entity, like actors).
The trick is to use ajax in order to reload entirely the form and use
PRE_SET_DATA and POST_SUBMIT.
I'm using Symfony 3.x but I think it's the same with 2.8.x
When you add data transformers and nothing seems to change, it sounds like the data never goes through your data transformers. The transformation probably fails before your new data transformers are called. Try to add a few lines to your code:
$builder->get('actors')->resetViewTransformers();
$builder->get('actors')->resetModelTransformers();
// and then add your own
I'm having a problem when trying to compare a data database, with data input by form.
I have a "pedido", this has many "items". I need compare "item" by "item" if this is modifies in the form.
Then I need get original data from database and data modified from form.
The problem is when i try get original data from database.
Always get the data modified by form.
How Can i get the original data from database after the submit the form?
NOTE: i have tried get PedidoAuxiliar before and after HandleRequest.This doesn´t work!
UPDATE CODE: Input how compare the items
This is my controller editAction:
public function editarAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$pedido = $em->getRepository('PedidosBundle:Pedido')->find($id);
//$pedidoAuxiliar = $em->getRepository('PedidosBundle:Pedido')->find($id);
$formulario = $this->createForm(new PedidoType(), $pedido, array(
'action' => $this->generateUrl('my_routing', array('id' => $id)),
'attr' => array(
'novalidate' => 'novalidate'
),
'method' => 'POST',
));
$formulario->handleRequest($request);
if($formulario->isValid()){
$pedidoAuxiliar = $em->getRepository('PedidosBundle:Pedido')->find($id);
foreach($pedido->getArticulos() as $articulo){
foreach($pedidoAuxiliar->getArticulos() as $articuloAuxiliar){
if($articuloAuxiliar->getId() == $articulo->getId()){
if($articuloAuxiliar->getCantidad() == $articulo->getCantidad()){
//Some code...
To get data from db u can use EntityManager::refresh($entity) its overwrite entity data using db. So u must use data from form, not entity to compare.
But u always can ust Doctrine to check changes eg: http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html
because Doctrine hold info about old and new value, but is not so easy to get (outside listeners)
I have found a solution. I have created a new connection entity manager in my config.yml.
Now I call my entity data from my new entity manager and i get datas from database! Thanks!
I have tried looking around for a possible solution to this but with no luck.
What I have is a Many to many relationship between properties and postcodes, I can't display the postcodes in a select for example due to the amount of possible entries.
My solution was to have it as a text field in the form and then catch it on PrePersist to search for the matching record and then apply this to the entity before persisting to the db.
The problem is when the form is validating it is still trying to pass the string value to the setter which is expecting an entity object.
Is there anyway to prevent this from causing an error?
I have attached my form code for you.
Thanks,
Harry
$propertyData = new PropertyData();
$builder
->add('reference')
->add('listing_type', 'choice', array('choices' => $propertyData->getListingTypes()))
->add('listing_status', 'choice', array('choices' => $propertyData->getStatusList()))
->add('title')
->add('house_number')
->add('address_line_1')
->add('address_line_2')
->add('town', 'text', array('data_class'=> 'Minos\Bundle\PropertyBundle\Entity\UtilTown'))
->add('county')
->add('country')
->add('council')
->add('region')
->add('postcode', 'text', array('data_class'=> 'Minos\Bundle\PropertyBundle\Entity\UtilPostcode'))
->add('short_description')
->add('long_description')
->add('size_sq_ft')
->add('floor_level')
->add('property_age')
->add('tenure_type', 'choice', array('choices' => $propertyData->getTenureTypes()))
->add('garage')
->add('num_living_rooms')
->add('num_bathrooms')
->add('num_bedrooms')
->add('num_floors')
->add('num_receptions')
->add('property_type')
//->add('prices')
;
You need a data transformer to convert your string input to an entity before processing the form.
$builder
// ...
->add('postcode', 'text', array(
'data_class'=> 'Minos\Bundle\PropertyBundle\Entity\UtilPostcode'
))
// ...
;
$builder->get('postcode')->addModelTransformer(new CallbackTransformer(
//Render an entity to a string to display in the text input
function($originalInput){
$string = $originalInput->getPostcode();
return $string;
},
//Take the form submitted value and convert it before processing.
//$submittedValue will be the string because you defined
// it in the builder that way
function($submittedValue){
//Do whatever to fetch the postcodes entity:
$postcodeEntity = $entityManager->find('AppBundle\postcodes', $submittedValue);
return $postcodeEntity;
}
));
This is just an example (I haven't tested it), you will need to change some stuff to match how your entities look.
I have a form that is the bottleneck of my ajax-request.
$order = $this->getDoctrine()
->getRepository('AcmeMyBundle:Order')
->find($id);
$order = $order ? $order : new Order();
$form = $this->createForm(new OrderType(), $order);
$formView = $form->createView();
return $this->render(
'AcmeMyBundle:Ajax:order_edit.html.twig',
array(
'form' => $formView,
)
);
For more cleaner code I deleted stopwatch statements.
My OrderType has next fields:
$builder
->add('status') // enum (string)
->add('paid_status') // enum (string)
->add('purchases_price') // int
->add('discount_price') // int
->add('delivery_price') // int
->add('delivery_real_price', null, array('required' => false)) // int
->add('buyer_name') // string
->add('buyer_phone') // string
->add('buyer_email') // string
->add('buyer_address') // string
->add('comment') // string
->add('manager_comment') // string
->add('delivery_type') // enum (string)
->add('delivery_track_id') // string
->add('payment_method') // enum (string)
->add('payment_id') // string
->add('reward') // int
->add('reward_status') // enum (string)
->add('container') // string
->add('partner') // Entity: User
->add('website', 'website') // Entity: Website
->add('products', 'collection', array( // Entity: Purchase
'type' => 'purchase',
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'property_path' => 'purchases',
'error_bubbling' => false,
));
Purchase type:
$builder
->add('amount')
->add('price')
->add('code', 'variant', array(
'property_path' => 'variantEntity',
'data_class' => '\Acme\MyBundle\Entity\Simpla\Variant'
))
;
Also Purchase type has a listener that is not significant here. It is represented in Symfony profiler below as variant_retrieve, purchase_form_creating. You can see that it takes about 200ms.
Here I put the result of profilers:
As you can see: $this->createForm(...) takes 1011ms, $form->createView(); takes 2876ms and form rendering in twig is also very slow: 4335ms. As stated by blackfire profiler all the deal in ObjectHydrator::gatherRowData() and UnitOfWork::createEntity().
Method createEntity() called 2223 times because there is some field that mapped with Variant entity and has form type Entity. But as you can see from above code there is no entity types for variant. My VariantType is simple extended text form type that has modelTransformer. To not mess up everything you can see code for similar Type class at docs.
I found with XDebug that buildView for VariantType has been called in Purchase's buildView with text form type. But after that from somewhere buildView for VariantType was called again and in this case it has entity form type. How can it be possible? I tried to define empty array in choices and preferred_choices on every my form type but it didn't change anything. What I need to do to prevent EntityChoiceList to be loaded for my form?
The described behavior looks as the work of the guesser. I have the feeling that there is need to show an some additional code (listeners, VariantType, WebsiteType, PartnerType).
Let's assume a some class has association variant to Variant and FormType for this class has code ->add('variant') without explicit specifying type (as I see there is a lot of places where the type is not specified). Then DoctrineOrmTypeGuesser comes in the game.
https://github.com/symfony/symfony/blob/2.7/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php#L46
This code assign the entity type (!) to this child. The EntityRepository::findAll() is called and all variants from DB are hydrated.
As for another form optimization ways:
Try to specify type in all possible cases to prevent a type guessing;
Use SELECT with JOINs to get an order as new sub-requests to DB are sent to set an underlying data for an every form maps relation;
Preserve keys for collection elements on a submission as a removing of a single element without a keys preserving will trigger unnecessary updates.
I also had the same problem with the entity type, I needed to list cities, there were like mire then 4000, what I did basically is to inject the choices into the form. In your controller you ask the Variants from the database, in a repository call, hydrate them as array, and you select only the id and the name, or title, and then you pass into the form, as options value. With this the database part will be much quicker.