Symfony2 - Entity Collection Choice Restriction - php

I have the following setup:
Entity: Customer
Entity: Account
Entity: Message
Now imagine the following problem:
The account 'Mark' is in charge of two customers, 'Ben' and 'Lili'.
The account 'Tim' is in charge of two other customers, 'Tom' and 'Ronny'.
The account 'Ben' now wants to send a message to his customers. In a form he can choose the customers he would like to send the message to. Those will be saved as an ArrayCollection in the Message entity (in relation with entity Customer).
However, later on account 'Tim' can view this message and also send it to his customers the same way - by adding his customers to the list of recepients.
Problem is: When 'Tim' adds his recepients, he should not see the recepients of 'Ben' as this is none of his concern.
Visual explanation: http://jsfiddle.net/q0nn62o5/
My solution so far:
I created a custom FormType called 'AccountCustomerType'. This FormType is an entity which includes the customers of one particular account as choices:
$builder
->add('customer', 'entity', array(
'class' => 'AppBundle:Customer',
'choices' => $this->customers,
));
This FormType is used in the main form as a collection:
$form->add('recepients', 'collection', array(
'type' => new AccountCustomerType($customers),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false,
));
Printing form...:
<div class="recepients" data-prototype="{{ form_widget(form.recepients.vars.prototype)|e }}">
{% for customer in form.recepients %}
<div>
{{ form_widget(customer) }}
</div>
{% endfor %}
</div>
One problem left:
I can now choose from the customers that one account is in charge of. However, the recepients I am not in charge of are still shown as blank select fields. How can I hide these? I don't want to duplicate messages to seperate recepients as there are a couple more features connected to this.

You can filter a collection in a form by restricting the query results.
E.g. something like:
$accountData = $this->getEntityManager()
->createQueryBuilder()->select('a, c')
->from('YourAccountBundle:Account', 'a')
->join('a.customers', 'c') // assuming there is a relationship like this
->where('a = :yourAccountManager')
->setParameter('yourAccountManager', $accountEntity)
->getQuery()->getResult();
Then use $accountData in your parent form.
This will restrict the Customer entities shown in the form to only the ones linked to $accountEntity.
Note this needs to be the first fetch of this relation in your page load, if you lazy load it with doctrine then it'll return all Customer entities regardless of filtering.

Related

Formatting choice label of EntityType

I have a Symfony form that display a list of patients with a checkbox to select each one. I'm trying to format the display of the choice_label sent to the form to make it more readable.
I've tried several PHP functions and HTML tags in the return statement to try and add formatting or even spaces but was unsuccessful.
->add('sampleExtractions', EntityType::class, [
'class' => SampleExtraction::class,
'multiple' => true, 'expanded' => true,
'choices' => $this->sampleExtractionRepository->getDNAExtractions(), 'by_reference' => false,
'choice_label'=> function(SampleExtraction $sampleExtraction) {
$patientName = $sampleExtraction->getPatientSample()->getPatient()->getLastNameFirst();
$patientMRN = $sampleExtraction->getPatientSample()->getPatient()->getMRN();
$patientDOB = $sampleExtraction->getPatientSample()->getPatient()->getDateOfBirth();
return 'Name: ' . $patientName . 'MRN: ' . $patientMRN . $patientDOB->format('Y-m-d'); }
])
As you can see I've tried adding details to the return but it all runs together.
I would like to be able to format the output to make it more readable for the end user.
For this you have to create (or update) your form theme, check How to Work with Form Themes > Fragment Naming for Individual Fields. Check also a good example in Symfony demo application.
Here for example
{% block _YOUR_FORM_TYPE_NAME_ sample_extractions_widget %}
{# your custom HTML here #}
{% endblock %}

Symfony 2.8 - forms fields depending on user status

I am wondering how to solve this problem:
I have form with 4 fields. I want 4th field to be dependent on user status (logged or unlogged). For logged user I will get ID from session but unlogged user should provide username manually.
I dont know which option should i use. Inherit_data, two form types (two much duplicated code) or validation groups based on the submitted data. Any ideas?
Ok, There are several ways to achive that.
Take a Look at FormEvents. In your case it would be FormEvents::PRE_SET_DATA and then read about dynamic forms
I personly prefer to do following
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$builder->add( ... )
//$builder->add( ... )
//$builder->add( ... )
//each-event with own method, but of cource it can be a callback like in a tutorial above
$builder->addEventListener(FormEvents::PRE_SET_DATA, array(this, 'onPreSetData');
}
and in the same class there is a method onPreSetData
public function onPreSetData ( FormEvent $formEvent )
{
$form = $formEvent->getForm();
$user = $formEvent->getData();
//pseudo-code
if( $user->isLoggedIn() )
{
$form->add('user', HiddenType::class, array(
));
}
else
{
$form->add('user', TextType::class, array(
'label' => 'Username',
'required' => true,
'constraints' => array(
new NotBlank(array('message' => 'please enter username...')),
// new YourCustomValidator(array('message' => 'not enough minerals...')),
)
));
}
}
I personally think a more elegant solution is to pass the user to the form builder from your controller. It's been covered in this answer here:
how to check the user role inside form builder in Symfony2?
you can create form by individual fields like
{{ form_row(form.username) }}
{{ form_row(form.email) }}
{{ form_row(form.phone) }}
{% if(app.user) %}
//your conditional field
{% endif%}
By this way, you have to create submit button as well as csrf token if there
I hope this will quite helpful :)

