ManyToMany relations in symfony doctrine - php

i have a Agency Entity
with a fields $owns
looks like
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\ShipType", inversedBy="owners")
*/
protected $owns;
on ShipType Entity
i have
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Agency", mappedBy="owns")
*/
private $owners;
doctrine created a relations table for association between tables with agency_id and ship_type_id
i'm trying to get a form to work for assign each agency to a ship type ( owns )
im trying to achieve logging as an agency
so far i got
public function gShips(Request $request): Response {
$u = $this->getUser();
$ag = new Agency();
$form = $this->createForm(ChooseShipsType::class, $ag);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$a = $form->getData();
$em->persist($a);
$em->flush();
}
return $this->render('agency/sships.html.twig', [
'adForm' => $form->createView()
]);
}
and the form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('ships', EntityType::class, [
'class' => 'AppBundle:ShipType',
'expanded' => true,
'multiple' => true,
])
->add('save', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Agency'
]);
}
the form is showing, but can't get it to persist because it's trying to create a new Agency, i can't figure out how to use the relation table between these two tables
thank you in advance

Check the constructor of your Agency class. Make sure it has the following:
$this->owns = new ArrayCollection();
And then, make sure you have an addOwns() method:
public function addOwns(ShipType $shipType)
{
$this->owns[] = $shipType;
}
And also a setter:
public function setOwns($owns)
{
if ($owns instanceof ArrayCollection) {
$this->owns = $owns;
} else {
if (!$this->owns->contains($owns)) {
$this->owns[] = $owns;
}
$this->owns;
}
return $this;
}
Also, make sure you have the getter with the default content. That should do.
PS: You shouldn't name your properties as verbs though, but that's another thing.

Related

Get certain form inputs separately

I have this form and I have to get the values of 3 fields (name and 2 dates) separately, formate the dates and put them into an arrayCollection.
I only want to get these 3 fields and let the rest of the form field get inserted automatically as usual.
In parallel, when I come back to edit the form, I want to know how to distribute these values to populate the form.
According to this documentation creating-form-classes , an attempt of solution is to do something like :
We assume that your have an entity name Article.
Your form can be something like :
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class)
->add('author', TextType::class)
->add('name', TextType::class, [
'mapped' => false
])
->add('date1', DatetimeType::class, [
'mapped' => false
])
->add('date2', DatetimeType::class, [
'mapped' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Article::class,
]);
}
}
Here there is option 'mapped' => false because you don't want to mappe it with the entity Article.
Then in your controller, you can have something like
/**
* #Route("/articles", methods={"POST", "GET"}, name="app_post_article")
*/
public function postArticle(Request $request, EntityManagerInterface $em)
{
$form = $this->createForm(ArticleType::class);
$form->handleRequest($request);
if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) {
$name = $form->get('name')->getData();
$date1 = $form->get('date1')->getData();
$date2 = $form->get('date2')->getData();
//.... do something
}
//... Do other thing
}
/**
* #Route("/articles/{id}", methods={"POST", "GET"}, name="app_edit_article")
*/
public function editArticle(Request $request, Article $article, EntityManagerInterface $em)
{
$form = $this->createForm(ArticleType::class, $article);
// $prevName, $prevDate1, $prevDate2 must be retreive first...
$form->get('name')->setData($prevName);
$form->get('date1')->setData($prevDate1);
$form->get('date2')->setData($prevDate2);
$form->handleRequest($request);
if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) {
$name = $form->get('name')->getData();
$date1 = $form->get('date1')->getData();
$date2 = $form->get('date2')->getData();
//.... do something
}
//... Do other thing
}
It's just an idea.

symfony4 inject parameter in form

