I want to add a getHomeAddress() to a customer entity in doctrine using symfony wondering what the best practice would be
Customer Entity
/**
* #ORM\ManyToMany(targetEntity="Addresses", inversedBy="customers")
* #ORM\JoinTable(name="Customer_Addresses")
*/
private $addresses;
Addresses Entity
/**
* #ORM\ManyToMany(targetEntity="Customers", mappedBy="addresses")
*/
private $customers;
Customers Entity
$customers->getHomeAddress();
This codes for in entity mapping annotation, more information this address Doctrine Association Mapping http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
Related
I've been trying to figure out how to get this to work for along time but without any luck. Due to a complex logic in an app I'm working on, I need to create an isolated clone of a entity collection without preserving what so ever relation to the database. Whatever changes I do on the cloned collection should not be tracked by Doctrine at all and should be treated as if it doesn't exist at all.
Here's an example code:
/*
* #ORM\Entity()
*/
class Person
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="person_id", type="integer",nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Car", mappedBy="person", cascade={"persist"})
*/
public $cars;
}
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Car
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="car_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/*
* #ORM\JoinColumn(name="person_id", referencedColumnName="person_id", nullable=true)
* #ORM\ManyToOne(targetEntity="Person", inversedBy="cars", cascade={"persist"})
*/
private $person;
}
I've already tried the following code in my controller to store the collection into the session but it still somehow stores the relationships:
$tmp = clone $person;
$this->get('session')->set('carCollection', $tmp->getCars());
$tmpCars = clone $person->getCars();
$tmpCollection = new ArrayCollection();
foreach($tmpCars as $car) {
$tmpCollection->add(clone $car);
}
$this->get('session')->set('carCollection', $tmpCollection);
$tmpCars = clone $person->getCars();
$tmpCollection = new ArrayCollection();
foreach($tmpCars as $car) {
$clone = clone $car;
$entityManager->detach($car);
$tmpCollection->add(clone $clone);
}
$this->get('session')->set('carCollection', $tmpCollection);
Apparently I'm doing something wrong here because I end up having more results in the Car collection when flushing the entity even though the collection itself has the correct number of records. I have a suspicion that somewhere in the chain Doctrine doesn't compute correctly what needs to be done.
Any ideas or directions on how to solve or debug this?
Follow-up question: When retrieving back the cloned collection from the session will it still be an isolated clone or Doctrine will try merge it back?
I'm writing this answer to give directions to anybody who might have similar issues. I couldn't find many topics or documentation in this manner which is why I decided to share my experience. I am no deep expert on Doctrine an how it internally works, so I won't go into big details of "how it works". I will rather focus on the end result.
Storing entities which have relations to other entities into a session is quite problematic. When you retrieve it from the session, Doctrine loses track of the relationships (OneToMany, ManyToOne, etc). This leads to some undesired effects:
Doctrine wrongly decides to insert a new record of an existing entity.
Doctrine might throw exceptions such as A new entity was found through the relationship 'Acme\MyBundle\Entity\Person#cars' that was not configured to cascade persist operations for entity: Opel. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). and at least 2 other types of exceptions which might seem totally irrelevant at first.
Apparently when fetching a result from the database and it "as-is" in your session things get really messy, specially if the entity has relations to other entities (which was my case). Pay big attention if you have entity relationships - they might need to be "refreshed" if you start getting strange exceptions.
There are a couple of ways to overcome this issue. One of which is to use the data sent via the form (as #flec suggested) by using $myForm->getData(). This approach might work well for you, but unfortunately it was not the case with me (too complex to explain).
What I ended up doing was implementing the \Serializable in the entity. I also created a method called __toArray() which converted my entity into an array. What data you return in the __toArray() method is totally up to you and your business logic. The array data is stored into the session and you use it to re-create a fresh object with all necessary relations.
Hope this helps somebody.
I think hydrators/extractors would be the way to go for you.
They can extract the data from an entity and you can pass them to a newly created instance of that entity via the hydrator.
The only thing you'll need to do in between is the unsetting of the relation properties.
They should be fetchable via a metadata class via doctrine somehow.
I am using Symfony2 and Doctrine2.
There are many instances when I have an entity and I need to loop through its associated entities. Of course it often triggers new queries and is not very performant.
Would there be a best practice to addselect other entities to an existing one?
Think about when you use a paramconverter in symfony. It just gets you the entity. What if I retrieve an order and want to loop through its orderLines? Do I need to build a new query and retrieve->leftjoin('order.orderlines', 'l')->addselect('l')->where('order = $order') ?
The best practice in this case is to use a custom repository method that explicitly joins the associated entity. Then Doctrine will not have to query individually through every iteration of the loop. You can also use this custom repository method in the ParamConverter.
Custom Repository Method:
Here's an example Controller:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/blog/{id}")
* #ParamConverter("post", class="MyBundle:Order",
options={"repository_method" = "findOrderWithLineItems"})
*/
public function showAction(Order $order)
{
}
Then specifying the custom repository on the entity:
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="MyBundle\Entity\OrderRepository")
*/
class Order
{
}
Then your custom repository:
namespace MyBundle\Entity;
use Doctrine\ORM\EntityRepository;
class OrderRepository extends EntityRepository
{
public function findOrderWithLineItems($id)
{
return $this->createQueryBuilder('o')
->join('o.orderLines', 'ol')
->where('o.id = :id')
->setParameter('id', $id)
->getQuery()
->getResult()
;
}
}
Always Join via Eager Fetching:
If instead you want to always fetch the associated entity (always join the table), even when doing a simple select on the base entity, you can specify an eager join on the associated entity:
class Order
{
/**
* #ManyToOne(targetEntity="OrderLines", fetch="EAGER")
*/
private $orderLines;
}
I'm trying to understand the cascade option in Doctrine in Symfony2.
I would like to be able to delete a child entity (and not trigger the foreign key constraint error.)
I have 3 entities:
Report
/**
* #ORM\OneToMany(targetEntity="Response", mappedBy="report")
*/
protected $responses;
/**
* #ORM\OneToMany(targetEntity="Response", mappedBy="report")
*/
protected $sms;
Response
/**
* #ORM\ManyToOne(targetEntity="Report", inversedBy="responses")
*/
protected $report;
SMS
/**
* #ORM\ManyToOne(targetEntity="Report")
*/
protected $report;
Now I would like to delete a Response entity but I get
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row:
a foreign key constraint fails (mybundle.sms, CONSTRAINT FK_B0A93A77BB333E0D FOREIGN KEY (reportId) REFERENCES report (id))
Where do I use the cascade option and which option should I use (detach or remove)?
I can do a lot of trial and error to figure this out, but I was hoping for an expert explanation, so I don't overlook something.
Try using
/**
* #ORM\ManyToOne(targetEntity="Report", inversedBy="responses")
* #ORM\JoinColumn(name="reportId", referencedColumnName="id", onDelete="CASCADE")
*/
protected $report;
And then update yor schema. It will add database level Cascading
Ziumin's answer
using the onDelete option for the ORM JoinColumn
method worked when you want to delete a child item (Owning Side).
But if you want to delete a Response which is a parent item (Inverse Side), this is when cascade comes in handy. In the Report entity I added the following for each of its collections (OneToMany relationships):
Report
/**
* #ORM\OneToMany(targetEntity="Response", mappedBy="report", cascade={"remove"})
*/
protected $responses;
/**
* #ORM\OneToMany(targetEntity="SMS", mappedBy="report", cascade={"remove"})
*/
protected $sms;
Now, when I delete a Report, it removes all of its associated entries in the Response and SMS tables.
You may also use cascade=all for update all actions.
Report
/**
* #ORM\OneToMany(targetEntity="Response", mappedBy="report", cascade={"all"})
*/
protected $responses;
/**
* #ORM\OneToMany(targetEntity="SMS", mappedBy="report", cascade={"all"})
*/
protected $sms;
I've got 2 Bundles in my Symfony2 Framework:
A "MainBundle" with a User Entity, and a "TicketBundle" with all my Stuff for the Issue Tracking System. In this Case only the Entities "Pool" and "Mappingpooluser" are important.
They both use different entity mangers, because i have to use 2 different databases. (see my config.yml below)
doctrine:
dbal:
default_connection: default
connections:
default:
[driver, host etc.]
dbname: m_symfony
ticket:
[driver, host etc.]
dbname: m_ticketbundle
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
XXXMainBundle: ~
ticket:
connection: ticket
mappings:
XXXTicketBundle: ~
XXXMainBundle: ~
Now I have an Entity (Mappingpooluser) which needs one Entity of the MainBundle (User) and one Entity (Pool) of the TicketBundle (and some unimportant stuff):
/**
* XXX\TicketBundle\Entity\Mappingpooluser
*
* #ORM\Table(name="MappingPoolUser")
* #ORM\Entity(repositoryClass="XXX\TicketBundle\Repository\MappingPoolUserRepository")
*/
class Mappingpooluser
{
/**
* #var integer $poolid
*
* #ORM\OneToOne(targetEntity="Pool")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
private $pool;
/**
* #var integer $userid
*
* #ORM\OneToOne(targetEntity="XXX\MainBundle\Entity\User")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
private $user;
[getter/setter and this stuff]
Till this point everything works fine :)
I can fetch the Mappingpooluser Entity in the Controller via
$em = $this->getDoctrine()->getEntityManager("ticket");
$entities = $em->getRepository('XXXTicketBundle:Mappingpooluser')->findAll();
If I call $entities[0]->getPool()->getId() I will get the correct ID of the Pool (as mentioned: Pool is in the same Bundle like Mappingpooluser), but if I call $entities[0]->getUser()->getId() I will get an Error:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'm_ticketbundle.User' doesn't exist
Thats quit correct, because the User table is in the other DB (m_symfony).
Long Story, short Question: How do I get Symfony2 to use the ticket entity-manger for the Pool and the default entity-manger for the User??
The actual problem with your scenario is that you need to map the same entity (pool) to two different entity managers (default and ticket). What I would recommend in this scenario is creating a hierarchy in your entity layer for this entity:
base pool, contains all common fields
pool "default", contains specialized fields for default em
pool "ticket", contains specialized fields for ticket em
Then you can map them in different entity managers. If you use two different namespaces for default and ticket entities you can specify the folder from which mappings should be loaded in your em configuration like this:
mappings:
MyBundle:
type: annotation
dir: Path/To/Entities
The path is relative to the Bundles's root directory. So for example you could have Entity/Default and Entity/Ticket namespaces and map them independently while having common fields in an unmapped class in Entity namespace.
class Account
{
...
/*
* #OneToMany(targetEntity="Address", mappedBy="account");
*/
private $addresses;
...
}
class Address
{
...
/**
* #ManyToOne(targetEntity="Account", inversedBy="addresses")
* #JoinColumn(name="account_id", referencedColumnName="id")
*/
private $account;
...
}
When i run console command to validate schema:
[Mapping] FAIL - The entity-class 'Entity\Address' mapping is invalid:
* The association Entity\Address#account refers to the inverse side
field Entity\Account#addresses which does not exist
Why?
I think the Doctrine annotation reader uses php's Reflection API, specifically getDocComment to read the annotations. That means your doc block comments must begin with /** otherwise they will be ignored. Your $addresses property annotation begins with /* so it won't get picked up.