Symfony - manyToMany not getting saved

The Scenario
I have two Entities linked together with manyToMany relation.
Entities
User
Interest
So on a user's profile form there is a field called Interests which is rendered using select2.
Now a user can select as many Interests as they want and upon saving doctrine is doing the nice job of saving the selected Interests in the linked table. When I reload the profile page I can see that interests that I already selected.
The Problem
Although the form field is linked with an Interest entity
$form->add('interest', EntityType::class, array(
'class' => 'AppBundle\Entity\Interest',
'multiple' => true,
'expanded' => false,
'by_reference' => false)
A user can also add interests of their own which do not exist in Interest table with the help of Tagging Support on front end and on backend to save this information I have Form Event Subscriber in place that checks if any of the Interests submitted by the user does not exist in Interest table add them and it is here where I get the following exception
Message
This value is not valid.
Origin
interest
Cause
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[interest] = [0 => 1, 1 => 4, 2 => 7, 3 => www]
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "interest": 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
Here is the Event Subscriber code
namespace AppBundle\Form\EventListener;
use AppBundle\Entity\Interest;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
class AddProfileFieldSubscriber implements EventSubscriberInterface
{
protected $authorizationChecker;
protected $em;
function __construct(AuthorizationChecker $authorizationChecker, EntityManager $em)
{
$this->authorizationChecker = $authorizationChecker;
$this->em = $em;
}
public static function getSubscribedEvents()
{
// Tells the dispatcher that you want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(
FormEvents::PRE_SUBMIT => 'onPreSubmit'
);
}
/**
* #param FormEvent $event
*/
public function onPreSubmit(FormEvent $event){
$interestTags = $event->getData();
$interestTags = $interestTags['interest'];
foreach($interestTags as $interestTag){
$interest = $this->em->getRepository('AppBundle:Interest')->findOneBy(array('id' => $interestTag));
if(!$interest){
$newInterest = new Interest();
$newInterest->setName($interestTag);
$this->em->persist($newInterest);
$this->em->flush();
}
}
}
}
The Attempt
I updated the form code to as following by adding choice_value
$form->add('interest', EntityType::class, array(
'class' => 'AppBundle\Entity\Interest',
'multiple' => true,
'expanded' => false,
'by_reference' => false,
'choice_value' => 'name'
)
);
and I changed the query inside the Event Subscriber from
$interest = $this->em->getRepository('AppBundle:Interest')->findOneBy(array('id' => $interestTag));
to
$interest = $this->em->getRepository('AppBundle:Interest')->findOneBy(array('name' => $interestTag));
This worked perfectly at first but when I reload the profile page my interest field appears empty
The reason why it appears empty is because (my assumption) of id="select2-user_interest-result-5z18-Education" I think that needs to look something like id="select2-user_interest-result-5z18-66"
<ul class="select2-results__options" role="tree" aria-multiselectable="true" id="select2-user_interest-results"
aria-expanded="true" aria-hidden="false">
<li class="select2-results__option" id="select2-user_interest-result-5z18-Education" role="treeitem"
aria-selected="false">Education
</li>
<li class="select2-results__option" id="select2-user_interest-result-rdka-History" role="treeitem"
aria-selected="false">History
</li>
<li class="select2-results__option select2-results__option--highlighted"
id="select2-user_interest-result-lfq4-Architecture" role="treeitem" aria-selected="false">Architecture
</li>
<li class="select2-results__option" id="select2-user_interest-result-qqiq-Entrepreneurship" role="treeitem"
aria-selected="false">Entrepreneurship
</li>
<li class="select2-results__option" id="select2-user_interest-result-qutx-Technology" role="treeitem"
aria-selected="false">Technology
</li>
<li class="select2-results__option" id="select2-user_interest-result-sfx4-Engineering" role="treeitem"
aria-selected="false">Engineering
</li>
I crossed check the data in Interest table and I can see the new interests added by the user which did not exist before, so its working but on front end its not being displayed. Out of curiosity i removed the choice_value and then reloaded the profile page and I could see the new interests
I will really appreciate if anything can push me in right direction and let me know what am I missing and how can i get this to work.
You can't do that as EntityType extend ChoiceType which doesn't allow adding new values in the choice list.
You should use CollectionType like in the cookbook http://symfony.com/doc/current/cookbook/form/form_collections.html
To make a proper select in your code, you'll also have to insert in the view, along with your form, the full list of all interests.
Then your view should looks like :
<select name='{{ form.interest.vars.full_name }}' id="{{ form.interest.vars.id }}" class='select2'>
{% for interestList as interestItem %}
<option value='{{ interestItem.id }}' {% if some_logic_to_check_whether_the_item_is_selected %}selected='selected'{% endif %} >{{ interestItem.name }}</option>
{% endfor %}
</select>

