How can I use two property on form_widget - symfony

I can show form by this codes.
$builder->add('icon', 'entity', array(
'class' => 'UserBundle:IconPics',
'property' => ‘label', 'expanded' => true, 'multiple' => false,
));
in twig
{{ form_label(form.icon) }}
{{ form_widget(form.icon) }}
There appeares radiobutton labeled 'PictureA' ,'PictureB','PictureC'....
But I want to use not only 'label' property but also 'pic' entity as well to make
the link to jpg file.
How can I use two property via one form_widget?
My code are below.
I have tables such as
in User.php
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\IconPics", inversedBy="icon")
* #ORM\JoinColumn(name="icon", referencedColumnName="id",nullable=true)
*/
private $icon;
in Icon.php
/**
* #var string
*
* #ORM\Column(type="string")
*/
private $label;
/**
* #var string
*
* #ORM\Column(type="string")
*/
private $pic;
/** * * #ORM\OneToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="icon")
* #ORM\JoinColumn(name="icon", referencedColumnName="id")
*/
private $icon;
icon table is like
|id |pic |label
|1 |aaa.png |pictureA
|2 |bbb.png |pictureB
|3 |ccc.png |PictureC

This might not be the most elegant solution but it's a quick one. The proper solution would probably be writing a custom field type which would involve writing a lot of code.
There is a simple trick to achieve what you want. Just add a method to your entity that will be used to get both values at once:
public function getFormViewData()
{
return $this->getLabel() . ';' . $this->getPicture();
}
Then use this method in the property attribute:
$builder->add('icon', 'entity', array(
// ...
'property' => 'formViewData',
));
Finally use the twig split filter to separate get the two values in your template (see the example) and adapt your template (i.e. by overriding the form_label widget ) to use these instead of the original value.
{#
inside the overriden widget set the label correctly before rendering it
and extract the picture url.
#}
{% set label = label|split(';')|first|trans %}
{% set picture = label|split(';')|last %}
{{ label }}
{{ picture }}
Got the idea?

Related

Symfony 4. ManyToMany association with attribute. Custom form to insert attribute value, how do I?

A Symfony 4 project. I am not able to see ho to code the following spec.
Say I have two entities Alpha and Beta.
When I create an Alpha object alpha, I wish to associate (ManyToMany) to it one or more Beta objects. I know how to render new and edit Forms to do so.
I wish to enrich the AlphaBeta join table with an attribute, say the Cost to associate a Beta to an Alpha. The issue is that I am not able to enrich the forms above as to insert or edit a Cost value, when I create a Alpha object and associate to it a Beta object.
Is there a standard way to code a situation of this kind?
I read that the way to go is to have two OneToMany associations Alpha->AlphaBeta, and Beta->AlphaBeta, but even doing so I am not able to define/render a Form for Alpha as to create a new Alpha object, to associate to it a Beta object (or more) and to assign to such association a Cost value.
Your advise is very welcome. Thanks
Generally, if the relation/association itself should have an attribute, then the many-to-many mapping in doctrine isn't sufficient anymore, since associations can't store additional data. So you correctly note, that you need an extra AlphaBeta entity/class that holds the data. (As already posted by Ali Kazemi, there is a tutorial for this)
But you wonder about how the cost field can be added to your form...
Since the cost is part of the AlphaBeta entity/class in your case, the form field should be in a form type AlphaBetaType that - probably depending on the options provided - should render an AlphaType and/or BetaType sub form, and a cost form field. Custom form themeing can display it in a way, that it doesn't appear as if it was a subform, if that is a concern. (However, it should be noted that custom form theming can be annoying at times...)
In general, the form structure/hierarchy usually very much resembles the entity structure/hierarchy. And only sporadically hiding data or mapping/transforming it to be displayed or handled differently.
Alternatively you can add an unmapped form field and later store that in your AlphaBeta, but that on average isn't simpler, since it involves "manual" handling.
It seems I did find a way, indeed it is pretty straightforward Synfony 4 code.
Hope it could be useful to somebody.
(note: I used php bin/console make:entity/crud/form to write the needed scripts. Below how I needed to modify the code I got from make)
So, say I have Alpha and Beta entities.
I wish to have a form to create a new Alpha object, to associate to it one or more Beta objects, and to fill in a Cost value for each Alpha-Beta association. I want a edit form too.
First I create a new AlphaBeta entity, whose fields are:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Alpha", inversedBy="alphabetas", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $alpha;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Beta", inversedBy="betaalphas", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $beta;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $cost;
Within class Alpha, I need
/**
* #ORM\OneToMany(targetEntity="App\Entity\AlphaBeta", mappedBy="alpha", orphanRemoval=true, cascade={"persist"})
*/
private $alphabetas;
with usual 'getAlphaBeta, addAlphaBeta and removeAlphaBeta methods. (similarly for Beta)
I create usual CRUD controllers for the new AlphaBeta entity. To have an AlphaBeta Form which could be used as a subform too, I define
class `AlphaBetaEmbeddedForm` extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder ->add('Beta', EntityType::class, array(
'class' => Beta::class,
'multiple' => false,
'expanded' => true,
'choice_label' => 'betaTitle' ))
->add('cost', TextType::class);
if(empty($options['remove_alpha_field'])) {
$builder->add('Alpha', EntityType::class, array(
'class' => Alpha::class,
'multiple' => false,
'expanded' => true,
'choice_label' => 'alphaTitle'
));}}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => AlphaBeta::class,
'remove_alpha_field' => false,
]);
}}
remove_alpha_field is the trick that let me use the form above as a subform within a form to create an Alpha object:
class AlphaType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('alphaTitle')
->add('AlphaBetas', CollectionType::class, array(
'entry_type' => AlphaBetaEmbeddedForm ::class,
'entry_options' => ['label' => true, 'remove_alpha_field' => true],
'allow_add' => true,
'label' => 'Betas',
'by_reference' => false
));}
To render the subforms within the main form, I need some JS, as suggested here, to be inserted within the new.html.twig and edit.html.twig templates for Alpha:
{% block javascripts %} <script type="text/javascript">
jQuery(document).ready(function () {
$("#add-another-collection-widget").click(function(e){
var list = jQuery(jQuery(this).attr('data-list-selector'));
var counter = list.data('widget-counter') | list.children().length;
var newWidget = list.attr('data-prototype');
newWidget = newWidget.replace(/__name__/g, counter);
counter++;
list.data('widget-counter', counter);
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
});});</script>{% endblock %}
To apply the latter, it seems you need to write each single row in the main form template _form.html.twig:
{{ form_start(form) }}
<div class="my-custom-class-for-errors">
{{ form_errors(form) }}
</div>
<div id="alphaTitle">
{{ form_row(form.alphaTitle) }}
</div>
{% if form.AlphaBetas %}
<b>Betas</b> </br></br>
{% for Beta in form.AlphaBetas %}
{% for BetaField in Beta %}
{{ form_row(BetaField) }}
{% endfor %}
{% endfor %}
{% endif %}
<div id="AlphaBeta-fields-list"
data-prototype="{{ form_widget(form.AlphaBetas.vars.prototype)|e }}"
data-widget-tags="{{ '<p></p>' |e }}"
data-widget-counter="{{ form.children|length }}">
{% for AlphaBetaField in form.AlphaBetas.vars.prototype.children %}
{{ form_row(AlphaBetaField) }}
{% endfor %}
</div>
<button type="button" id="add-another-collection-widget" data-list-selector="#AlphaBeta-fields-list">Insert Beta</button>
</br>
<button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
That's all.
Note: within the main form to edit an Alpha object, I would like to insert a delete buttons for each Beta associated object. As much as I can see, it is not possible, since it would mean to insert a html form within an html form. Since I may delete a AlphaBeta association by the corresponding AlphaBeta delete action, it is not a big deal.
If you see how I can improve my code, you are welcome to advise me.

