Doctrine join a table with two - php

Good morning, as seen in the image below, I have some tables linked.
Using Doctrine (in Symfony2) I'm trying to get an array of Objects Issue which itself contains all IssueMessages and IssueStatusChanged objects but can not.
I have no idea how I can do to join two tables (IssueMessage and IssueStatusChanged) to through their identifiers.
The most we've done is get all Issue with an account of the messages that have:
$dql = 'SELECT x, COUNT(im.id) FROM PanelBundle:Issue x LEFT JOIN PanelBundle:IssueMessages im WITH x.id = im.idIssue';
Does anyone could give me a hand?
THANKS!

You want to use assication mapping; this will have Doctrine manage all the joins for you.
Once in place, $issue will always have the other associated models available automatically without you having to worry about joins.
For the example below (assuming you use annotation), to get messages for an issue just get the issue objects and then use $issue->getMessages();.
<?php
/** #Entity */
class issue
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
// ...
/**
* #OneToMany(targetEntity="issueMessages", mappedBy="issue")
*/
private $messages;
// ...
public function __construct()
{
$this->messages = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class issueMessages
{
// ...
/**
* #ManyToOne(targetEntity="issue", inversedBy="messages")
* #JoinColumn(name="issue_id", referencedColumnName="id")
*/
private $issue;
// ...
}

If you using yml format for schema orm files than
first you need to write schema and mention oneToMany, manyToOne relationship with table fields & generate entity, repository class.
Than you can use join with two or more tables as below example:
Example of repository class file function:
----------------------------------------------------
public function getReportInfo($idUserDetail)
{
$query = $this->createQueryBuilder('UR')
->select("UR.report_period_start_date, UR.report_period_end_date")
->leftJoin('UR.UserReportDetail', 'URD')
->andWhere('UR.id_user_detail = :id')
->setParameter('id', $id)
->orderBy('UR.report_year', 'DESC')
->addOrderBy('UR.report_month', 'DESC')
->setMaxResults(1);
$resultArray = $query->getQuery()->getArrayResult();
return $resultArray;
}
You can call this function from controller action as below:
-------------------------------------------------------------
public function getUserDetailAction($idUserDetail)
{
$em = $this->getDoctrine()->getManager();
$userDetail = $em->getRepository(
'DemoBundle:UserDetail')
->getReportInfo($idUserDetail);
return $userDetail;
}
I hope this would be useful to you.

I think the problem reside in the DQL syntax (+ missing inverse relation?).
By writing this:
SELECT x, COUNT(im.id) FROM PanelBundle:Issue x
LEFT JOIN PanelBundle:IssueMessages im WITH x.id = im.idIssue
you are joining two "random" table based on the condition provided in the WITH clause. This should usually be ok, but it may confuse the Hydrator component.
In your case you should configure the OneToMany side of the relation in Issue entity, then write something like this:
SELECT x, COUNT(im.id) FROM PanelBundle:Issue x
LEFT JOIN x.issueMessages im
Hope it helps!

Related

Doctrine2 join column

I have defined the follow entity in doctrine2 (with symfony).
/**
*
* #ORM\Table(name="order")
* #ORM\Entity
*/
class Order
/**
* #var integer
*
* #ORM\Column(name="personid", type="integer", nullable=false)
*/
private $personid;
/**
* #ORM\OneToOne(targetEntity="People")
* #ORM\JoinColumn(name="personid", referencedColumnName="personid")
*/
private $person;
public function getPersonId()
{
return $this->personid;
}
public function getPerson()
{
return $this->person;
}
}
I realize that if I call $order->getPersonId() it return always an empty value and I have to call the getPerson()->getId() method to get the correct personid.
Could anyone explain me why the variable $personid is not filled?
Should I to delete the column id used for the join if I defined one?
Thanks
Gisella
You should remove private $personid;, it's better to work with objects only in an ORM.
It's not a problem if you get the ID with $order->getPerson()->getId(), because Doctrine won't load the complete entity. The People entity will only be loaded if you call an other field than the join key.
You can still have a getter shortcut like this :
public function getPersonId()
{
return $this->getPerson()->getId();
}
Edit :
You can also still work with "ID" if you use Doctrine references, like this :
$order->setPerson($em->getReference('YourBundle:People', $personId));
With this way, Doctrine won't perform a SELECT query to load data of the person.
You don't need to have the $personid field when you already have the $person field.
$people contains the People object (with all People's attributes including the id).
Moreover, when doctrine translate your object into sql tables, he knows that he have to join with th id so it will create a field (in database) named personid. (It's the name that you defined in your ORM)
/**
* #ORM\OneToOne(targetEntity="People")
* #ORM\JoinColumn(name="personid", referencedColumnName="personid")
*/
private $person;
Sorry for bad english :p

Hierarchal data with Doctrine2 using closure table model

I have some existing data stored using the closure table model. I'm new to Doctrine, and trying to implement an Entity for this the "Doctrine way", and not really sure how to proceed. The philosophy I'm trying to follow is that the Entity should just be a plain-old-PHP-object, and that some kind of annotation should be used to configure the parent-child associations.
In this post I'll use Category as an example entity. Here's what I imagine the entity looking like:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Table(name="categories)
* #ORM\Entity
*/
class Category
{
/**
* #ORM\Column(name="categoryID", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $categoryID;
/**
* #ORM\Column(name="title", type="string", length=255)
*/
protected $title;
/**
* #MyORM\TreeParent(targetEntity="Category", closureTable="categories_paths", ancestorColumn="ancestorID", descendantColumn="descendantID")
*/
protected $parent;
/**
* #MyORM\TreeChildren(targetEntity="Category", closureTable="categories_paths", ancestorColumn="ancestorID", descendantColumn="descendantID")
*/
protected $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
public function getChildren()
{
return $this->children;
}
public function addChild(Category $child)
{
$this->children[] = $children;
}
public function getParent()
{
return $this->parent;
}
public function setParent(Category $parent)
{
$this->parent = $parent;
}
}
The closure table looks as follows:
categories_paths(ancestorID, descendantID, pathLength)
This table is essentially a join table -- it only stores the parent-child relations, so I don't think it makes sense for there to be an entity here, similar to how there's no entity when creating a many-to-many relationship with #JoinTable.
I'd like to be able to use my Category entity like any other Entity, with $parent / $children populated when I fetch it from the repository and when $em->flush() is called, have SQL executed to reflect newly added children.
Some examples of SQL used here:
Add a new child:
INSERT INTO categories_paths (ancestorID, descendantID, pathLength)
SELECT a.ancestorID, d.descendantID, a.pathLength+d.pathLength+1
FROM categories_paths a, categories_paths d
WHERE a.descendantID = $parentCategoryID AND d.ancestorID = $childCategoryID
Move a subtree to a new parent:
// Delete all paths that end at $child
DELETE a FROM categories_paths a
JOIN categories_paths d ON a.descendantID=d.descendantID
LEFT JOIN categories_paths x
ON x.ancestorID=d.ancestorID AND x.descendantID=a.ancestorID
WHERE d.ancestorID = $subtreeCategoryID and x.ancestorID IS NULL
// Add new paths
INSERT INTO categories_paths (ancestorID, descendantID, pathLength)
SELECT parent.ancestorID, subtree.descendantID,
parent.pathLength+subtree.pathLength+1
FROM categories_paths parent
JOIN categories_paths subtree
WHERE subtree.ancestorID = $subtreeCategoryID
AND parent.descendantID = $parentCategoryID;
Get all children of a Category:
SELECT * FROM categories
JOIN categories_paths cp ON cp.descendantID=categories.categoryID
WHERE cp.ancestorID = $catogeryID
AND cp.depth=1
I have a few questions here. First of all, does this seem like a reasonable approach / something that is possible to implement with Doctrine? If not, is there a better way to approach this?
If this does seem like a reasonable approach, I'm wondering how to go about attacking this? I'm more looking for where I need to put these files / how I need to set up classes vs. someone giving me an actual implementation. Any documentation or examples that would help me get started would be much appreciated. I have pretty much zero experience with Doctrine--hopefully I'm not missing anything obvious here.
I think if you want to build a hierarchical database you should look for the doctrine ODM project. All the things you want are built in into that and you can customize your node.
There's a mongoDB adapter and also you can take a look at DoctrinePHPCR project that has adapters for several databases.
Even if you want to implement your own approach using doctrine ORM you can look at their implementations to get an idea how they work. They have node based relationship so you always have reference to adjacent nodes in the tree in your object.
Hope that helps.

Symfony Entity - Order joined Entities in a ManyToMany join

So let's say we use a User and a Ticket class. They are normal entities, nothing fancy.
The User class contains this lines:
/**
* #ORM\ManyToMany(targetEntity="Ticket", mappedBy="collaborateurs")
**/
private $tickets;
The Ticket class contains this:
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="tickets")
* #ORM\JoinTable(name="users_to_tickets")
**/
private $collaborateurs;
To get all ticket's a user has I can just call the getTickets() function created by Symfony. As far as good. The Ticket class has a few additional fields like updated which is a DateTime field or status which is an integer. I would like to sort those tickets by status DESC and updated DESC
I know I could just make a function in the repository like findTicketsByUserOrderedByFooBar($user), but I'm wondering if there isn't a better way.
If you always want your tickets to be in that order you can set and orderBy on the association.
/**
* #ORM\ManyToMany(targetEntity="Ticket", mappedBy="collaborateurs")
* #ORM\OrderBy({"status" = "DESC", "updated" = "DESC"})
**/
private $tickets;
You can add an Helper method to your User entity and sort/filter DIRECTLY on the ArrayCollection with doctrine2 criteria. Something like this:
/**
* this return a ArrayCollection
*/
public function getTicketsByUserOrderedByFooBar()
{
$criteria = Criteria::create()
->orderBy(array('foo'=>'DESC','bar' =>'ASC'))
return $this->tickets->matching($criteria);
}
/**
* this return a ArrayCollection
*/
public function getTicketsByUserOrderedBytitle()
{
$criteria = Criteria::create()
->orderBy(array('title'=>'DESC'))
return $this->tickets->matching($criteria);
}
See also this
Hope this help.
Creating a function the way you suggested would be the suggested approach.

Symfony2 error while display entity related ManyToOne

I am trying to display a "CategorieActivite" in an activite.html twig page.
I have 2 entities. The first one is Activite. The second one is CategorieActivite.
I have put annotations in the Activite.
I have prepared my query with join in ActiviteRepository.
It's ok in my database (foreign key).
BUT i get this error :
Method "CategorieActivite" for object "AssoFranceRussie\MainBundle\Entity\Activite" does not exist in (in my twig page)
Do I have to do something else to get some entity data from another entity?
Thanks
EB
The code :
class Activite
{
// $categorieActiviteId lié à l'entité CategorieActivite
// ManyToOne
/**
* #ORM\ManyToOne(targetEntity="AssoFranceRussie\MainBundle\Entity\CategorieActivite")
* #ORM\JoinColumn(name="categorie_activite_id", referencedColumnName="id")
*/
private $categorieActiviteId;
...
}
ActiviteRepository :
public function getAllActivites()
{
$query = $this->getEntityManager()->createQuery(
'SELECT a,c,n
FROM AssoFranceRussieMainBundle:Activite a
JOIN a.categorieActiviteId c
JOIN a.niveauActiviteId n
ORDER BY a.nom ASC '
);
return $query->getResult();
}
And in the twig html:
<p><strong>{{activite.CategorieActivite.libelle}}</strong></p>
You should create getter for $categorieActiviteId property.
So in Activite class you should have
public function getCategorieActivite() {
return $this->categorieActiviteId;
}
and in twig you should have:
<p><strong>{{activite.getCategorieActivite.libelle}}</strong></p>
Dont forget libelle have to be public method or property
Thank you for your help. It works now.
Actually i had already the getter with annotation in the class Activite. So, I should have write in twig {{activite.categorieActiviteId.libelle}} instead of {{activite.categorieActivite.libelle}}. The 2 ways to access the data work.
the getter in Activite class:
/**
* Get categorieActiviteId
*
* #return \AssoFranceRussie\MainBundle\Entity\CategorieActivite
*/
public function getCategorieActiviteId()
{
return $this->categorieActiviteId;
}

How to specify several join conditions for 1:1 relationship in Doctrine 2

Documentation states:
class Cart
{
// ...
/**
* #OneToOne(targetEntity="Customer", inversedBy="cart")
* #JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
// ...
}
This annotation represents such sql:
JOIN Customer c ON c.id = cart.customer_id
And the issue is that I need to add additional comparison there, like:
JOIN Customer c ON c.id = cart.customer_id AND c.anotherField = <constant>
Any solutions for that?
UPD:
the real additional condition I need for now is <const> BETWEEN c.f1 AND c.f2
There doesn't seem to be any solution to your problem that Doctrine could do auto-magically.
Since #ficuscr already gave you a solution for queries, there's only one more thing to handle - check your additional criteria and return the Customer instance in your getter on success and NULL on failure to meet additional criteria.
class Cart
{
const VALUE = '<some_constant_value>';
/**
* #OneToOne(targetEntity="Customer", inversedBy="cart")
* #JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
// ...
/**
* #return Customer|null
*/
public function getCustomer()
{
if ($this->customer->getField1() <= self::VALUE
&& $this->customer->getField2() >= self::VALUE
) {
return $this->customer;
}
return null;
}
}
If this was a One-To-Many relation, Collection Filtering API (a.k.a. Criteria) could be used to filter the collection created by the mappings:
use Doctrine\Common\Collections\Criteria;
class Cart
{
const VALUE = '<some_constant_value>';
/**
* #OneToMany(targetEntity="Customer", mappedBy="cart")
*/
private $customers;
// ...
/**
* #return Customer[]|ArrayCollection
*/
public function getCustomers()
{
$expr = Criteria::expr();
$criteria = Criteria::create()
->where($expr->andX(
$expr->lte('field1', self::VALUE),
$expr->gte('field2', self::VALUE)
));
return $this->patientProblems->matching($criteria);
}
}
you can use the WITH keyword to specify additional join conditions, as you can see in some of the examples.
i think this should get you going:
SELECT l, c FROM location
INNER JOIN Customer c
WITH CURRENT_TIMESTAMP() BETWEEN c.f1 AND c.f2
WHERE CURRENT_TIMESTAMP() BETWEEN l.f1 AND l.f2
i removed the ON clause because i think there's no need to explicitly specify the join's ON fields unless they are not the "standard" ones (id of each entity)
also notice the call to CURRENT_TIMESTAMP() which translates into MySQL's NOW(). check out a list of other pretty useful aggregate functions and expresions here

Categories