Symfony2 form entity processing - forms

I have a User entity that has an ArrayCollection of Subscriptions. I have these setters and getters.
public function addSubscription(\Doge\WowBundle\Entity\Subscription $subscription)
{
$this->subscriptions[] = $subscription;
return $this;
}
public function removeSubscription(\Doge\WowBundle\Entity\Subscription $subscription)
{
$this->subscriptions->removeElement($subscription);
}
public function getSubscriptions()
{
return $this->subscriptions;
}
There is another entity called Plan. A Subscription is basically the intermediate entity between User and Plan, except it holds an extra field so it is necessary to be a dedicated entity.
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="subscriptions")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
/**
* #ORM\ManyToOne(targetEntity="Plan", inversedBy="subscriptions")
* #ORM\JoinColumn(name="plan_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $plan;
/**
* #ORM\Column(type="date")
*/
protected $nextDue;
Otherwise, it would just be a many-to-many relationship with an automatic intermediate table generated.
Now in the User registration form, a user can choose between plans available in the Plans table with this code in the FormBuilder
$builder->add('subscriptions', 'entity', array('class' => 'DogeWowBundle:Plan'))
How can I create a new Subscription object given the Plan object? Would I do so in the controller? Use a datatransformer? What is the best practice for this?

you have 2 options, the first is that you have a form that contains a form. One form is mapped to your user and the second is mapped to your subscription. So basically in your user form you would have
$builder->add('subscriptions', new SubscriptionsType())
and within that SubscriptionsType you would have your entity for plans like:
$builder->add('plan', 'entity', array(
'class' => 'DogeWowBundle:Plan',
'property' => 'plan_name',
));
this way your subscriptions will be auto generated and updated as necessary.
You could also use a data transformer, but i personally like using forms within forms.

Related

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).

Symfony2 - validating multiple fields, DTO, no annotations

I know that similar questions have been asked here on SO, but answers don't satisfy me.
Having following:
Standard form with fields foo, bar, baz, bat, x
DTO (not entity, dummy object, no annotations)
Constraints on some fields attached using FormBuilder
Form is used in multiple places.
In some places that form is used, I would like to add single validator to fields foo, bar, baz. This validator can get only values of those fields or whole propagated DTO. It should have access to DI Container to call service that will check data against database.
For now I'm thinking about one of two solutions:
Adding extra constraint to the form in controllers that require it (sound dirty)
Adding extra field to form constructor/DTO (not boolean but business logic that tells if extra validation is needed) and adding extra constraint to form.
Problem is that I can't figure out how to handle either of those cases.
Finally, I want to emphasize that I don't want to use validation groups and annotations - both will add extra dependencies and logic to DTO.
Found the answer. For short answer you can check out This Matt Daum post.
Here's full example, presenting not only how to create custom form validator, but also how to inject services and extra data to form (because that was my case).
If you want simple receipe, go straight to the bottom.
Let's have DTO:
class MyFormDTO
{
/** #var string */
private $name;
/** #var string */
private $surname;
/** #var string */
private $phone;
/** getters and setters ommited */
}
Now, define dependencies in form. First two are services, last one (Calendar) is some extra data needed from controller.
class MyForm extends AbstractType
{
(fields hidden)
/**
* #param Sender $sender
* #param TranslatorInterface $translator
* #param Calendar $calendar
*/
public function __construct(Sender $sender, TranslatorInterface $translator, Calendar $calendar)
{
$this->translator = $translator;
$this->sender = $sender;
$this->calendar = $calendar;
}
}
Now there are two ways - if you only need services in your form, you can just define your form as a service. If you, like me, need extra data, you need to write form factory service:
class MyFormFactory
{
(fields hidden)
/**
* #param Sender $sender
* #param TranslatorInterface $translator
*/
public function __construct(Sender $sender, TranslatorInterface $translator)
{
$this->sender = $sender;
$this->translator = $translator;
}
/**
* #param Calendar $calendar
*
* #return MyForm
*/
public function getMyForm(Calendar $calendar)
{
return new MyForm($this->sender, $this->translator, $calendar);
}
}
Let's define this factory as a service with right dependencies:
mybundle.form.myform_factory:
class: MyBundle\Service\FormFactory\MyFormFactory
arguments: [ #text_message.sender, #translator ]
How to get the form in controller? Easy as that:
class MyController extends Controller
{
/**
* #ParamConverter("calendar", options={"mapping"={"calendarId":"id"}})
*
* #param Request $request
* #param Calendar $calendar
*
* #return Response
* #throws Exception
*/
public function myAction(Request $request, Calendar $calendar)
{
$formDTO = new MyFormDto();
$myForm = $this->get('mybundle.form.myform_factory')->getMyForm($calendar);
$form = $this->createForm($myForm, $formDTO);
(handling post hidden)
}
}
And now the most important part - we have services injected properly into our form. How to use them and validate selected data? like this:
class MyForm extends AbstractType
{
(fields hidden, constructor shown in previous example)
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var MyFormDTO $myDTO */
$myDTO = $options['data'];
(build form as usual, using services and data from $options and $this->calendar injected by controller and factory)
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults([
'csrf_protection' => true,
'constraints' => [
new Callback(function (MyFormDTO $data, ExecutionContextInterface $context) //notice that we have access to fully propageted DTO here
{
//use injected service
$isValid = $this->sender->validateSomething($data->getSurname(), $data->getPhone());
if (false === $isValid)
{
$context
->buildViolation($this->translator->trans('wrong_surname_phone_pair'))
->addViolation();
}
return $isValid;
})
],
]);
}
}