What is the better solution to get the Entity I want in a biderectional OneToMany relation?

I have one entity Article and an other entity Image with a bidrectional relation OneToMany and ManyToOne :
class Article
{
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Image", mappedBy="article")
*/
private $images;
}
class Image
{
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Article", inversedBy="images")
* #ORM\JoinColumn(nullable=true)
*/
private $article;
}
In my controller I use #paramconverter to get the article I want :
/**
* #Route("/blog/{slug}", name="article")
* #ParamConverter("article", class="AppBundle:Article")
*/
public function articleAction(Article $article)
{
return $this->render('default/article.html.twig', array(
'article' => $article,
));
}
Now my problem is that I want to identify the ONLY image with the attributes "main = true" in all the "article.images" I have.
What is the best solution?
In my wiew I can do somehting like this but it's not the best I think :
{% for image in article.images %}
{% if image.main %}
<img src="{{ asset( image.src ) }}" alt="{{ image.alt }}" title="{{ image.title }}">
{% endif %}
{% endfor %}
I'd like to use something like :
{{ article.mainImg }}
How can I achieve this please? And is this the best solution?
Doctrine provides a collection filter mechanism you could use to get the "main image":
public function articleAction(Article $article)
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("main", true))
->setMaxResults(1);
$mainImg = $article->getImages()->matching($criteria)->first();
return $this->render('default/article.html.twig', array(
'article' => $article,
'mainImg' => $mainImg
));
}
More information on filtering doctrine collections: Filtering collections
I did not test the code myself, but it should convey the idea of how it can be done.