Symfony2 editForm datetime object in formbuilder but I need it to be a string

For starters, I'm new to symfony2 -> coming from Laravel and ZF1 I have to admit it is not what I expected. I had a lot of struggle with simple tasks and by the moment of writing this twig editing is still a pain.
So scenario:
I have a invoice form with 2 datetime fields in it, I really hate that ugly 3 select field that is generated so I included my jQuery datepicker.
http://tinypic.com/r/eg5kyg/8
For my initial insertion of data because of the datetime I needed to create my form with a input type "text" :
$form=$this->createFormBuilder($invoice)
->add('date_ref','text', array(
'required'=> true))
->add('date_sent','text', array(
'required' => true ))
In this way that ugly 3 select datepicker is not shown and my users can select a date in an elegant way.
All good until the edit form:
I don't know why I didn't managed to get this done as i initially wished :
$invoice = $em->getRepository('AppBundle:Invoice')->find($id);
$form=$this->createFormBuilder($invoice)
->add('date_ref','text', array(
'required'=> true))
->add('date_sent','text', array(
'required' => true ))
->add('value','integer',array('required'=>true))
->add('nr_ref','text',array('required'=>true))
So i wanted to create a form from a entity with his properties but in the view
<div class="form-group">
{{ form_label(theForm.date_ref, 'Date Refferenced', { 'label_attr': {'class': 'control-label col-md-5'} }) }}
{{ form_errors(theForm.date_ref) }}
<div class="col-md-6">
{{ form_widget(theForm.date_ref,{'attr': {'class': 'form-control', 'data-provide':'datepicker'}}) }}
</div>
</div>
Was not working so I couldn't create my Edit Form as my first form with the date_ref input with a "text" value.
I searched and found out that creating a form from a entity data you need to create a "Type" so I created my "InvoiceType"
which has :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('is_paid')
/* not working
->add('date_ref', 'date', array('input' => 'datetime',
'format' => date('y-M-d'),
'widget' => 'single_text'))
*/
->add('date_ref') //this is a datetime obj which results in those 5 selects
->add('date_sent')
->add('value')
->add('nr_ref')
->add('name_provider')
;
}
$invoice = $em->getRepository('AppBundle:Invoice')->find($id);
$form = $this->createForm(new InvoiceType(), $invoice);
But because this method creates a form from the entity, if I change the type from the buildForm() function .. I get an error that he expects a Object of Datetime instead of a string ("text").
So now I am stuck at making the Edit Form .. that would be a type, 1 input Text and pre-populated with the date from the Database(something like 2014/02/02) using datepicker and when you click on it, it should appear as in the create form.
Like I said I am new, I learned a lot of things that I normally done other ways in other MVC and I struggle a lot with this datetime Object and symfonys 3 select datetime.
http://tinypic.com/r/28bz5h4/8
<- This looks awful
Thanks a lot Khalid Junaid, after I looked up the post you have mentioned I finally got it working as i wanted:
Solution:
->add('date_ref', 'date' ,array(
'widget'=> 'single_text',
'format'=>'d/M/y'))
->add('date_sent','date' ,array(
'widget'=> 'single_text',
'format'=>'d/M/y'))

