symfony many to many orm controller - symfony

I have a many-to-many relationship this is my table
site
------
id
name
site_landingpage
---------------
id
site_id
landingpage_id
landingpage
----------
id
name
Page.php
-----------------------
/**
* #ORM\ManyToMany(targetEntity="Page\DefaultBundle\Entity\Site", mappedBy="landingpages")
**/
private $sites;
Site.php
/**
* #ORM\ManyToMany(targetEntity="Site\PageBundle\Entity\Page", inversedBy="sites")
* #ORM\JoinTable(name="site_landingpage")
**/
private $landingpage;
If I add a landingpage it should get the current site and populate site_landingpage table how am I able to do this in the controller part where you add a landingpage given that my site_id is $site_id

I'm not sure if your entities have their getters & setters created, if not generate them by running:
app/console doctrine:generate:entities PageBundle:Site
app/console doctrine:generate:entities PageBundle:Page
Then you can simply do something like:
$landingpage = new Page();
$site->addLandingpage($landingpage);
This blog post by Kontroversial Keith provides a detailed example of populating many-to-many relationship entities from user input.
As a side note, join tables (ie site_landingpage) don't need an extra id column (although shouldn't break anything), simply have site_id & landingpage_id as a joint primarykey.

Related

doctrine nested entities cascade persist : how to reuse existing entities

If entity A contains multiple entity B and has cascade:persist, how to reuse existing entities B when persisting ?
B entity has one primary key, an integer, and the id of the A parent. The only data it contains is the primary key.
Example:
A has 2 B entities, identified by their id, 14 and 23.
A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}]
Now if I modify this managed entity, to add a B entity to A, with id = 56.
A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}, {id=56}]
Relationships
Entity A
/**
* #var B[]|ArrayCollection
*
* #ORM\OneToMany(targetEntity="B", mappedBy="A", cascade={"persist", "remove"}, orphanRemoval=true)
* #Assert\Valid
*/
private $Bs;
Entity B
/**
* #var A
*
* #ORM\ManyToOne(targetEntity="A", inversedBy="Bs")
* #ORM\JoinColumn(name="A_id", referencedColumnName="A_id")
* #Assert\NotNull()
*/
private $A;
If I try to persist I get Integrity constraint violation, because Doctrine tries to persist the existing entities, that have id 14 and 23.
I understand this is expected behaviour, but how can I make it persist new entities, and reuse existing ones ?
More details:
If I get an existing entity A with $em->find($id) and directly use persist and flush, I will get UniqueConstraintException because it tries to persist the already persisted B entities.
Example code:
/** #var A $existingEntityA */
$existingEntityA = $this->getEntity($id);
$this->serializerFactory->getComplexEntityDeserializer()->deserialize(json_encode($editedEntityADataJson), A::class, 'json', ['object_to_populate' => $existingEntityA]);
$this->entityValidator->validateEntity($existingEntityA);
$this->_em->flush();
Example error : Integrity constraint violation: 1062 Duplicate entry '777111' for key 'PRIMARY'
If I understand your example properly - you're doing something like this:
$b = new B();
$b->setId(56);
$a->getB()->add($b);
and you having a row with primary key 56 into database table that is represented by B?
If my assumption is correct - it is wrong way to go. Reason is that Doctrine internally stores so called "identity map" that keeps track of all entities that either being fetched from database or persisted by calling EntityManager::persist(). Every entity that is scheduled for commit but not available into identity map is considered as "new" and scheduled for insertion. If row with same primary key is already available in database - you're receiving UniqueConstraintException.
Doctrine doesn't handle a case "let me look if there is an entity with such primary key in database" by itself because it will hurt performance significantly and is not needed in most cases. Each such test will result into database query, imagine if you will have thousands of such entities. Since Doctrine doesn't know business logic of your application - it will spend even more resources with attempts to guess optimal strategy so this is intentionally left out of scope.
Correct way for you would be to get your entity by itself before adding to collection:
$newB = $em->find(B::class, 56);
if ($newB) {
$a->getB()->add($newB);
}
In this case new entity will internally have "managed" status and will be correctly handled by Doctrine at a time of commit.

Symfony: Ordered one-to-many relationship

