I am trying to do a complex query in Doctrine but had no luck so far.
I had an Entity called Category.
Category has reports (1 to many)
Category has subcategories (1 to many self referencing)
class Category extends
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
* #Expose
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=".....\Category", mappedBy="parentCategory", cascade={"persist"})
*
* #MaxDepth(1)
*
* #Expose
*
**/
private $subcategories;
/**
* #ORM\ManyToOne(targetEntity="....\Category", inversedBy="subcategories", cascade={"persist"})
* #ORM\JoinColumn(name="idparent", referencedColumnName="id", nullable=true)
*
* #Expose
*/
private $parentCategory;
/**
* #ORM\OneToMany(targetEntity="....\Report", mappedBy="category")
*/
private $reports;
I use these entities in a rest API set up in symfony with FosRestBundle and HATEOASBundle.
So my objective is to return the following:
All parent categories (categories with idparent=null, meaning they are roots) with total_count reports and total_sum_reports of their subcategpories, with their subcategories with total_count reports and total_sum_reports.
Something like that:
name: parentCategory1
total_reports: 20
total_amount 100
subcategories: [
[name: child 1, total_reports: 15, total_amount 80],
[name: child 2, total_reports: 5, total_amount 20],
]
Unfortunately haven't figured this out.
How to take all info in need into a single json object?
Is it possible with Doctrine take all this into a big object or need to create this manually?
Related
Hy,
I have problem with validation and schema creation
I am creating an api via the api platform, (this is my 1st project under symfony)
I have a constraint, the database exists and I cannot touch it.
I have an headerOrder entity and an LineOrder entity.
But the column of join are not a key.
class enteteCommande
{
/**
* #var int
*
* #ORM\Column(name="I_ID", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $IId;
/**
* #var string
*
* #ORM\Column(name="C_CDE_NUMERO", type="string", length=50, nullable=true)
*
*/
#[Groups(['write:commande'])]
private $CCdeNumero;
/**
*
* #ORM\ManyToMany(targetEntity="lignesCommande", mappedBy="enteteLigne")
*
*/
private $detailLigne;
class lignesCommande
{
/**
* #varint
*
* #ORM\Column(name="I_IDL", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $IIdL;
/**
* #varstring|null
*
* #ORM\Column(name="LIGNE_C_CDE_NUMERO", type="string", length=50, nullable=true)
*/
private $ligneCCdeNumero;
/**
*
*
* #ORM\ManyToMany(targetEntity="enteteCommande", inversedBy="detailLigne")
* #ORM\JoinColumn(name="LIGNE_C_CDE_NUMERO", referencedColumnName="C_CDE_NUMERO")
*
*/
private $enteteLigne;
My schema :
enteteCommande
I_ID
C_CDE_NUMERO
lignesCommande
I_IDL
LIGNE_C_CDE_NUMERO
And error log :
The referenced column name 'id' has to be a primary key column on the target entity class 'App\Entity\OrderLignes'.
The referenced column name 'id' has to be a primary key column on the target entity class 'App\Entity\OrderEntete'.
How to make manytomany without key ?
thank !
You need to configure the JoinTable on the owning side of the relationship (lignesCommande). Not just a JoinColumn. If the JoinTable configuration is missing, Doctrine will fall back to its default ManyToMany configuration and try to connect id primary key columns, which in your case won't work.
Here's an example (but untested) JoinTable annotation:
/**
* #ORM\ManyToMany(
* targetEntity="enteteCommande",
* inversedBy="detailLigne"
* )
* #ORM\JoinTable(
* name="foobar",
* joinColumns={
* #ORM\JoinColumn(
* name="LIGNE_C_CDE_NUMERO",
* referencedColumnName="LIGNE_C_CDE_NUMERO"
* ),
* },
* inverseJoinColumns={
* #ORM\JoinColumn(
* name="C_CDE_NUMERO",
* referencedColumnName="C_CDE_NUMERO"
* ),
* }
* )
*/
private $enteteLigne;
I have this structure of Entities
class Group
{
/**
* #var int
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private int $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=50, nullable=false)
*/
private string $name;
}
class GroupUser
{
/**
* #var int
*
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private int $id;
/**
* #var Group
* #ORM\ManyToOne(targetEntity="Group", inversedBy="GroupUser")
* #ORM\JoinColumn(name="group_id", referencedColumnName="id", nullable=false)
*/
private Group $group;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
private string $type;
/**
* #var int
* #ORM\Column(type="integer", nullable=false)
*/
private int $user;
}
And there are two types of users. Admins and Clients. There's a ManyToMany relationship between Group and User. And the property in GroupUser $type is saving the Class of either Admin or Client and the property $user is saving the id.
id
group_id
user
type
1
1
1
Entity\Admin
2
2
1
Entity\Client
How do I join it using doctrine from the Admin and Client-side? Or maybe someone could point out to some resources how this kind of relationship works on doctrine? As I'm having a hard time googling anything out.
I imagine it could be like a conditional leftJoin, but I can't seem to figure it out.
Normaly, you cannot do that on database, because it not safe.
Why is it not safe? because you can have an id in the user column of userGroup table that refer to nothing as it is not linked.
I will write what you should have done, and how you can achieve what you want using your own method:
What you should have done:
In your UserGroup entities, have 2 columns (admin and client) which are linked to the related entities. They can be null (Client is null and admin contain the id of admin entity if it is an admin and vice versa). Then you can delete the type column
How to achieve what you using your method:
As it cannot be done in the database, you will have to do it in some manager. Have a method getUser which will check on your type attribute and return the associated entity from the current id stored in $user
example:
public function getUserFromGroupUser(GroupUser $groupUser){
if('Entity\Admin' ===$groupUser->getType()){
return $this->adminRepository->find($groupUser->getUser());
}
if('Entity\Client' ===$groupUser->getType()){
return $this->ClientRepository->find($groupUser->getUser());
}
throw new \RuntimeException('the type does not exist');
}
I have 2 entities related ManyToMany
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100, unique=true)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="ProductTransfer", inversedBy="product")
* #ORM\JoinTable(name="products_transfers")
*/
private $transfers;
and second one
class ProductTransfer
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="stock_from_id", type="integer")
*/
private $stockFromId;
/**
* #var int
*
* #ORM\Column(name="stock_to_id", type="integer")
*/
private $stockToId;
/**
* #var int
*
* #ORM\Column(name="product_id", type="integer")
*/
private $productId;
/**
* #ORM\ManyToMany(targetEntity="Product",mappedBy="transfers")
*/
private $product;
All works great, additional table were created by Doctrine.
But when I'm trying to get all rows from ProductTransfers I see in profiler that each row need one single query with 2 joins.
So for 5000 products it will be really many queries.
Is there any way to get them all in one query like in "clean SQL"?
If there is no way to do it with Doctrine, what is best way to achieve that?
Like:
SELECT * FROM product_transfer pt
LEFT JOIN products_transfers pts ON pt.product_id=pts.product_id
LEFT JOIN product p ON pts.product_id=p.id;
edit:
created method in repository
public function loadTransfersByStock($stockId)
{
$q = $this->getEntityManager()
->createQuery(
'SELECT pt,p FROM AppBundle:ProductTransfer pt
LEFT JOIN AppBundle:Product p WITH pt.productId=p.id
WHERE pt.stockToId = :stockId'
);
return $q->setParameter('stockId', $stockId)->getResult();
}
and then result is
ProductsController.php on line 495:
array:3 [▼
0 => ProductTransfer {#1071 ▶}
1 => Product {#1084 ▶}
2 => ProductTransfer {#1099 ▶}
]
By default Doctrine uses lazy loading in object relations - the rows for the relationships are only loaded when you try to access them. If you know you're going to be needing all the related rows, try changing to eager fetching.
/**
* #ORM\ManyToMany(targetEntity="ProductTransfer", inversedBy="product", fetch="EAGER")
* #ORM\JoinTable(name="products_transfers")
*/
When you are writing the query like that in the repository, you are skipping the way doctrine "wants" you to write it. So instead of that, I would do something like this:
//ProductTransferRepository.php
$qb = $this->createQueryBuilder('pt');
$qb
->select('pt, p') // here you can choose the columns you want if you don't need everything from both tables
->leftJoin('pt.product', 'p')
->where('pt.stockToId = :stockId')
->setParameter('stockId', $stockId)
;
return $qb
->getQuery()
->getArrayResult()
;
Use getArrayResult to fetch results as array rather than objects, especially because you have many queries.
hi i'm trying to link a class test with 2 entities, the Administrator that post the test and the competence (subject of the test ), but whatever i do i only get 1 index FK on my database after schema update
namespace Admin\AdminBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Test
*
* #ORM\Table(name="test")
* #ORM\Entity
*/
class Test
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="type", type="string", length=50, nullable=false)
*/
private $type;
/**
* #var integer
* #ORM\ManyToOne(targetEntity="ProjetCompetenceListe")
* #ORM\JoinColumn(name="id_competence", referencedColumnName="id")
*/
private $idCompetence;
/**
* #var \Administrateur
*
* #ORM\ManyToOne(targetEntity="Administrateur")
* #ORM\JoinColumn(name="id_administrateur", referencedColumnName="id")
*/
private $idAdministrateur;
please can any one tell me why ?
Have you tried creating indexes with #index annotation, maybe you should give it a try. Ref
add index with #index annotation and then run schema update command
You may need to clear doctrine meta data :
php app/console doctrine:cache:clear-metadata
Otherwise do a :
php app/console doctrine:schema:validate
to check if the relations are correct.
How can I select all items from one specific author ? Its possible this way ? Or how can I edit entities if I want many item types and item packages (item has many items) too ?
Item
/**
* #ORM\Table()
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({
* "cd" = "ItemCD",
* "dvd" = "ItemDVD",
* "pack" = "ItemPack",
* })
*/
class Item
{
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=250, nullable=false)
*/
private $name;
}
ItemCD
/**
* #ORM\Table()
* #ORM\Entity
*/
class ItemCD extends Item
{
/**
* #ORM\ManyToOne(targetEntity="Author", inversedBy="item")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
private $author;
}
ItemDVD
/**
* #ORM\Table()
* #ORM\Entity
*/
class ItemDVD extends Item
{
/**
* #ORM\ManyToOne(targetEntity="Author", inversedBy="item")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
private $author;
}
ItemPack
/**
* #ORM\Table()
* #ORM\Entity
*/
class ItemPack extends Item
{
/**
* #ORM\ManyToMany(targetEntity="Item", inversedBy="item")
* #ORM\JoinTable()
*/
private $items;
}
Author
/**
* #ORM\Table()
* #ORM\Entity
*/
class Author
{
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=250, nullable=false)
*/
private $name;
}
You will have to query for specific elements. This is a known (and wanted) limitation, since DQL is a static typed language: see http://www.doctrine-project.org/jira/browse/DDC-16
Related: how to access fields in inherited table in doctrine2 / dql query
A way of handling this with a workaround is using 2 subqueries in your DQL:
SELECT
i
FROM
Item i
WHERE
i.id IN(
SELECT
i2.id
FROM
ItemDvd i2
WHERE
i2.author = :author
)
OR
i.id IN(
SELECT
i3.id
FROM
ItemCd i3
WHERE
i3.author = :author
)
As you can see you have to extract the identifiers for each possible subtype manually.
Edit: to get all the packs from a given author (along with single DVDs or CDs), the query becomes even worse:
SELECT
i
FROM
Item i
WHERE
i.id IN(
SELECT
i2.id
FROM
ItemDvd i2
WHERE
i2.author = :author
)
OR
i.id IN(
SELECT
i3.id
FROM
ItemCd i3
WHERE
i3.author = :author
)
OR
i.id IN(
SELECT
i4.id
FROM
ItemPack i4
JOIN
i4.items i5
WHERE
i5.id IN (
SELECT
i6.id
FROM
Item i6
WHERE
i6.id IN(
SELECT
i7.id
FROM
ItemDvd i7
WHERE
i7.author = :author
)
OR
i6.id IN(
SELECT
i8.id
FROM
ItemCd i8
WHERE
i8.author = :author
)
)
)
Make $author in Item and have ItemPacks $author value always be null. Then you can do:
$em->findBy("Item", array("author" => $author));
And you always get instances of ItemDVD or ItemCD.
It's tricky and lengthy the answer.
I think the Entities aproach is ok, and by querying the item entity you would get what you want.
Now for forms you'll probably need one FormType per sub-item and then user the aproach for Form Collections (http://symfony.com/doc/2.1/cookbook/form/form_collections.html) and I'm certain that you will need to hook into the pre-bind event to prepare the data.
This is a quick thought, may be it can help you.