symfony4 inject parameter in form - php

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.

Related

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.

ManyToMany relations in symfony doctrine

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.

A new entity was found through the relationship *** that was not configured to cascade persist operations

I am trying to make a simple two-step form:
Step One: Select target Product
Step Two: See Product Price, put Order Title and place order.
Using Symfony Form Component in Silex, I am getting this error:
A new entity was found through the relationship 'Order#product' that was not configured to cascade persist operations for entity: 'Product'
Entities:
1 - Product
Class Product {
OneToMany(targetEntity="Order", mappedBy="product", cascade={"persist","remove"})
private $orders;
public function __construct() {
$this->orders = new ArrayCollection()
}
}
2 - Order
Class Order {
ManyToOne(targetEntity="Product", inversedBy="orders")
private $product;
}
Form Types:
1 - OrderProductType (Choose target product)
Class OrderProductType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => Order::class,
));
}
public function buildForm($builder) {
$builder
->add('product', EntityType::class, array(
'class' => Product::class,
)->getForm();
return $form;
}
}
2 - OrderType
Class OrderType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => Order::class,
));
}
public function buildForm($builder) {
$builder
->add('product_price', TextType::class, array(
'data' => $order->getProduct()->getPrice(),
'mapped' => false,
'disabled' => true,
))
->add('title', TextType::class)->getForm();
return $form;
}
}
Controllers:
1 - Set Product
public function setProduct(Application $app, Request $request)
{
$order = new Order();
$form = $app['form.factory']->create(OrderProductType::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$order = $form->getData();
$app['session']->set('dummyOrder', $order);
return $app->redirect($app['url_generator']->generate('orders_add'));
}
return $app['twig']->render('orders/add.html.twig', array(
'form' => $form->createView(),
));
}
2 - Add Order
public function add(Application $app, Request $request)
{
$order = $app['session']->get('dummyOrder');
$form = $app['form.factory']->create(OrderType::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$order = $form->getData();
$app['em']->persist($order);
$app['em']->flush();
$app['session']->remove('dummyOrder');
return $app->redirect($app['url_generator']->generate('orders_show', ['id' => $order->getId()]));
}
return $app['twig']->render('orders/add.html.twig', array(
'form' => $form->createView(),
));
}
Attempts:
What I tried so far:
Tried to replace the redirect with a sub-request forward, passing json-encoded product entity, with no success.
Tried to switch the Product-Order relationship from BiDirectional to UniDirectional with no success.
Tried to merge the product with no success.
Tried to persist order in first form step and merge it at second with no success.
The problem is in the serialisation of the order object.
When you serialise $order by $app['session']->set('dummyOrder', $order); and then deserialise it at $order = $app['session']->get('dummyOrder'); the Product object is no longer managed by Doctrine.
That's why when you try to save $order at add() you get an error.
For your case I'd suggest two solutions.
Save the order to the DB (with some flag like 'temporary') and pass the order ID to the session. Then retrieve the order from the DB with Doctirne.
Replace session setting in setProduct() with
$app['session']->set('dummyOrderProductId', $order->getProduct()->getId());
Then replace retrieving of order in add() (pseudocode)
$order = new Order();
$product = $em->getRepository(Product::class)->findOneById($app['session']->get('dummyOrderProductId'))
$order->setProduct($product);
Read product ID from the $order at setProduct() and pass it to the session. Then again create new $order in add(), read product from the DB and add it to the $order.
In both cases you will keep Product managed by Doctrine and avoid the issue you have.
First of all, please provide full code. This is not valid PHP code:
OneToMany(targetEntity="Order", mappedBy="product", cascade={"persist","remove"})
private $orders;
And information about columns is missing.
But talking about the problem, it seems you have a wrong Doctrine mapping. You should have cascade option where you have a mapping, in this case - in Order entity. On product side it's not really a mapping, it's only a reference to a mapping (mappedBy="product")
* #ORM\ManyToOne(targetEntity="Product", inversedBy="orders", cascade={"persist","remove"})

Symfony 2 prefilled form with data from the database

