Entity field form Symfony2 - forms

My goal is to display a social networks form in users dashboard like :
- Facebook : <input>
- Twitter : <input>
- Linkedin : <input>
Social networks are dynamic (manage by administration panel).
I've got currently 3 Entities (Social, UserSocial and User (FOS...)).
In User entity, I just added it :
/**
*#ORM\OneToMany(targetEntity="Application\SocialBundle\Entity\UserSocial", mappedBy="user")
*/
private $userSocials;
My Social entity :
class Social
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $name;
/**
* #var string
*/
private $class;
...
}
My UserSocial entity :
class UserSocial
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*#ORM\ManyToOne(targetEntity="Admin\UserBundle\Entity\User", inversedBy="userSocials")
*/
private $user;
/**
*#ORM\ManyToMany(targetEntity="Admin\SocialBundle\Entity\Social", inversedBy="userSocials")
*/
private $social;
/**
* #var string
*
* #ORM\Column(name="value", type="string", length=255)
*/
// Not really sure about it
private $value;
....
And finally, the UserSocialType, this is where i'm stuck :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('social', 'entity', array(
'class' => 'Admin\SocialBundle\Entity\Social',
'property' => 'name',
'required' => false,
))
->add('value')
;
}
It gave me just a select with all social name in DB and a value input. I would like a value input for each social entity in DB.
Thank you in advance for your help.

May be you do it a little different?
Craete a SocialType:
$builder
->add('social', 'text', array(
'data' => $existingSocial
))
...
;
UserSocialType:
$builder
->add('social', new SocialType(), array(
// pass social to the Social type
...
))
...
;
PS// These are just my general thoughts, here are some wrong lines my snippet, you should not pass an object to the text field, but you can use data transformers to get an object from the text value.. Also I think your UserSocial entity unproperly configured, $social field should receive manyToOne relationship

Related

Symfony Validation: Error message not showing at associated field

Symfony 5.2.5
Minified code
//Entities
class Article {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity=ArticleTranslation::class, mappedBy="article", cascade={"persist"}, orphanRemoval=true)
* #Assert\Valid
*/
private $translations;
}
class ArticleTranslation {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
private $title;
/**
* #ORM\Column(type="text")
* #Assert\NotBlank
*/
private $body;
/**
* #ORM\ManyToOne(targetEntity=Article::class, inversedBy="translations")
* #ORM\JoinColumn(nullable=false)
*/
private $article;
/**
* #ORM\Column(type="string", length=5)
* #Assert\NotBlank
*/
private $locale;
}
//FormTypes
class ArticleType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(/*other fields*/)
->add('translations', ArticleTranslationType::class, ['label' => false, 'data' => new ArticleTranslation(), 'mapped' => false])
->add('save', SubmitType::class, ['label' => 'Save']);
$builder->get('translations')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$entity = $event->getForm()->getParent()->getData();
$translation = $event->getData();
$translation->setLocale($this->localeService->getCurrentLocale()); //custom service returns e.g. "en"
$entity->addTranslation($translation);
});
}
}
class ArticleTranslationType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('body', TextareaType::class);
}
}
I have two entities called Article and ArticleTranslation with a OneToMany relationship. When creating an Article I want to add a ArticleTranslation to it (e.g. English) - that way there is atleast 1 translation provided. The Article itself just stores generic data like publish-date, while the translation stores title and the content (called body). The above code works fine my only issue is following:
When the validation for title or body fails, the error message is shown above the formular, instead of right next to the associated field. Every other field correctly has the error message right next to it. I am using the default bootstrap 4 form theme.
How can I move the error message to the correct field? The Symfony profiler returns that data.translations[0].body should not be null (since its a collection it has an index) - I guess I need somehow make that into data.translations.body for it to work?
Temporary fix: When adding the validation inside my ArticleTranslationType & remove the Assert\Valid constraint it works. Still interested in another solution with my provided code - Thanks
What you're looking for is the error_bubbling FormType field option.
error_bubbling
type: boolean default: false unless the form is compound.
If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.
Your ArticleTranslationType is compound, therefore error_bubbling defaults to true.
The following should do the trick.
$builder->add(
'translations', ArticleTranslationType::class, array(
'data' => new ArticleTranslation(),
'error_bubbling' => false,
'mapped' => false,
'label' => false
)
);
After playing around I finally got my solution. Since the validation tries to validate the first element in my collection e.g. data.translations[0].body I needed just to provide the correct property path for it to know.
$builder->add(
'translations', ArticleTranslationType::class, array(
'data' => new ArticleTranslation(),
'mapped' => false,
'label' => false,
'property_path' => 'translations[0]' //first element of collection
)
);
This maps the error messages to the corresponding field.

