Doctrine query one entity in one-to-many unidirectional with join table - php

I have two entities linked by a one-to-many unidirectional with join table association.
use Doctrine\ORM\Mapping as ORM;
/**
* #Entity(repositoryClass="FooRepository")
*/
class Foo
{
/**
* #var Collection
* #ORM\ManyToMany(targetEntity="Bar")
* #ORM\JoinTable(
* name="foo_bar",
* inverseJoinColumns={#ORM\JoinColumn(unique=true)}
* )
*/
private $bars;
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// [...]
}
/**
* #Entity(repositoryClass="BarRepository")
*/
class Bar
{
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// [...]
}
I'd like to create a method into my BarRepository class using the a foo id and a bar id which return one or null Bar object.
Actually my class looks like this:
use Doctrine\ORM\EntityRepository;
class BarRepository extends EntityRepository
{
/**
* Finds a single bar.
* #param int $fooId The foo identifier.
* #param int $barId The bar identifier.
* #return Bar|null
*/
public function findOneByFoo($fooId, $barId)
{
$qb = $this->createQueryBuilder('b');
$qb->innerJoin('Foo', 'f', Expr\Join::WITH, 'f.id = :fooId')
->where('b.id = :barId')
->setParameter('fooId', $fooId)
->setParameter('barId', $barId)
->getQuery()->getOneOrNullResult();
}
}
But this always returns a bar object even if the bar id is not associated to the foo object.

OK, thanks to staskrak, I rewrite my "query" like this, and it works fine.
The Foo and Bar entities are the same.
I kept the same base of the query but added an inner join between the Foo->bars property and the Bar entity.
use Doctrine\ORM\EntityRepository;
class BarRepository extends EntityRepository
{
public function findOneByFoo($fooId, $barId)
{
$parameters = [
':fooId' => $fooId,
':barId' => $barId
];
$qb = $this->createQueryBuilder('b');
return $qb
->innerJoin('Foo', 'f', 'WITH', 'f.id = :fooId')
// below, the added inner join
// it makes the link between the Foo->bars property and the Bar entity
->innerJoin('f.bars', 'fb', 'WITH', 'b.id = fb.id')
->where('b.id = :barId')
->setParameters($parameters)
->getQuery()
->getOneOrNullResult();
}
}

