Doctrine one-to-many association: Issues with composite primary key - mysql

I am trying to populate the below one-to-many doctrine association however I am hitting a problem because every Customer (primary key: id) has their visits (primary key: customer_id & visitday) captured in the Visit table (I am deriving visitday as the number of days since 1st Jan 2000 before persisting to the database (since datetime objects can not be in the primary key)):
Entities
class Customer
{
/**
* #ORM\Column(type="integer", options={"unsigned"=true})
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Visit", mappedBy="visitday")
*/
protected $visits;
public function __construct()
{
$this->visits = new ArrayCollection();
}
/* -- */
}
Class Visit
{
/**
* #ORM\Column(name="customer_id", type="integer", options={"unsigned"=true})
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
* #ORM\Id
*/
private $customer;
/**
* #ORM\Column(type="smallint")
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="visits")
* #ORM\JoinColumn(name="visitday", referencedColumnName="id")
* #ORM\Id
*/
protected $visitday;
/* -- */
}
My problem is that my Customer objects are not getting populated with the customer's corresponding visits. I assume this is because doctrine can't see that it should also include its own customer ID in the lookup. Is there a way to fix this?

I would recommend you to change $visitday attribute to DateTime. It will be your visit date timestamp. Then customer attribute should be inversed with visits.
/**
* #ORM\Column(name="customer_id", type="integer", options={"unsigned"=true})
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="visits")
* #ORM\Id
*/
private $customer;
And as an option you could change relation Customer to Visits as ManyToMany. So you wont have duplicate visit dates.

Related

Doctrine - ManyToMany with custom primary key

I've a unidirectional ManyToMany relation:
class Account {
/* other attributes ... */
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Item")
* #ORM\JoinTable("account_items",
* joinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="item_id", referencedColumnName="vnum")}
* )
*/
private $items;
}
What I want to do:
I would like to make the account possible to have one or more identical Item (the same identifier.). For example:
`account_id` 1, `item_id` 1
`account_id` 1, `item_id` 1
should've been allowed.
It's not possible when doctrine generates DDL query with two primary keys (account_id, item_id) and if I try to do it I got mysql duplciation entry error.
I customized migration that creates id as primary key (as only one), but I think it's not proper solution.
`id`: 1, account_id` 1, `item_id` 1
`id` : 2, account_id` 1, `item_id` 1
Do you have any?
Make a bi-directionnal OneToMany <=> ManyToOne relation between 3 entities.
src/AppBundle/Entity/Account.php
class Account {
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\AccountItem", mappedBy="accounts")
*/
private $accountItems;
}
src/AppBundle/Entity/AccountItem.php
class AccountItem {
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Account", inversedBy="accountItems")
*/
private $accounts;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Item", inversedBy="itemAccounts")
*/
private $items;
}
src/AppBundle/Entity/Item.php
class Item {
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\AccountItem", mappedBy="items")
*/
private $itemAccounts;
}
It's in fact a ManyToMany relation, but you can now add id, which will be unique, en still have duplicate entries.
Side now, you're referencing item.vnum in your annotation. Know that Symfony hate it when you don't use id.

Doctrine query builder select count onetomany entities

I have Group and User entities. They are associated through GroupUser entity - which is manytomany entity with extra fields like status.
Class Group
/**
* #ORM\OneToMany(targetEntity="GroupUser", mappedBy="group")
*/
private $groupUsers;
Class User
/**
* #ORM\OneToMany(targetEntity="GroupUser", mappedBy="user")
*/
private $userGroups;
Class GroupUser
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="userGroups", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $user;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Group", inversedBy="groupUsers", cascade={"persist"})
* #ORM\JoinColumn(name="group_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $group;
How to get list of groups which User has joined with number of users in each group with QueryBuilder
$qb = $this->createQueryBuilder('group')
->select('group.id', 'group.name', 'COUNT(groupUser.user) as membersCount')
->join('group.groupUsers', 'groupUser')
->where('groupUser.user = :userId')
->andWhere('groupUser.status = :status')
->addGroupBy('groupUser.group')
->setParameter('userId', $user->getId())
->setParameter('status', $status);
$results = $qb->getQuery()->getResult();
This query return membersCount as 1 for all groups even if there are more than one member. How to change query to get correct number of members for each group?