I have two separate entities that I want to link by a one-to-many relationship. But I want this relationship to be ordered, meaning every time I call on the first entity, the elements of the second entity come in a pre-ordered way. I cannot use 'order by' calls because this order has nothing to do with the fields of the second entity. I thought about having one field of the first entity be an array of entities, but I'm not sure how to accomplish that either..
EDIT
So far I have something like this:
/**
* #ORM\OneToMany(targetEntity="FusionDesign\BlogBundle\Entity\Element", mappedBy="page")
*/
private $elements;
and
/**
* #ORM\ManyToOne(targetEntity="FusionDesign\BlogBundle\Entity\Page", inversedBy="elements")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $page;
I'm aware that I can put "ORDER BY whatever ASC" somewhere in there but that orders according to a column in Element, and that's not what I need, because Element entities and Page entities would never be persisted at the same time, nor by the same process. What I want to do is constructing a basic CMS where the user could generate new pages. First choose the kind of elements a page could potentially have (like header image, banner, title, and so on) and persist Element entities with fields describing the html, routing and controller content according to those choices. Then, when a new page is created, give the user the choice to order those potential elements at will, and bind Element entities following an order that reflects the layout desired.
I thought about having something like this
/**
* #var array
*
* #ORM\Column(name="structure", type="array")
*/
private $structure;
Where the array stores Element entities but I have no idea how to do that.
You just need to define the orderBy attribute in doctrine's mapping configuration for the relation.
YAML Mapping Example:
'Example\Entity\Article':
# [..]
oneToMany:
images:
targetEntity: 'Example\Entity\Article\Image\ArticleImage'
mappedBy: 'article'
orderBy: # <--- here
position: 'ASC'

Definitions of one-to-many, many-to-many etc

I'm building a database application using Doctrine2. I'm getting somewhat confused by the foreign key mappings. I'm wondering, have I got these examples correct:
One-To-One: An X has exactly one Y.
One-To-Many: An X can have multiple Ys.
Many-To-One: Multiple Xs can have the same Y.
Many-To-Many: Multiple Xs can have multiple Ys.
This is the specific situation that got me confused:
A User has exactly one HomeTown. Many users can belong to the same home town, so the link for the User is:
/**
* #ORM\ManyToOne(targetEntity="HomeTown", inversedBy="localUsers")
*/
$homeTown;
And, the corresponding HomeTown link is:
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="homeTown")
*/
$localUsers;
OR is it:
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="homeTown")
*/
$localUsers;
Some clarification would be much appreciated!
I've been looking at http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
When you have OneToMany association, the inverted has to be ManyToOne. Saying that, your second option is correct.
TIP: Using Doctrine CLI command orm:validate-schema might also help to identify this issue.
The full path in Symfony app: php app/console doctrine:schema:validate
If you want one city to have many users the mapping should be as it follows
Entity City
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="homeTown")
*/
private $users;
...
public function __construct()
{
$this->users = new ArrayCollection();
}
...
Entity User
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="users")
* #ORm\JoinColumn(name="home_town", referencedColumnName="id")
*/
private $homeTown;
This mapping shows us that the City which is owning side has On-To-Many relation with User(target Entity). Respectively the User which is inversed side has to be annotated with ManyToOne relation because many users have same city. Of course, here the target entity should be City. Its important to specify which column is pointing the foreignkey with the referencedColumnName attribute in JoinColumn annotation. It shows what column of other table points this key. In this example in table User there is column named "home_town" which is a foreign key pointing to column id of table City
In ManyToOne relation you shod use JoinColumn annotation
This mapping is also Bidirectional.
You can make id Unidirectional as in the User Entity do not use "inversedBy=" attribute and remove OneToMany annotation with $user property from the City entity. It is something like when you have to know the city of a particular user, but you do not need to know all users for a specific city

How do I setup up Doctrine's Class Inheritance in Symfony?

