FormComponent - Pass Entity with Form to Template - symfony

Lets say i have a one to many relation:
A company can own several pictures
I have company entity and image entity
Now, I want to display all pictures of a company within a template. And i also want to make theese pictures directly editable. I thought of adding a form to each Entity of the DoctrineArrayCollection and pass them to the template. In Template if somebody clicks on a picture the corresponding should be fade in, the should be able to edit the pictures description and pass it through ajax to a controller.
In my entity I added a field without annotations:
private $form;
public function setForm(MyPictureForm $form)
{
$this->form = $form;
}
public function getForm()
{
return $this->form;
}
Now in my controller I add a form instance to every picture of the company:
//office images with forms
$officeImages = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$image->setForm($form->createView());
array_push($officeImages, $image);
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages
));
}
And in my template i render it like this way:
{% for image in officeImages %}
<a href="#" title="{% if image.description %}{{ company.description }}{% else %}CLICK HERE FOR EDIT{% endif %}">
{% if image.image %}
<img src="{{ vich_uploader_asset(image, 'image') | imagine_filter('company_office_image_thumb') }}"
alt="{% if image.description %}{{ company.description }}{% endif %}"/>
{% else %}
{% image '#UserBundle/Resources/public/img/nopic_logo.jpg' output='/images/nopic_logo.jpg' %}
<img src="{{ asset_url }}" alt="Joblogo"/>
{% endimage %}
{% endif %}
</a>
{{ form(image.form) }}
{% else %}
<p>Es sind noch keine Images vorhanden</p>
{% endfor %}
At the end there is a lot if javascript stuff which handels the fade in / fade out of the form and their submitting.
Is this the correct way to handle my case? I think not cause passing a form for every picture seems like overhead?
The reason why I am working with forms instead of simply passing data out of a manual added input field is csrf protection and the smart usage of the form component.