Building OneToMany and ManyToOne associations beetwen more than three tables via additional table Doctrine 2

Building associations beetwen more than three tables
via additional [EntityTypes] table Doctrine2
I have next schema of database:
//==== some Tables ====
articles (
id
entity_type_id
)
posts (
id
entity_type_id
)
other... (
id
entity_type_id
)
//==== additional table of Types ====
entity_types (
id
)
//==== Comments ====
comments (
id
entity_type_id
entity_id
)
I use Symfony2 with Doctrine2 thereby I've been written next assotiations:
class Article
{
/**
* #ORM\ManyToOne(targetEntity="EntityType", inversedBy="articles", cascade={"persist"})
* #ORM\JoinColumn(name="entity_type_id", referencedColumnName="id")
*/
protected $entityType;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="article")
*/
protected $comments;
}
class Post
{
/**
* #ORM\ManyToOne(targetEntity="EntityType", inversedBy="posts", cascade={"persist"})
* #ORM\JoinColumn(name="entity_type_id", referencedColumnName="id")
*/
protected $entityType;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="post")
*/
protected $comments;
}
...
class EntityType
{
/**
* #ORM\OneToMany(targetEntity="Article", mappedBy="entityType")
*/
protected $articles;
/**
* #ORM\OneToMany(targetEntity="Post", mappedBy="entityType")
*/
protected $posts;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="entityType")
*/
protected $comments;
}
class Comment
{
/**
* #ORM\ManyToOne(targetEntity="EntityType", inversedBy="comments", cascade={"persist"})
* #ORM\JoinColumn(name="entity_type_id", referencedColumnName="id")
*/
protected $entityType;
/**
* #ORM\ManyToOne(targetEntity="Article", inversedBy="comments", cascade={"persist"})
* #ORM\JoinColumn(name="entity_id", referencedColumnName="id")
*/
protected $article;
/**
* #ORM\ManyToOne(targetEntity="Post", inversedBy="comments", cascade={"persist"})
* #ORM\JoinColumn(name="entity_id", referencedColumnName="id")
*/
protected $post;
}
There are no any mistakes in mapping and all operations are available
including commenting articles
but an exception appeared when I'm trying to comment post
need someone to help...

Can I set up a Symfony2 ORM inheritance relationship using MySQL?

