Doctrine select many from the one (many-to-one unidirectional (different bundles)) - symfony

Working on a legacy project which restricts the options available, has left me in a situation where I need to solve the following problem, ideally with doctrine.
I have two entities in different bundles that have a unidirectional many-to-one link.
BundleA has dependency on BundleB and the entities are linked similar to this:
BundleA/Entity/TheMany:
/**
* #var TheOne $theOne
* #ORM\ManyToOne(targetEntity="BundleB\Entity\TheOne")
* #ORM\JoinColumn(name="theone_id", referencedColumnName="id", onDelete="SET NULL")
*
*/
private $theOne;
From BundleB I now need to select all TheOne entities, and for each I need all of the TheMany entities.
The query also needs to be sortable on any property of TheOne entity, or the count of related TheMany entities.
It is fairly simple in Doctrine to build a query which brings back all TheOne entities and one of TheMany for each... however I am having some difficulty coming up with a Doctrine query that will bring back all of the related TheMany entities rather than just one.
I was hoping someone might have encountered a similar issue and therefore have some insight?
This may not have been explained clearly enough, in which case please direct me to explain further.

In the end I was able to achieve what I needed by using GROUP_CONCAT (which required inclusion of https://github.com/beberlei/DoctrineExtensions).
The query looks something like this:
$queryBuilder->select(
'to,
GROUP_CONCAT(DISTINCT tm.id SEPARATOR \',\') as theManyIds,
COUNT(DISTINCT tm.id) as HIDDEN theManyCount'
)
->from('BundleB\Entity\TheOne', 'to')
->leftJoin(
'BundleA\Entity\TheMany',
'tm',
Join::WITH,
'to.id = tm.theOne'
)
->groupBy('to.id')
->orderBy($sortString, $direction)
->setFirstResult($start)
->setMaxResults($limit);
I compromised by accepting the consequences of linking the two bundles - however that could have been avoided by making use of Native SQL and Result Set Mapping (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html).

So what you are trying to do is to get all the ones and for each one find all the many. But you want to put all the many in one array or you want to create an array of array for the entities ? (what i did here)
$em = $this->getDoctrine()->getManager();
$theones = $em->getRepository('BundleA:theOne')
->createQueryBuilder('p')
->OrderBy(//your ordering)
->getQuery()
->getArrayResult()
$theManies = [];
for($theones as $theOne){
$theManies [] = $em->getRepository('BunbleB:theMany')
->createQueryBuilder('p')
->Where('p.theOne = :theOne')
->setParameter('theOne', $theOne)
->getQuery()
->getArrayResult();
$finalOnes[$theOne->getId()] = sizeof($theManies)
}
asort($finalOnes);
return array_keys($finalOnes);

Related

How to build this query on Symfony with QueryBuilder?

Please I am a beginner on symfony and I have 2 entities: category and professionnal with ORM many to many. And 'City' is an entity with ORM oneToMany with 'professionnal'.
In professionnel I have a property city. I want to show professionnal grouped by categories who have a special cityId putted on parameter in the url.
I put this query on sql
(for example city_id= 10873)
, it gives me the results.
SELECT * FROM sub_category AS a LEFT JOIN professionnel ON professionnel.city_id = 10873
but I don't know how to write with querybuilder.
I put this solution but I have errors:
$city = $paramFetcher->get('city');
$queryBuilder = $em->getRepository(SubCategory::class)
->createQueryBuilder('a')
->leftJoin('App\Entity\Professionnel','p')
->where('p.city = :city')
->setParameter('city', $city);
return $queryBuilder->getQuery()->getResult();
in log:
request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\Exception\SyntaxErrorException: "An exception occurred while executing 'SELECT c0_.id AS id_0, c0_.name AS name_1 FROM category c0_ LEFT JOIN professionnel p1_ WHERE p1_.city_id = ?' with params ["10873"]: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'WHERE p1_.city_id = '10873'' at line 1"
I search on forums too and I found that I can replaced the 'ON' of leftJoin as following, but shows me all professionnals:
$city = $paramFetcher->get('city');
$queryBuilder = $em->getRepository(SubCategory::class)
->createQueryBuilder('a')
->leftJoin('App\Entity\Professionnel','p','WITH','p.city = :city')
->setParameter('city', $city);
return $queryBuilder->getQuery()->getResult();
For the mapping, I put it like this:
class Category
{
/**
* Many professionnels have Many categories.
* #ORM\ManyToMany(targetEntity="App\Entity\Professionnel", mappedBy="Categories")
* #ORM\JoinTable(name="professionnel_category")
*/
private $professionnels;
}
class Professionnel extends User
{
/**
* Many professionel have Many categories.
* #ORM\ManyToMany(targetEntity="App\Entity\Category", inversedBy="professionnels")
* #Groups({"Default", "professionnel", "user"})
*/
private $categories;
}
Thank you so much for your help.
So, assuming that you have your entity relation set up correctly (please update your question with those), I see one mistake above.
If you look at the official Doctrine Query Builder documentation, the leftJoin method's third argument is either WITH or ON constant and not join predicate. That one comes as a 4th argument. But, even then, that join predicate is only used is a very specific use-case, where you want to further define your join relation. By looking at your example, I doubt that is your case.
With that being said, I think your code should look like this:
$city = $paramFetcher->get('city');
$queryBuilder = $em->getRepository(Category::class)
->createQueryBuilder('a');
->leftJoin('App\Entity\Professionnel','p')
->where('p.city_id = :city')
->setParameter('city', $city);
return $queryBuilder->getQuery()->getResult();
Now the question: is that really just a city_id (plain integer) or is that relation to some entity named City?
Please update your question with those details and I will update the answer, if necessary...
Hope this helps...
Firstly, you need describe relations in entities. Read more about this here
Secondary, you must call in join field (read as property), not a Entity.
inside CategoryRepository:
/** #return ArrayCollection|Category[] */
public function getByCityId(int $cityId): ArrayCollection
{
$qb = $this->createQueryBuilder('category');
$qb->leftJoin('category.professional', 'professional')
->leftJoin('category.city', 'city')
->where($qb->expr()->eq('city.id', $cityId));
return $qb->getQuery()->getResult();
}
Inside SomeController:someAction()
$cityId = $paramFetcher->get('city');
$categoriesList = $this->get('doctrine.orm.entity_manager')
->getRepository(Category::class)
->getByCityId($cityId);
As result you'll be have list of category, with joined professional and city.
I suggest to read this article on symfony docs

Doctrine2 QueryBuilder select entity and count of associated entities

I'm having a huge problem with ORM QueryBuilder. What I need to do is:
I need to fetch order with count of its products and plenty of associated entities (associated with order), but I assume they're not relevant here. I also need to order result by that count.
Could anyone give me an example of how this can be achieved? I would like to avoid "inline" DQLs if possible.
You can get data via Doctrine Query Builder.
You are supposed to left join products from Order and then group by order id. You can have COUNT(product.id) in your select statement and use the alias in order by clause to make your orders sorted. Below is a small code snippet from Repository.
/**
* #return \Doctrine\ORM\Query
*/
public function getHotelAndRoomType()
{
$qb = $this->createQueryBuilder('order')
->select('partial order.{id, orderId} as order, count(product.id) as total_products_in_order')
->leftJoin('AppBundle:Product', 'product', 'WITH', 'product.order = order.id')
->groupBy('order.id')
->orderBy('total_products_in_order', 'DESC')
;
return $qb->getQuery()->execute();
}
Note : Code not tested.

Doctrine many-to-many association is not resolved

When i am using a Many-To-Many relationship in Symfony2 using Doctrine ORM i get the problem that my many-to-many relationship is not resolved.
Example:
Class A:
/**
* #ORM\ManyToMany(targetEntity="StoreItem", mappedBy="itemOptions")
*/
protected $storeItems;
Class B:
/**
* #ORM\ManyToMany(targetEntity="StoreItemOption", inversedBy="storeItems")
* #ORM\JoinTable(name="store_item_itemoptions")
*/
protected $itemOptions;
now i store the object in a session, note that i did not called the many to many relationship yet by using
->getItemOptions()
When i get my session object now and do ->getItemOptions() then it is empty.
Anybody has an idea what is causing this?
(PS: I found a hacky solution by saying that when i add an item to my cart i do a empty foreach that calls the method ->getItemOptions())
This is called 'lazy loading', and is a doctrine feature designed to reduce memory overhead.
You can set loading to 'eager' or explicitly add a select for the other field in your DQL to avoid lazy loading: e.g.:
$objectsA=$em->createQueryBuilder('\Class\A', 'a')
->join('a.b', 'b')
->addSelect('b')
->getQuery()
->getResult();

Mapping and DQL

I have 3 tables - user, area, and contacts. A contact can belong to a user or an area. A user can belong to many areas.
I want to pull all the contacts that belong to a user (as specifically defined in the DB), as well as all contacts that belong to the same area as the user.
Can I get a fresh set of eyes on my Database mapping, and the query I need to write in DQL to get what I want. Am I doing something wrong in my database mapping?
I'm definitely a SQL person, and am able to easily fetch what I want in plain SQL. In plain SQL, here's what I want to do:
select c.* from contact c LEFT JOIN user_area ua ON c.area_id=ua.area_id where (ua.user_id=XXX OR c.user_id=XXX);
USER
/**
* #ORM\ManyToMany(targetEntity="area", inversedBy="areas")
* #ORM\JoinTable(name="user_area",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="area_id", referencedColumnName="id")}
* )
*/
private $areas;
/**
* #ORM\OneToMany(targetEntity="Contact", mappedBy="user")
*/
private $contacts;
CONTACT
/**
* #ORM\ManyToOne(targetEntity="Area")
* #ORM\JoinColumn(name="area_id", referencedColumnName="id")
*/
private $area;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="Contacts")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
AREA
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="users")
*/
private $users;
/**
* #ORM\OneToMany(targetEntity="Contact", mappedBy="area")
*/
private $contacts;
The main problem I'm running into is that DQL really wants you to query an object, and it's just plain easier in SQL to query the user/area relationship table to get what I want. I tried to write an query that pulls areas from contacts, then users from contacts, and then users from areas but I get an error message that "users" isn't a defined index in my areas object. Again, I'm a Doctrine newbie, so I'm probably doing something wrong.
Here's my attempt at a query, from the User object in Symfony:
$qb = $em->createQueryBuilder()
->addSelect('c')
->from('MyBundle:Contact', 'c')
->leftJoin('c.area', 'ca')
->leftJoin('c.user', 'cu')
->leftJoin('ca.users', 'cau')
->add('where', 'c.user = ?1 OR cau.id = ?1')
->add('orderBy', 'c.name')
->setParameter(1, $this->getId());
Someone should have slapped me for providing that previous answer. While it got the job done, I was absolutely right, it was not optimized. Queries using that method were taking 3 seconds to go back and forth to the database (3 seconds!). Clearly, there were plenty of other things going on in my world that took away from performance as a requirement for getting this done, but things have changed. I managed to break down this query into two smaller (Doctrine generated) ones, each taking maybe 0.2 or 0.3s.
$areas = $user->getAreas();
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('c')
->from('MyBundle:Contact', 'c')
->where($qb->expr()->in('c.area', '?1'))
->orWhere('c.user = ?2')
->setParameter(1, $areas->toArray())
->setParameter(2, $user);
$query = $qb->getQuery();
$result = $query->getResult();
return $result;
The fact that I have to call $user->getAreas() adds a database query (if Doctrine doesn't already have that information), but this code, using Query Builder expressions, works much better (0.3s vs. 3s is 10% of the original query time!).
I think the main concept I was missing back then was that the Query Builder wants to work with your objects (Entities), and the properties you've defined in your entities. Coming from a strong SQL background, and knowing the specific SQL query I wanted Doctrine to produce, I wasn't approaching the problem properly.
Hope this update to an 8-month old question helps somebody!
So it turns out you can't fetch objects of objects in DQL. I needed to fetch all "Area"s (an object of "Contact") and then fetch all of that Area's "User"s.
In DQL, you can specify multiple "from()" helper methods, and this was what I needed to get the job done.
$qb = $em->createQueryBuilder()
->addSelect('c')
->from('MyBundle:Contact', 'c')
->from('MyBundle:User', 'u')
->leftJoin('c.area', 'ca')
->leftJoin('c.user', 'cu')
->leftJoin('u.areas', 'ua')
->add('where', 'c.user = ?1 OR (ua.id=ca.id AND u.id = ?1')
->add('orderBy', 'c.name')
->setParameter(1, $this->getId());
The resulting SQL generated from Doctrine doesn't seem particularly optimized, but it gets the job done. If anyone has any thoughts on getting Doctrine to better optimize the following query, I'd love to hear opinions.
SELECT m0_.id AS id0, m0_.name AS name1, m0_.email AS email2, m0_.media_area_id AS media_area_id3, m0_.user_id AS user_id4 FROM contact m0_ LEFT JOIN user u1_ ON m0_.user_id = u1_.id LEFT JOIN area m2_ ON m0_.area_id = m2_.id, user u3_ LEFT JOIN user_area u5_ ON u3_.id = u5_.user_id LEFT JOIN area m4_ ON m4_.id = u5_.area_id WHERE u1_.id = ? OR (m4_.id = m2_.id AND u3_.id = ? );

How to update an unidirectional ManyToMany mapping in Symfony2?

I have been searching for an answer now for several hours and I can't find a clean solution by myself.
I have an entity called "Tag". You can add these tags to nearly everything, f.e. to an article, a news and so on. Therefor my entites (in this example an article) refer to these tags with an unidirectional ManyToMany mapping:
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #ORM\JoinTable(name="tag2article",
* joinColumns={#ORM\JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
* )
*/
private $tags;
This relation is unidirectional, because then I don't want to have an articleTag, a newsTag etc. or several entities as joinTables. The doctrine manual calls unidirection ManyToMany relations "less common", because you can build such relations as 3 entities with an additional joinTableEntity. But this would result in too much entities of this kind.
When I create an article, these tags are added by:
foreach ($tagArray as $tagId) {
$tag = $entityManager->getRepository('myBundle:Tag')->findOneById($tagId);
if ($tag != null) {
$article->addTag($tag);
}
}
$entityManager->persist($article);
$entityManager->flush();
This works fine on insert. On update I do the same, but it doesn't work and I don't know why. The only solutions I found was to add this article to the tag, but this isn't possible though I am working with an unidirectonal relation.
In my ArticleForm, I just have a hidden field:
->add('tags', 'hidden', array(
'data' => '',
'property_path' => false
))
In this way I can add tags pretty easy with Ajax and write these tagIds in this hidden field.
My overall question is: Why does this way work on insert, but not on update? What can I do to fix this?
Many thanks for your help or any hints!!

Resources