I have a counting problem in phalcon volt. I have a table named category and there I have two columns id and cname, and also have a table blog and there is a column category. I want to show how many post have in each category.
When I insert a post into blog table, in category column I'm inserting its category id. First of I just retrieve list of all category like this:
[controller]
$categories = Category::find();
$this->view->setVar('category', $categories);
$cx = Blogs::find();
$this->view->setVar('cates',$cx);
[Volt]
{% for categories in category %}
<a href="blog/category/{{categories.cname}}" class="tags">{{ categories.cname }}
<span>[
{% for cx in cates %}
{%if cx.category === categories.id %}
<?php echo(count($cx->category)); ?>
{% endif %}
{% endfor %}
]</span></a>
{% endfor %}
Its render like "1 1 1" or "1 1" or "1" but it should render like "3" or "2" or "1" whats my wrong?
I also tried like this but did not get the expected output:
{% for categories in category %}
<a href="blog/category/{{categories.cname}}" class="tags">{{ categories.cname }}
<span>[
{% for cx in cates %}
{%if cx.category === categories.id %}
{% if loop.first %} {{ loop.length }} {% endif %}
{% endif %}
{% endfor %}
]</span></a>
{% endfor %}
Have you defined your relationships between your models in Phalcon?
If so, you can use the built in commands to query the total amount of posts for each category
Example from the documentation:
You can also use “count” prefix to return an integer denoting the count of the related records:
$robot = Robots::findFirst(2);
echo "The robot has ", $robot->countRobotsParts(), " parts\n";
I don't have much experience with Volt templating, but I guess it will be something like:
{% for categories in category %}
<a href="blog/category/{{categories.cname}}" class="tags">{{ categories.cname }}
<span>[
{{ categories.countBlogs }}
]</span></a>
{% endfor %}
Refer to: https://docs.phalconphp.com/en/latest/reference/models.html#taking-advantage-of-relationships
UPDATE - model relations
[model: Category]
public function initialize()
{
// id => primary key name of the Category table
// Blogs => name of the table you want to create a relationship with
// category => name of the foreign key column in your relationship table
$this->hasMany('id', 'Blogs', 'category');
}
[model: Blogs]
public function initialize()
{
// category => blog column name which refers to the ID in the Category table
// Category => name of the Category table
// id => name of the primary key column in the Category table
$this->belongsTo('category', 'Category', 'id');
}
No Sir, its not working. But i just solved my problem like this :
[controller]
$categories = Category::find();
$this->view->setVar('category', $categories);
[volt]
{% for categories in category %}
<a href="blog/category/{{categories.cname}}" class="tags">{{ categories.cname }}
<span>[
<?php
$catcount = $this->modelsManager->executeQuery("SELECT Blogs.category FROM Blogs WHERE Blogs.category = $categories->id");echo(count($catcount));
?>
]</span></a>
{% endfor %}
Now its working as expected. and here i dont make any relations ion model.Is it ok Sir. Please! Thnx
Related
The title is a bit ambiguous I know, but let me explain what I'm trying to achieve.
I am attempting to generate a CSV based on data pulled from a doctrine query in my Symfony2 CRM. The data retrieved is based on OpenCart Product and Attribute data, as well as some bespoke information which is irrelevant for this issue.
Each product can have up to 5 different attribute values, named A, B, D, L1 and L2. However, some products do not have all of them, only A, B and L1. The CSV requires each attribute value to be in a separate cell - so the headers are as follows:
ATTRIBUTE: A | ATTRIBUTE: B | ATTRIBUTE: D | ATTRIBUTE: L1 |
ATTRIBUTE: L2
And then I loop through in my Twig file as follows:
{% for attribute in row.product.attributes %}
{% if attribute.text is not null %}
{{ attribute.text }},
{% else %}na,{% endif %}
{% endfor %}
If the product has all 5 attributes, the structure of the CSV is fine. However, if the product only has 3 attributes, it means that all of the subsequent values are pulled back a cell, meaning that the other data is under the wrong headings. I tried checking for values first:
{% for attribute in row.product.attributes %}
{% if attribute.attributeName.name == "A" %}
{% if attribute.text is not null %}
{{ attribute.text }},
{% else %}na,{% endif %}
{% endif %}
{% endfor %}
And I did this for each possible attribute name, but unfortuantely this does not work since if the name does not exist, it just skips it anyway. I'm having trouble trying to think of a way to loop through these attributes and entering a n/a if it's non existent - I'm sure there is a way but I don't know what it is.
For reference, here is the controller code that's generating the data for the CSV:
public function adminCsvAction($filter) {
$repository = $this->getDoctrine()->getRepository('AppBundle:Project');
$stages_repository = $this->getDoctrine()->getRepository('AppBundle:Stage');
$users_repository = $this->getDoctrine()->getRepository('AppBundle:User');
$results = $repository->getSearchResults($filter);
$users = $users_repository->findAll();
$stages = $stages_repository->findBy(array('deleted' => 0), array('sortOrder' => 'ASC'));
$filename = "export_".date("Y_m_d_His").".csv";
$response = $this->render('AppBundle:pages:csv.html.twig', array('data' => $results,'users' => $users, 'stages' => $stages));
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename='.$filename);
return $response;
}
The Project Entity has various mappings, one of which links to the Product table in OpenCart which means all attributes and linked values are accessible via this.
Any help in this is much appreciated.
I also agree with Cerad from comment section - that is not the job for Twig. In case your really need to do it, I would try roughly something like this:
{% set allAttr = ["A","B","D","L1","L2"] %}
{% for attribute in allAttr %}
{% if row.product.attributes[attribute] is defined %}
{{ row.product.attributes[attribute].text }}
{% endif %}
{% if not loop.last %},{% endif %}
{% endfor %}
I guess the is defined is critical here...
OK I figured it out. Using what Jovan Perovic suggested, I came up with this:
{% set allAttr = ["A","B","D","L1","L2"] %}
{% set prodAtts = [] %}
{% for row in data %}
{% set existingAtts = [] %}
{% for att in allAttr %}
{% if att not in prodAtts %}
{% set prodAtts = prodAtts|merge([att]) %}
{% endif %}
{% endfor %}
{% for rowAtt in row.product.attributes %}
{% set existingAtts = existingAtts|merge({(rowAtt.attributeName.name|trim):(rowAtt.attributeName.name|trim~'_'~rowAtt.text|trim)}) %}
{% endfor %}
{% for prodAtt in prodAtts %}
{% if prodAtt not in existingAtts|keys %}
{% set existingAtts = existingAtts|merge({(prodAtt):(prodAtt~'_na')}) %}
{% endif %}
{% endfor %}
{% set orderedAtts = existingAtts|sort %}
....
Then the loop for each row thereafter. I used the attribute name with an underscore in order to be able to sort it correct (as it only sorts by value not key) then used preg_replace to remove it along with any instance of the name so I just ended up with the value.
Bit of a lengthy - and probably over thought - solution but it does work!
I'm struggeling to select a previous for loop in Twig. I'm trying to display a few category titles in a category if that category has subcategories. If the category has no subcategories then it shhould display the titles from the previous loop. Normally this wouldn't be a problem if any category has the same depth. Unfortunatly the categories have different depths.
So what I try to do is create some sort of function that does this for me.
So for example:
Category A -> Category A.sub -> Category A.subsub
Title1 Title1.1 Title1.2
Title1 Title1.1 Title1.2
Category B -> Category B.sub -> Category A.subsub
Title1 Title1.1 Title1.1
Title1 Title1.1 Title1.1
As you can see Category B.sub.sub hasn't any subcategories. If that's the case it should show the subcategories from Category B.sub. Normally I would do something like this:
{% for category in shop.categories %}
{{ category.title }}
{% if category.subs %}
{% for category in category.subs %}
{{ category.title }}
{% if category.subs %}
{% for category in category.subs %}
{{ category.title }}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
Is there any way to create somesort of function that checks if a category has subcategories. If that's not the case the access the previous loop and display those category names.
I thought this was as simple as:
{% elseif not category.subs %}
{# Do this #}
But that's not the case :(
My suggestion is to make the structure of the arrays alike within your php code and don't put such kind of logic in the template.
So in php you wold have something like this:
if (!isset($categoryB['sub']['subsub']) {
$categoryB['sub']['subsub'] = $categoryA['sub']['subsub'];
}
and then your template you just iterate:
{% for category in shop.categories %}
{{ category.title }}
{% for category in category.subs %}
{{ category.title }}
{% for category in category.subs.subsub %}
{{ category.title }}
And I would also suggest to make it recursive so you would have something like:
{% itarerateCategoryes categories %}
Agreeing with Fyntasia I wouldn't have lots of logic in the template, I would parse the data in the controller to the form I wanted.
However assuming your data array is something like (couldn't understand your notation);
$categories = [
0 => [
'top' => ['Atop1', 'Atop2'],
'middle' => ['Amiddle1', 'Amiddle2'],
'bottom' => ['Abottom1', 'Abottom2'],
],
1 => [
'top' => ['Btop1', 'Btop2'],
'middle' => ['Bmiddle1', 'Bmiddle2'],
],
];
Something like;
{% for main_index, category in categories %}
{% if category.top is defined and category.top|length > 0 %}
{{ loop.index0 }} has top values
{% endif %}
{% if category.middle is defined and category.middle|length > 0 %}
{{ loop.index0 }} has middle values
{% endif %}
{% if category.bottom is defined and category.bottom|length > 0 %}
{{ loop.index0 }} has bottom values
{% else %}
{{ loop.index0 }} has no value so using {{ categories[loop.index0 - 1].bottom|join(', ') }}
{% endif %}
<br />
{% endfor %}
Outputs something like;
0 has top values 0 has middle values 0 has bottom values
1 has top values 1 has middle values 1 has no value so using Abottom1, Abottom2
Lets say I have some data called 'brands ' in an array past into a twig template like this:
{ 0:1,1:2,2:3,3:4,4:5,5:6,6:7,7:8}
Second array called 'designs' like this
{120:11,123:22,189:32,300:34,400:53,500:63,688:37,799:28}
o/p excepted
12,24,35,38,59,70,45,36
the below code shows what i have tried and the o/p expected for 8 times, but i am getting 64 times the output like
{% for key ,value in brands %}
{% for key,design in designs %}
// for example i need to add the value and design for 8 times
{% set total = value + design %}
{% endfor %}
{% endfor %}
You are incorrectly nesting your loops, and adding each element of designs to each element of brands. You only need one loop:
{% for key, value in brands %}
{% set total = value + designs[key] %}
{% endfor %}
Don't forget you can't access total unless you define it before starting the loop:
{% set total = 0 %}
{% for key, value in brands %}
{% set total = value + designs[key] %}
{% endfor %}
If you're looking for the sum of both of the arrays into one total with varying numbers of elements for brands and designs:
{% set total = 0 %}
{% for key, value in brands %}
{% set total = total + value %}
{% endfor %}
{% for key, value in designs %}
{% set total = total + value %}
{% endfor %}
If you're looking to add together each corresponding element between designs and brands without depending on matching keys - it's a much more difficult process when trying to do it in Twig, and you're missing what Twig is meant for entirely. You should handle this data in the controller or build a better data model to pass to Twig. For example:
$brands = array(0=>1,1=>2,2=>3,3=>4,4=>5,5=>6,6=>7,7=>8);
$designs = array(120=>11,123=>22,189=>32,300=>34,400=>53,500=>63,688=>37,799=>28);
$brands_designs = array();
foreach ($brands as $key => $brand) {
$design_key = key($designs);
$brands_designs[$key] = array(
'brand' => $brand,
'brand_key' => $key,
'design' => next($designs),
'design_key' => $design_key
);
}
return $this->render('AcmeBundle:Folder:template.html.twig', array('brands_designs' => $brands_designs));
Then in your Twig template:
{% for key, value in brands_designs %}
{% set total = value.brand + value.design %}
{% endfor %}
But if you insist on matching each element together and adding them individually...
{% for brandKey, brand in brands %}
{% set outerLoopIndex = loop.index %}
{% for designKey, design in designs %}
{% if outerLoopIndex == loop.index %}
{% set total = brand + design %}
{# do stuff with total here, like {{ total }} #}
{% endif %}
{% endfor %}
{% endfor %}
The above isn't tested, I'm a little worried about variable scope in there. It actually iterates a total of 64 times with your sample set but only outputs on the diagonal (when the outer index == inner index, so 1 == 1, 2 == 2, etc.)
I have some related entities, let's say for convenience Product and Category. They are related OneToMany and ManyToOne. (each product has one category and one category has many products)
My goal is to display in a template all categories with all their products, but in alphabetical order. For example like this:
Furniture
Bed
Sofa
TV
Meals
Banana
Fish
Lemon
Office
Paper
Pen
I want both categories and products to be sorted.
So:
Approach 1
Write queries with doctrine to get all categories orderedBy name ASC, and to get all products orderedBy name ASC and then in the template:
{% for c in all_categories %}
{{ c.name }}
{% for p in all_products %}
{% if p.category == c %}
{{ p.name }}
{% endif %}
{% endfor %}
{% endfor %}
Approach 2
Get only the categories no matter how they are ordered. Change fetch to eager. Then write a sort filter as Twig Extension like this:
public function sortByName($a, $b)
{
if($a->getName() === $b->getName()) {
return 0;
}
if($a->getName() < $b->getName()) {
return -1;
}
return 1;
}
and using an iterator and uasort() with this function and then in the template:
{% for c in all_categories|sortbyname %}
{{ c.name }}
{% for p in c.products|sortbyname %}
{{ p.name }}
{% endfor %}
{% endfor %}
What I can see is that in 1 the if check is bad because many checks will be redundant. And getting all products when I already have all categories is also redundant. But I think that sorting with Doctrine should be faster than that with the twig extension. So I can't tell which one should I use. If it matters, in my case, I have 3 entities, like this: Each shop has categories and each category has products.
Can you please help me? Thanks very much in advance! :)
This is more an algo choice rather than a symfony/doctrine issue.
My approach is to get all products sorted by (category-name, product-name), and then loop over them to store them in an array indexed by your category. It can be used if you have many products, and enough memory to load all objects at one, but will be faster ( #nbproducts, and not #nbproducts*#nbcategories ), and will use only one ORM request, that will use indexes to sort data.
Php code would look like this :
$sorted_products = array();
foreach ($products as $product) {
$category = $product->getCategory();
if (!array_key_exists($category->getId(), $sorted_products)) {
$sorted_products[$category->getId()] = array('category' => $category, 'products' => array());
}
$sorted_products['products'][] = $product;
}
And then you just have to do 2 foreach in your twig :
{% for category_infos in sorted_products %}
{{ category_infos.category.name }}
{% for product in category_infos.products %}
{{ product.name }}
{% endfor %}
{% endfor %}
How can I change the order of displaying the collection, and group them by a category that is in the FooBar entity.
$builder
->add('foobar', 'collection', array(
'type' => new FooBarType()
));
The query_builder only works for Entities, not Collections. So it is not about ordering the result, but about Grouping based on the category of the FooBarType, and show them in that order.
Like:
[COLLECTION]
Category One name : [list with that category]
Category Two name : [list with that category]
The view is now showing the collection, with the category name in front:
{% for db in edit_form.foobar %}
<li>[Category: {{ db.foobar.vars.data.name }}]
{{ form_widget(db.value) }}</li>
{% endfor %}
I want to group this by category. So they are sorted by Category name.
This will help:
{% set category = '' %}
{% for db in edit_form.foobar %}
<li>
{% set currentCategory = db.foobar.vars.data.category.name %}
{% if category != currentCategory %}
<p>{{ currentCategory }}</p>
{% set category = currentCategory %}
{% endif %}
{{ form_widget(db.value) }}</li>
{% endfor %}
And order the Category in your Entity like Joao said.
In your entity, on your oneToMany or ManyToMany annotation you can add this:
/**
* #ManyToMany(targetEntity="FooBar")
* #OrderBy({"name" = "ASC"})
*/
private $foobar;
With this, when you loop foobar on a template, they are ordered and sorted by what you defined.
source:
http://docs.doctrine-project.org/en/2.0.x/reference/annotations-reference.html#annref-orderby