Doctrine - relation of three tables - php

I have trouble with writing Doctrine Query Builder.
I have a Posts table which is related to the AccountToken table like:
class Posts
/**
* #ORM\ManyToOne(targetEntity="App\Entity\AccountToken")
* #ORM\JoinColumn(name="account_token_id", referencedColumnName="uuid", nullable=false)
*/
protected $accountTokenId;
I am writing a query builder where I want to pull all the results which are related to that table via UUID and have a specific status. The type value is defined in the AccountToken table.
In that table, type has relation also and it's defined in AccountToken table:
class AccountToken
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Types")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=false)
*/
protected $type;
It's a tricky one for me because I have to relations.
Does someone know how to pull the results:
"Return me posts which have the type='success' value".
$this->createQueryBuilder('p')
->select('p')
->join('App\Entity\AccountToken', 'at', Expr\Join::WITH, 'at.uuid = p.accountTokenId')
->join('App\Entity\ChannelType', 'ct', Expr\Join::WITH, 'ct.id = at.type')
->where('p.accountTokenId = :accountTokenId')
->setParameter('successStatus', Types::STATUS_SUCCESS)
->getQuery()
->getResult();
This is my draft of how I am trying to accomplish it but as I am new with Doctrine, don't have an idea how to join a table that has value in its related table. It's confusing.

Firstly you don't have any parameter successStatus in your query.
I think you over complicate the things.
return $this->createQueryBuilder('p')
->leftJoin('p.accountToken', 'a')
->leftJoin('a.type', 'ct')
->where('ct.successStatus = :successStatus')
->setParameter('successStatus', Types::STATUS_SUCCESS)
->getQuery()
->getResult()
;

Related

How to order by using doctrine?

I used to get my data using this command so the first array is for find variable and the second array is for the order.
So I need to order with specific column:name of test(that is an object that has many columns: id/name ) But I dont know how !! when I enter _test.name I get unrecognized field
->findBy(
array('_id' => $id),
array('_test' =>'ASC')
);
class model1
{
private $_idModel1;
/**
*
* #ORM\ManyToOne()
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id1",
* referencedColumnName="id2")
* })
*/
private $_test;
}
You cannot order using a relation with the findBy, but you can do it creating a query builder:
$model1Repository->createQueryBuilder('model1')
->leftJoin('model1._test', 't')
->where('model1._id = :id')
->setParameter('id', $id)
->orderBy('t.name', 'ASC');
The code has not been tested, you should adapt it to your code.
It creates a query builder over the model1 class , then joins it with your ManyToOne relation, filters via _id and then orders the result by the _test relation using the name field.
Aslo you need to declare in the #ManyToOne the target entity (https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/association-mapping.html#many-to-one-unidirectional)
#ManyToOne(targetEntity=TheNameOfTheTestClass)

DQL Query Doctrine places joined element OrderBy clause in another element's juncture condition's subQuery

