Symfony2 ORM FK Composite - symfony

I'm new with Symfony2 and Doctrine 2 ORM, then i got a problem with composite FK association.
While the association OneToMany-ManyToOne by a single auto-increment ID works good, i got many troubles with the same kind of association but through a composite PK.
What i would like to do is an entity-structure that mirrors the below shema.
http://imageshack.us/photo/my-images/9/z07m.jpg (Sorry but I cannot insert images into the post)
where
prd_product_data.product_id
prd_product_data.language_id
make up together the PK of 'prd_product_data'
and
prd_product_image.pdoduct_id
prd_product_image.language_id
make up the FK linked to the table 'prd_product_data'.
These below
prd_product_image.pdoduct_id
prd_product_image.language_id
prd_product_image.file_image_id
make up, all together, the PK of 'prd_product_image'.
I read that Doctrine 2.1 and up, supports natively the composed PK - FK association but there are very few examples. Moreover, surfing on the web, i found just scattered fragments of similar situations but nothing of useful.
The main issue now, is that i'm unable to create one unique object for the two ids, like such an unique id.
For example...
Snapshot for the Entity ProductData where "product and language should make up the same object:
/**
*
* #ORM\OneToMany(targetEntity="ProductImage", mappedBy="product")
*/
protected $products_Pimgs;
/**
*
* #ORM\OneToMany(targetEntity="ProductImage", mappedBy="language")
*/
protected $products_Limgs;
Snapshot for the Entity ProductImage where *product_id* and *language_id* made up the FK for the entity ProductData:
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="ProductData", inversedBy="products_Pimgs")
* #ORM\JoinColumn(name="product_id", referencedColumnName="product_id")
*/
protected $product;
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="ProductData", inversedBy="products_Limgs")
* #ORM\JoinColumn(name="language_id", referencedColumnName="language_id")
*/
protected $language;
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="FileImage", inversedBy="files_images")
* #ORM\JoinColumn(name="file_image_id", referencedColumnName="id")
*/
protected $file_image;
From these snapshots it's evident that those keys are working separately.
In the end, this is my doctrine version, taken from the composer:
"doctrine/orm": ">=2.2.3,<2.4-dev",
How can i create an unique reference with two objects with ORM?

Read the documentation chapter Composite Primary Keys.
/**
* #ORM\Id
* #ORM\OneToMany(targetEntity="ProductImage", mappedBy="product")
*/
protected $products_Pimgs;
/**
* #ORM\Id
* #ORM\OneToMany(targetEntity="ProductImage", mappedBy="language")
*/
protected $products_Limgs;

Related

How to limit OneToMany/ManyToOne associations depth/loop in Doctrine2?

I've 3 doctrine entities. One is User, second is Product and third is ProductUsers.
So, User have OneToMany association with ProductUsers and the same Product have OneToMany association with ProductUsers. ProductUsers has ManyToOne association with both User and Product. Like so:
class Product
{
/**
* #var ProductUsers
*
* #ORM\OneToMany(targetEntity="ProductUsers", mappedBy="product")
*/
private $productUsers;
}
class ProductUsers
{
/**
* #var Product
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Product", inversedBy="productUsers")
* #ORM\JoinColumn(name="product_ID", referencedColumnName="ID")
*/
private $product;
/**
* #var User
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="productUsers")
* #ORM\JoinColumn(name="user_ID", referencedColumnName="ID")
*/
private $user;
// extra fields ...
}
class User
{
/**
* #var ProductUsers
*
* #ORM\OneToMany(targetEntity="ProductUsers", mappedBy="user")
*/
private $productUsers;
}
A user can use multiple products and a product can have multiple users. ProductUsers has some extra info about the relation other than just the relation.
The problem is when I fetch one User object it comes with associated ProductUsers and it's associated Product. Not only that but the Product also comes with all it's associated ProductUsers and it's respective User objects which is quite an overhead.
This question closely relates to my problem.
I'm looking to limit that at doctrine level just like what JMSSerializerBundle MaxDepth does. Is there a way to limit such overhead in doctrine?
I faced this issue long time back. I tried lazy loading. Which didn't work as expected and that was not a proper solution to my issue. So I did some R&D and came up with a solution that I don't need a bidirectional relationship from Product to ProductUsers.
I can manage same relationship with unidirectional handling only from ProductUsers side. You will need One-To-Many Association when you need a cascade-persist or similar feature. I wrote a small blog regarding this as well.
So, for your solution, just have Many-To-One association from ProductUsers with both Product and User entity. You will not need any change in your database association.
And when you need Products associated for a single user, you can always save a Querybuilder in Repository to use when you need associated data.
It will save a lot of performance. Hope it helps!