My issue is, I'm having trouble grasping DiscriminatorColumn and DiscriminatorMap in Doctrine's Class Inheritance.
I have a products entity that is considered the parent class / table.
There are several child entities that inherit the product entity. (models, parts, and options)
I feel like I should be able to use the primary key to link both tables... But how do I do that with DiscriminatorColumn?
Here is the general idea of what I want to happen...
Fetch all model objects from database while inheriting product parent entity
SELECT object
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id
Or... Fetch all part objects from database while inheriting product parent entity
SELECT object
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id
Ideally I want this done using Doctrine instead of some custom SQL.
Do I need to setup a "type" column for the parent table so each row defines whether it's a part, model, or option?
Doctrine inheritance docs
Okay, I'll try to explain this as simple as possible.
Let's start with DiscriminatorColumn
Discriminator column is basically, as it says, a column in your database. Its used to store, a key, if you like which helps to identify what kind of object you're currently querying, based on your DiscriminatorMap configuration.
DiscriminatorMap is the way you map each of those keys to an entity. You said you have the following
Product [parent]
Model [child of parent]
Part [child of parent]
Option [child of parent]
Then, your discriminator map should look something like this, for example:
#DiscriminatorMap({
"model" = "AppBundle\Entity\Model",
"Part" = "AppBundle\Entity\Part",
"Option" = "AppBundle\Entity\Option"
})
Always pay attention to your last definition in your mapping. The last line must end without a comma!
As of InheritanceType I would suggest you to use #InheritanceType("JOINED") because this will let you have single table for each of your child classes.
Every child class must extend your Product entity class, which is obviously the parent. Each child class must not define $id property, because of the inheritance mapping.
Then querying for records by specific type comes with the following query:
"SELECT product FROM AppBundle\Entity\Product product WHERE product INSTANCE OF AppBundle\Entity\Part"
The query will search only for records mapped to this entity only.
If you have any questions, don't hesitate to ask.
Edit as of new comment
-----------------------
A little bit more explanation. You do not need to create any extra property/column in your entity mappings. The moment you add this annotation #DiscriminatorColumn(name="discr", type="string") doctrine will create that column automatically for you. The column from this example would be named discr with type of VARCHAR.
I still don't understand what is used to join the tables. How does doctrine know to link the ids between the product and model
About this part. If you use #InheritanceType("JOINED") this would mean that your GeneratedValue ID would be set in your main entity - Product. Then each of the child entities that extend Product would automatically get the same ID, which is why you don't need to specify $id property in your child entities.
Lastly, how can you check which entity type you're currently viewing for example. Consider the following scenario, each of your child entities extends Product and we will perform a dummy search for a record:
$product = $entityManager->find('AppBundle:Product', 1); // example
Now, if you actually go and do a var_dump($product) you will notice something interesting. The object would be an instance of either Model,Part or Option because each of these entities are defined in your discriminator map and Doctrine automatically maps your records based on that.
Later, this can come handy in situations like this:
if( $product instanceof \AppBundle\Entity\Part ) {
// do something only if that record belongs to part.
}
If you want to use DiscriminatorMap for Doctrine, so you should use Doctrine, but not SQL.
Basic setup is:
/**
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ProductRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="productType", type="string")
* #ORM\DiscriminatorMap({
* "Product" = "Product",
* "Model" = "Model",
* "Part" = "Part",
* "Option" = "Option",
* })
*/
class Product
{
...
}
MyApp\ProductBundle\Entity\Model
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ModelRepository")
*/
class Model extends Product
{
}
MyApp\ProductBundle\Entity\Part
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\PartRepository")
*/
class Part extends Product
{
}
MyApp\ProductBundle\Entity\Option
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\OptionRepository")
*/
class Option extends Product
{
}
Then if you need to get all products at controller
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository("MyAppProductBundle:Product");
$products = $repo->findAll();
Then if you need select all models, just setup proper repository
$repo = $em->getRepository("MyAppProductBundle:Model");
$models = $repo->findAll();

Doctrine2 reinitializes a related entity (one to one) when saving a main entity

The context is a Symfony2 project (2.0.23) using Doctrine2.
I have a Candidate entity with one to one relation Website. When creating a new candidate I set the Website entity like this:
Candidate.php:
<?php
use MyProject\Bundle\CoreBundle\Entity\Website;
public function initialize(Website $website)
{
$this->setWebsite($website);
The one to one relation is declared like this:
<?php
/**
* #var Website $website
*
* #ORM\OneToOne(targetEntity="Website")
* #ORM\JoinColumn(name="Website", referencedColumnName="Code")
*/
private $website;
Everything works fine locally. But on out testing and production servers, when creating a new candidate with a given Website, sometimes the associated entity is updated with default values just when persisting and flushing the main Candidate entity, here is the MySQL log:
/*!*/;
# at 11400207
#130628 9:26:32 server id 1 end_log_pos 11400399 Query thread_id=53611133 exec_time=0 error_code=0
SET TIMESTAMP=1372404392/*!*/;
UPDATE website SET Name = NULL, bEnabled = NULL ... WHERE Id = 2 AND Code = 2000
I persist with the method of the FOSUserBundle:
<?php
/**
* Updates a user.
*
* #param UserInterface $user
* #param Boolean $andFlush Whether to flush the changes (default true)
*/
public function updateUser(UserInterface $user, $andFlush = true)
{
$this->updateCanonicalFields($user);
$this->updatePassword($user);
$this->em->persist($user);
if ($andFlush) {
$this->em->flush();
}
}
I don't understand why. What is really weird is that only happens sometimes and it is quiet unpredictable.
Any suggestion or hint would be welcome... Thanks.
PS: The doctrine metadata cache was deactivated.
Edit1: Added persist, note that this is called through a FormHandler service.
Well it seems it was related to the main entity that had 2 #ORM\Id annotations. As it is an old database, it is not "Doctrine" friendly. There is a PK an auto-increment field, but the joins are made on a second column. So I removed the #ORM\Id annotation for the PK field.

Resources