First of all! It's not the required but I suggest you always write
the full mapping in annotations.
Let's look at you entities. We can make the next assertion about your
One-To-Many:
One Foo objects can have many Bar objects, but every Bar object refer to
only one and no more Foo object.
For example One person can have a lot of credit cards, but every credit
card belongs to one single person.
Therefore we can write down:
/**
* #Entity(repositoryClass="FooRepository")
*/
class Foo
{
/**
* Unidirectional One-To-Many
* One Foo has many Bar, however Bar has only one Foo
*
* #ORM\ManyToMany(targetEntity="Bar")
* #ORM\JoinTable(
* name="foo_bar_table",
* joinColumns={#JoinColumn(name="foo_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="bar_id", referencedColumnName="id", unique=true)
*/
private $bars;
/**
* Foo constructor
*/
public function __construct()
{
$this->bars = new \Doctrine\Common\Collections\ArrayCollection();
}
Of course you always will have a bar object with id you have typed.
Why? Let's look at your relations(tables).
Foo - this is a table, which has field id.
Bar - this is a table, which has field id.
foo_bar_table - this table has foo_id, bar_id.
When you make the join - you just add to the one table another one.
These tables don't have association between each other. So you wanted
the Bar object - you got it.
You need to get the Bar object from the Bar repository. It will be
better.

Related

How to map type when map keys are not in the entity table with Doctrine?

Can the discriminator mapping strings of a Doctrine entity be fetched from a database foreign table field? If not, how to handle inheritance type mapping in such a situation?
Considering a Doctrine 2.7.2 abstract entity Person with concrete entities A and B like:
/**
* #MappedSuperclass
* #Table(name="PERSON")
*/
abstract class Person {
/**
* #Id
* #GeneratedValue
*/
protected $id;
/**
* #Column(name="name",type="string")
*/
protected $name;
/**
* #OneToOne(targetEntity="PersonType")
* #JoinColumn(name="type", referencedColumnName="id")
*/
protected $type;
}
/**
* #Entity
* #Table(name="PERSON")
*/
class A extends Person {}
/**
* #Entity
* #Table(name="PERSON")
*/
class B extends Person {}
A Person has a type bound to a PersonType entity like:
/**
* #Entity
* #Table(name="PERSON_TYPE")
*/
class PersonType {
/**
* #Id
* #GeneratedValue
*/
protected $id;
/**
* #Column(name="code",type="string")
*/
protected $code;
/**
* #Column(name="name",type="string")
*/
protected $name;
}
How to get the right class instantiated by repository query methods like find(), findBy(), findById(), etc?
Database set:
SELECT * FROM PERSON:
id name type
1 John 1
2 Tom 2
SELECT * FROM PERSON_TYPE:
id code name
1 A Type A
2 B Type B
Expected results:
$entityManager->getRepository("A")->findOneById(2); // null
$entityManager->getRepository("B")->findOneById(2); // B object (Tom)
$entityManager->getRepository("A")->findOneById(1); // A object (John)
I can't find how to specify this using a discriminator.
Alternatively I have a working solution implementing a SQLFilter with the inconvenience to have to enable or disable filter.
An alternative solution may be to populate $discr?
(I can of course use the Person::findByType() method but it would be nice to have this feature directly managed by the respective repositories).

Doing a COUNT(*) GROUP BY based on a ManyToMany relation using DQL?

I have two entities Person and Skill, where a Person may have multiple skills.
Person
/**
* #ORM\Entity(repositoryClass="App\Repository\PersonRepository")
*/
class Person
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Skill", inversedBy="people")
*/
private $skills = [];
// other fields and getters/setters
}
Skill
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\SkillRepository")
*/
class Skill
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Person", mappedBy="skills")
*/
private $people = [];
// other fields and getters/setters
}
I have a form where I can filter people by skills, each skill is a checkbox and I want the number of people having that skill along with the checkbox's label.
The result is that:
I got it working using the following native query:
SELECT s.id, COUNT(*) AS c
FROM skill s
JOIN person_skill ps /* table required by the M2M relation between person and skill */
ON s.id = ps.skill_id
GROUP BY s.id
As you can see, I require a JOIN on the ManyToMany table in order to get those counts.
How could I do this using Doctrine's DQL instead of using a native query?
Actually when mapping entities with relations, Doctrine uses a custom object named ArrayCollection.
It comes with many methods, such as filter() and count().
You can add a method to your skill entity if you want that would use the count method of the ArrayCollection (people).
To make sure you use the ArrayCollection properly you'll have to change your Skill class like this:
class Skill
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Person", mappedBy="skills")
*/
private $people; //<-- Removed the default array definition
public function __construct()
{
$this->people = new ArrayCollection(); //Add this line in your constructor
}
public function countPeople()
{
return $this->people->count(); //Will return the number of people joined to the skill
}
// other fields and getters/setters
}
Allright, I found the solution:
$rows = $this->_em->createQueryBuilder('s')
->select('s.id, COUNT(p.id) AS c')
->from(Skill::class, 's')
->join('s.people', 'p')
->groupBy('s.id')
->getQuery()
->getArrayResult();
It generates the following query:
SELECT s0_.id AS id_0, COUNT(p1_.id) AS sclr_1
FROM skill s0_
INNER JOIN person_skill p2_
ON s0_.id = p2_.skill_id
INNER JOIN person p1_
ON p1_.id = p2_.person_id
GROUP BY s0_.id

Doctrine - Entity loaded without existing associations

I have two entity types: \Model\News and \Model\News\Category.
\Model\News: (without couple of fields)
namespace Model;
/**
* #Entity
* #Table(name="news")
*/
class News extends \Framework\Model {
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #ManyToOne(targetEntity="\Model\User", inversedBy="news")
*/
protected $author;
/**
* #ManyToOne(targetEntity="\Model\News\Category", inversedBy="news")
*/
protected $category;
}
\Model\News\Category:
namespace Model\News;
/**
* #Entity
* #Table(name="news_category")
*/
class Category extends \Framework\Model {
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #Column(type="string", length=50, unique=TRUE)
*/
protected $name;
/**
* #OneToMany(targetEntity="\Model\News", mappedBy="category")
*/
protected $news;
/**
* Constructor
*/
public function __construct() {
parent::__construct();
$this->news = new \Doctrine\Common\Collections\ArrayCollection;
}
}
Table data from \Model\News:
id | category_id | author_id
------------------------------- ...
4 | 1 | NULL
Table data from \Model\News\Category:
id | name
---------------
1 | General
---------------
2 | Other
While I'm loading News type Entity with this particular code and doing dump with \Kint class:
$sId = '4';
$sModel = 'Model\News';
$oObject = $entity_manager->find($sModel, $sId);
d($oObject);
It returns me this:
My question is, why category property from $oObject variable has NULL values despite the fact that the category with id = 1 exists in database?
UPDATE:
After above code, I want to load this category (with ID=1) separately. Same thing. But... when I'm loading a category with other ID (for example, 2) it's loading with no problems:
$oObject2 = $entity_manager->('\Model\News\Category', '2');
I have no idea what to do now...
If you know that the category entity will be accessed almost each time you load news, you might want to eager load it (force doctrine to load it when News is loaded instead of when a Category property is called).
In order to do so, just add the fetch="EAGER" annotation on your association
/**
* #ManyToOne(targetEntity="\Model\News\Category", inversedBy="news", fetch="EAGER")
*/
protected $category;
So... I finally came to solve the problem thanks to #asm89 from #doctrine IRC chat in freenode server.
Doctrine is creating a proxy class which uses "lazy loading". Data is loaded only when one of getters is used.
So, after using $oObject->getCategory()->getName(), my category object available from $oObject->getCategory() was filled up with proper data.

