I have two entities, invoices and sales tax. Recently my team has decided to upgrade to Symfony 2.3 from 2.1. We had to rewrite a lot of the ways we were doing queries because of this, and some entities that didn't have relationships needed to have them.
Before the update, my invoices and sales tax records were created by getting the transaction id date('U'); and setting up both with the same transaction id (both tables have a primary index id as well).
So you can imagine it looks like this:
Sales Tax: id, transaction_id, amount
Invoice: id, transaction_id, amount
So when I queried for them I just joined on the transaction id. Now joins aren't working without relationships, so I'm having to update this to have a relationship. But when I go to create an invoice or sales tax record, I get this error: Notice: Undefined index: transactionId in C:\folders\vendor\doctrine\orm\lib\Doctrine\ORM\Persisters\BasicEntityPersister.php line 539.
In the code the sales tax record is being created just fine, but when it goes to create the invoice it fails:
public function insertSalesTax($transactionId, $amount)
{
$tax = new SalesTax();
$tax->setAmount($amount);
$tax->setTransactionId($transactionId);
$this->entityManager->persist($tax);
$this->entityManager->flush();
return $tax;
}
That inserts, but then I take that tax record and try to create the invoice:
$invoice = new Invoice();
$em = $this->entityManager;
$invoice //omitted other invoice data
->setSalesTax($salesData['salesTax']);
$em->persist($invoice);
$em->flush();
Here's the relevant portions of my mapping in yml:
Bundle\Entity\Invoice:
type: entity
table: invoices
indexes:
transactionId:
columns: [ transaction_id ]
id:
id:
type: integer
generator: { strategy: AUTO }
manyToOne:
salesTax:
targetEntity: SalesTax
inversedBy: invoice
joinColumn:
name: transaction_id
referencedColumnName: transaction_id
And SalesTax:
Bundle\Entity\SalesTax:
type: entity
table: sales_taxes
indexes:
transactionId:
columns: [ transaction_id ]
id:
id:
type: integer
generator: { strategy: AUTO }
oneToMany:
invoice:
targetEntity: Invoice
mappedBy: salesTax
If you're wondering why oneToMany, that's because invoices are stored as individual line items. There may be many invoices with the same transaction ID. One transaction ID in the invoices table represents one order, each row only represents a line item. So this invoice entity will probably need a self-referencing relationship at some point.
Related
I have 2 entities: User and Metadata. They have a bi-directional one-to-many relationship (a user can have many metadata records).
In a context decorator I'm using for flagception, I query the user and some of the user's relationships that may be needed using DQL:
$dql = "
SELECT user
FROM
\meQ\Entity\User user
JOIN user.profile profile
JOIN profile.client client
JOIN user.metadata metadata
WHERE user.uid = :user_id
";
$this->user = $em->createQuery($dql)
->setParameter('user_id', $session_user->getId())
->useQueryCache(true)
->getOneOrNullResult();
// for debug
dump($this->user);
exit();
This query returns the user, complete with the user->profile and user->profile->client fields populated correctly. However, it does not have its metadata field populated.
The user in question has 18 metadata records, but performing dump($this->user) shows me an empty ArrayCollection:
Here is my Doctrine mapping for this relationship:
AppBundle\Entity\User:
# ...
oneToMany:
metadata:
fetch: EXTRA_LAZY
targetEntity: AppBundle\Entity\UserMetadata
mappedBy: user
cascade: [all]
orphanRemoval: true
indexBy: name
and the other side of the relationship:
AppBundle\Entity\UserMetadata:
# ...
manyToOne:
user:
targetEntity: AppBundle\Entity\User
inversedBy: metadata
joinColumn:
name: user_id
referencedColumnName: uid
Does this not work because User isn't the owning side?
I had to explicitly list metadata in my SELECT:
$dql = "
- SELECT user
+ SELECT user, metadata
FROM
\meQ\Entity\User user
JOIN user.profile profile
JOIN profile.client client
JOIN user.metadata metadata
WHERE user.uid = :user_id
";
I have two entities, Invoice and InvoiceItem definined as follows in yml.
Invoice.orm.yml
AppBundle\Entity\Invoice:
type: entity
table: invoices
repositoryClass: AppBundle\Repository\InvoiceRepository
fields:
id:
id: true
type: integer
generator:
strategy: AUTO
......
oneToMany:
items:
targetEntity: InvoiceItem
mappedBy: invoice
deductions:
targetEntity: InvoiceDeduction
mappedBy: invoice
payments:
targetEntity: InvoicePayment
mappedBy: invoice
InvoiceItem.orm.yml
AppBundle\Entity\InvoiceItem:
type: entity
table: invoice_items
repositoryClass: AppBundle\Repository\InvoiceItemRepository
fields:
id:
id: true
type: integer
generator:
strategy: AUTO
....
manyToOne:
invoice:
targetEntity: Invoice
inversedBy: items
joinColumn:
invoice_id:
referencedColumnName: id
onDelete: CASCADE
My controller receives invoice details and saves the elements as illustrated below
public function createInvoiceAction(Request $request)
{
....
$invoice = new Invoice();
$invoice->setReference($ref);
$invoice->setInvoiceDate(\DateTime::createFromFormat('d/m/Y',$request->query->get('invoiceDate')));
$invoice->setDescription($request->query->get('summary'));
$invoice->setAttn($request->query->get('att'));
$invoice->setIsVatable($request->query->get('vattable') == 'true' ? 1 : 0);
$invoice->setAdditionalInstructions($request->query->get('additional'));
$invoice->setJob($job);
$invoice->setCurrency($request->query->get('currency'));
$invoice->setGeneratedBy($createdBy);
$invoice->setFile($filename);
$job->setStatus('Invoiced');
$em->persist($invoice);
$em->persist($job);
$em->flush();
$items = $request->query->get('items');
for($i=0;$i<count($items);$i++)
{
if($items[$i]['description']!= '' && $items[$i]['amount'] != '')
{
$item = new InvoiceItem();
$item->setDescription($items[$i]['description']);
$item->setAmount($items[$i]['amount']);
$item->setInvoice($invoice);
$em->persist($item);
$em->flush();
}
}
$deductions = $request->query->get('deductions');
for($i=0;$i<count($deductions);$i++)
{
if($deductions[$i]['description'] != '' && $deductions[$i]['value'] != '')
{
$deduction = new InvoiceDeduction();
$deduction->setDescription($deductions[$i]['description']);
$deduction->setValue($deductions[$i]['value']);
$deduction->setIsPercentage($deductions[$i]['isPercentage'] == 'true' ? 1 : 0);
$deduction->setInvoice($invoice);
$em->persist($deduction);
$em->flush();
}
}
$html = $this->renderView('AppBundle:Default:invoice.html.twig', array('invoice' => $invoice));
return new Response(
$html
);
}
invoice.html.twig which renders the invoice details including items and deductions.
.....
{% for item in invoice.items %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ item.description }}</td>
<td>{{ item.amount|number_format(0, '', ',') }}</td>
</tr>
{% endfor %}
.....
The challenge I'm having is that the $invoice object sent to the template is has no items or deductions which are supposed to be collections. I've checked in the database and can confirm that the items and deductions are persisted into the respective tables so I don't understand why there are no invoice.items or invoice.deductions in the tables.
Out of desperation I flushed after every InvoiceItem object creation thinking that maybe the template was rendered before the persist transaction was completed but this didn't help either.
UPDATE:
Atleast one other error I've spotted is the onDelete option being defined under joinColumn. It should be:
manyToOne:
invoice:
targetEntity: Invoice
inversedBy: items
joinColumn:
invoice_id:
referencedColumnName: id
onDelete: CASCADE
--------
If you are having this problem only when the view is rendered during createInvoiceAction, then the problem might be that since you don't actually add the InvoiceItem(s) to the Invoice object via a method called something like addItem() (the naming depends on how you name the relation in the invoice entity), those items might only be present in the Invoice entity the next time you query the invoice, because even though you have set the invoice of each item, it might be that if those entities were created just now, doctrine might not be able to retrieve them using the proxy classes. So the solution would be simple: use the addItem method on invoice entity to add the items to the invoice and then don't forget to add the logic that sets the invoice for the given item inside that method
public function addItem(InvoiceItem $item)
{
$item->setInvoice($this);
$this->items[] = $item;
return $this;
}
However if this problem is still present the next time you query for the already persisted invoice and its items, I would suggest you check whether the object actually contains the getter methods and you aren't simply trying to access the items via a public property. In other words inside the invoice class the property named items should be defined as private, and you should have a getItems() method, which retrieves the items.
Objects used with doctrine must have these methods, since it overrides the logic inside those methods using its proxy classes in order to add code that would also send out the query to retrieve the, in your case, InvoiceItem(s) from the database and place them inside of the private property of the object and only then run the getter code inside of your class. Without these methods private properties would stay empty, since there is no other way for doctrine to retrieve them form the database for you (unless maybe fetch: "EAGER").
I have a relationship like the one in doctrine's docs so i'll use it as an example:
Product:
type: entity
oneToOne:
shipping:
targetEntity: Shipping
joinColumn:
name: shipping_id
referencedColumnName: id
I'm attempting to delete a Shipping entity but am getting a foreign key constraint exception because the Product's row holds a reference to it. What's the proper way of handling this? Is there something in the yaml that i can add to take care of this? Or do i need to do something like below:
$product->setShipping(null);
$entityManager->persist($product);
$entityManager->remove($shipping);
$entityManager->flush();
You should set the onDelete option to CASCADE if you want the Shipping to be removed too, or to SET NULL if you want to delete just the Product.
Product:
type: entity
oneToOne:
shipping:
targetEntity: Shipping
joinColumn:
name: shipping_id
referencedColumnName: id
onDelete: "SET NULL"
You can read more about this on the Doctrine docs.
There is a User class which has two types of users such as patient and doctor. So, I created another attribute that is used to describe the relation between patient and doctor, a doctor can have many patients. So, If the user is patent, he should have doctorId, if not, the doctorId is NULL.
Here is my User.orm.yml:
Acme\Bundle\DemoBundle\Entity\User:
type: entity
table: User
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
name:
type: string
length: 30
lastName:
type: string
length: 30
email:
type: string
length: 60
unique: true
username:
type: string
length: 10
unique: true
password:
type: string
length: 100
unique: false
doctorId:
type: integer
unique: false
nullable: true
dateCreated:
type: datetime
manyToMany:
roles:
targetEntity: Role
mappedBy: users
How can I map the doctorId as a foreign key that refers to the id?
You can find instructions for self-referencing One-To-Many relations in the Doctrine documentation:
User:
type: entity
oneToMany:
patients
targetEntity: User
mappedBy: doctor
manyToOne:
doctor:
targetEntity: User
inversedBy: patients
http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#one-to-many-self-referencing
With doctrine you might want to take a look at association mapping. http://docs.doctrine-project.org/en/latest/reference/association-mapping.html. In your case you are after a one to many self referencing relationship http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#one-to-many-self-referencing.
A doctor can have many patients but can a patient have many doctors? If so you have a many to many relationship which you could map out using a cross Reference table as the joining table. The best way to model this I feel is to have a userTypeId in your users Table where a value of 1 could be doctor, 2 a patient etc. Then a doctor can have many patients but also a patient can see many doctors. This would also be extensible if you added new user types like nurses etc.
I have a Stats entity that is related to a Journey entity through a ManyToOne association.
id:
index:
type: integer
fields:
idJourney:
type: string
// data fields...
manyToOne:
journey:
targetEntity: Journey
joinColumn:
name: idJourney
referencedColumnName: idJourney
The Journey is related to the Station entity through two ManyToOne association: one for the first Station of the Journey, one for the last.
id:
idjourney:
type: string
fields:
idFirstStation:
type: string
idLastStation:
type: string
// other info fields
manyToOne:
firstStation:
targetEntity: Station
joinColumn:
name: idFirstStation
referencedColumnName: idStation
lastStation:
targetEntity: Station
joinColumn:
name: idLastStation
referencedColumnName: idStation
Finally, the Station entity :
id:
idStation:
type: string
fields:
name:
type: string
// other station info
I retrieve a collection of Stats objects with all related sub-objects via a custom Repository method which works fine.
$statCollection = $statsRepository->getStatsByDateAndArea($date, $area);
//This retrieves the expected data
$statCollection[0]->getJourney()->getFirstStation()->getName();
However, iterating through the collection with a foreach loop doesn't work:
foreach($statCollection as $stat) {
$journey = $stat->getJourney(); // works fine
$firstStationId = $journey->getFirstStationId(); // works too
$firstStation = $journey->getFirstStation(); // still works, but returns a Proxies\AcmeAppPathWhateverBundleEntityStationProxy object instead of a AcmeAppPathWhateverBundleEntityStation, but this should be transparent (as per Doctrine documentation)
$firstStationName = $firstStation->getName(); // throws an EntityNotFoundException
}
Any idea what's going on ? Should I force Doctrine to fetch all sub entities ?
EDIT
The error message is fairly laconic :
EntityNotFoundException: Entity was not found.
Not very helpful...
I ended up querying explicitly for the full set of sub-entities in my custom repository method...
I changed this query :
->select('stats')
->leftJoin('stats.journey', 'j')
->leftJoin('j.firstStation', 'fs')
->leftJoin('j.lastStation', 'ls')
to :
->select('stats, j, fs, ls')
->leftJoin('stats.journey', 'j')
->leftJoin('j.firstStation', 'fs')
->leftJoin('j.lastStation', 'ls')
I guess Doctrine's use of Proxy objects are not all that transparent...