I have a Business Entity and a BusinessObject Entity, and I would like to link the BusinessObject to the current Business when I create a new BusinessObject.
For example, if my route is business/{id}/object/new, I would like to have the object related with the Business (thanks to the id).
In my BusinessObject Controller, I managed to use #ParamConverter to get the Business id.
In my BusinessObject Form, I put an HiddenType to my business entry because I don't want it to appear, and set data to business_ID.
I struggle in configureOptions to get the business ID, I can't figure out how to get the business id from here.
BusinessObject Controller (route new):
/**
* #Route("/{post_id}/new", name="business_object_new", methods="GET|POST")
* #ParamConverter("business", options={"id" = "post_id"})
*/
public function new(Request $request,Business $business): Response
{
$businessObject = new BusinessObject();
$businessID = $business->getId();
$form = $this->createForm(BusinessObjectType::class, $businessObject,array(
'business_ID'=>$businessID,
));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($businessObject);
$em->flush();
return $this->redirectToRoute('business_object_index');
}
return $this->render('business_object/new.html.twig', [
'business_object' => $businessObject,
'business'=>$business,
'form' => $form->createView(),
]);
}
BusinessObjectType:
class BusinessObjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('object',TextType::class)
->add('complement')
->add('status')
->add('durationExpected')
->add('durationAchieved')
->add('client')
->add('projectManager')
->add('business',HiddenType::class,array(
'data' => $options['business_ID']
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => BusinessObject::class,
'business_ID'=>Business::class
]);
}
}
With this code, I get an error Expected argument of type "App\Entity\Business or null", "string" given. I think this have something to do with the function configureOptions() in my Form
The approach can be:
public function new(Request $request,Business $business): Response
{
$businessObject = new BusinessObject();
$form = $this->createForm(BusinessObjectType::class, $businessObject);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// I suppose your setter is `setBusiness`, otherwise use more suitable one
$businessObject->setBusiness($business);
$em = $this->getDoctrine()->getManager();
$em->persist($businessObject);
$em->flush();
Form builder is:
builder
->add('object',TextType::class)
->add('complement')
->add('status')
->add('durationExpected')
->add('durationAchieved')
->add('client')
->add('projectManager'); // No business field
Another option is to embed BusinessType form into BusinessObjectType, you can read more about form embedding here.

How do you pass the parent entity to a form in Symfony?

Suppose I have two entities: a post and a comment. Each post can have many comments. Now, suppose I have a comment form. It is supposed to take user input and store it in the database.
Simple stuff. At least, it should be, but I can't get it to work.
How do I refer to the post (parent) when creating the comment (child)? I tried manually passing the post_id to the comment form as a hidden field, but received an error complaining about how the post ID is a string.
Expected argument of type "App\Entity\Post or null", "string" given.
Here is my code so far. Can someone nudge me into the right direction?
CommentType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$post_id = $options['post_id'];
$builder->add('content', TextareaType::class, [
'constraints' => [
new Assert\NotBlank(['message' => 'Your comment cannot be blank.']),
new Assert\Length([
'min' => 10,
'minMessage' => 'Your comment must be at least {{ limit }} characters long.',
]),
],
])->add('post', HiddenType::class, ['data' => $post_id]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Comment::class,
'post_id' => NULL,
]);
}
PostController.php (this is where the comment form appears)
// Generate the comment form.
$comment = new Comment();
$form = $this->createForm(CommentType::class, $comment, [
'action' => $this->generateUrl('new_comment'),
'post_id' => $post_id,
]);
CommentController.php
/**
* #param Request $request
* #Route("/comment/new", name="new_comment")
* #return
*/
public function new(Request $request, UserInterface $user)
{
// 1) Build the form
$comment = new Comment();
$form = $this->createForm(CommentType::class, $comment);
// 2) Handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// 3) Save the comment!
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($comment);
$entityManager->flush();
}
return $this->redirectToRoute('homepage');
}
Thank you very much for your help!
You just need to pass the actual Post entity, not just the id. Try this:
CommentController.php
public function new(Request $request, UserInterface $user, Post $post)
{
// 1) Build the form
$comment = new Comment();
$comment->setPost($post); //where $post is instance of App\Entity\Post
$form = $this->createForm(CommentType::class, $comment);
// 2) Handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// 3) Save the comment!
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($comment);
$entityManager->flush();
}
return $this->redirectToRoute('homepage');
}
CommentType
public function buildForm(FormBuilderInterface $builder, array $options)
{
//don't need to set the $post here
$builder->add('content', TextareaType::class, [
'constraints' => [
new Assert\NotBlank(['message' => 'Your comment cannot be blank.']),
new Assert\Length([
'min' => 10,
'minMessage' => 'Your comment must be at least {{ limit }} characters long.',
]),
],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Comment::class
//don't need the default here either
]);
}
Comment Entity
class Comment
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post")
*/
private $post;
//other vars
public function setPost(\App\Entity\Post $post): void
{
$this->post = $post;
}
public function getPost(): \App\Entity\Post
{
return $this->post;
}
//other functions
}
This code works for me:
CommentController.php
As suggested by flint above, you just need to pass the actual Post entity, not just the id. Then if you have this error "Unable to guess how to get a Doctrine instance from the request information for parameter "post" this is because you need to add the post slug in the path of the new_comment route. The ParamConverter is called implicitly and it need this slug {post} with the same name as the name you used for the post parameter in the function.
/**
* #param Request $request
* #return \Symfony\Component\HttpFoundation\RedirectResponse
* #Route("/comment/new/{post}", name="new_comment")
*/
public function new(Request $request, Post $post)
{
$comment = new Comment();
$comment->setPost($post); //where $post is instance of App\Entity\Post
$form = $this->createForm(CommentType::class, $comment);
// 2) Handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// 3) Save the comment!
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($comment);
$entityManager->flush();
}
return $this->redirectToRoute('homepage');
}
PostController.php
/**
* #Route("/post/{id}", name="get_post")
*/
public function getPostAction(Post $post)
{
// Generate the comment form.
$comment = new Comment();
$form = $this->createForm(CommentType::class, $comment, [
'action' => $this->generateUrl('new_comment', ['post' => $post->getId()]),
]);
return $this->render('listeArticles.html.twig', [
'form' => $form->createView()
]);
}
CommentType.php
class CommentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//don't need to set the $post here
$builder
->add('content', TextareaType::class, [
'constraints' => [
new Assert\NotBlank(['message' => 'Your comment cannot be blank.']),
new Assert\Length([
'min' => 10,
'minMessage' => 'Your comment must be at least {{ limit }} characters long.',
]),
],
])
->add('submit', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Comment::class
]);
}
}
With this you don't need to remove the Doctrine relationship between the two tables and manually set an ID.
Dont put in to form field,
for exampled
public function new(Request $request, UserInterface $user)
{
// 1) Build the form
$comment = new Comment();
$form = $this->createForm(CommentType::class, $comment);
// 2) Handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
comment->setPostId($post_id)
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($comment);
$entityManager->flush();
}
return $this->redirectToRoute('homepage');
}
The error message says it all:
Expected argument of type "App\Entity\Post or null", "string" given.
If you go to your comment Entity (App\Entity\Comment) you'll see that your class refers to the parent post as a Post Class (App\Entity\Post) and not as a "post_id".
It is the ORM (doctrine in this case) who does the link in your physical database and your Entity classes and add a post_id field in your table.
This is the what ORM (Object Relational Model) is for. You should no more consider Post and Comment as Sql tables but as Classes (OOP).
Thus is I want to add a comment related to someParent I should do something like:
$comment = new Comment();
$comment->setPost($post);
Where $post is an instance of the class Post.