Implementing getters for joined doctrine 2 collection

I'm trying to create simple getter methods in original entity for specific item in doctrine collection of joined entity.
Main entity looks something like this:
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\OneToMany(targetEntity="File", mappedBy="product")
*/
private $files;
}
And joined entity:
class PrankFile
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="files")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $product;
/**
* #var string
*
* #ORM\Column(name="type", type="string", length=16)
*/
private $type;
...
My DQL in repository class is very simple:
return $this->getEntityManager()
->createQuery('SELECT p, f FROM AppProductBundle:Product p INNER JOIN p.files f ORDER BY p.created DESC')
->setMaxResults($limit)
->getResult();
In files entity type field tells me what kind of a file is it (image, sound, video, demo etc...)
Problem comes when I wish to print out a list of all products and display image next to product details, I would hate to loop through product files for each product displayed.
Is it possible to create some simple getter on product entity to fetch file of certain type?
Or maybe it would be better to create more complex DQL query for this, again how to do this?
I can't just fetch image record from files in DQL because I need all files for certain products.
Any help with this would be most welcome.
You can filter directly on collections using the filter api. If the collection is not loaded already Doctrine will apply your filter on a SQL level, giving you max performance. In case the collection is already eager loaded Doctrine will filter the ArrayCollection in memory.
use Doctrine\Common\Collections\Criteria;
class Product
{
public function getFilesByType($type)
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("type", $type))
return $this->files->matching($criteria);
}
}

How to prevent redundant lookups when joining entity relationships?

I'm getting up to speed with Symfony 2 and Doctrine and having difficulties with the number of unnecessary database lookups being performed to hydrate with joined entities.
After performing a joined query with a child object, the child is automatically pulling its other mappings from the database. It's doing this despite that I'm not attempting to access any of its properties. It's as if they're being accessed inside the find query.
My example looks like the below - There are entities called Person and Building that both join an entity called Place:
class Person {
/**
* Where this person lives
* #var Place $Home
*
* #ORM\OneToOne(targetEntity="Place", cascade={"persist"}, inversedBy="Resident" )
* #ORM\JoinColumn(name="place_id", referencedColumnName="id")
*/
private $Home;
}
class Building {
/**
* Where this building stands
* #var Place $Site
*
* #ORM\OneToOne(targetEntity="Place", cascade={"persist"}, inversedBy="Landmark" )
* #ORM\JoinColumn(name="place_id", referencedColumnName="id")
*/
private $Site;
}
class Place {
/**
* Reverse mapping
* #var Person $Resident
*
* #ORM\OneToOne(targetEntity="Person", mappedBy="Home")
*/
private $Resident;
/**
* Reverse mapping
* #var Building $Landmark
*
* #ORM\OneToOne(targetEntity="Building", mappedBy="Site")
*/
private $Landmark;
}
My Person repository join looks like this:
/**
* #override
* #return Person
*/
public function find( $id ){
$query = $this->getEntityManager()
->createQuery('
SELECT p, h
FROM MyBundle:Person p
JOIN p.Home h
WHERE p.id = :id'
)->setParameter('id', $id);
return $query->getSingleResult();
}
How can I prevent the Place fetching its Building relationship separately during the find operation? Is there something I can pass into the Query instance to stop this?
add fetch option to your mapping.
Like so:
class Place {
/**
* Reverse mapping
* #var Person
*
* #ORM\OneToOne(targetEntity="Person", mappedBy="Home", fetch="EXTRA_LAZY")
*/
private $Resident;
}

Categories