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

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 %}

Related

How to reuse sonata Admin paginator with a custom crud controller

I have a custom CRUD controller with an action which retrieves a custom doctrine result set and passes it to a custom template. Is it possible to make use of Sonata Admin's pagination mechanism without reusing the standard list templates?
If so, what object format does the paginator expect? I could not see any mention of this in the documentation
The template I would need to populate with results would be base_results.html.twig:
{% block num_pages %}
{{ admin.datagrid.pager.page }} / {{ admin.datagrid.pager.lastpage }}
-
{% endblock %}
{% block num_results %}
{% transchoice admin.datagrid.pager.nbresults with {'%count%': admin.datagrid.pager.nbresults} from 'SonataAdminBundle' %}list_results_count{% endtranschoice %}
-
{% endblock %}
{% block max_per_page %}
<label class="control-label" for="{{ admin.uniqid }}_per_page">{% trans from 'SonataAdminBundle' %}label_per_page{% endtrans %}</label>
<select class="form-control per-page small" id="{{ admin.uniqid }}_per_page">
{% for per_page in admin.getperpageoptions %}
<option {% if per_page == admin.datagrid.pager.maxperpage %}selected="selected"{% endif %} value="{{ admin.generateUrl('list', {'filter': admin.datagrid.values | merge({'_page': 1, '_per_page': per_page})}) }}">
{{ per_page }}
</option>
{% endfor %}
</select>
{% endblock %}
If I want to add an additional constraint on the result set, how would I adapt the following code from Sonata's list action?
if (false === $this->admin->isGranted('LIST')) {
throw new AccessDeniedException();
}
$datagrid = $this->admin->getDatagrid();
$formView = $datagrid->getForm()->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->renderer->setTheme($formView, $this->admin->getFilterTheme());
return $this->render($this->admin->getTemplate('list'), array(
'action' => 'list',
'form' => $formView,
'datagrid' => $datagrid,
'csrf_token' => $this->getCsrfToken('sonata.batch'),
));
The DatagridBundle is a work in progress in order to decouple the list, pagination and filtering mechanisms from the Admin for them to be reusable elsewhere. Hence it is not stable yet. I strongly recommend you use the pagination from the AdminBundle at the moment.
Regarding your main question, if you wish to understand fully how to use the pager, I recommend you take a look at the Datagrid::buildPager method (https://github.com/sonata-project/SonataAdminBundle/blob/master/Datagrid/Datagrid.php#L90).

FormComponent - Pass Entity with Form to Template

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 %}

Returning a collection in collection from controller to twig in Symfony

I guess this is pretty easy but I am a novice at Symfony so I got troubles with it.
So basiccaly here is what I got in Controller
public function pollResultsAction( $pollId ) {
$poll = $this->get('doctrine')->getRepository('Myproject:Poll')->find( $pollId );
$questions = $poll->getItems();
return array(
'questions' => $poll->getItems()
);
}
where $questions is a Collection of questions of the Poll given
then I have
{% if questions %}
<ul>
{% for question in questions %}
<li> {{ question.question }}</li>
// here I'd like to have answers to given question
{% endfor %}
</ul>
in a Twig file.
I can get to answers like this
$answer = $onequestion->getAnswers();
I'd like to print answers to each question in twig. I think I need to some foreach loop in controller but I don't know how to return it to twig.
Thank you for your help in advance.
In your view:
{% for question in questions %}
{{ qustion.question }}
{% for answer in question.answers %}
{{ answer }}
{% endfor %}
{% endfor %}

Symfony 2 - Access mapped Object property form twig

I hava a entity with the following flied:
/**
* #ORM\ManyToOne(targetEntity="Document", inversedBy="posts")
* #ORM\JoinColumn(name="document_id", referencedColumnName="id")
*/
protected $image;
which I get with the following method:
public function indexAction() {
$posts = $this->getDoctrine()
->getRepository('airpaprcms2Bundle:Post')
->findByPageType(1);
if (!$posts) {
throw $this->createNotFoundException('No posts in Database!');
}
return $this->render('airpaprcms2Bundle:Default:index.html.twig', array('posts' => $posts));
}
How can I access a property of the mapped object form within twig? I tried post.image.file (the mapped entity Document has a property file)
{% for post in posts %}
<div>
<h1>
{{ post.title }}
</h1>
<p>{{ post.text }}</p>
<img src="{{ post.image.name }}" alt="{{ post.title }}" />
</div>
{% endfor %}
and get the following error message:
Item "file" for "" does not exist in airpaprcms2Bundle:Default:index.html.twig at line 11
What is the right syntax to access the mapped document property?
You can access the property of your linked entity with the following twig syntax:
{% for post in posts %}
{{ post.entityName.propertyName }}
{% endfor %}
In your case that would be:
{% for post in posts %}
{{ post.image.propertyName }}
{% endfor %}
Be sure to check that all post entities are linked to an image object. If one post entity has no link to an image, you will get a property not found error when trying to render your page.

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