Symfony2 + Doctrine2 onDelete="restrict"

I want to restrict a delete action on my object, but it's not working.
My code from my first entity:
/**
* #ORM\ManyToMany(targetEntity="Season", inversedBy="clubs")
* #ORM\JoinTable(
* name="clubs_to_seasons",
* joinColumns={
* #ORM\JoinColumn(name="club_id", referencedColumnName="id", onDelete="cascade")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="season_id", referencedColumnName="id", onDelete="restrict")
* }
* )
**/
private $seasons;
And second:
/**
* #ORM\ManyToOne(targetEntity="League")
* #Assert\NotBlank(message="validation.custom.not_blank")
*/
private $league;
On database side everything is fine - I cannot delete a Season object, because it has a reference to Club object, but when I use a remove fuction from Entity Manager, Season object is deleted.
I know that on ORM side is a cascade={..} but I need a restrict, not cascade.
BUMP:
If I'm using one-to-many, many-to-one or one-to-one, everything is ok - when I try to delete an object (and I'm using onDelete="restrict" on the database side), then exceptions appears with message, that I cannot delete because I have other object connected with. But when I have many-to-many (as in my example), and I have a season associated with any club (and I have onDelete="restrict"), still I can delete a season using entity manager. But when I'm deleting season directly on the db side, message appears, and I cannot delete this.
What is wrong with many-to-many?
EDIT:
OK, now it's working.
My solution for the future:
I removed many-to-many relation from both entities. I have made a third entity - clubToSeason. Previously this entity was autogenerated, now it's not. And inside file of this third entity I have two many-to-one relations:
class ClubToSeason {
/**
* #ORM\ManyToOne(targetEntity="season", inversedBy="clubs")
**/
private $season;
/**
* #ORM\ManyToOne(targetEntity="club", inversedBy="seasons")
**/
private $club;
...
}

Doctrine: authorize NULL in a foreign composite key

I have the following entity:
/**
* SeriesAuthorRole
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Blog\Bundle\CoreBundle\Entity\SeriesAuthorRoleRepository")
*/
class SeriesAuthorRole extends AuthorRoleAbstract
{
/**
* #var Series
*
* #ORM\ManyToOne(targetEntity="Blog\Bundle\CoreBundle\Entity\Series", inversedBy="authors")
* #ORM\JoinColumn(name="series", referencedColumnName="id", nullable=false)
* #ORM\Id
*/
private $series;
/**
* #var Author
*
* #ORM\ManyToOne(targetEntity="Blog\Bundle\CoreBundle\Entity\Author")
* #ORM\JoinColumn(name="author", referencedColumnName="id", nullable=false)
* #ORM\Id
*/
protected $author;
/**
* #var Role
*
* #todo Must be nullable
*
* #ORM\ManyToOne(targetEntity="Blog\Bundle\CoreBundle\Entity\Role")
* #ORM\JoinColumn(name="role", referencedColumnName="id", nullable=true)
* #ORM\Id
*/
protected $role;
// ... Getters, setters
}
The idea behind it is quite simple: We have author, role and series entities. A series can have several authors with various roles. A same author can fulfill multiple roles in a series.
Sometimes, we don't know exactly what was the role of the author. In this case, the NULL value will be used for the role, the NULL value standing for "I don't know".
I was taught not to use NULL in foreign composite keys unless it has meaning. Well, it has meaning here, and I know that this could be implemented without Doctrine. However, for now, Symfony 2 throws that error:
Entity of type Blog\Bundle\CoreBundle\Entity\BandAuthorRole is missing an assigned ID for field 'role'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called. If you want automatically generated identifiers instead you need to adjust the metadata mapping accordingly.
500 Internal Server Error - ORMException
So how can I authorize NULL values in foreign composite keys ? Is it possible at all with Doctrine ?
Your #JoinColumn annotation is correct with referencing to http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annref-joincolumn
However,
Every entity with a composite key cannot use an id generator other
than “ASSIGNED”. That means the ID fields have to have their values
set before you call EntityManager#persist($entity).
http://docs.doctrine-project.org/en/2.0.x/tutorials/composite-primary-keys.html#general-considerations