I am trying to populate form fields with data from the database in order to edit them. I already searched on google.
Here is my controller which returns empty fields
public function userViewAction($id,Request $request){
$em = $this->getDoctrine()->getManager()->getRepository('BFVMailingBundle:MailingList');
$user = $em->findById($id);
$form = $this->get('form.factory')->createBuilder('form',$user)
->add('unsubscribed','checkbox')
->add('name','text')
->add('givenName','text')
->add('additionalName','text',array('required'=>false))
->add('familyName','text',array('required'=>false))
->add('emailValue','text')
->add('language','choice',array(
'choices' => array('en_GB' => 'en_GB', 'es_ES' => 'es_ES', 'fr_FR' => 'fr_FR'),
'required' => true,
))
->add('commentary','textarea',array('required'=>false))
->add('save','submit')
->getForm();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// perform some action, such as save the object to the database
$em->flush();
return $this->redirect($this->generateUrl('user_view',array('id'=>$id)));
}
}
and this is my template
<div class="cell">
{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}
{{ form_end(form) }}
</div>
Did I miss something?
EDIT - READ THIS FOR THE SOLUTION
As John Noel Implied I build an externalised form with the command
php app/console doctrine:generate:form BFVMailingBundle:MailingList
my entity was MailingList instead of User
the MailingListType is a form template which is generated in BFV\MailingBundle\Form. I've added the data types myself.
<?php
namespace BFV\MailingBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class MailingListType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$str = date("U");
$codeValue = sha1($str);
$builder
->add('secretCode','hidden',array( 'data' => $codeValue ))
->add('name','text')
->add('givenName','text')
->add('additionalName','text',array('required'=>false))
->add('familyName','text',array('required'=>false))
->add('emailValue','text')
->add('language','choice',array(
'choices' => array('en_GB' => 'en_GB', 'es_ES' => 'es_ES', 'fr_FR' => 'fr_FR'),
'required' => true,
))
->add('unsubscribed','checkbox')
->add('commentary','textarea',array('required'=>false))
->add('save','submit')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BFV\MailingBundle\Entity\MailingList'
));
}
/**
* #return string
*/
public function getName()
{
return 'bfv_mailingbundle_mailinglist';
}
}
In the reformated controller I add to add the the form generator the instance of MailingList $user[0] instead of $user. I read in many websites that usually you put $variable directly in the form builder but that generated the following error:
The form's view data is expected to be an instance of class
BFV\MailingBundle\Entity\MailingList, but is a(n) array. You can avoid
this error by setting the "data_class" option to null or by adding a
view transformer that transforms a(n) array to an instance of
BFV\MailingBundle\Entity\MailingList
Thus in the controller:
public function userViewAction($id,Request $request){
if (!$id) {
throw $this->createNotFoundException('No id !!');
}
$em = $this->getDoctrine()->getManager()->getRepository('BFVMailingBundle:MailingList');
$user = $em->findById($id);
if (!$user){
throw $this->createNotFoundException('No user with the id selected');
}
$form = $this->createForm(new MailingListType(), $user[0]);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('user_view',array('id'=>$id)));
}
}
return $this->render('BFVMailingBundle:Default:user_view.html.twig',array(
'user'=>$user,
'form'=>$form->createView()
));
}
Conclusion: I got the view form rendering with populated data from the database.
To do this you'll want to look into form classes which will then act as a view (and will also populate) the data you provide. So in your example you'd create a form class UserType:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('unsubscribed','checkbox')
->add('name','text')
->add('givenName','text')
->add('additionalName','text',array('required'=>false))
->add('familyName','text',array('required'=>false))
->add('emailValue','text')
->add('language','choice',array(
'choices' => array('en_GB' => 'en_GB', 'es_ES' => 'es_ES', 'fr_FR' => 'fr_FR'),
'required' => true,
))
->add('commentary','textarea',array('required'=>false))
->add('save','submit')
;
}
public function getName()
{
return 'user';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Your\Entity\Class',
));
}
}
Then within your controller you'd do something along the lines of:
$form = $this->createForm(new UserType(), $user);
Then the rest of your controller as you have. Definitely read up on form classes though as that's the starting point for a lot of the advanced functionality of Symfony forms.
I'm not sure if this is what makes the difference, but did you try with:
$form = $this->createFormBuilder($user)
->add(...)
->getForm()
You may also check that the User you get is correctly read from the DB.
For example:
if (!is_object($user)) {
$this->createNotFoundException('The user does not exist');
}

Symfony2: $form->isValid() always returns true

I create a form type UserBundle/form/UserType.php.
$form->isValid() returns true if the username or the email exist even if other fields are empty.
class UserType extends AbstractType
{
private $type;
const TYPE_CREATE = 1;
public function __construct($type) {
$this->type = $type;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
switch ($this->type){
case self::TYPE_CREATE :
$builder
->add('email', 'email', array('label' => 'user.email'))
->add('username', null, array('label' => 'user.name'))
;
break;
default :
$builder
->add('username')
->add('usernameCanonical')
->add('email')
->add('emailCanonical')
->add('enabled')
->add('salt')
->add('password')
->add('lastLogin')
->add('locked')
->add('expired')
->add('expiresAt')
->add('confirmationToken')
->add('passwordRequestedAt')
->add('roles')
->add('credentialsExpired')
->add('credentialsExpireAt')
;
break;
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Project\UserBundle\Entity\User'
));
}
public function getName()
{
return 'project_userbundle_user';
}
}
And my controller
class UserController extends Controller
{
public function CreateAction()
{
//create user
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->createUser();
//create form
$form = $this->createForm(new UserType(UserType::TYPE_CREATE),$user);
//form submit
$request = Request::createFromGlobals();
if($request->getMethod() === "POST"){
$form->submit($request);
//test if form is valid
if($form->isValid()){
//generate random password
$password = User::randomPassword();
$user->setPassword($password);
//save user
$userManager->updateUser($user);
}
}
return $this->render('Project:User:create.html.twig',array(
'form' => $form->createView()
));
}
}
tip:
You shouldn't try to re-create the Request inside a controller. It is definitely a bad practice and can lead to undesired behavior of your application - as you're currently experiencing.
symfony will pass the request automatically to your controller actions if you add it as a method parameter:
public function createAction(Request $request)
{
// $request is holding the current request
}
Further you can get the current request from the container inside a ContainerAware class like a controller.
$request = $this->getRequest();
$request = $this->get('request');
$request = $this->container->get('request');
Further please stick with the symfony coding standards. There are multiple violations in your code.
Method names should be lower camelCased not CamelCased.
UserBundle/form should be UserBundle/Form.
A method's opening curly brackets/braces belong to the next line.
public function __construct($type) // {
{
$this->type = $type;
}
You have to use Validation Constraints NotNull or NotBlank ( http://symfony.com/doc/current/reference/constraints.html ).
My guess is that, default "required=true" on fields isn't working because You are recreating request.

Categories