Details list in symfony2, from two bundles

I have little in the development of Symfony and I have a question: To show a list with details of two Bundles.
My project have a Owner, but can have 1 o more consultants. The Entity has been related but when the results are listed only can view the user id. How can see the user name?
I will attach a image with my entities.
Thanks,
View
Entities
I need show the name of the differents consultants in a project. These are users too. I have already related the owner of the project, but this consultant is a field in my entity.
Attachment images to reference, and the php and twig files content. See the image: Database relation, and view.
UserProjectController
public function showAction(UserProject $userProject, User $user) {
$id = $userProject->getId();
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('ProjectBundle:Project')->find($id);
$userProjects = $em->getRepository('ProjectBundle:UserProject')->findby(array('idproject' => $id, 'status' => '1'));
if (empty($userProjects)) {
return $this->redirectToRoute('userproject_new');
}
$users = $em->getRepository('UserBundle:User')->findby(array('id' => 1));
return $this->render('ProjectBundle:Userproject:show.html.twig', array(
'userProjects' => $userProjects, 'project' => $project, 'User' => $user,
));
}
Entity User
/**
* #ORM\OneToMany(targetEntity="ProjectBundle\Entity\UserProject", mappedBy="user")
*/
protected $uproject;
Entity UserProject
/**
* #ORM\ManyToOne(targetEntity="ProjectBundle\Entity\Project", inversedBy="userproject")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $idproject;
/**
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\User", inversedBy="uproject")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $user;
Entity Project
/**
* #ORM\OneToMany(targetEntity="ProjectBundle\Entity\UserProject", mappedBy="idproject")
*/
protected $userproject;
View show.html.twig
{% for userProject in userProjects %}
<br>
<dl>
<dt><span class="text-primary">{{'User_id'|trans}}</span></dt>
<dd>
{{ userProject.user }}
</dd>
<br>
<dt><span class="text-primary">{{'Consultor_id'|trans}}</span></dt>
<dd>
{{ userProject.consultorId }}
</dd>
<br>
<dt><span class="text-primary">{{'Status'|trans}}</span></dt>
<dd>
{% if userProject.status == 1 %}
<span class="text-success">{% trans %}Enabled{% endtrans %}</span>
{% elseif userProject.status == 0 %}
<span class="text-danger">{% trans %}Disabled{% endtrans %}</span>
{% endif %}
</dd>
<br>
</dl>
{% endfor %}

Overriding SensioGeneratorBundle's twig template: trying to translate the delete button