As you said keeping form object in entity isn't a good idea. It should represents model data.
I've got two solutions for this:
I. Pass array of forms with image id as a key
$officeImages = array();
$imageForms = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$imageForms[$image->getId()] = $form->createView();
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages,
'imageForms' => imageForms
));
and in show.html.twig
{% for image in officeImages %}
{# your image display code #}
{{ form(imageForms[image.id]) }}
{% endfor %}
II. render partial for single image
in controller
public function showAndEditImageAction(Image $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
return $this->render(
'CompanyBundle:Company:Show/showAndEditImage.html.twig', array(
'image' => $image,
'imageForm' => $form->createView()
));
}
in twigs
{# CompanyBundle:Company:Show/showAndEditImage.html.twig #}
{# your image display code #}
{{ form(imageform) }}
{# CompanyBundle:Company:Show/show.html.twig #}
{% for image in officeImages %}
{{ render(controller('CompanyBundle:Company:showAndEditImage',
{ 'image': image })) }}
{% endfor %}

Related

Issue with Post Object Timber/Twig WordPress

I have an ACF field, It's a Repeater, and inside the Repeater is a Post Object.
Controller
/* Template Name: Strategic Partners */
use Timber\Timber;
use Timber\PostQuery;
use Flynt\Utils\Options;
use const Flynt\Archives\POST_TYPES;
$context = Timber::get_context();
$context['slider'] = get_field('slider');
$context['featured'] = get_field('featured');
if (isset($_GET['contentOnly'])) {
$context['contentOnly'] = true;
}
Timber::render('templates/StrategicPartners/index.twig', $context);
Here's the snippet from the View
{% for partner in Post(featured) %}
{{ partner.link }}
{% endfor %}
I'm var_dumping the context, and it's getting the Timber Post... but {{ partner.title }} does not grab the Post Title, it's blank.
{{ partner.link }} comes out with the direct link.
Output
http://***/strategic-partners/
http://***/strategic-partners/
What am I doing wrong here? I'm using {{ Post() }}} within a seperate repeater, within for foreach itself and that works on another page, but it does not work here either, so I tried Post() included in the foreach and it still is not working.
No one answered this for me. I answered it myself.
There isn't much documentation on this within the formal Timber documentation so I hope this helps somebody.
Do not call Post with your foreach
Do this for a Post Object within a Repeater (ACF)
{% for item in seq %}
{{ Post(item.post_object_name).field }}
{% endfor %}
seq is your ACF Field
post_object_name is the Post Object within the field.
field is the field within the Post Object type.
Hope this helps someone.
For more Context:
Controller
<?php
/* Template Name: Strategic Partners */
use Timber\Timber;
use Timber\PostQuery;
use Flynt\Utils\Options;
use const Flynt\Archives\POST_TYPES;
$context = Timber::get_context();
$context['slider'] = get_field('slider');
$context['featured_sps'] = get_field('featured_strategic_partners');
$context['rebate'] = get_field('rebate_information');
if (isset($_GET['contentOnly'])) {
$context['contentOnly'] = true;
}
Timber::render('templates/StrategicPartners/index.twig', $context);
Twig View
{% for partner in featured_sps %}
{{ Post(partner.featured).title }}
{% endfor %}
Images are more complex
{% for partner in featured_sps %}
<div class="slide">
<a href="{{ Post(partner.featured).link }}">
<img src="{{ Image(Post(partner.featured).logo).src }}" alt="{{ Post(partner.featured.title) }} Logo">
</a>
</div>
{% endfor %}

Error generating custom url that shows a phpcr document version in SonataAdmin

So i'm trying to extend the SonataAdmin show view to have a list of the document's versions (phpcr checkpoint or checkin).
I've got the list of saved versions showing correctly, and now i need to make them into links that display that version of the content, but i'm getting the following error when trying to add a custom Route:
An exception has been thrown during the rendering of a template
("Parameter "id" for route "admin_cmsbundle_product_show_version" must
match "[^/]++" ("mstr/product/product-253562" given) to generate a
corresponding URL.") in CmsBundle:product:show.html.twig at line 18.
This is my configureRoutes in my admin class:
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('show_version', $this->getRouterIdParameter() . '/show/{version}');
}
This is my overriden template:
{% block show %}
<ul>
{% for version in versions %}
<li>Version: {{ version.name }} </li>
{% endfor %}
</ul>
{{ parent() }}
{% endblock %}
This is my edited show action (to include the versions list):
public function showAction($id = null)
{
...
return $this->render($this->admin->getTemplate('show'), array(
'action' => 'show',
'object' => $object,
'elements' => $this->admin->getShow(),
'versions' => $this->getVersionHistory($object)
));
}
And this is my showVersion action in the controller:
public function showVersionAction($id = null, $version = "1.0")
{
...
return $this->render($this->admin->getTemplate('show'), array(
'action' => 'show',
'object' => $this->getVersion($object, $version),
'elements' => $this->admin->getShow(),
'versions' => $this->getVersionHistory($object)
));
}
Note, generateUrl gives the same error:
Version: {{ version.name }}
What am i doing wrong?
Any help on fixing this would be greatly appreciated :)
According to the error message there is some problem with $object. So maybe use generateUrl instead of generateObjectUrl() and pass your id in array:
{% block show %}
<ul>
{% for version in versions %}
<li>Version: {{ version.name }} </li>
{% endfor %}
</ul>
{{ parent() }}
{% endblock %}
Bit of digging around and the answer was simple, just had to override the pattern for id to be .+
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('show_version', $this->getRouterIdParameter() . '/show/{version}', array(), array('id' => '.+'));
}

How can I avoid a gazillion database queries with related doctrine entities?

On the profiler toolbar I noticed a lot of db queries, 400+, this happens when I print a related entity info. I am very new to setting doctrine mapping information within my entities so I don't know whether it's something I have misconfigured or if that's the way it is or maybe there is a better way to do it.
Basically the Estimates entity has two one-to-one relations to the CustomersHomes entity. I'll post my code, maybe you can spot something or perhaps you could tell me if my code so far is going in the right direction.
The Estimates class:
class Estimates
{
...
/**
* #ORM\OneToOne(targetEntity="MG\AdminBundle\Entity\CustomersHomes")
* #ORM\JoinColumn(name="work_address_id", referencedColumnName="homes_id")
*
*/
private $workAddress;
/**
*
* #ORM\OneToOne(targetEntity="MG\AdminBundle\Entity\CustomersHomes")
* #ORM\JoinColumn(name="homes_id", referencedColumnName="homes_id")
*
*/
private $homeAddress;
...
// getters and setters are in place below
...
}
And here is the action method in the CustomersController.php
public function viewAction($customersId)
{
$em = $this->getDoctrine()->getManager();
$customer = $em->getRepository('MGAdminBundle:Customers')->find($customersId);
if (!$customer){
throw $this->createNotFoundException($this->get('translator')->trans('No record found for this customer.'));
}
$allHomes = $em->getRepository('MGAdminBundle:CustomersHomes')->getHomes($customersId,null,10);
$billingHomes = $em->getRepository('MGAdminBundle:CustomersHomes')->getHomes($customersId, 'billing', 2);
$allHomesCount = $em->getRepository('MGAdminBundle:CustomersHomes')->getHomesCountByCustomer($customersId);
$customerSecondaryEmails = $customer->getEmails();
$customerMessages = $customer->getMessages();
$estimateRequests = $customer->getRequests();
$estimates = $customer->getEstimates();
return $this->render('MGAdminBundle:Customers:view.html.twig', array(
'customer' => $customer,
'allHomes' => $allHomes,
'billingHomes' => $billingHomes,
'allHomesCount' => $allHomesCount,
'customerSecondaryEmails' => $customerSecondaryEmails,
'messages' => $customerMessages,
'estimateRequests' => $estimateRequests,
'estimates' => $estimates,
));
}
Here is MGAdminBundle:Customers:view.html.twig
{% extends 'MGAdminBundle::layout.html.twig' %}
{% block title 'Profile: ' | trans ~ customer.firstname | upper ~ ' ' ~ customer.lastname | upper %}
{% block content %}
{% include 'MGAdminBundle:Customers/Partials:_customer-details.html.twig' %}
{% include 'MGAdminBundle:Customers/Partials:_secondary-contacts.html.twig' %}
{% include 'MGAdminBundle:Customers/Partials:_addresses.html.twig' %}
{% include 'MGAdminBundle:Customers/Partials:_estimate-requests.html.twig' %}
{% include 'MGAdminBundle:Customers/Partials:_messages.html.twig' %}
{% include 'MGAdminBundle:Customers/Partials:_estimates.html.twig' %}
{% endblock %}
And here is MGAdminBundle:Customers/Partials:_estimates.html.twig
{% if estimates|length > 0 %}
<h3>{{ 'Estimates' | trans }}</h3>
<fieldset>
{% for estimate in estimates %}
<div class="row {{ cycle(['even','odd'],loop.index) }}">
<div class="col-sm-1">{{ estimate.estimatesId | default('-') }}</div>
<div class="col-sm-2">{% include 'MGAdminBundle:Customers/Partials:_estimate-address.html.twig' with {'homeAddress': estimate.homeAddress, 'workAddress': estimate.workAddress} %}</div>
<div class="col-sm-1"></div>
<div class="col-sm-1"></div>
<div class="col-sm-1">{{ estimate.total }}</div>
<div class="col-sm-1">{{ estimate.status | default('-') }}</div>
<div class="col-sm-3">payments</div>
<div class="col-sm-1">options</div>
</div>
{% endfor %}
</fieldset>
{% endif %}
And finally the partial to print the estimate address: MGAdminBundle:Customers/Partials:_estimate-address.html.twig (I'm still working with this template)
{% if homeAddress is defined %}
{{ homeAddress.name }}
{% endif %}
{% if workAddress is defined %}
{{ workAddress.name }}
{% endif %}
And here is a screenshot:
Questions:
What is the best strategy to get the home address and work address of each estimate?
Should the estimates that belong to a customer be fetched from a repository method? Right now the estimates are gotten using a getter inside the customer entity.
Please bear with me, I'm new to all of this and I don't even know If I make sense. Thank you.
With Doctrine you can find all estimates using..
$repository = $this->getDoctrine()->getManager()
->getRepository('MGAdminBundle:Customers');
Either...
$estimates = $repository->findAll();
// Find all estimates on DB
Or...
$estimates = $repository->findBy(array(
'constraint1' => 'blah1',
'constraint2' => 'blah2',
));
// Find all estimates that fit constraints
// Constraints can take object if they are associations
From there the whole tree is available. So..
foreach ($estimates as $estimate) {
$things = $estimate->getThings();
foreach ($things as $thing) {
.. something with $thing ..
}
}
Or with twig..
return $this->render('MGAdminBundle:Estimates:view.html.twig', array(
'estimates' => $estimates,
);
then actual twig..
{% for estimate in estimates %}
{% for thing in estimate.things %}
.. something with {{ thing }} ..
{% else %}
No things // Twig has a nice incorporated for, else thing
{% endfor %}
{% else %}
No estimates
{% endfor %}

symfony2: remove widget attributtes

When I've created a form:
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'button')->getForm();
and render it:
<div><button type="button" id="form_add" name="form[add]">Add</button></div>
the attributes type, id and name are created.
I want to erase this attributes but I don't know how to do it. I've tried to do:
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'button', array( 'attr' => array() ) )->getForm();
without any success.
How could I do it?
Greetings and thanks
I messed around with this for a while and the closest I could get was to have them render with empty attributes. However, that's because the project I tested with is Symfony 2.0, and in that version it's impossible to completely remove the attributes since Symfony\Component\Form\FormView::$vars is private.
However, in Symfony 2.1 and later, that same property is public so you should be able to modify (or delete) the attributes/vars directly without being constrained by the FormView api.
First, create your own type to represent this "naked button"
src/Your/Bundle/Form/NakedButtonType.php
<?php
namespace Your\Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class NakedButtonType extends AbstractType
{
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.FormTypeInterface::getName()
*/
public function getName()
{
return "naked_button";
}
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.AbstractType::getParent()
*/
public function getParent(array $options)
{
return 'button';
}
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.AbstractType::buildViewBottomUp()
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
// Symfony 2.0
// This will still render the attributes, but they will have no value
$view->set('id', null);
$view->setAttribute('type', null);
// Symfomy >= 2.1
// This *should* remove them completely
unset( $view->vars['id'] );
unset( $view->vars['attr']['type'] );
}
}
Now, tell the service container how to build your type
app/config/config.yml
services:
form.type.naked_button:
class: Your\Bundle\Form\NakedButtonType
tags:
- {name: form.type, alias: naked_button}
Then update your parent form to use your new type instead of the ootb "button" type.
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'naked_button')->getForm();
All of that being said...
If you want these buttons w/o any attributes, why not just put them like that directly into your view?
<form>
{{ form_errors(form) }}
{{ form_rest(form) }}
<div>
<button>Add</button>
</div>
</form>
All of this custom type nonsense seems like alot of overhead to render something you clearly don't need Symfony to manage for you.
While not a good idea, I will help you load the gun and shoot yourself in the foot :-)
Create a new Resources/views/Form/fields.html.twig file, and put the following in it:
{% block widget_attributes %}
{% spaceless %}
id="{{ id }}" name="{{ full_name }}"{% if read_only %} readonly="readonly"{% endif %}{% if disabled %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %}
{% for attrname, attrvalue in attr %}{% if attrname in ['placeholder', 'title'] %}{{ attrname }}="{{ attrvalue|trans({}, translation_domain) }}" {% else %}{{ attrname }}="{{ attrvalue }}" {% endif %}{% endfor %}
{% endspaceless %}
{% endblock widget_attributes %}
If you really, really want to remove all the attributes on there, you can remove the {% for attrname ... %} line. That will remove the attributes from all form fields. If you add some logic, you can have it apply to just specific fields.
Next step, you need to register your fields helpers. In your app/config/config.yml file, add the following line:
twig:
form:
resources:
- 'SomeBundle:Form:fields.html.twig'

