I'm trying to make product filters but I can't generate a correct query
А quick look at the base
db visually
here are my entities:
AttributeType:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=AttributeValue::class, mappedBy="attributeType")
*/
private $attributeValue;
public function __construct()
{
$this->attributeValue = new ArrayCollection();
}
AttributeValue:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Product::class, inversedBy="attributeValues")
*/
private $product;
/**
* #ORM\Column(type="string", length=100)
*/
private $value;
/**
* #ORM\ManyToOne(targetEntity=AttributeType::class, inversedBy="attributeValue")
*/
private $attributeType;
For example AttributeType(Color) has AttributeValue(Red, Blue, Green) & i retrieve hundred of red, blue, green AttributeValue for a single Color option
that query returns options with all value(not unique):
return $this->createQueryBuilder('at')
->innerJoin('at.attributeValue', 'attribute_value')
->addSelect('attribute_value')
->getQuery()
->getResult();
I tried to modify the request like this:
return $this->createQueryBuilder('at')
->innerJoin('at.attributeValue', 'attribute_value')
->addSelect('attribute_value.value')->distinct()
->getQuery()
->getResult();
(there were other attempts, but they were all not even close)
How do I get unique values for each option?
I will be grateful for any help
And thx for your time.
I get unique values for each option
public function findOptionsWithUniqueValue()
{
$result = $this->getEntityManager()->createQueryBuilder()
->addSelect('attribute_type.name, attribute_value.value')
->distinct()
->from(AttributeType::class,'attribute_type')
->from(AttributeValue::class, 'attribute_value')
->andWhere('attribute_type.id = attribute_value.attributeType')
->getQuery()
->getResult()
;
$out = [];
while( $a = array_shift($result)) {
$out[$a['name']][] = $a['value'];
}
return $out;
}
Related
I'm building a query that would be for creating a list of Posts that have a Project that is associated to the user, and within that structure hit the right criteria for "tierAccess."
My query builder:
$qb = $this->em->createQueryBuilder();
foreach($subs as $sub)
{
if($sub->getDisabled() == true)
{
continue;
}
$qb->select('p')
->from('App\Entity\ProjectPost', 'p')
->where('project = '.$sub->getProject()->getId())
->andwhere('p.Published = true')
->andwhere('p.TierAccess = '.$sub->getProjectTier()->getId())
->orderBy('p.PostTime', 'DESC');
$query = $qb->getQuery();
$object[] = $query->execute();
}
What I am aiming to do is add posts that the user subscription will allow for, and within that subscription making sure their access to this post is allowed (ie: tierAccess).
I then return the object variable to pass along to my Twig template file.
The error I'm receiving is:
[Semantical Error] line 0, col 45 near 'project = 3 AND': Error: 'project' is not defined.
My ProjectPost entity:
class ProjectPost
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $PostTitle;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $PostHero;
/**
* #ORM\Column(type="string", length=255, nullable=false)
*/
private $PostType;
/**
* #ORM\Column(type="text")
*/
private $PostBody;
/**
* #ORM\ManyToOne(targetEntity=Project::class, inversedBy="projectPosts")
* #ORM\JoinColumn(nullable=false)
*/
private $Project;
/**
* #ORM\Column(type="array", nullable=true)
*/
private $TierAccess = [];
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $PostTime;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="projectPosts")
* #ORM\JoinColumn(nullable=true)
*/
private $PostBy;
/**
* #ORM\Column(type="array", nullable=true)
*/
private $PostCategories = [];
/**
* #ORM\Column(type="boolean")
*/
private $Published;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $PostCover;
/**
* #ORM\Column(type="boolean")
*/
private $PostSupporter = 0;
}
The basic mistake is this one:
->where('p.Project = '.$sub->getProject()->getId())
Notice that you declare p to be the alias of Post, and then you don't use it. And even if you define the property as Project, you were trying to use it as project.
Nevertheless, the whole thing is rather suspect. Executing a query within a loop usually points to something wrong with the design.
A simpler approach, using WHERE IN instead of a loop and multiple selects:
// get the "subs" ids in an array:
$subsIds = array_map(fn($s) => $s->getProject()->getId(), $subs);
qb->select('p')
->from('App\Entity\ProjectPost', 'p')
->where('p.Project IN :subsIds')
->andwhere('p.Published = true')
->andwhere('p.TierAccess = '.$sub->getProjectTier()->getId())
->orderBy('p.PostTime', 'DESC')
->setParameter('subsIds', $subsIds)
;
$result = $qb->getQuery()->getResult;
I have entity post and points, the are connected by oneToMany relation. I want make method that will return objects with most count of related comments.
Is it possible?
Please help, i don't have any idea.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/tutorials/ordered-associations.html - should i use this?
entities:
post:
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Points
* #ORM\OneToMany(targetEntity="Points", mappedBy="post", fetch="EAGER")
*/
private $points;
/**
* #return Collection|Points[]
*/
public function getPoints(): Collection {
return $this->points;
}
...
points
/**
* #ORM\Entity(repositoryClass="App\Repository\PointsRepository")
*/
class Points
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Post
* #ORM\ManyToOne(targetEntity="Post", inversedBy="points", fetch="EAGER")
*/
private $post;
public function getPost(): Post {
return $this->post;
}
public function setPost(Post $post ){
$this->post = $post;
}
...
On the assumption that you are already able to return a post with its points you might try something like this:
in App\Repository\PostRepository:
public function postsByPoints() {
return $this->getEntityManager()->createQueryBuilder()
->select('p.post, count(pt.points) N)
->from('App:Points', 'pt')
->join('pt.post', 'p')
->where('some where clause') <- delete this if you're not selecting a subset
->groupBy('p.post')
->orderBy('N')
->getQuery()->getResult();
}
In some controller:
$em = $this->getDoctrine()->getManager();
$postsByPoints = $em->getRepository('App:Post')->postsByPoints();
NB: not tested
This is an working (for me) code
return $this->createQueryBuilder('p')
->innerJoin('p.user', 'c')
->innerJoin('p.points', 'pp')
->andWhere("p.date > '".$now->format("Y-m-d H:i:s")."'")
->setMaxResults($max)
->groupBy('pp.post')
->orderBy('pp.post','DESC')
->getQuery()
->getResult();
I have a family tree like that:
class Family
{
/**
* #var integer
*
* #ORM\Column(type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var Family
*
* #ORM\ManyToOne(targetEntity="Family", inversedBy="children")
*/
private $parent;
/**
* #var string
*
* #ORM\Column(name="name", type="string")
*/
private $name;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Family", mappedBy="parent")
*/
private $children;
// [...]
}
I'm trying to findAll() and get the parent and children attached
$familyRepo = $this->em->getRepository(Family::class);
$families = $familyRepo->findAll();
foreach ($families as $family) {
dump($family->getParent()->getName());
}
I can see the parents name dumped and only one query executed, so they are well attached.
However if I try to show the children:
dump($family->getChildren()->count());
I'm seeing as much queries as there are families.
How can I get the children attached as the parents are ? (without more queries)
What am I forgetting ?
On the one-to-many relation for $children you can specify to fetch objects eagerly as follows:
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Family", mappedBy="parent", fetch="EAGER")
*/
private $children;
See also the docs for other params.
Following #dlondero's suggestion, I forced the deep fetch into the repository.
Here is how I did:
public function getRootNodes($eagerLevels = 5)
{
$qb = $this->createQueryBuilder('entity0')
->select('partial entity0.{id, name, parent}')
->where('entity0.parent IS NULL')
;
for ($i = 0; $i < $eagerLevels; $i++) {
$qb
->leftJoin('entity'.$i.'.children', 'entity'.($i+1))
->addSelect('partial entity'.($i+1).'.{id, name, parent}')
;
}
return $qb->getQuery()->getResult();
}
This partially fetches just what I need so no lazy loading happens.
I also made the level of deepness configurable.
I have a many to many relation between entities Content and Filter.
I would like to get all Contents which have the filters "1", "2" AND "3" (all three). My query gives me results as if I would use "OR, because I get a lot of contents which have only one of the three Filter.
My query:
public function getContentByFilters($categ, $filter, $filter2, $filter3){
$query = $this->createQueryBuilder('c')
->leftJoin('c.filterfilter', 'f')
->where('f.idfilter = :filter_idfilter')
->setParameter('filter_idfilter', $filter)
->andWhere('f.idfilter = :filter_idfilter')
->setParameter('filter_idfilter', $filter2)
->andWhere('f.idfilter = :filter_idfilter')
->setParameter('filter_idfilter', $filter3)
->andWhere('c.contentCategorycontentCategory = ?2')
->setParameter(2, $categ)
->getQuery()->getResult();
return $query;
}
Entity Content:
/**
* Content
*
* * #ORM\Entity(repositoryClass="loic\ContentBundle\Entity\ContentRepository")
* #ORM\Table(name="content", uniqueConstraints={#ORM\UniqueConstraint(name="idcontent_UNIQUE", columns={"idcontent"})}, indexes={#ORM\Index(name="user_id", columns={"user_id"}), #ORM\Index(name="fk_content_content_category1_idx", columns={"content_category_idcontent_category"})})
*/
class Content
{
/**
* #var integer
*
* #ORM\Column(name="idcontent", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idcontent;
.............
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="loic\FilterBundle\Entity\Filter", inversedBy="contentcontent")
* #ORM\JoinTable(name="content_has_filter",
* joinColumns={
* #ORM\JoinColumn(name="content_idcontent", referencedColumnName="idcontent")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="filter_idfilter", referencedColumnName="idfilter")
* }
* )
*/
private $filterfilter;
/**
* Constructor
*/
public function __construct()
{
$this->contentLinked = new \Doctrine\Common\Collections\ArrayCollection();
$this->filterfilter = new \Doctrine\Common\Collections\ArrayCollection();
$this->creationDate = new \DateTime();
}
.........
/**
*
* #return the \Doctrine\Common\Collections\Collection
*/
public function getFilterfilter() {
return $this->filterfilter;
}
/**
*
* #param
* $filterfilter
*/
public function setFilterfilter($filterfilter) {
$this->filterfilter = $filterfilter;
return $this;
}
}
Entity Filter:
use Doctrine\ORM\Mapping as ORM;
/**
* Filter
*
* * #ORM\Entity(repositoryClass="loic\FilterBundle\Entity\FilterRepository")
* #ORM\Table(name="filter", uniqueConstraints={#ORM\UniqueConstraint(name="idfilter_UNIQUE", columns={"idfilter"})}, indexes={#ORM\Index(name="fk_filter_filter_category1_idx", columns={"filter_category_idfilter_category"})})
*/
class Filter
{
/**
* #var integer
*
* #ORM\Column(name="idfilter", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idfilter;
............
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="loic\ContentBundle\Entity\Content", mappedBy="filterfilter")
*/
private $contentcontent;
.....
/**
* Constructor
*/
public function __construct()
{
$this->contentcontent = new \Doctrine\Common\Collections\ArrayCollection();
$this->user = new \Doctrine\Common\Collections\ArrayCollection();
$this->status = 1;
}
......
/**
*
* #return the \Doctrine\Common\Collections\Collection
*/
public function getContentcontent() {
return $this->contentcontent;
}
/**
*
* #param
* $contentcontent
*/
public function setContentcontent($contentcontent) {
$this->contentcontent = $contentcontent;
return $this;
}
Since you want to check for 3 filters, you need to JOIN 3 times as well. It is as if you had 3 different ManyToMany relationships and only want one row, that fits 3 specific requirements - only difference is, that you join the same table. Untested, but it should work like this
$query = $this->createQueryBuilder('c')
->join('c.filterfilter', 'f1')
->join('c.filterfilter', 'f2')
->join('c.filterfilter', 'f3')
->where('f1.idfilter = :filter_idfilter1')
->andWhere('f2.idfilter = :filter_idfilter2')
->andWhere('f3.idfilter = :filter_idfilter3')
->andWhere('c.contentCategorycontentCategory = :category')
->setParameters(array(
'filter_idfilter1' => $filter,
'filter_idfilter2' => $filter2,
'filter_idfilter3' => $filter3,
'category' => $categ,
))
->getQuery()->getResult();
Inner join is probably better as you only want rows, that do fulfill these requirements. Also be aware, that using too many joins is usually considered bad practice and may slow down performance. Preferred solution is usually to construct a specific view instead of multiple joins.
Here's what I'm having trouble with.
I've a Table which contains a column called shown_on_homepage and only one row should be set to 1, the rest should all be set to 0. I'm trying to add a new row to the database and this one should be set to 1, setting the one that previously had a 1 to 0.
In MySQL I know this can be achieved by issuing an update before the insert:
UPDATE table_name SET shown_on_homepage = 0
Here's my Entity:
class FeaturedPerson {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="content", type="string", length=2500, nullable=false)
*/
private $content;
/**
* #var \DateTime
*
* #ORM\Column(name="date_updated", type="datetime")
*/
private $dateUpdated;
/**
* #var bool
*
* #ORM\Column(name="shown_on_homepage", type="boolean", nullable=false)
*/
private $isShownOnHomepage;
//...
public function getIsShownOnHomepage() {
return $this->isShownOnHomepage;
}
public function setIsShownOnHomepage($isShownOnHomepage) {
$this->isShownOnHomepage = $isShownOnHomepage;
return $this;
}
}
And for the Controller I've:
$featured = new FeaturedPerson();
$featured->setContent('Test content.');
$featured->setDateUpdated('01/02/2013.');
$featured->setIsShownOnHomepage(TRUE);
$em = $this->getDoctrine()->getManager();
$em->persist($featured);
$em->flush();
It does add the new row, but the one that had a shown_on_homepage set to 1 still has it. I've researched but I couldn't find a way to achieve this, I hope you can help me.
You could execute a query prior to your existing code in your controller:
$queryBuilder = $this->getDoctrine()->getRepository('YourBundleName:FeaturedPerson')->createQueryBuilder('qb');
$result = $queryBuilder->update('YourBundleName:FeaturedPerson', 'd')
->set('d.isShownOnHomepage', $queryBuilder->expr()->literal(0))
->where('d.isShownOnHomepage = :shown')
->setParameter('shown', 1)
->getQuery()
->execute();
Change 'YourBundleName' to your bundle name.