Here is my situation:
I gotta an entity User, with several joined entitites collections:
class User
{
private $id;
/**
* #var UserData
* #ORM\OneToMany(targetEntity="UserData", mappedBy="user")
*/
private $userDatas;
/**
* #var ServiceAccess
* #ORM\OneToMany(targetEntity="ServiceAccess", mappedBy="user")
*/
private $serviceAccesses;
...
}
ServiceAccess has also a OneToMany junction on Offers, with an OrderBy instruction to Doctrine ORM on its level field:
class ServiceAccess
{
private $id;
/**
* Offers
* #ORM\OneToMany(targetEntity="Offer",
* mappedBy="access",
* cascade={"persist","remove","refresh"})
* #ORM\OrderBy({"level" = "DESC"})
*/
private $offers;
/**
* #ORM\ManyToOne(
* targetEntity="User"
* )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $user;
...
}
My Offer class:
class Offer
{
private $id;
/**
* The user access
*
* #var ServiceAccess
* #ORM\ManyToOne(targetEntity="Fitnext\ServiceAccessBundle\Domain\Entity\UserServiceAccess", inversedBy="offers")
* #ORM\JoinColumn(name="access_id", referencedColumnName="id", nullable=false)
*/
private $access;
/**
* #ORM\Column(name="level", type="integer", nullable=false, options={"default"=0})
*/
private $level;
...
}
I am implementing the DQL query to fetch one User with several dependencies, including its UserDatas, its ServiceAccesses, and the ServiceAccesses.offers.
The main trick here is the condition for the User.userDatas juncture:
UserData has a name and a createdAt field, and can contain several rows having the same name.
My goal is to query the the latest row for each defined name group.
For that, I use a subquery (which was sent to me here on Stackoverflow :) ), which works perfectly.
Also, I have several more classical junctures, in which one on User.serviceAccesses and then one on ServiceAccesses.offers, which causes me the issue, when combined with the UserData subquery...
Here is my QueryBuilder:
// First I define the subquery for the UserDatas juncture condition
$queryDatas = $this->_em->createQueryBuilder()
->select('a')
->from('UserData', 'a')
->leftJoin( 'UserData', 'b', 'WITH', 'a.name = b.name AND a.createdAt < b.createdAt' )
->where( 'b.createdAt IS NULL' )
->andWhere('a.name IN (:names)')
->andWhere('a.user = u')
->orderBy( 'a.createdAt', 'DESC' )
->getDQL();
// Notice the orderBy clause here, which is part of the trick in order to get the latest registration of each named data group, and which is also part of the issue...
// And here is the main QueryBuilder
$this->createQueryBuilder('u')
->where('u.id = :id')
->setParameter('id', $id)
// UserDatas
->leftJoin('u.userDatas', 'd', 'WITH', 'd IN ('. $queryDatas .')')
->addSelect('d')
->setParameter('names', ['height', 'weight'])
// ServiceAccess
->leftJoin('u.serviceAccesses', 'svacc', 'WITH', 'svacc.activated = 1 AND svacc.type = :type' )
->setParameter('type','default')
->addSelect('svacc')
// ServiceAccessOffers
->leftJoin('svacc.offers', 'offers', 'WITH', 'offers.activated = 1' )
->addSelect('offers')
...
This returns me a Doctrine\DBAL\Exception\InvalidFieldNameException:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'u7_.level' in 'order clause'
And Here is the generated SQL code part causing the issue:
FROM users u0_
LEFT JOIN user_data e1_ ON u0_.id = e1_.user_id
AND (e1_.id IN (
SELECT e8_.id
FROM user_data e8_
LEFT JOIN user_data e9_ ON (e8_.name = e9_.name AND e8_.created_at < e9_.created_at)
WHERE e9_.created_at IS NULL AND e8_.name IN ('height', 'weight')
AND e8_.user_id = u0_.id
ORDER BY e8_.created_at DESC, u7_.level DESC
)
)
LEFT JOIN service_access u6_ ON u0_.id = u6_.user_id AND (u6_.activated = 1 AND u6_.type = 'default')
LEFT JOIN offer u7_ ON u6_.id = u7_.access_id AND (u7_.activated = 1)
Doctrine takes the OrderBy clause defined in Access.offers attribute (orderBy level DESC), and adds it to the subquery orderBy!! Which doesn't know about the offers table and its level field of course, as these are not in the subquery scope!
It works as if Doctrine adds the orderBy to the only/first OrderBy it finds in the Query, which in this case can't work..
IMO this is a Doctrine bug.
I'm currently under "symfony/symfony": "^3.4",
"doctrine/orm": "2.5.12",.
Any idea someone? Am I the first to encounter this issue?
Thank you very much.
Well, I feel kind of noob now.. but I solved this issue..
I simply reorganized my QueryBuilder methods order to place the Offers juncture before the other one, so that it generates the first orderBy clause in the right place, instead of adding it to the one created in the subquery.
It works.
Still thinking it is kind of weird, as QueryBuilder methods are supposed to work no matter the order they are called..

Doctrine query with nullable/optional join