How to mark selected items as checked in laravel multiselect dropdown

I am using this multiselect dropdown plugin . I can get all the ids of the selected items in the dropdown during the store method. However during the edit method when ever i am trying to load the entity that has multiple values , i am unable to mark the items as checked in the dropdown.
so for example -
Suppose I am working with Contacts. Each contact can belong to many categories. There is a belongsToMany relationship between the contacts and the categories. Whenever I am adding a new contact (and if the user has selected many categories) i get the id of all the categories and assign it to the contact. Now when I am trying to load the contact again, i have to display the list of categories that were selected for this contact - which i have ben unable to do so till now.
Travis answers really helped me a lot. Hence I am marking this as the correct answer. However there were some updates that I had to do . Following is what I had to do ..
#if(isset($contact))
<?= Form::select(
'category_ids[]',
Category::lists("name", "id"),
$contact->categories()->select('categories.id AS id')->lists('id'),
[
'class' => 'form-control multiselect',
'multiple'
]
)?>
#else
{{ Form::select("category_ids[]", Category::lists("name", "id"), Input::old("category_id"), array( "class" => "form-control multiselect" , "multiple" => "multiple" )) }}
#endif
I am using the same form for the create and edit operations , so in the create form , it was throwing me an error on the contact->categories line which is true because in the create method the contact is null. Hence the check.
Here's how I accomplish multi-selects in Laravel 4:
<?= Form::select(
'category_ids[]',
App::make('Category')->lists('name', 'id'),
$contact->categories()->select('categories.id AS id')->lists('id'),
[
'class' => 'form-control',
'multiple'
]
)?>
The resulting select markup looks like this:
<select class="form-control" multiple="multiple" name="category_ids[]">
<option value="1" selected="selected">category 1</option>
<option value="2">category 2</option>
</select>
And then, when you update, you'll need to add this line after validating your model:
$contact->categories()->sync(Input::get('category_ids'));
Use this in both your create and edit forms. In your create action,
$contact->categories() will be empty, so the select will not be populated, but in the edit action, you will get the properly selected values.
Edit: In order to share the form like this, you'll need to pass in a new instance of the contact model in your create action like so:
public function create()
{
$contact = App::make('Contact');
return View::make('contact.create', concat('contact'));
}
In your shared form, $contact will always be available even if it's not yet persisted.

Categories