Symfony Twig : get Object property with a variable property - symfony

Symfony 3.0 :
In my project, I have many entities which contain more than 50 fields, so for the twig which shows every entity, I decided to automate the display of the 50 fields by a simple loop.
First problem: how to get entity's all fields names, I resolved this by creating a custom twig filter:
<?php
// src/HomeBundle/Twig/HomeExtension.php
namespace HomeBundle\Twig;
class HomeExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('object_keys', array($this, 'getObjectKeys')),
);
}
public function getObjectKeys($object)
{
//Instantiate the reflection object
$reflector = new \ReflectionClass( get_class($object) );
//Now get all the properties from class A in to $properties array
$properties = $reflector->getProperties();
$result=array();
foreach ($properties as $property)
$result[] = $property->getName();
return $result;
}
public function getName()
{
return 'app_extension';
}
}
?>
The second problem which generates the error right now is: how to access object properties within a loop:
{% for property in article|object_keys%}
<tr>
<th>
{{property|capitalize}}
{# that's work clean #}
</th>
<td>
{{ attribute(article,property) }}
{# that's generate the error #}
</td>
</tr>
{% endfor %}
The error :
An exception has been thrown during the rendering of a template
("Notice: Array to string conversion"). 500 Internal Server Error -
Twig_Error_Runtime
Finally, the error is fixed on the getObjectKeys method of the filter,
so when it returns an array that I create manually it works:
return array("reference","libelle");
But, when I send an array created within a loop => Error.
I dumped the two arrays in the twig, they were equivalents, but the second still generating an error.

Most likely one of your properties is returning an array rather than a simple string, integer, .... A solution here could be to store the value in a variable and check whether the stored value is an array. Depending on that check do something with the value or otherwise just output the variable
{% for property in article|object_keys%}
<tr>
<th>
{{property|capitalize}}
</th>
<td>
{% set value = attribute(article,property) %}
{% if value is iterable %}{# test if value is an array #}
{{ value | join(', ') }}
{% else %}
{{ value }}
{% endif %}
</td>
</tr>
{% endfor%}

Related

Symfony FOSUserBundle Role array

I use This function to get data
public function UserAction()
{
$easyuser = $this->getDoctrine()->getrepository('AppBundle:User')->findall();
foreach($easyuser as $user){
$id = $user->getid();
$username = $user->getUsername();
$email = $user->getEmail();
$roles = $user->getRoles();
}
return $this->render('easycall/user.html.twig', ['easyuser' => $easyuser, 'roles' => $roles]);
}
and in twig i use this code to show data
{% for entity in easyuser %}
<tr>
<td>{{entity.id}}</td>
<td>{{entity.username}}</td>
<td>{{entity.email}}</td>
{% for role in entity.roles %}
<td>{{role}}</td>
{% endfor %}
</tr>
{% endfor %}
The problem is that i get all the roles if the user is ROLE_SUPER_ADMIN, i want to get only the first value from every array.
i tried something like reset() but it did'nt work, any suggestion??
This is also a picture how the results look likes.
1st item from array shoul be somethink like
{{entity.roles | first}}
but it's simply 1st item from array, i'm not sure if it always be more "powerfull role"

How to implement two tables intersection on Doctrine2 Symfony2

Im pretty new on all this stuff so please excuse my foolness.
I have two tables, clients and orders.
I need to show on a template all clients with their total orders amount for each.
According to MCV model, I just want the controller to call a method of the Clients repository that returns an array with all the data ready for sending it to the template.
But I having problems with implementing this.
So in summary I have:
Entities:
Clients.php: with client.id
Orders.php: with a manyToOne relatioonship to clients, and a amount property.
DefaultController.php
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$clientsWithTotals= $em->getRepository('ACMEAppBundle:Client')->getClientsWithTotals();
return $this->render('ACMEAppBundle:Default:index.html.twig',$clientsWithTotals);
}
ClientRepository.php:
class ClientRepositoryextends EntityRepository
{
public function getClientsWithTotals()
{
???
}
}
index.html.twig
{% extends '::base.html.twig' %}
{% block content %}
<table>
<thead>
<tr>
<th>Id</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr>
<td><{{ entity.id }}</td>
<td>{{ entity.total }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
Many thanks indeed
First off the template is using a variable which hasn't been defined (entities):
return $this->render(
'ACMEAppBundle:Default:index.html.twig',
array(
'entities' => $clientsWithTotals
)
);
Implementing the Repository:
class ClientRepository extends EntityRepository
{
public function getClientsWithTotals()
{
$this
->createQueryBuilder('client')
->join('client.orders', 'order');
}
}
As long as you have the oneToMany association correctly defined from Client to Orders and it's on the $client->orders property then the join will know that it has to filter out only the orders associated with a particular user.
Implementing the totals virtual property:
Your Client object will have no way of knowing what the totals are unless you do one of the following:
select the value from the DB using an aggregation function like SUM(orders.total) AS clientTotals.
select the orders associated with a client from the DB (Doctrine2 will hydrate them into $client->orders) and then using a foreach loop to sum up all the values.
As described here.
Ex. for #1:
You can construct a DQL query such as:
SELECT
client,
SUM(orders.total) as clientTotal
FROM ACMEAppBundle:Client AS client
JOIN client.orders AS orders;
The above query would normally return an array containing a all of the rows in the clients table in the following format:
array(
'client' => $client // an ACMEAppBundle:Client entity object
'clientTotals' => $clientTotals // an integer value representing the aggregate sum
);
Implementing a custom hydrator will allow you to decide what to do with the raw array data which is returned by the DB query.
Ex. for #2:
class Client
{
...
public function getTotals()
{
$this->totals = 0;
foreach ($this->getOrders as $order) {
$this->totals += $order->getTotal();
}
return $clientTotals;
}
...
}

symfony trying to export a csv file from a link

I have this function in the controller, that returns a form with a select box. When the values are selected, I retrieve them with
$manifestations = $form['manifestations']->getData();
then put them in a repository function that queries the database $invites = $repository->searchInviteByManif($manifestations);
public function indexAction() {
$entity = new Invite();
$form = $this->createForm(new ManifSearchType(), $entity);
$request = $this->get('request');
$invites = null;
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
$message = '';
$manifestations = $form['manifestations']->getData();
$repository = $this->getDoctrine()
->getManager()
->getRepository('PrifProtocoleBundle:Invite');
$invites = $repository->searchInviteByManif($manifestations);
$response1 = $this->render('PrifProtocoleBundle:Invite:index.html.twig', array(
'form' => $form->createView(),
'entities' => $invites,
'message' => $message,
));
return $response1;
}
return array(
'form' => $form->createView(),
'entities' => $invites,
);
}
This function then returns a view index.html.twig with a table and all the fields found in the db.
What I want is to export all the queried data $invites in a CSV file, by clicking on a link directly from the HTML table.
So I've put an href="" link in the Twig file,
{% if message is defined %}
<div class="pdf">
<img height="40px" width="40px" src={{ asset('bundles/prifprotocole/images/excel.jpg') }}>
{% for entity in entities %}
<tr class="{{ cycle(['odd', 'even'], loop.index0) }}">
<td>{% if entity.etat == 1 %} Actif {% else %} Inactif {% endif %}</td>
<td>{{ entity.titreGrade }} {{ entity.prenom }} {{ entity.nom }}</td>
<td>{{ entity.fonction }}</td>
This is how I use to export the CSV file without the link :
$response2 = $this->render('PrifProtocoleBundle:Invite:export.csv.twig', array(
'entities' => $invites));
$response2->headers->set('Content-Type', 'text/csv');
$csvfile = $response2->headers->set('Content-Disposition', 'attachment; filename="export.csv"');
return $csvfile;
export.csv.twig file
{% for entity in entities %}
Id {{ entity.id }};
Etat {{ entity.etat }};
Titregrade {{ entity.titreGrade }};
Prenom {{ entity.prenom }};
Nom {{ entity.nom }};
Fonction {{ entity.fonction }};
{% endfor %}
Can someone give me a detailed solution on how to perform this? Much thanks!
You should simply pass the filter criterias, here the $manifestations array as a route parameter, by serializing it first.
Then, you should put a controller like downloadCsvAction() handling this route, querying the $invites from the database, and rendering your export.csv.twig template.
Another option, if you cannot serialize the data in URI, is to create a form with hidden fields containing the data. Then you replace the download link with the submit button of this hidden form.
Guillaume's answer is great if you don't mind fetching your data twice. Once when you display your page, and then when you download the CSV. Another way to do this is to cache the answer. I will give an example using memcache (which is what I use).
First, make sure that memcache is active in your php.ini.
Then before rendering your first page (after fetching the data), cache it:
$key = "csv.$userId";
$memcache = new \Memcache();
$memcache->connect($yourServerHost, $yourServerPort);
$memcache->set($key, json_encode($invites), MEMCACHE_COMPRESSED, 86400); // 24h
The trick is to find a good key, to add your report to the cache, and then be able to retrieve it. It has to be unique. Here, I assume that you have a user that is logged in, and I use the user ID to create a key. If that is not the case, you could create yourself a token, and pass it when rendering the page (along with $invites). This way, you can add the token to the URL to generate the CSV.
Then, in your second action, where you download the CSV, you just fetch the cached data:
$memcache = new \Memcache();
$memcache->connect($yourServerHost, $yourServerPort);
$json = $memcache->get($key);
$invites = json_decode($json , true);
That's it. You should probably create yourself a service for Memcache, so you don't have to create an object, and connect everytime.

Using doctrine database object in a template

I am new to Symfony and am finally beginning to understand how to query a database using a Doctrine. However, I am lost as far understanding how to use the database object content in a Twig template.
Lets say my database object contains product Id's, names, prices, for 50 different products. After I am done querying the database in the controller, I do the following, to pass the database object into the Twig template:
public function searchAction($word)
{
//query database using the $word slug and prepare database object accordingly
$dataObject; // contains query results
return $this->render('GreatBundle:Default:search.html.twig', array('word' => $word));
}
This is where I am stuck. Now I have a Twig template, I would like to pass the DB object from the controller and then print out the database data in my Twig template.
I appreciate any suggestions as to how I can accomplish this.
Many thanks in advance!
I'll respond with an example (more easier for me to explain)
You want to search something with a slug (the var $word in your example). Let's say you want to find a article with that.
So your controller :
public function searchAction($word)
{
//query database using the $word slug and prepare database object accordingly
// Search the list of articles with the slug "$word" in your model
$articleRepository = $this->getDoctrine()->getRepositoy('GreatBundle:Article');
$dataObject = $articleRepository->findBySlug($word);
// So the result is in $dataObject and to print the result in your twig, your pass the var in your template
return $this->render('GreatBundle:Default:search.html.twig', array('result' => $dataObject));
}
The twig template 'GreatBundle:Default:search.html.twig'
{% for item in result %}
{{ item.title }} : {{ item.content }}
{% endfor %}
Just look the second example in the Symfony2 Book (Sf2 Book - templating), you have to use the function "for" to parse your object (like an array in php !)
Example in your twig template :
{% for item in word %}
{{ item.id }} - {{ item.name }} - {{ item.description }}{# etc... #}<br>
{% else %}
<h2>Aoutch ! No data !</h2>
{% endfor %}
Ah, and it's not the good var in your render method (but it's was for your example !)
public function searchAction($word)
{
//query database using the $word slug and prepare database object accordingly
$dataObject; // contains query results
return $this->render('GreatBundle:Default:search.html.twig', array('word' => $dataObject));
}

How to apply a filter whose name is stored in a variable

Basically, I am looking for the filter equivalent of the attribute() function for objects and arrays. I want to be able to apply a filter, whose name is stored in a variable.
{#
This works and is really useful
prints object.someVar
#}
{% set varName = 'someVar' %}
{{ attribute(object,varName) }}
{#
The function "filter" does not exist
#}
{% set filterName = 'somefilter' %}
{{ filter(object,filterName) }}
To reach this goal you have to extend your TwigFilter.
How to initially write you Extension you can read here.
Assuming that you have created you extension, you have define your function, let's say applyFilter.
//YourTwigFilterExtension.php
public function getFunctions()
{
return array(
...
'apply_filter' => new \Twig_Function_Method($this, 'applyFilter'),
);
}
Then, you have to define this function
public function applyFilter($context, $filterName)
{
// handle parameters here, by calling the
// appropriate filter and pass $context there
}
After this manipulations you'll be able to call in Twig:
{{ apply_filter(object, 'filterName') }}
Cheers ;)

Resources