I have my category tree in twig template:
{% for category in categories %}
<li>
<div class="li">{{ category.name|trans({}, 'categories')|raw }}</div>
{% if category.children is not empty %}
<ul>
{% include "default/_menu_links.html.twig" with {'categories':category.children} only %}
</ul>
{% endif %}
</li>
{% endfor %}
It creates +- 53 database queries if I have 6 categories and 7 subcategories in each single category.
Is there a way to decrease that number?
I'm using useResultCache(true) in doctrine, but looks like it's not loading from the cache(at least not in dev mode).
How are you handling category trees?
UPDATE:
Entity:
...
/**
* One Category has Many Subcategories.
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent", cascade={"persist"}))
*/
private $children;
/**
* Many Subcategories have One Category.
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children", cascade={"persist"})
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
...
/**
* Add child
*
* #param \App\Entity\Product\Category $child
*
* #return Category
*/
public function addChild(\App\Entity\Product\Category $child): Category
{
$this->children[] = $child;
return $this;
}
/**
* Remove child
*
* #param \App\Entity\Product\Category $child
*/
public function removeChild(\App\Entity\Product\Category $child)
{
$this->children->removeElement($child);
}
/**
* Get children
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChildren(): Collection
{
return $this->children;
}
/**
* Set parent
*
* #param \App\Entity\Product\Category $parent
*
* #return Category
*/
public function setParent(\App\Entity\Product\Category $parent = null): Category
{
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* #return \App\Entity\Product\Category
*/
public function getParent()
{
return $this->parent;
}
...
According to my comment: you have to JOINyour subcategories. I'm assuming you are currently doing something like this to retrieve your categories:
public function getCategories()
{
return $this->getEntityManager()->createQueryBuilder()
->select("category")
->from("App:Category", "category")
->where("category.parent IS NULL")
->getQuery()->getResult();
}
So if you are now iterating over this array of categories and trying to access the children property, a sub query will be fired for each children which causes this high amount of database queries.
Instead you should JOIN them like this:
public function getCategories()
{
return $this->getEntityManager()->createQueryBuilder()
->select("category", "subcat")
->from("App:Category", "category")
->leftJoin("category.children", "subcat")
->where("category.parent IS NULL")
->getQuery()->getResult();
}
If you now iterate over your categories and access the children property, no extra query will be fired!
Using the query above and this snippet in twig will result only in one database query:
{% for category in categories %}
<p>cat={{ category.id }}</p>
{% for subcat in category.children %}
<p>subcat={{ subcat.id }}</p>
{% endfor %}
<hr>
{% endfor %}
Related
I am trying to make a QueryBuilder with a join between my entity Product and my entity Store. I'd like to display all products that are related to OneStore. They have a many-to-many relation. I can't figure out how to get the ID's. Welp please. Thanks in advance!
here's a screenshot of my product_store table :
https://imgur.com/a/wUZMEEn
So if I'm clear enough and my english legit, you understood that I'd like to display only the product that are related to one store id.
Let's make an example. If I'm on the /store/detail/{1} page, I'd like to display product id 1,3,6,8,10,13 since they are related to store.id = 1.
So I updated as I have been advised, but it's not working, and I don't get what is wrong. Where am I mistaking? (controller updated)
Here's the error I get :
https://imgur.com/a/q3Uun3E
----------------------my repository function -----------------------------
public function getProductsForStore($sid)
{
return $this->createQueryBuilder('s')
->join('s.product', 'p')
->addSelect('p')
->andWhere('s.id = :sid')
->orderBy('p.name', 'ASC')
->setParameter('sid', $sid)
->getQuery()
->getResult();
}
$produt in store entity :
/**
*
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="App\Entity\Product", mappedBy="store")
* * #ORM\JoinTable(name="product_store",
* joinColumns={#ORM\JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="store_id", referencedColumnName="id")}
* )
*/
private $product;
$store in product entity :
/**
* * #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="App\Entity\Store", inversedBy="product")
* * #ORM\JoinTable(name="product_store",
* joinColumns={#ORM\JoinColumn(name="store_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="product_id", referencedColumnName="id")}
* )
*/
private $store;
my controller function :
/**
* #Route("stores/detail/{id}", name="detail_store"))
*/
public function getOneStore(StoreRepository $repository, Store $store): Response
{
$store = $repository->findOneBy(array(
'id' => $store->getId(),
));
$products_store = $repository->getProductsForStore(':id');
return $this->render('store.html.twig', array(
'store' => $store,
'product' => $products_store,
));
}
you can better call your properties $stores and $products instead of the singular version to make clear that we talk about an array or collection with possible multiple stores or products. You dont really need the 2nd - 4th rule of your annotations. Just
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Product", mappedBy="store")
*/
and
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Store", inversedBy="product")
*/
will do. It tells us that you use a bidirectional ManyToMany relation. Both Store and Product entities hold a list of the opposite enitity. Therefor you can query Stores with a collection of Products or Products with a collection of Stores.
and what is the correct way to use the $store->getProducts() in my controller?
So exchange store for product and vica versa in the first answer:
public function getStore($sid)
{
return $this->createQueryBuilder('s')
->join('s.product', 'p')
->addSelect('p')
->andWhere('s.id = :sid')
->orderBy('p.name', 'ASC')
->setParameter('sid', $sid);
->getQuery()
->getResult()
;
}
Controller:
/**
* #Route("/{id}", name="store_show", methods={"GET"})
*/
public function show($id): Response
{
$em = $this->getDoctrine()->getManager();
$em->getRepository('App:Store')->getStore($id);
if(null === $store) {
throw $this->createNotFoundException('Store not found.');
}
return $this->render('store/show.html.twig', [
'store' => $store,
]);
}
Twig:
{% extends 'base.html.twig' %}
{% block title %}Store{% endblock %}
{% block body %}
<h1>{{ store.name }}</h1>
<table class="table">
<thead>
<tr>
<th>ProductId</th>
<th>ProductName</th>
</tr>
</thead>
<tbody>
{% for product in store.products %} {# loop through all products in store #}
<tr>
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
</tr>
{% else %}
<tr>
<td colspan="4">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
I do wonder why you make a custom query if you want to get all products from one store.
When you made your ManyToMany relation (with maker bundle hopefully), you should also have added in Store entity a getter ($store->getProducts()) to get all products of said store.
Anyway, here is your query...
public function getAllProductsFromOneStore(Store $store) {
$qb=$this->createQueryBuilder('store');
$qb->addSelect('product')
->join('store.product', 'product')
->andWhere('store.id = :storeId')
->setParameters(array(
'storeId'=>$store->getId();
));
return $qb->getQuery->getResult();
}
The above query should return all products from one store.
Also, never use where(), always andWhere(). Read more about it here.
[EDIT]
In response to your edit. As Frank B said, you can do better.
First, as I said, you don't need a custom query.
Instead, make a getter in Store and Product entity.
src/Entity/Store.php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
class Store {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Product", inversedBy="stores")
*/
private $products;
public function __construct() {
$this->products=new ArrayCollection();
}
/**
* GETTER
*
* #return Collection|Product[]
*/
public function getProducts(): Collection {
return $this->products;
}
/**
* ADD A PRODUCT TO A STORE
*/
public function addProduct(Product $product): self {
if(!$this->products->contains($product)) {
$this->products[]=$product;
}
return $this;
}
/**
* REMOVE A PRODUCT FROM A STORE
*/
public function removeProduct(Product $product): self {
if($this->products->contains($product)) {
$this->products->removeElement($product);
}
return $this;
}
}
src/Entity/Product.php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
class Product {
private $stores;
public function __construct() {
$this->stores=new ArrayCollection();
}
/**
* GETTER
*
* #return Collection|Store[]
*/
public function getStores(): Collection {
return $this->stores;
}
/**
* ADD A STORE TO A PRODUCT
*/
public function addStore(Store $store): self {
if(!$this->stores->contains($store)) {
$this->stores[]=$store;
$store->addProduct($this);
}
return $this;
}
/**
* REMOVE A STORE FROM A PRODUCT
*/
public function removeStore(Store $store): self {
if($this->stores->contains($store)) {
$this->stores->removeElement($store);
$store->removeProduct($this);
}
return $this;
}
}
Now in your controller, all you have to do is to call your getter.
Doctrine will handle the query for you :
/**
* #Route("stores/store-{id}", name="index_store"))
*/
public function index(Store $store): Response {
$products=$store->getProducts();
return $this->render('store.html.twig', array(
'store'=>$store,
'products'=>$products,
));
}
And then in your twig view :
{% for product in store.products %}
// Your code here
{% endfor %}
And that is it. Once you have your getter in your entities, you don't need a custom query.
Take note that you can do the same with Product ($product->getStores()) to know in which store you can find your product.
I am checking for friends that has accepted my friend request and thus are my real friends. But when displaying my friends I get myself as friend and not the person I have for friend sometimes. This appears when for example my id is second in the table.
I have tried checking if the userid is in either of the columns. Also tried with having two way friendship like A friend with B but B friend with A too. I couldnt get the repository work for the second method so I sticked to this one.
My repository:
public function personalFriends($userId){
$em = $this->getEntityManager();
$result = $em->createQuery('SELECT friends FROM AppBundle\Entity\Friends friends
INNER JOIN AppBundle\Entity\User myuser WHERE (friends.friendsWithMe = :userId OR friends.afriendof = :userId) AND friends.friends = 1');
$result->setParameter('userId', $userId);
return $result->getResult();
}
My controller:
public function indexAction(Request $request)
{
$user = $this->get('security.token_storage')->getToken()->getUser();
$userId = $user->getId();
$posts = $this->getDoctrine()->getRepository(UserPosts::class)->findUserPosts($userId,1);
$friends = $this->getDoctrine()->getRepository(Friends::class)->personalFriends($userId);
return $this->render('default/index.html.twig',
['formed' => null,
'posts' => $posts,
'posted'=>null,
'search' => null,
'friends' => $friends
]);
}
My view :
<div class="card card-body">
{{ app.user.username|capitalize }}'s friends:
{% if friends %}
<div>
{% for friend in friends %}
{{ friend.afriendof.username }}
{% endfor %}
</div>
{% else %}
<div>
You have no friends.
</div>
{% endif %}
</div>
Friends Entity:
class Friends
{
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="myfriends", fetch="EAGER")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $friendsWithMe;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="friendof", fetch="EAGER")
* #ORM\JoinColumn(name="friend_id", referencedColumnName="id", nullable=false)
*/
private $afriendof;
/**
* #var integer
*
* #ORM\Column(name="request_sent", type="smallint")
*/
private $requestSent;
/**
* #var integer
*
* #ORM\Column(name="friends", type="smallint")
*/
private $friends;
I want to get only the friends and not myself. I get that I have to change the friend.afriendof.username with friend.friendswithme.username, but how to know when to change between those two.
I have a problem with ManyToMany association. I cant't figure out how to fetch data from database.
I have 2 entities - Teacher and Subject.
Class Subject
/**
*
* #ORM\ManyToMany(targetEntity="Teacher", mappedBy="subjects")
*/
private $teachers;
public function __construct() {
$this->teachers = new ArrayCollection();
}
/**
* Add teacher
*
* #param \AppBundle\Entity\Teacher $teacher
*
* #return Subject
*/
public function addTeacher(\AppBundle\Entity\Teacher $teacher)
{
$this->teachers[] = $teacher;
return $this;
}
/**
* Remove teacher
*
* #param \AppBundle\Entity\Teacher $teacher
*/
public function removeTeacher(\AppBundle\Entity\Teacher $teacher)
{
$this->teachers->removeElement($teacher);
}
/**
* Get teachers
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTeachers()
{
return $this->teachers;
}
And entity Teacher
Class Teacher
/**
*
* #ORM\ManyToMany(targetEntity="Subject", inversedBy="teachers")
*/
private $subjects;
public function __construct()
{
$this->subjects = new ArrayCollection();
}
/**
* Add subject
*
* #param \AppBundle\Entity\Subject $subject
*
* #return Teacher
*/
public function addSubject(\AppBundle\Entity\Subject $subject)
{
$this->subjects[] = $subject;
return $this;
}
/**
* Remove subject
*
* #param \AppBundle\Entity\Subject $subject
*/
public function removeSubject(\AppBundle\Entity\Subject $subject)
{
$this->subjects->removeElement($subject);
}
/**
* Get subjects
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getSubjects()
{
return $this->subjects;
}
All i want to do is fetch all information from Teacher + Subjects associated with them.
How should looks like my controller to do this?
I try do this by
$teacher = $this->getDoctrine()->getRepository(Teacher::class)->findAll();
return $this->render('admin/adminDashboard.html.twig', [
'teachers' => $teacher]);
But i still got errors
First, my advice is to change your $teacher variable to $teachers as
findAll() method returns an array so you have to iterate through it. In your controller:
foreach($teachers as $teacher) { // Here you have $teacher and you can do $teacher->getSubjects() }
In your twig template:
{% for teacher in teachers %}
{{ dump(teacher) }}
{% endfor %}
If that doesnt work, let us know what kind of errors do you get.
This is all assuming that you have entries inside your database, or from wherever you're fetching your entities.
if {% for t in teachers %}{{dump(t.subjects.count)}}{% endfor %} worked, it means the data is there.
If you mean {{teacher.subjects}} throws error: An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class Doctrine\ORM\PersistentCollection could not be converted to string. Well in case you have never seen this error before, take a close read:
Object of class Doctrine\ORM\PersistentCollection refers to subjects, which as a collection that it is, cannot be just thrown into an html tag like a string.
Depends on how you want to present your data but the very basics would be:
{% for t in teachers %}
<tr>
{% for s in t.subjects %}
<td>s.getName</td>
{% endfor %}
</tr>
{% endfor %}
assuming subject entity has a property name and a getter for it. Hope you get the gist.
let me know
Object of class Proxies__CG__\AppBundle\Entity\Formation could not be converted to float
in vendor\easycorp\easyadmin-bundle\src\Resources\views\default\field_integer.html.twig (line 4)
{% if field_options.format %}
{{ field_options.format|format(value) }}
{% else %}
{{ value|number_format }}
{% endif %}
The error is fixed by commenting out my getters and setters for my foreign key in my entity i'm trying to set them up because they show inaccessible in my easy-admin Bundle
Entity code :
/**
* Questions
*
* #ORM\Table(name="questions", indexes={#ORM\Index(name="question_form_id", columns={"formid"})})
* #ORM\Entity
*/
/**
* #var \AppBundle\Entity\Formation
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Formation")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="formid", referencedColumnName="Form_id")
* })
*/
private $formid;
getters and setters :
/**
* #return Formation
*/
public function getFormid()
{
return $this->formid;
}
/**
* #param Formation $formid
*/
public function setFormid($formid)
{
$this->formid = $formid;
}
Fixed it, the #param was using the wrong column, sorry.
I need to set follower, following (myFriends) hasFriend logic to my project. column "odobera" means "following" to example nick(id user) odobera (following to this user). User (25) is following user(37).
Requests table:
User entity:
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="odobera")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="nick")
*/
protected $myFriends;
public function __construct()
{
parent::__construct();
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get myFriends
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getMyFriends()
{
return $this->myFriends;
}
/**
*
* #param \TB\UserBundle\Entity\User $user
* #return bool
*/
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
class Requests
{
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", inversedBy="myFriends")
* #ORM\JoinColumn(name="nick", referencedColumnName="id")
*/
protected $nick;
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User",inversedBy="followers")
* #ORM\JoinColumn(name="odobera", referencedColumnName="id")
*/
protected $odobera;
In controller:
$myFollowers=$user->getMyFriends();
returns:
What is good: returns 1 record. I actually follow just one person as you can see here the id of record is 24395
DB requests table:
I don't know if is good that getMyFriends function returns response in that "format". Please look at it carefully.
Then I have select followers from query and in loop:
{% for follower in followers %}
and i print data like this (works greate) {{ follower.nick }}
or if i want some fields from user entity {{ follower.nick.rank }}
{% endfor %}
But the problem is here:
{% if (app.user.hasFriend(follower.nick)) %}
That returns false, why? I follow this user as I checked in controller with dump :P Few lines over.
The problem seems to be that you are comparing two different type variable.
When you do this: {% if (app.user.hasFriend(follower.nick)) %} the following function is called:
/**
*
* #param \TB\UserBundle\Entity\User $user
* #return bool
*/
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the
value but also the type must match.
http://www.doctrine-project.org/api/common/2.1/class-Doctrine.Common.Collections.ArrayCollection.html