Save EntityType choice as string - Symfony 4

I have entity1 and entity2.
In the entity1's form, I am displaying a choice list where the options are comming from entity2.
I want to save the selected choice as string inside a column in entity1's table, but I dont want to create any relations between the tables.
How Should I do that?
class Entity1 {
/**
* #ORM\Column(type="string")
*/
private $historico;
}
class Entity2 {
/**
* #ORM\Column(type="string")
*/
private $description;
}
Entity1FormType.php
$builder->add('historico', EntityType::class, [
'class' => Entity2::class,
'choice_label' => 'description',
'choice_value' => 'description',
'placeholder' => ''
]);
The choices display fine, but when I submit I get the following error:
Expected argument of type "string", "App\Entity\Entity2" given.
If I use 'mapped' => false, the input submit as null.
How do I convert the entity object to string?
Help a symfony noob :)
If you use mapped => false you have to fetch the data manually in your controller after the form is submitted.
so you will have something like this:
public function postYourFormAction(Request $request)
{
$entity1 = new Entity1();
$form = $this->createForm(Entity1Type::class $entity1);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$entity1 = $form->getData;
$historico = $form->get('historico')->getData();
$entity1->setHistorico($historico);
$em->persist($entity1);
$em->flush();
}
}
This can be done with data transformers so you would not have to unmap fields.
Your form could be as below. Note the choice value getting the string
class OrderType extends AbstractType
{
public function __construct(private ItemToStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('itemCode', EntityType::class, [
'class' => Item::class,
'autocomplete' => true,
'required' => true,
'choice_value' => function (?Item $entity) {
return $entity ? $entity->getCode() : '';
},
// validation message if the data transformer fails
'invalid_message' => 'That is not a valid Item Code',
]);
$builder->get('accountCode')->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
And then the data transformer can be as below
class ItemToStringTransformer implements DataTransformerInterface
{
public function __construct(private EntityManagerInterface $entityManager)
{
}
//transforming item object to string
public function reverseTransform($item): ?string
{
if (null === $item) {
return null;
}
return $item->getCode();
}
// transforming string to item object
public function transform($itemCode): ?Item
{
if (!$itemCode) {
return null;
}
$item = $this->entityManager
->getRepository(Item::class)
// query for the glCode with this id
->findOneBy(['code' => $itemCode])
;
if (null === $item) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf('Item code "%s" does not exist!', $itemCode));
}
return $item;
}
}
You can read further in the symfony documentation https://symfony.com/doc/current/form/data_transformers.html#example-2-transforming-an-issue-number-into-an-issue-entity