Form setter option Symfony 3

I'm using Symfony 3 to build a website. I have an Entity (Users) that is in OneToOne relation with itself in order to make couples. (I didn't have others idea on how to do it easily)
The end goal is to create a form to reference the id of the other Users in the couple. So I created an IntegerType field and assign it the id but I can't set it (because there are no setId(...)). So I would know if there is a setter option (can't find in Doc/Tests), and if there isn't how could I achieve this ?
The steps to register a new couple would have been:
Send new id (of the other Users) [FORM]
Fetch the other Users ($userCouple = ...findOne...) [BDD]
If he have $couple == null then $userCouple->setCouple($this) and $this->setCouple($userCouple)
So my Users entity looks like:
<?php
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* Users
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="Acme\UserBundle\Repository\UsersRepository")
*/
class Users extends BaseUser
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Acme\UserBundle\Entity\Users")
* #ORM\JoinColumn(nullable=true)
*/
protected $couple;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set couple.
*
* #param \Acme\UserBundle\Entity\Users|null $couple
*
* #return Users
*/
public function setCouple(\Acme\UserBundle\Entity\Users $couple = null)
{
$this->couple = $couple;
return $this;
}
/**
* Get couple.
*
* #return \Acme\UserBundle\Entity\Users|null
*/
public function getCouple()
{
return $this->couple;
}
}
And my form looks like :
<?php
namespace Acme\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
class ProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('couple', IntegerType::class, array(
'label' => 'Couple ID',
'property_path' => 'couple.id',
'attr' => array('min' => 0),
));
}
public function getBlockPrefix()
{
return 'acme_user_profile';
}
}
You should solve this using a one-to-one self-referencing relation (see more here). Basically your couple would be replaced by partner which suites best the case:
...
/**
* #OneToOne(targetEntity="User")
* #JoinColumn(name="partner_id", referencedColumnName="id")
*/
protected $partner;
...
Then in the form you could use the EntityType (not sure why you wanted to use IntegerType in the first place) and do something like this:
$builder->add('users', EntityType::class, array(
// query choices from this entity
'class' => 'UserBundle:User',
// use the User.username property as the visible option string
'choice_label' => 'username',
));
Of course you can exclude the user you're editing the profile from the list of users you show as possible partners using query_builder option (passing a custom query) or choices to pass the collection of User entities you want to use (getting them first and filter out current user).

Symfony 3 form assigns error to the FormType instead of the actual field

