Binding Entity Manager To Entity in Symfony2? - doctrine

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.

Related

Using doctrine, apiplatform and openapi generator leads to duplcate id field in the frontend side, how to solve this?

I m using Symfony 4 with Doctrine and API Platform to build the backend of my application.
I generated entities using php bin/console make:entity. Each class has an id field like this one
/**
* #ApiResource()
* #ORM\Entity(repositoryClass=CategoryRepository::class)
*/
class Category
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
...
I generated the api doc file php bin/console api:openapi:export --output=swagger.json
Then I use OpenAPI Generator to generate the client part for the frontend npx openapi-generator-cli generate -i swagger.json -g typescript-angular --enable-post-process-file -o build
The resulting model file categoryJsonld.ts containes 2 id fields
export interface CategoryJsonld {
readonly context?: string | any | null;
readonly id?: string;
readonly type?: string;
readonly id?: number;
...
The field idis duplicated and the frontend doesn't compile.
I guess the first id stands for #id defined in jsons+ld format.
Any idea to solve/prevent the duplication of id ?
Thanks.
You can set your Content-Type to application/json to exclude the extra json-ld fields. However this will result in problems when you have collections and need to use pagination, because you would need some meta data that is not being returned with plain json. I would recommend changing your own id field to something else like uuid as the id, type and context names are reserved for json-ld format. Try just changing the getters and/or setters name first OpenApi should generate it correctly then. e.x. In your entity change getId() to getUuid() or you could just add an annotation to change the serialized name for example: #SerializedName("uuid") above the getId()

Doctrine many to many with linktable code

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

How to create a clone of an entity collection without preserving relationship in doctrine?

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.

How to count many-to-many relations in Symfony2 using DQL

I would like to count the number of tags given a specific article. I've got two entities (Article, Tag) which are related by a many-to-many association:
//Bundle/Entity/Article.php
/**
* #ORM\ManyToMany(targetEntity="Tag")
*/
private $tags;
Now I've got n articles with m tags and I would like to know how often a specific tag has been used.
I'm relatively new to both Symfony2 and Doctrine. The problem is that I don't know where to fit such a query (I guess it should reside in the ArticleRepository but on the other hand it would make sense to have it in the TagRepository) and how to JOIN the correct tables (in this case Article, article_tag, Tag).
The simplest way that I can think of is to just set up a bidirectional relationship between Article and Tag:
class Article
{
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="articles")
*/
private $tags;
}
class Tag
{
/**
* #ORM\ManyToMany(targetEntity="Article", mappedBy="tags")
*/
private $articles;
}
Then you can (assuming you've set up standard getters and setters) use $tag->getArticles()->count();, where $tag is a managed Tag entity, to get the number of articles attached to that tag. This works because when populating ToMany relationship properties, Doctrine uses an instance of Doctrine\Common\Collections\ArrayCollection. Check out the source here.
Also, if you go this route, make sure you read the documentation on picking an owning and inverse side here.
You can count the number of tags by using a specific article..lets say article id=5:
$query=$em->createQuery("SELECT count(t.id) FROM Tag t WHERE ?1 MEMBER OF t.articles");
$query->setParameter(1,5 );
$result = $query->getSingleScalarResult();
This gives me the number of tags in Article Entity only for article id = 5.

Doctrine 2.0 One To Many Schema Validation Problem

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.

Resources