Doctrine2 - Custom persister

I'm busy working on a project and I've ran into a slight issue. I was just wondering whether there is any way to customize the persist action of a specific entity? In my case specifically I want to, on update, remove some fields from other tables before re-saving the entity.
Let's, for arguments sake, say my entity that I want a custom persist action on looks like this:
/**
* #ORM\Table()
* #ORM\Entity
*/
class A {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="B", mappedBy="bar")
* #ORM\Column(name="foo")
*/
private $foo;
//Some additional getters and setters here
}
/**
* #ORM\Table()
* #ORM\Entity
*/
class B {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="A", inversedBy="foo")
* #ORM\JoinColumn(name="bar", referencedColumn="id")
*/
private $bar;
//Getters and setters here.
}
Now I know with a simple example like this doctrine will automatically just update $bar in class B if you update that, but let's just say I'd like to first remove $bar completely (not just update it) and re-save it with the new value? Is this possible?
This could also just be done manually before persisting in my update action, but that feels a bit hacky?
The actual code I want to do this with is much too long to post here, so I'm just opting for a simple proof-of-concept here.
Thanks for any assist!
EDIT
Technically the other entities will be related to the current one, via a OneToMany/ManyToMany/ManyToOne relationship, as in the example above. So isn't there something like preHydrate that I can use to clear current data before hydrating the entity with the submitted data?
You should use event-listeners or -subscribers instead of LifecycleCallbacks (i.e. #PrePersist ) as recommended in Cyrus's answer.
Using LifecycleCallbacks you don't have access to unrelated entities while you can change/remove these with a listener/subscriber where you have direct access to the entity-manager with dependency injection.
Please see the documentation chapter How to Register Event Listeners and Subscribers.
You can use prepersist:
/**
* #ORM\PrePersist
*/
There are preupdate, preremove, etc.
Here you have all the info to do that: http://symfony.com/doc/current/book/doctrine.html

Symfony: How do I annotate entity properties that are objects to get Doctrine to store a foreign key?

I'm still getting to grips with Symfony and Doctine and I appreciate this might sound overly simple.
I have at present two basic entities: WebSite (having id and canonicalUrl properties) and Job which has, as one property, a WebSite.
A Job has one WebSite; a WebSite can be referenced by many Jobs. Both are under the same namespace.
Relevant here is the Job entity:
/**
*
* #ORM\Entity
*/
class Job
{
/**
*
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
*
* #var WebSite
*/
protected $website;
}
In database terms, a persisted Job should be storing the id of the relevant WebSite.
Without any changes to the above, calling php app/console doctrine:migrations:diff generates a new migration for a table named Job with a single id field.
How do I annotate Job::website such that Doctrine knows to create an integer field and to get the value as the id of the Website object?
You must explicitly define the relationship. The shortest would be
/**
* #ORM\Entity
*/
class Job
{
/**
* #var WebSite
*
* #ORM\ManyToOne(targetEntity="Website")
*/
protected $website;
}
However, should you find yourself wanting to tweak the relationship to better suit your needs, have a look at the annotation reference (ManyToOne and JoinColumn for this particular case). There's also quite a comprehensive article about association mapping, which you might find interesting.

Resources