I have one table tech_note and I would like to have 3 tables more tech_note_es, tech_note_en and tech_note_fr (in a future many more tables). And the tech_note can be in the 3 tables at same time.
My idea is attach to tech_note_en and automatically receive attributes from tech_note.
SECOND EDIT:
I would like somthing like that: I search in Tech_note_EN by ID=3 (I recive fields from tech_note_EN with ID=3 and fields tech_note with ID=3.
But if I search in Tech_note_ES BY ID=3 (I recive fields from tech_note_ES with ID=3 and tech_note fields from tech_note with ID=3 (the same fields from above)
The tech_note have a ID=3. Tech_note_EN have ID=3, and Tech_note_ES have ID=3
EDIT WITH NEW CLASSES:
Now I have this error:
Entity 'EVTS\FrontendBundle\Entity\Tech_note_en' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported.
TECH_NOTE
abstract class Tech_note {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var datetime $control_date
*
* #ORM\Column(name="control_date", type="datetime")
*/
private $control_date;
/**
* #var string $comment
*
* #ORM\Column(name="comment", type="string")
*/
private $comment;
TECH_NOTE_EN
class Tech_note_en extends Tech_note {
/**
* #var integer $tech_note_id
*
* #ORM\Id
* #ORM\Column(name="tech_note_id", type="integer")
* #ORM\OneToOne(targetEntity="tech_note")
* #ORM\JoinColumn(name="tech_note_id",referencedColumnName="id")
*/
private $tech_note_id;
/**
* #var text $symptom
*
* #ORM\Column(name="symptom", type="text")
*/
private $symptom;
/**
* #var text $cause
*
* #ORM\Column(name="cause", type="text")
*/
private $cause;
/**
* #var string $solution
*
* #ORM\Column(name="solution", type="string")
*/
private $solution;
OLD CLASSES
TECH_NOTE
class Tech_note {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var datetime $control_date
*
* #ORM\Column(name="control_date", type="datetime")
*/
private $control_date;
/**
* #var string $comment
*
* #ORM\Column(name="comment", type="string")
*/
private $comment;
TECH_NOTE_EN
class Tech_note_en
{
/**
* #var integer $tech_note_id
*
* #ORM\Id
* #ORM\Column(name="tech_note_id", type="integer")
* #ORM\OneToOne(targetEntity="tech_note")
* #ORM\JoinColumn(name="tech_note_id",referencedColumnName="id")
*/
private $tech_note_id;
/**
* #var text $symptom
*
* #ORM\Column(name="symptom", type="text")
*/
private $symptom;
/**
* #var text $cause
*
* #ORM\Column(name="cause", type="text")
*/
private $cause;
/**
* #var string $solution
*
* #ORM\Column(name="solution", type="string")
*/
private $solution;
There is an awesome solution to your problem, which is single table inheritance
What this does is basically Have one central table with general fields, and links to other table with specific fields.
In your case I would use this architectural model:
abstract class TechNote (abstract common entity, which contains common fields)
class TechNoteEn extends TechNote (specific entity which inherits common fields and adds specific EN fields)
and so on.
I find this as the best architectural solution for your problem.
That's your ORM's job.
// Assuming $tech_note_en is a Tech_note_en instance
$tech_note_en->getTechNote();
(something like this ^^)

Doctrine2 Query where Subquery would be needed

Let me first introduce the Entities used in this example:
Order (_order in mysql)
$id (primary key, auto increment)
OrderStatus (order_status in mysql)
$id (primary key, auto increment)
$order (storing the order object it is related to, named order_id in mysql)
$statuscode (Storing the integer code)
$created_at (Storing the datetime of creation)
The relationship is Order n:1 OrderStatus. For every status change, I create a new OrderStatus with the new statuscode. So for one Order there can be many OrderStatus. The actual OrderStatus can be figured out by looking at the OrderStatus with the latest created_at.
I now want to get all objects which have the status 0 right now. In SQL, my query would look like this:
SELECT o.id,os.statuscode,os.created_at
FROM `_order` o
LEFT JOIN `order_status` os ON o.id = os.order_id
WHERE os.created_at = (SELECT MAX(created_at)
FROM order_status
WHERE order_id = os.order_id);
Can I do such a query in DQL or do I have to work with objects? If so, would I need to read all OrderStatus objects and manually figure out which is the most current one or can I somehow preselect?
I figured out a way which is not quite what I was searching for, but it doesn't require any changes in my application, so it's ok as long as I don't find a better solution.
What I'll do is I'll store the actual status directly within the Order. Through LifecycleCallbacks I'll update the order as soon as a OrderStatus related to it is created.
So my Order-Object looks like this:
/**
* #ORM\Entity
* #ORM\Table(name="_order")
*/
class Order{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//..
/**
* #ORM\Column(type="smallint", nullable=false)
*/
protected $status = -1;
// Getter and Setter!
}
My Order-Status will look like this:
/**
* #ORM\Entity
* #ORM\Table(name="order_status")
* #ORM\HasLifecycleCallbacks
*/
class OrderStatus{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="order_status", cascade={"persist"})
* #ORM\JoinColumn(name="order_id", referencedColumnName="id", nullable=false)
*/
protected $order;
/**
* #ORM\Column(type="smallint", nullable=false)
*/
protected $statuscode;
// ..
/**
* #ORM\prePersist
*/
public function prePersist(){
$this->order->setStatus($this->statuscode);
}
// Getter and Setter!
}
Mind the , cascade={"persist"} within the OrderStatus so the Order will also be persisted when the OrderStatus is persisted, otherwise the status will be set, but not stored!
On the plus side of this solution is that I now get the OrderStatus by calling $order->getStatus() instead of having to query the database for the status and that I'm able to do $repository->findByStatus(0). On the other hand, the moment the status field gets changed due to some error, the data will be inconsistent.
As said, if you got a solution where I don't need an extra field to store the status, post it! I'm very interested in a better solution!