Symfony Form CollectionType with API post of new/existing entities

Building an internal API endpoint which allows another service to update specific fields for users, identified by email addresses. If the user does not exist, it needs to be created.
The code is working perfectly fine providing only new users are submitted.
This is the POST request to the API endpoint.
[
{
"email":"existing#user.com",
"favouriteFood": "pizza"
},
{
"email":"new#user.com",
"favouriteFood": "sweets"
}
]
Controller action
public function postUsersAction(Request $request)
{
$form = $this->createForm(UserCollectionType::class);
$form->submit(['users' => json_decode($request->getContent(), true)], true);
if (!$form->isValid()) {
return $form;
}
/** #var User $user */
foreach ($form->getData()['users'] as $user) {
$this->userManager->updateUser($user);
}
$this->em->flush();
return $form->getData()['users'];
}
UserCollectionType
class UserCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('users', 'collection', [
'allow_add' => true,
'by_reference' => false,
'type' => UserType::class
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
'cascade_validation' => true
]);
}
public function getBlockPrefix()
{
return '';
}
}
UserType
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('favouriteFood', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'validation_groups' => ['user_api'],
'cascade_validation' => true
]);
}
}
How can I update the above code so that it will check to see if the user exists first, otherwise continue to create a new one how it's currently working.
I'd have assumed a DataTransformer could have been used, but not exactly how.
EDIT: Proposed solution by ShylockTheCamel
Inside the controller.
$post = json_decode($request->getContent(), true);
$users = [];
foreach ($post as $userRaw) {
$user = $this->em->findOneBy($userRaw['email']); // example search logic in the DB
if (!$user) {
$user = new User();
}
$users[] = $user;
}
$form = $this->createForm(UserCollectionType::class, $users);
$form->submit(['users' => $post], true);
If I understand correctly, your user(s) entity(ies) exist when you enter the foreach loop. So you must create them in the form creation process. In that case, why not check th existence of a user in one of your form validators?

Categories