I created a form in Symfony with this setup:
#\src\AppBundle\Entity\Order.php
class Order
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $orderID
*
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
protected $orderID;
/**
* #var string $email
*
* #ORM\Column(type="string")
* #Assert\NotBlank()
* #Assert\Email()
*/
protected $email;
...
In my form type:
# \src\AppBundle\Form\OrderType.php
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('order_id', TextType::class, [
'label' => 'index.order_id',
'attr' => array(
'placeholder' => 'index.order_id.placeholder'
)
])
->add('email', EmailType::class, [
'label' => 'index.email',
'attr' => array(
'placeholder' => 'index.email.placeholder'
)
]);
}
...
So if I submit the form with empty fields, Symfony detects both errors but the order ID error is assigned to the Class instead of the actual field.
Any ideas?
Update
I renamed protected $orderID; to protected $orderid; and the problem is fixed! So why this happened? Is there any reserved names when it comes to the use of ID in the property name?
I think you should have this because that's what you specified in buildForm:
/**
* #var string $order_id
*
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
protected $order_id;
Can you try it?

Unclear Symfony form error "This value is already used"

I use Symfony 2.7.3 and I have following form ("app_cargo_source"):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', 'choice', [
'choices' => $this->worldManager->getCountriesByRegionIds([1, 2]),
'choice_label' => 'name',
'choice_value' => 'id',
'label' => false,
// *this line is important* see docs for choice type
'choices_as_values' => true
])
// ...
;
// ...
}
The form is used in another form (app_cargo):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('source', 'app_cargo_source')
// ...
;
// ...
}
“choices” field in first form is an array of WorldCountry objects, which are selected from database by regions id. Here is a picture of the array from Profiler:
WorldCountry and WorldRegion have unidirectional ManyToOne relationship.
The problem is, when I select country in country field and submit the form, back I receive following error for country field:
Why that happens, and why Symfony at all try assign a value to region?
P.S. Please let me know if more information is needed.
Right! It seems that problem was in uniqueness of region field in my WorldCountry entity. My WorldRegion entity is following:
/**
* WorldRegion
*
* #ORM\Table(name="world_region")
* #ORM\Entity(repositoryClass="AppBundle\Repository\WorldRegionRepository")
*
* #UniqueEntity(fields={"code"}, message="Region code must be unique")
*/
class WorldRegion
{
/**
* #var integer
*
* #ORM\Column(name="region_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Assert\NotBlank()
*
* #ORM\Column(name="region_code", type="string", length=5,
* unique = true, nullable=false)
*/
protected $code;
// ...
}
And WorldCountry:
/**
* WorldCountry
*
* #ORM\Table(name="world_country")
* #ORM\Entity(repositoryClass="AppBundle\Repository\WorldCountryRepository")
*
* #UniqueEntity(fields={"iso2"})
* #UniqueEntity(fields={"iso3"})
* #UniqueEntity(fields={"region"}) // This line should be removed!
*/
class WorldCountry
{
/**
* #var integer
*
* #ORM\Column(name="country_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var WorldRegion
*
* #ORM\ManyToOne(targetEntity="WorldRegion",
* cascade={"all"}
* )
* #ORM\JoinColumn(name="region_id", referencedColumnName="region_id")
*/
protected $region;
// ...
}
As seen WorldCountry has unique constraint on region field. In this case it is logical error. Because ManyToOne states that in database table for WorldCountry, countries are unique, but regions - not. But I, using #UniqueEntity, claim that regions also should be unique. So, annotation #UniqueEntity(fields={"region"}) should be removed from WorldCountry entity.
P.S. Please let me know if I can improve my answer.

In the Symfony2 documentation you can embed a collection of forms to add object, but how do you select from existing objects to add to the collection?

Not sure that was the clearest way of asking my question, but basically I want to try and achieve the below in Symfony2:
It is important to note that I have tried the embed a collection as per the documentation, but I am not looking to create a new object and add to the collection I am trying to select an existing object to add to the collection.
I currently have a product group entity that simply consists of an ID, Name and collection of products:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Product Group
* #ORM\Table(name="productGroups")
* #ORM\HasLifecycleCallbacks
*/
class ProductGroup
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=255)
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="Product", mappedBy="productGroups")
**/
protected $products;
...
I then have the product entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Product
* #ORM\Table(name="products")
* #ORM\HasLifecycleCallbacks
*/
class Product
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=255)
*/
protected $name;
/**
* #var string
* #ORM\Column(name="code", type="string", length=20)
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="ProductGroup", mappedBy="products")
**/
protected $productGroups;
...
What I am trying to do is create a form that allows me to create and update a product group.
I want to enter a name for the product group and then select products by ticking a check box against the rows of a table. The idea being that I could use something like datatables to filter the products to make it easier to add.
To start this off I have added a Product Group Type:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductGroupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('products', 'collection', array(
'type' => new ProductType()
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\ProductGroup',
));
}
public function getName()
{
return 'product_group';
}
}
I then have the Product Type:
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('code');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
));
}
public function getName()
{
return 'product';
}
}
Now I am very confused about how I can have a table of products that have a checkbox that once selected and the form saved are saved against the product group.
Can anyone help or suggest any other way of doing this?
I have attached an image of what I am trying to achieve if that helps.
Thanks in advance.
Use the entity form type:
http://symfony.com/doc/current/reference/forms/types/entity.html
$builder->add('name', 'text')
->add('code', 'text')
->add(
'ProductGroups', 'entity', array(
'class' => 'YourBundle:ProductGroups',
'label' => 'Select ProductTypes',
'placeholder' => 'Select ProductGroups',
'multiple' => true,
'expanded' => true
))
->getForm();
}
The Multiple tag will generate a list of checkboxes that allows you to select multiple productGroups