I have searched for this specific issue for a long time and not sure if I have a misunderstanding in using Criteria in queries or if it is just not possible (yet?)
This is an example with only the minimum fields and conditions.
I have two Entities:
Product
field: visible
Brand
field: active
Within the Brand entity, I have a method
"getVisibleProducts()" where I want to get all Products with
product.visible = 1
And in my ProductRepository I have a method findAllVisibleForBrand(Brand $brand) where I want to get all Products with product.visible = 1 AND brand.active = 1
So my idea for best reusability was to make a criteria for the product.visible value within my ProductRepository and use it for query within the repository
/**
* #param Brand $brand
*
* #return Product[]
*/
public function findAllVisibleForBrand(Brand $brand)
{
return $this->createQueryBuilder('product')
->leftJoin('product.brand', 'brand')
->addCriteria(ProductRepository::createVisibleCriteria())
->andWhere('product.brand = :brand')
->setParameter('brand', $brand)
->getQuery()
->execute();
;
}
/**
* #return Criteria
*/
public static function createVisibleCriteria()
{
return Criteria::create()
->andWhere(Criteria::expr()->eq('visible', '1'))
;
}
and within my Brand entity:
/**
* #return Product[]|ArrayCollection
*/
public function getVisibleProducts()
{
return $this->getProducts()->matching(ProductRepository::createVisibleCriteria());
}
This is working fine and without problems.
The same idea for the BrandRepository
/**
* #return Brand[]
*/
public function findAllActiveBrands()
{
return $this->createQueryBuilder('brand')
->addCriteria(BrandRepository::createActiveCriteria())
->getQuery()
->execute()
;
}
/**
* #return Criteria
*/
public static function createActiveCriteria()
{
return Criteria::create()
->andWhere(Criteria::expr()->eq('active', '1'))
;
}
Now my idea was to use the Brand Active Criteria within my ProductRepository to get only Products that are visible but only IF the brand is active. So my method now looks like this:
/**
* #param Brand $brand
*
* #return Product[]
*/
public function findAllVisibleForBrand(Brand $brand)
{
return $this->createQueryBuilder('product')
->leftJoin('product.brand', 'brand')
->addCriteria(BrandRepository::createActiveCriteria()) // added this line
->addCriteria(ProductRepository::createVisibleCriteria())
->andWhere('product.brand = :brand')
->setParameter('brand', $brand)
->getQuery()
->execute();
;
}
by adding the brandActiveCriteria this way, I get the following error:
[Semantical Error] line 0, col 97 near 'active = :active': Error: Class AppBundle\Entity\Product has no field or association named active
The Query doctrine is building looks like this:
SELECT product FROM
AppBundle\Entity\Product product
LEFT JOIN product.brand brand
WHERE
product.active= :active
AND product.visible = :visible
AND product.brand = :brand
adding the table name into the criterias work for the Query within the repository but not for the query within the entity since the table alias is not the same.
Is there any chance to have the rules as criteria or in any other place without duplicating it over multiple classes?
I got the idea of using static function to create criterias from
https://knpuniversity.com/screencast/collections/criteria-collection-filtering
You can add the alias in your criteria:
public static function createActiveCriteria($alias = null)
{
return Criteria::create()
->andWhere(Criteria::expr()->eq(($alias ? $alias.'.' : '').'active', '1'))
;
}
And then you call your criteria like this:
// "brand" is the name of the given alias in leftJoin
BrandRepository::createActiveCriteria('brand');
So your findAllVisibleForBrand() method will look like this:
public function findAllVisibleForBrand(Brand $brand)
{
return $this->createQueryBuilder('product')
->leftJoin('product.brand', 'brand')
->addCriteria(BrandRepository::createActiveCriteria('brand')) // Notice the alias parameter here
->addCriteria(ProductRepository::createVisibleCriteria())
->andWhere('product.brand = :brand')
->setParameter('brand', $brand)
->getQuery()
->execute();
;
}
And your DQL should be like this:
SELECT product FROM
AppBundle\Entity\Product product
LEFT JOIN product.brand brand
WHERE
brand.active= :active
AND product.visible = :visible
AND product.brand = :brand
Related
I have 2 entities, MyItem and MyItemCategory. When I try to create a query with query builder I get the following error:
Error: Class App\Entity\MyItem has no
field or association named myitem_category_id (500 Internal Server Error)
This is the where part of my query builder:
$queryBuilder = $this->getDoctrine()
->getRepository('App\Entity\MyItem')
->createQueryBuilder('m');
// adds where for category_id:
$queryBuilder->where('m.myitem_category_id = :category_id')->setParameter('category_id',$category_id);
Here're first entities:
/**
* #ManyToOne(targetEntity="MyItemCategory")
* #JoinColumn(name="myitem_category_id", referencedColumnName="id")
*/
private $myItemCategory;
...and my category entity:
/**
* #ORM\OneToMany(targetEntity="MyItem", mappedBy="myItemCategory")
*/
private $myItemCategories;
The querybuilder actually doesn't care about database fields, but instead uses the object mapper, so there is no field my_item_category_id on your Entity, but instead a field myItemCategory
So, you can either do:
$querybuilder
->where('m.myItemCategory = :category')
->setParameter('category', $category) // <-- an actual MyItemCategory object
or you can join it in and check for the id:
$querybuilder
->leftJoin('m.myItemCategory', 'mic')
->where('mic.id = :micid')
->setParameter('micid', $category_id)
(I actually don't know if m.myItemCategory.id = :micid might work ........ you could try ;o))
I'm trying to make topic / comment system with symfony and I have some question about doctrine's querybuilder.
if I have two entities connected:
/**
* #ORM\ManyToOne(targetEntity="Topicit", inversedBy="comments")
* #ORM\JoinColumn(name="topic_id", referencedColumnName="id")
*/
protected $topicit;
and
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="topicit")
*/
protected $comments;
Currently I have builder (getting every column):
$repository = $this -> getDoctrine() -> getRepository('FTFairyBundle:Comment');
$query = $repository->createQueryBuilder('p')
->orderBy('p.topicit', 'ASC')
->getQuery();
$comment = $query ->getResult();
How I make it get only wanted topic_id? Like if I open page with id #1 I wanna get comments connected to that id.
Thanks for your time
You don't need to use query builder for that kind of tasks, you can simply use a getter in your Topicit entity (that of course you need to retrieve from DB)
Something like ->getComments() *(of course that method needs to be defined in Topic class)
Doctrine will take care of load only "consistent" data from your Topic object
Method definition
public getComments()
{
return $this->comments;
}
If you want to query directly you can modify your code as follows
$topicit_id = ... //code to retrieve topicit_id
$repository = $this->getDoctrine()-> getRepository('FTFairyBundle:Comment');
$query = $repository->createQueryBuilder('p')
->where('p.topicit = :topicit_id')
->setParameter('topicit_id', $topicit_id)
->orderBy('p.topicit', 'ASC')
->getQuery();
$comment = $query ->getResult();
I am trying to display a "CategorieActivite" in an activite.html twig page.
I have 2 entities. The first one is Activite. The second one is CategorieActivite.
I have put annotations in the Activite.
I have prepared my query with join in ActiviteRepository.
It's ok in my database (foreign key).
BUT i get this error :
Method "CategorieActivite" for object "AssoFranceRussie\MainBundle\Entity\Activite" does not exist in (in my twig page)
Do I have to do something else to get some entity data from another entity?
Thanks
EB
The code :
class Activite
{
// $categorieActiviteId lié à l'entité CategorieActivite
// ManyToOne
/**
* #ORM\ManyToOne(targetEntity="AssoFranceRussie\MainBundle\Entity\CategorieActivite")
* #ORM\JoinColumn(name="categorie_activite_id", referencedColumnName="id")
*/
private $categorieActiviteId;
...
}
ActiviteRepository :
public function getAllActivites()
{
$query = $this->getEntityManager()->createQuery(
'SELECT a,c,n
FROM AssoFranceRussieMainBundle:Activite a
JOIN a.categorieActiviteId c
JOIN a.niveauActiviteId n
ORDER BY a.nom ASC '
);
return $query->getResult();
}
And in the twig html:
<p><strong>{{activite.CategorieActivite.libelle}}</strong></p>
You should create getter for $categorieActiviteId property.
So in Activite class you should have
public function getCategorieActivite() {
return $this->categorieActiviteId;
}
and in twig you should have:
<p><strong>{{activite.getCategorieActivite.libelle}}</strong></p>
Dont forget libelle have to be public method or property
Thank you for your help. It works now.
Actually i had already the getter with annotation in the class Activite. So, I should have write in twig {{activite.categorieActiviteId.libelle}} instead of {{activite.categorieActivite.libelle}}. The 2 ways to access the data work.
the getter in Activite class:
/**
* Get categorieActiviteId
*
* #return \AssoFranceRussie\MainBundle\Entity\CategorieActivite
*/
public function getCategorieActiviteId()
{
return $this->categorieActiviteId;
}
I have the following query:
$query = $em->createQueryBuilder()->select('p.id, p.name, p.price, p.cover')
->from("SiteMainBundle:Product", 'p')
->innerJoin('p.category', 'c')
->innerJoin('p.shop', 'shop')
;
And inside my product entity the cover is actually another entity, which looks like this:
/**
* #Accessor(getter="getCover")
*/
private $cover;
public function getCover()
{
if($this->pictures->count() > 0) {
return $this->pictures[0];
}
return new ProductPicture();
}
However when I do this it gives me an error:
"[Semantical Error] line 0, col 32 near 'cover FROM SiteMainBundle:Product': Error: Class Site\\MainBundle\\Entity\\Product has no field or association named cover"
I know this is because it is declared as private, but can I call the method getCover from the query builder?
If you want to load only specific fields, instead of the whole entity, you need to use the partial object syntax:
$em->createQueryBuilder()->select('p.{id,name,price,cover}')
This will select only the 4 specific columns you want.
It's not because of private visibility, but because cover is not defined as an entity field.
/**
* #ORM\Column(type="string")
*
* #Accessor(getter="getCover")
*/
private $cover;
Documentation states:
class Cart
{
// ...
/**
* #OneToOne(targetEntity="Customer", inversedBy="cart")
* #JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
// ...
}
This annotation represents such sql:
JOIN Customer c ON c.id = cart.customer_id
And the issue is that I need to add additional comparison there, like:
JOIN Customer c ON c.id = cart.customer_id AND c.anotherField = <constant>
Any solutions for that?
UPD:
the real additional condition I need for now is <const> BETWEEN c.f1 AND c.f2
There doesn't seem to be any solution to your problem that Doctrine could do auto-magically.
Since #ficuscr already gave you a solution for queries, there's only one more thing to handle - check your additional criteria and return the Customer instance in your getter on success and NULL on failure to meet additional criteria.
class Cart
{
const VALUE = '<some_constant_value>';
/**
* #OneToOne(targetEntity="Customer", inversedBy="cart")
* #JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
// ...
/**
* #return Customer|null
*/
public function getCustomer()
{
if ($this->customer->getField1() <= self::VALUE
&& $this->customer->getField2() >= self::VALUE
) {
return $this->customer;
}
return null;
}
}
If this was a One-To-Many relation, Collection Filtering API (a.k.a. Criteria) could be used to filter the collection created by the mappings:
use Doctrine\Common\Collections\Criteria;
class Cart
{
const VALUE = '<some_constant_value>';
/**
* #OneToMany(targetEntity="Customer", mappedBy="cart")
*/
private $customers;
// ...
/**
* #return Customer[]|ArrayCollection
*/
public function getCustomers()
{
$expr = Criteria::expr();
$criteria = Criteria::create()
->where($expr->andX(
$expr->lte('field1', self::VALUE),
$expr->gte('field2', self::VALUE)
));
return $this->patientProblems->matching($criteria);
}
}
you can use the WITH keyword to specify additional join conditions, as you can see in some of the examples.
i think this should get you going:
SELECT l, c FROM location
INNER JOIN Customer c
WITH CURRENT_TIMESTAMP() BETWEEN c.f1 AND c.f2
WHERE CURRENT_TIMESTAMP() BETWEEN l.f1 AND l.f2
i removed the ON clause because i think there's no need to explicitly specify the join's ON fields unless they are not the "standard" ones (id of each entity)
also notice the call to CURRENT_TIMESTAMP() which translates into MySQL's NOW(). check out a list of other pretty useful aggregate functions and expresions here