Directly access a form field's value when overriding widget in a twig template

What I want to do is get variables stored in form view.
{% form_theme edit_form _self %}
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{# NEED TO REPLACE VAR HERE #}">Aperçu</a>
{% endif %}
{# MY ATTEMPT #}
{{ form.title.get('value') }}
{{ form.vars.value.url }}
{% endspaceless %}
{% endblock field_widget %}
My form has properties like url, title, etc and I am trying to access them here to use it in the field widget block.
I searched for it and came on https://groups.google.com/forum/?fromgroups=#!topic/symfony2/onor9uFte9E that suggested:
{{ form.title.get('value') }}
{{ form.vars.value.url }}
which didn't work for me.
Note: If I do a var_dump on $form->createView() in my controller, I get:
object(Symfony\Component\Form\FormView)[331]
private 'vars' =>
array (size=15)
'value' =>
object(Panasonic\TestEtAvisBundle\Entity\Product)[168]
protected 'reviewArray' =>
object(Doctrine\ORM\PersistentCollection)[234]
...
protected 'testArray' =>
object(Doctrine\ORM\PersistentCollection)[221]
...
protected 'fbshareArray' =>
object(Doctrine\ORM\PersistentCollection)[317]
...
private 'id' => int 2
private 'name' => string 'Nom du produit' (length=14)
private 'title' => string '<span>Titre </span>' (length=19)
private 'image' => string 'bundles/testetavis/uploads/product/0d9d9550.png' (length=47)
private 'fbImage' => string 'bundles/testetavis/uploads/product/facebook//product_e928cd96.jpg' (length=65)
private 'description' => string '<span>Descriptif </span>' (length=24)
private 'url' => string 'http://www.google.com' (length=21)
private 'creationDate' =>
object(DateTime)[210]
...
private 'modificationDate' =>
object(DateTime)[209]
...
private 'isDeleted' => int 0
'attr' =>
array (size=0)
empty
'form' =>
&object(Symfony\Component\Form\FormView)[331]
'id' => string 'panasonic_testetavisbundle_producttype' (length=38)
'name' => string 'panasonic_testetavisbundle_producttype' (length=38)
'full_name' => string 'panasonic_testetavisbundle_producttype' (length=38)
I want to access that url for instance but can't seem to be able to do it after many variations. Including use of {{ value }}, {{ value.url }}
But inspite of vars, I can do {{ full_name }} and get panasonic_testetavisbundle_producttype.
Any ideas?
Edit2: I found out the real problem...
Edit3: Seeing that this question is quite popular I decided to clarify on what I attempted to do in case it helps someone in the same situation. If you rely strictly on what the question asks, as I stated from my research and that Besnik supported are indeed correct.
Now what I wanted to do is for every input type file, get url from object used to render form and append a preview link, using retrieved url, beside the input type file.
If you try to get the form var of an input type "file" like this "{{ form.vars.value.url }}" in my code, this doesn't work since, if I recall correctly, you receive a token instead of the url stored inside the object.
You can access the current data of your form via form.vars.value:
{{ form.vars.value.title }}
See Symfony2 Forms documentation: http://symfony.com/doc/current/book/forms.html#rendering-a-form-in-a-template
Dump vars by using dump function:
{{ dump(form.vars.value) }}
If you are using subforms or want to have a value of a specific field:
{{ form.FIELD.vars.VALUE }}
You can access values of the parent parent from a widget block using form.parent.vars
For example, we want to render the value from a type text field called primerNombre we will need
{{ form.vars.value.primerNombre }}
If we wanted to render the name of one of the children we will need
{% for hijo in form.hijos %}
<td><div align="left">{{ form_widget(hijo.vars.value.primerNombre) }}</div></td>
{% endfor %}
Good luck!
In Symfony > 3 you may use:
form.vars.value.Entity.someValue
Edit2:
Finally, I was indeed getting the value of the current row in {{ value }} here:
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ **value** }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{{ value }}">Aperçu</a>
{% endif %}
{% endspaceless %}
{% endblock field_widget %}
But in my case I get a token instead of the value since I am using input type file. This is due to a security measure in Symfony2.

Resources