I got a method on my repository class to get me Article with their join (category, image ...) and not every Article have categories or image, what don't give me back all expected result, cause my current repository function return only Article who have Categories and Image not null and just ignore what having null value.
My Article entity have the following relationship.
/**
* #ORM\ManyToMany(targetEntity="App\ArticleBundle\Entity\Category", cascade={"persist"})
*/
private $categories;
/**
* #ORM\ManyToOne(targetEntity="App\ArticleBundle\Entity\Image", cascade={"persist"})
*
*/
private $image;
And this is my repository function
public function getArticle($id)
{
$qb = $this->createQueryBuilder('a')
->where('a.id = :theId')
->setParameter('theId', $id)
->join('a.author', 'auth')
->addSelect('auth')
->join('a.categories', 'cat')
->addSelect('cat')
->join('a.image', 'img')
->addSelect('img');
return $qb->getQuery()->getOneOrNullResult();
}
Now I want to know if I can get Article which have categories, image or not in one query with the join. I want to say when using the Doctrine lazy loading (by avoid the join in the query) I get the expected result.
Use ->leftJoin() to get Article which have categories, image or not in one query:
public function getArticle($id)
{
$qb = $this
->createQueryBuilder('a')
->addSelect('auth', 'cat', 'img')
->join('a.author', 'auth')
->leftJoin('a.categories', 'cat')
->leftJoin('a.image', 'img')
->where('a.id = :theId')
->setParameter('theId', $id)
;
return $qb->getQuery()->getOneOrNullResult();
}
Thus, avoids extra queries when Doctrine try to load the related properties in a lazy way.
Explanation:
Using ->join() or ->innerJoin():
This is the simplest, most understood Join and is the most common. This query will return all of the records in the left table (table A) that have a matching record in the right table (table B).
Using ->leftJoin():
This query will return all of the records in the left table (table A) regardless if any of those records have a match in the right table (table B). It will also return any matching records from the right table.
Source: Visual-Representation-of-SQL-Joins explained in detail by C.L. Moffatt

Doctrine ManyToMany relation with join condition

I've got a Many-To-Many relation between two tables but I want to add a join condition on the join table.
/**
* #ManyToMany(targetEntity="Company", inversedBy="Accounts")
* #JoinTable(name="account_company",
* joinColumns = { #JoinColumn(name="account_id", referencedColumnName="id") },
* inverseJoinColumns = { #JoinColumn(name="company_id", referencedColumnName="id") }
* )
*/
protected $companies;
I would have a condition like "account_company.is_deleted = 0", how can I make it ?
Yes, you have the choice to hydrate your object and its ManyToMany collection using custom dql:
make a repository method that adds conditions on your join:
// repository class:
public function getAccountWithNonDeletedCompanies($id)
{
return $this->createQueyBuilder('account')
->leftJoin('account.companies', 'companies', 'WITH', 'companies.deleted = 0')
->andWhere('account.id = :id')
->setParameter('id', $account)
->getQuery()
->getOneOrNullResult()
;
}
From what I can understand, you never want to display deleted companies ? (soft delete).
In this case, you may want to use SQL Filters: http://blog.loftdigital.com/doctrine-filters-and-soft-delete
Have dealt with this before and the only solution I got was creating a custom function that will make the query.
So on your Entity create a custom function getCompanies().

Doctrine2 fetching rows that have manyToMany association by QueryBuilder

everyone.
I have 2 entities City and POI. Mapping looks like this:
class City {
/**
* #ORM\ManyToMany(targetEntity="POI", mappedBy="cities")
* #ORM\OrderBy({"position" = "ASC"})
*/
protected $pois;
and
class POI {
/**
* #ORM\ManyToMany(targetEntity="City", inversedBy="pois")
* #ORM\JoinTable(name="poi_cities")
*/
protected $cities;
I would like to fetch all POIs that have at least 1 association with some City using QueryBuilder. I should probably use exists() function but I don't quiet know how.
You'd have to Left join them and check if cities is null.
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c IS NOT NULL');
I haven't tested it, but I hope it gives you the general direction. You can read more about the QueryBuilder from here.
Docrine2 was changed in 2013, so the other solution displays error Error: Cannot add having condition on a non result variable. Now we cannot use joined alias just as a condition variable. We should use any of its properties like c.id
So you should fix the code to
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c.id IS NOT NULL');
$results = $qb->getQuery()->execute();
If you want to select entities that does not have any cities, use IS NULL.
$qb->leftJoin('p.cities', 'city')
->where('city.id IS NULL')
->getQuery()
->execute();
Description of a problem and link to the commit that responsible for that - http://www.doctrine-project.org/jira/browse/DDC-2780

Categories