Search within multiple values in Doctrine - php

I've got a Product Entity and it can have multiple attributes like applications. Where "applications" may have mutliple values too.
So the product has an array of values for "applications", how can I save that with Doctrine so I can do a findBy('applications' => 'whatever') or similar later?
Product 1
application1
application3
Product 2
application1
application2
So a later findBy('applications' => 'application1') should find both products.

Supposing you have configured a one to many relation between your entities, like this:
Product entity:
/**
* #ORM\OneToMany(targetEntity="Aplication", mappedBy="product", cascade={"persist"})
*/
protected $aplications;
Aplication entity:
* #ORM\ManyToOne(targetEntity="Product", inversedBy="aplications")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
* })
protected $product;
you can create a DQL like this on your product repository:
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('p')
->from('bundleNamespace:Product', 'p')
->leftJoin('p.applications', 'a')
->andWhere($qb->expr()->in('a.id', ':aplication'))
->setParameter('aplication', $aplication);
return $qb->getQuery()->getResult();

Try this:
public function findProductByApplication($app) {
$query = $this->em->createQuery('SELECT DISTINCT p FROM Product p JOIN p.applications app WHERE app = :app');
$query->setParameter('app', $app);
return $query->getResult();
}

Related

Doctrine 2 joining table, ManyToOne unidirectional, where tbl2.value = :value

I'm trying to figure out how to join two tables, while querying the second table. I thought it was as simple as:
// Within ServerServiceRepository
return $this->createQueryBuilder('ss')
->join(ServiceType::class, 't')
->where('t.serviceTypeName = :name')
->getQuery()
->execute(['name' => $name]);
But turns out, not so much...
The issue is the query is NOT joining the keys (service_type_id on both tables). What's going on here? I have all the OneToMany relationships setup correctly:
/**
* ServerService
*
* #ORM\Table(name="server_services")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ServerServiceRepository")
*/
class ServerService extends AbstractEntity
{
/**
* #var ServiceType
*
* #ORM\Column(name="service_type_id", type="integer", nullable=false)
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Supportal\ServiceType", fetch="LAZY", inversedBy="serviceTypeId")
* #ORM\JoinColumn(name="service_type_id", referencedColumnName="service_type_id")
*/
private $serviceType;
// [...]
}
/**
* ServiceType
*
* #ORM\Table(name="service_types")
* #ORM\Entity
*/
class ServiceType extends \AppBundle\Entity\AbstractEntity
{
/**
* #var integer
*
* #ORM\Column(name="service_type_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\OneToMany(targetEntity="AppBundle\Entity\ServerService", fetch="EXTRA_LAZY", mappedBy="serviceType")
*/
private $serviceTypeId;
/**
* #var string
*
* #ORM\Column(name="service_type_name", type="string", length=255, nullable=true)
*/
private $serviceTypeName;
// [...]
}
I've added / removed the OneToMany relationship from ServiceType to no change. This is really a unidirectional relationship. Per Doctrine's own docs (Chapter 5), ServerType does not require a relationship mapping.
The SQL query is generating a JOIN that's missing the actual keys:
INNER JOIN service_types s1_ ON (s1_.service_type_name = ?)
What am I missing here on Doctrine to get this working right? I've looked at the tutorials. Symofny's example is almost exact what I'm after, except I need to select by "category name" not Product id: https://symfony.com/doc/2.8/doctrine/associations.html
I've got to be missing something so super simple. But I can't for the life of me peg it...
Edit:
I've removed the OneToMany from ServiceType in my code. It's optional. Not needed for this anyway. This is a unidirectional relationship.
I've tried this:
return $this->createQueryBuilder('ss')
->join('ss.serviceType', 't')
->where('t.serviceTypeName = :name')
->getQuery()
->execute(['name' => $name]);
Resulting in this error:
[Semantical Error] line 0, col 85 near 't WHERE t.serviceTypeName': Error: Class AppBundle\Entity\ServerService has no association named serviceType
Solution
The solution was removing the #ORM Column definition. Looks like it's a conflict in the relationship definitions.
First of all, change the ManyToOne docblock definition to:
/**
* #var ServiceType
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Supportal\ServiceType", fetch="LAZY", inversedBy="serverService")
* #ORM\JoinColumn(name="service_type_id", referencedColumnName="service_type_id")
*/
private $serviceType;
Also change the OneToMany, remove line #ORM\OneToMany(targetEntity="AppBundle\Entity\ServerService", fetch="EXTRA_LAZY", mappedBy="serviceType")
Create new field serverService for the OneToMany relation:
/**
*
* #ORM\OneToMany(targetEntity="ServerService", mappedBy="serviceType")
*/
private $serverService;
You should join on the relation field, in this case serviceType. The way you defined the join is like selecting both tables.
Change to:
return $this->createQueryBuilder('ss')
->join('ss.serviceType', 't')
->where('t.serviceTypeName = :name')
->getQuery()
->execute(['name' => $name]);
Since you are applying a condition on the joined result here, using a LEFT JOIN or simply JOIN is the same.
References:
How to Work with Doctrine Associations / Relations
how to do left join in doctrine
Left join ON condition AND other condition syntax in Doctrine