Entity field form Symfony2

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

Symfony2 Doctrine insert entity with related - NOT UPDATE

I have problem with inserting User entity with related entity UserProfile.
class User
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="id", type="integer", options={"unsigned"=true})
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var UserProfile
*
* #ORM\OneToOne(targetEntity="UserProfile", mappedBy="user", cascade={"persist", "remove"})
*/
protected $profile
....
}
class UserProfile
{
/**
* #var User
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\OneToOne(targetEntity="User", inversedBy="profile" )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
.....
}
Standard User-UserProfile bidirectional relation. When I submit form contains fields from User entity and UserProfile entity, $form->getData() gives me User object with UserProfile. So far so good.
Mysql generate a unique identity by auto-increment PK. To insert entity I have to do:
$em->persist($user)
$em->flush()
to get PK as id for persist and flush UserProfile.
But doctrine CANT insert $user because $user object has related $user->profile received from form Type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', 'email', array('label' => 'email'))
->add('profile', new RegistrationProfileFormType(), array('required'=>true,))
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'User',
'cascade_validation'=>true,
));
}
Update: Exception msg
Entity of type Entity\UserProfile has identity through a foreign entity Entity\User, however this entity has no identity itself. You have to call EntityManager#persist() on the related entity and make sure that an identifier was generated before trying to persist 'Entity\UserProfile'. In case of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call EntityManager#flush() between both persist operations.
Q: How to handle this problem properly ?
Sorry for my rusty English.
The problem is you are going to perist objects through not owning side. Owning side is the one with "inversedBy" property. And you should persist objects that way.
You can read more about it here: http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html
So, example for your case:
$profile->setUser($user);
$em->persist($profile).
$em->flush();
Although, if you want to keep the way you already have, there are two options:
1) Make User entity as the owning side and UserProfile as the inverse side
or
2) In your profile setter in User class do something like that:
public function setUserProfile(UserProfile $profile)
{
$this->profile = $profile;
$this->profile->setUser($this);
}
edit:
I've just notice that you don't have "id" field in your UserProfile entity - probably this is the reason. the easiest way would be separate primary and foreign keys. But if you want treat foreign as primary take a look at this article: http://docs.doctrine-project.org/en/latest/tutorials/composite-primary-keys.html#identity-through-foreign-entities

Joining-Table with Metadata Impairs Getters/Setters - Doctrine 2

I'm writing a feature which calls for the records of my joining table to carry extra metadata (Joining-Table with Metadata). I've attempted to implement this in accordance with this section of the Doctrine documentation.
See below for example Entity definitions.
The challenge now is that getGroups and setGroups do not yield/set Group entities (& the same is true from the Group instance perspective), but they yield GroupUser entities.
This adds a substantial delay to process of managing this relationships, which so far have been extremely smooth - for example, I cannot simply add, remove, or check for the existence of a Group to the collection which getGroups yields.
Can anyone identity any errors in my implementation, or else recommend a more fluid way of implementing this concept?
Thanks in advance for any input.
EDIT:
My main concern is this: using this implementation, retrieving a collection of Users from a Group entity requires this Entity method's mediation:
public function getUsers() {
return $this->users->map(function($groupUser){
return $groupUser->getUser();
});
}
I'm concerned that this could imply a major performance hit down the road. Am I incorrect?
Furthermore, how does one re-implement the setUsers method?
Group entity:
<?php
/**
* #Entity
* #Table(name="group")
*/
class Group {
/**
* #Column(type="integer", nullable=false)
* #Id
*/
protected $id = null;
/**
* #OneToMany(targetEntity="GroupUser", mappedBy="group")
* #var \Doctrine\Common\Collections\Collection
*/
protected $users;
}
User entity:
<?php
/**
* #Entity
* #Table(name="user")
*/
class User {
/**
* #Column(type="integer", nullable=false)
* #Id
*/
protected $id = null;
/**
* #OneToMany(targetEntity="GroupUser", mappedBy="user")
* #var \Doctrine\Common\Collections\Collection
*/
protected $groups;
}
Joining entity:
<?php
/**
* #Entity
* #Table(name="group_user")
*/
class GroupUser {
/**
* #Id
* #ManyToOne(targetEntity="User", inversedBy="groups")
* #JoinColumn(name="userId", referencedColumnName="id")
*/
protected $user;
/**
* #Id
* #ManyToOne(targetEntity="Group", inversedBy="users")
* #JoinColumn(name="groupId", referencedColumnName="id")
*/
protected $group;
/**
* #Column(type="integer")
*/
protected $relationship;
}
Related -
Same goal, slightly different approach, which consistently produced errors once the resulting collections were manipulated: http://www.doctrine-project.org/jira/browse/DDC-1323
Supports the approach, no technical details: Doctrine 2 join table + extra fields
I've found just two examples (see question) of entity definitions for this specific type of relationship, however no example code for how they're used. As such it was fairly unclear how fluid (or otherwise) the resulting setters & getters could be expected to be. Hopefully this code will help clear up the approach for anyone else making a similar attempt.
The ideal solution under the circumstances (thanks #doctrine # freenode) was to implement a custom repository - a more flexible & efficient place for creating & managing the association.
Example Custom Repository for Join-Table with Metadata Class - Solution accompanies code in original question
<?php
use Doctrine\ORM\EntityRepository;
class GroupUserRepository extends EntityRepository {
/**
* #param \User $user
* #param \Group $group
* #param integer $type One of the integer class constants defined by GroupUser
* #param string $role Optional string defining user's role in the group.
* #return \GroupUser
*/
public function addUserToGroup(User $user, Group $group, $relationship, $role = '') {
$groupUser = $this->findOneBy(array('user' => $user->getId(), 'group' => $group->getId()));
if(!$groupUser) {
$groupUser = new GroupUser();
$groupUser->setGroup($group);
$groupUser->setUser($user);
$groupUser->setRole($role);
$groupUser->setRelationship($relationship);
$this->_em->persist($groupUser);
}
return $groupUser;
}
/**
* #param \User $user
* #param \Group $group
* #return null
*/
public function removeUserFromGroup(User $user, Group $group) {
$groupUser = $this->findOneBy(array('user' => $user->getId(), 'group' => $group->getId()));
if($groupUser)
$this->_em->remove($groupUser);
}
}
Then, from the join-table class, modify the Entity meta-data accordingly to specify the custom repository.
<?php
/**
* #Entity(repositoryClass="\Path\To\GroupUserRepository")
*/
class GroupUser {
// ...
}
This causes the custom repository to yield in place of the default one, making a proxy method from the Entity class simple.
<?php
/**
* #Entity
*/
class Group {
/**
* #param \User $user
* #param integer $relationship One of the integer class constants defined by GroupUser
* #param string $role Optional string defining user's role in the group.
* #return \GroupUser
*/
public function addUser(User $user, $relationship, $role = '') {
return $this->_em->getRepository('GroupUser')
->addUserToGroup($user, $this, $relationship, $role);
}
}
And things are about as manageable as they were before.