I'm trying to override Resources/crud/actions/delete.php.twig from SensioGeneratorBundle.
I created this file located at app/Resources/SensioGeneratorBundle/skeleton/crud/actions/delete.php.twig:
{% extends "#SensioGenerator/Resources/crud/actions/delete.php.twig" %}
{% block form %}
/**
* Creates a form to delete a {{ entity }} entity by id.
*
* #param mixed $id The entity id
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm($id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('{{ route_name_prefix }}_delete', array('id' => $id)))
->setMethod('DELETE')
->add('submit', 'submit', array('label' => 'Supprimer'))
->getForm()
;
}
{% endblock form %}
Problem is that #SensioGenerator is not recognized:
There are no registered paths for namespace "SensioGenerator" in "crud/controller.php.twig" at line 58.
I tried to manually register the namespace in app/config/config.yml:
twig:
[...]
paths:
"%kernel.root_dir%/../vendor/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle/": SensioGenerator
But still not working. Idea?
Why you didn't do it like in docs ? SensioGeneratorDocs
{% extends "skeleton/crud/actions/delete.php.twig" %}
{% block form %}
/**
* Creates a form to delete a {{ entity }} entity by id.
*
* #param mixed $id The entity id
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm($id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('{{ route_name_prefix }}_delete', array('id' => $id)))
->add('submit', 'submit', array('label' => 'Supprimer'))
->getForm()
;
}
{% endblock form %}
If you're using a project that's based-on or is similar-to the Symfony Standard Edition, then the SensioGeneratorBundle is only loaded for the dev environment.
This is configured in both the composer.json and the AppKernel.
So, make sure your execution is using the dev environment or alter these configurations so that the bundle is always loaded.

Load tagging in query with FPNTagBundle

Bundle docs explain how to load tagging of a simple object:
$this->tagManager->loadTagging($article);
But I need to load a list (ArrayCollection from doctrine query) of taggable resources with their tags. And later iterate over a collection in twig and print:
Object: tag1, tag2, tag..n
Old post, but hopefully this answer will help someone, as I ran into the same issue trying to implement the tagging bundle. The problem is that your entity will have a private or protected property for tags, but the way the documentation reads on the bundle, there is no association mapping for this property, and it's not an actual field (column). So trying to access the tags property or use the getTags method on your entity won't work, either in your controller or in Twig. I feel like the documentation on the bundle may be missing some mapping annotations on the tags property, but I haven't been able to narrow down exactly what it should be.
I ended up taking the approach a couple others recommended by looping thru my entities in the controller, and loading the tagging using the tagmanager for each entity. What I also did which turned out to be helpful was to add a setTags method to the entity that accepts an ArrayCollection, that way when looping thru the entities in your controller, you can set the Tags on each one, then access them in twig like you want to do. For example:
Add this setTags method to your entity:
/**
* #param ArrayCollection $tags
* #return $this
*/
public function setTags(ArrayCollection $tags)
{
$this->tags = $tags;
return $this;
}
This will allow you to set the tags property from your controller.
Then in your controller:
/**
* #param Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('ContentBundle:Post')->findAll();
// here's the goods... loop thru each entity and set the tags
foreach ($posts as $post) {
$post->setTags($this->getTags($post));
}
// replace this example code with whatever you need
return $this->render('AppBundle::index.html.twig',array(
'posts' => $posts
));
}
/**
* #param Post $post
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getTags(Post $post) {
$tagManager = $this->get('fpn_tag.tag_manager');
$tagManager->loadTagging($post);
return $post->getTags();
}
The getTags method in this controller simply takes your entity and uses the tagmanager to find and return it's tags. You'll see the loop in the index method that adds the tags to each entity.
Then in Twig you can access your tags on each post in the loop:
{% for post in posts %}
<h2>{{ post.title }}</h2>
{% for tag in post.tags %}
{{ tag.name }}
{% endfor %}
{% endfor %}
You could iterate over the collection in your controller, like this:
foreach($articles as $article){
$this->tagManager->loadTagging($article);
}

Resources