Doctrine join entities

I'm trying to make some efficient improvements when querying for an entity which has a relationship to another entity.
Entity A:
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=225)
* #Assert\NotBlank(message="Please enter a name for your profile.")
*/
protected $display_name;
/**
* #ORM\OneToOne(targetEntity="Posts", mappedBy="profile", cascade={"all"}, fetch="LAZY")
*/
protected $posts;
Entity B:
/**
* #var integer
* #ORM\Column(name="profile_id", type="integer")
* #ORM\Id
*/
protected $profile_id;
/**
* #ORM\OneToOne(targetEntity="Profile", inversedBy="posts")
* #ORM\JoinColumn(name="profile_id", referencedColumnName="id")
*/
protected $profile;
/**
* #var string
* #ORM\Column(name="content", type="string")
*/
protected $content;
When I count the number of queries being executed I get two, I guess being doctrine runs two separate queries for each entity even through they have a relationship.
I am currently fetching entity A like so:
public function fetchById($id)
{
return $this->createQueryBuilder('p')
->where('p.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
And then calling entity B like so:
$profile = $profileRepository->fetchById($user->getUserId());
$lastpost = $profile->getPosts()[0];
But I want to be able to join the second entity in this query so that I can just call one query rather than two. I was hoping to do something like this:
public function fetchById($id)
{
return $this->createQueryBuilder('p')
->select('p','pp')
->leftJoin(Posts::class, 'pp', \Doctrine\ORM\Query\Expr\Join::WITH, 'p.id = pp.profile_id')
->where('p.id = :id')
->setParameter('id', $id)
->getQuery()
->getResults();
}
However the left join returns an array of two entities. This is not what I am wanting, because I want to still be able to call for example the getPosts() method in entity A.
I essentially want to populate entity A, including all the related entities. But by only executing one query rather than two, is this possible in doctrine?
Some time ago I had a similar scenario (not with 1-1 but 1-n though), which I solved like this:
// ...Repository
public function findAllForUserWithAll(\AppBundle\Entity\User $u)
{
$query = $this->getEntityManager()->createQueryBuilder('ca1')
->add('select', 'c, s, i, m')
->add('from', 'AppBundle:Contact c')
->leftJoin('c.skills', 's')
->leftJoin('c.interests', 'i')
->leftJoin('c.metAt', 'm')
->where('c.user = :user')
->orderBy('c.lastname', 'ASC')
->setParameters([
'user' => $u,
])
->getQuery();
return $query->getResult();
}
This was in Symfony 2.8.* with Doctrine ^2.4.8 - and the result is one query with 3 joins (instead of 4 queries). Probably not the best code and honestly all the magic happened under the hood. Also your code looks alot alike. But maybe this is expected due to fetch="LAZY" (on entity A) - which my entity does not have?

Fetch entity without related objects doctrine

I have the following classes:
class Category {
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
private $products;
...
}
class Product {
...
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
...
}
when i try to fetch one product from my db like this:
$query = $doctrineManager->createQuery(
"
SELECT p FROM AppBundle:Product p
WHERE p.id = :id
"
)->setParameter('id', $id);
$result = $query->getSingleResult();
i get not only my product, but also get category with all products (except the one i found). So, how can i fetch only model what i want without any related model?
They are just stubs, you don't actually fetch any related entity information unless you are using fetch=EAGER.
This answer explains it pretty well.
What is the difference between fetch="EAGER" and fetch="LAZY" in doctrine
In summary, you can't get rid of the associations, but they don't load the other entities until you call the data unless you specifically request otherwise. So don't worry about it.

How to JOIN without relational table in Symfony Doctrine with QueryBuilder between 2 entities

I have an Entity Video related with a Entity Category and I need to run this SQL with Doctrine QueryBuilder, with this I can get the most used categories in all videos (1000+):
SELECT c.*
FROM Video v
INNER JOIN video_category vc ON vc.video_id = v.id
INNER JOIN Category c ON vc.category_id = c.id
GROUP BY c.id
HAVING COUNT(v.id) > 1000
ORDER BY c.name ASC;
My querybuilder:
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->select('c')
->from('AcmeVideoBundle:Video', 'v')
// Can Doctrine join itself silently with relational info in the Entities?
->join('AcmeCategoryBundle:Category', 'c', Expr\Join::WITH, 'v.id = c.id')
->groupBy('c.id')
->having('COUNT(v.id) > 1000')
->orderBy('c.name', 'ASC')
->getQuery();
But the SQL query output by queryBuilder is this:
SELECT c0_.id AS id0, c0_.NAME AS name1
FROM Video v1_
INNER JOIN Category c0_ ON (v1_.id = c0_.id)
GROUP BY c0_.id
HAVING COUNT(v1_.id) > 1000
ORDER BY c0_.NAME ASC
Without the relational table (video_category)
The Entities mapping:
/**
* Video
*
* #ORM\Table
* #ORM\Entity(repositoryClass="Acme\VideoBundle\Entity\VideoRepository")
*/
class Video
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Acme\CategoryBundle\Entity\Category", cascade={"persist"})
*/
private $category;
// More fields, getters and setters etc...
}
/**
* Category
*
* #ORM\Table
* #ORM\Entity(repositoryClass="Acme\CategoryBundle\Entity\CategoryRepository")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
// More fields, getters and setters etc...
}
How can I use the relation table to run the original SQL query with doctrine Querybuilder? I missed something?
INFO: When I findBy{field}, persist, flush, clear on all entities works fine, the Doctrine relations are ok, I have a Video, Category and video_category tables fine, the original SQL query works perfect.
// Can Doctrine join itself silently with relational info in the Entities?
->join('AcmeCategoryBundle:Category', 'c', Expr\Join::WITH, 'v.id = c.id')
Yes! In fact that is one of the major reasons for using an ORM such as Doctrine 2.
Try:
->leftJoin('v.category','c')
The manual goes into more details though oddly enough is does not seem to have a join example. Hence the common confusion.
http://docs.doctrine-project.org/en/latest/reference/query-builder.html
And you may not be aware of this but the United Nations has passed a resolution outlawing the use of abbreviations for aliases. Just to be safe, try:
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->addSelect('category')
->from('AcmeVideoBundle:Video', 'video')
->leftJoin('video.category', 'category')
->groupBy('category.id')
->having('COUNT(video.id) > 1000')
->orderBy('category.name', 'ASC')
->getQuery();
Ok, solved, the problem was that the Entities isn't fully mapped for ManyToMany bidirectional relationship.
The Entities are now:
class Video
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Acme\CategoryBundle\Entity\Category", inversedBy="video") // Note the inversedBy key
*/
private $category;
}
class Category
{
/**
* #var Video
*
* #ORM\ManyToMany(targetEntity="Acme\VideoBundle\Entity\Video", mappedBy="category") // Again the inversed
*/
private $video; // New field for bidirectional ManyToMany
}
And the final QueryBuilder working (now with full alias version :P):
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->select('category')
->from('AcmeCategoryBundle:Category', 'category')
->join('category.video', 'video')
->groupBy('category.id')
->having('COUNT(video.id) > 1000')
->orderBy('category.name', 'ASC')
->getQuery();
Best regards

findBy with JOIN criteria in Symfony2

I have 3 simple tables: user, role, user_x_role with Many-To-Many relation. I have 2 entities: User and Role. User entity has $userRoles property with relation annotation. In Controller I need to fetch all users with specific role. But I don't know how to use JOIN in controller. Current wrong code:
$role = $this->getDoctrine()->getRepository('TestBackEndBundle:Role');
$roles = $role->findBy(array('name' => 'ROLE_PARTNER'));
$user = $this->getDoctrine()->getRepository('TestBackEndBundle:User');
$partners = $user->findBy(array('userRoles' => $roles));
It thows "Undefined index: joinColumns in ...". But I have joinColumns in User entity:
/**
* #ORM\ManyToMany(targetEntity="Role")
* #ORM\JoinTable(name="user_x_role",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE", onUpdate="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id", onDelete="CASCADE", onUpdate="CASCADE")}
* )
* #var ArrayCollection
*/
protected $userRoles;
IMO the best way for it is create your own repository for User entity. Then in that repository create method like "getUsersByRole" where you make the query you want with query builder.
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('u')
->from('\namespace\for\User', 'u')
->join('u.roles', 'r')
->where(...)
return $qb->getQuery()->getResult();

Categories