using querybuilder and DQL to pull specific attributes of entity - php

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;

Related

Query builder cannot associate with category entity

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))

Doctrine reusable Criteria in Entity and Repository with joins

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

Get relation of ManyToMany entities

I stuck with getting ManyToMany relation. I have two entities Offer and OfferType.
I defined ManyToMany relation only on OfferEntity and it looks like saving offers with multiple OfferType works fine in database I see correct joining table.
OfferEntity.php
/**
* Offer Entity
*
* #ORM\Table(name="offer")
* #ORM\Entity(repositoryClass="ProjectBundle\Repository\OfferRepository")
*/
class Offer
{
/**
* #Assert\Count(
* min = "1",
* minMessage = "You must specify at least one offer type."
* )
* #ORM\ManyToMany(targetEntity="OfferType")
*/
private $types;
public function __construct()
{
$this->types = new ArrayCollection();
}
/**
* #return ArrayCollection
*/
public function getTypes()
{
return $this->types;
}
(...)
Right now, I would like to get all types assigned to the offer. I tried it like this:
// $offer is an Entity
$query = $em->createQueryBuilder()
->select('offer.types')
->from('ProjectBundle:Offer', 'offer')
->where('offer = :offer')
->setParameters([
'offer' => $offer
]);
Unfortunately I getting error:
[2/2] QueryException: [Semantical Error] line 0, col 13 near 'types FROM ProjectBundle:Offer': Error: Invalid PathExpression. Must be a **StateFieldPathExpression**.
[1/2] QueryException: SELECT offer.types FROM ProjectBundle:Offer offer WHERE offer = :offer
Based on StackOverflow answers for similar questions tried it also to use IDENTITY():
// $offer is an Entity
$query = $em->createQueryBuilder()
->select('IDENTITY(offer.types)')
->from('ProjectBundle:Offer', 'offer')
->where('offer = :offer')
->setParameters([
'offer' => $offer
]);
But then I have:
[2/2] QueryException: [Semantical Error] line 0, col 22 near 'types) FROM ProjectBundle:Offer': Error: Invalid PathExpression. Must be a SingleValuedAssociationField.
[1/2] QueryException: SELECT IDENTITY(offer.types) FROM ProjectBundle:Offer offer WHERE offer = :offer
I would really appreciate any hints about getting ManyToMany relation, maybe my approach for this is incorrect?
Your approach is not quite right. You need to join the entity you want to load. You should spend a bit more time reading the doctrine documentation on entity relations & querybuilder.
Like so.
$query = $em->createQueryBuilder()
->select('offer', 'types')
->from('ProjectBundle:Offer', 'offer')
->join('offer.types', 'types')
->where('offer = :offer')
->setParameters([
'offer' => $offer
]);
Then your result will contain what you want.
Note: you don't even have to join types to get types from an offer, you could also use
$offer->getTypes()
And types will lazy load in standard doctrine manner.
But querying it as above will load types in advance - so you avoid lazy loading. This may or may not be a better approach depending on requirements.
If you have your many to many mappings set up only on one side, you'll only be able to query for the Offer and then retrieve the types from it:
$builder = $em->createQueryBuilder()
->select('o', 't')
->from('ProjectBundle:Offer', 'o')
->join('o.types', 't')
->where('o.id = :offer')
->setParameter('offer', $offerId);
$offer = $builder->getQuery()->getOneOrNullResult();
$types = $offer->getTypes();
That was assuming you only had $offerId to work with. If you already have the whole Offer instance, you might just as well just call getTypes() on it and doctrine will take care of the rest.
If you were to define the inverse mappings on the OfferType entity like so:
class OfferType
{
/**
* #ORM\ManyToMany(targetEntity="Offer", mappedBy="types")
*/
private $offers;
}
You could make queries for just the types:
$builder = $em->createQueryBuilder()
->select('t')
->from('ProjectBundle:OfferType', 't')
->join('t.offers', 'o')
->where('o = :offer')
->setParameter('offer', $offer);
$types = $builder->getQuery()->getResult();

Symfony2/Doctrine joined query

I need to create a simple query that produces a result set of a database entry plus the username of the person that posted it.
I've tried to setup the associations properly but I'm not sure if that's right either. I'm finding the whole idea of using these small string identifiers quite confusing. Surely there must be a simpler way of doing a join?
My two entities:
class Users
{
// ...
/**
* #ORM\Column(type="string")
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $username;
// ..
}
and
class Titles
{
// ....
/**
* #ORM\Column(type="string")
* #ORM\ManyToOne(targetEntity="Users", inversedBy="username")
*/
protected $addedBy;
// ....
}
with the following in the controller:
$titles = $em->createQueryBuilder()
->select('t.*', 'u.*')
->from('dvdLoggerdvdBundle:Titles', 't')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC')
->getQuery()
->getResult();
I'm getting the following error:
[Semantical Error] line 0, col 69 near 'u ORDER BY t.title': Error: Class
dvdLogger\dvdBundle\Entity\Titles has no association named addedBy `
Update 1
I made all the changes suggested by Tom and did lots of reading!
It appears that in order to overcome the lazy loading feature I need to carry out a leftJoin. I have rewritten my query as follows:
public function getAllTitles()
{
// view all records in db
$titles = $this->createQueryBuilder('t')
->select('t, u')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC');
return $titles->getQuery()->getResult();
}
I am getting a result set, but the addedBy is returning NULL when I dump the result set. As far as I'm aware shouldn't this pull the associated field in from the other table?
Best practice is to reference the entity by its id, you are trying to reference it using the username. The inversed field should also be a specific field not an existing one that holds data. And keep it mind this field is optional and defines the associations as bidirectional, for the specified use case you don't actually need it as you are joining from the Titles entity. I would advice reading the doc here http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations as well as here http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
Bidirectional association (w/ inversed field)
First get rid of that line:
#ORM\Column(type="string")
In your $addedBy annotations and change inverseBy="username" to inversedBy="titles" (note the typo)
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Then in your Users Entity add
/**
*
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $titles;
And get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Last make sure you update the database schema
Then your query should return the expected result.
Unidirectional association (w/out inversed field)
Get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Then get rid of that line in your $addedBy annotations:
#ORM\Column(type="string")
As well as inverseBy="username"
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Last make sure you update the database schema
Then your query should return the expected result.

Symfony2 error while display entity related ManyToOne

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;
}

Categories