How to many to many with repeated relationships (third key in table) - symfony

It's possible in Doctrine2 to manage Many to Many relations with a third key to be able to add more than one identic relationship?
I have one "Users" table and one other "Plans" table and i did the normal many to many relationship that produces user_plan table with the two primary keys (user_id and plan_id) but I need in my application to be able to add the same plan to user more than one time. For example: user_plan(generated_id, user_id, plan_id)
My current user yml definition:
Entity\FosUser:
type: entity
table: fos_user
fields:
id:
id: true
type: integer
unsigned: false
nullable: false
generator:
strategy: IDENTITY
manyToMany:
plans:
targetEntity: Plan
inversedBy: users
joinTable:
name: user_plan
joinColumns:
plan_id:
referencedColumnName: id
inverseJoinColumns:
user_id:
referencedColumnName: id
lifecycleCallbacks:
prePersist: [ setUserValue ]
preUpdate: []
My current plan yml definition:
Entity\Plan:
type: entity
table: plan
fields:
id:
id: true
type: integer
unsigned: false
nullable: false
generator:
strategy: IDENTITY
planName:
type: string
length: 50
fixed: false
nullable: false
column: plan_name
manyToMany:
users:
targetEntity: FosUser
mappedBy: plans
LifecycleCallbacks:
prePersist: [ setCreatedAtValue ]
preUpdate: [ setUpdatedAtValue ]
Someone knows if it's possible to do that with symfony2?

I don't know about third key, but i see another solution. You can add another model PlantBed. User has_many PlantBeds (PlantBed has_one User). PlantBed has_one Plant (Plant has_many PlantBeds) and quantityOfPlantsInBed.

Related

Unique constraints on foreign key in Doctrine

Is there any way to add constraints by foreign key in Doctrine? This is my config for an entity in Symfony 3.3.
doctrine:scheme:validation command gives me an answer like "There is no column with name 'product' on table 'tariff'"
Rg\ApiBundle\Entity\Tariff:
fields:
price:
type: float
column: price
manyToOne:
product:
targetEntity: Product
inversedBy: tariffs
timeunit:
targetEntity: Timeunit
inversedBy: tariffs
uniqueConstraints:
no_double_tariff_idx:
columns:
- product
- timeunit
You need to reference the column's names (not the name of the relation used by doctrine). By default doctrine will suffix the relation's name with _id but you can configure the exact name of the join-column as shown in the following example configuration:
'Your\Entity\ProductVariant':
manyToOne:
image:
targetEntity: 'Your\Entity\Product\Image'
joinColumn:
name: '`image_id`'
referencedColumnname: 'id'
nullable: false
options:
unique: false
color:
targetEntity: 'Your\Entity\Product\Color'
joinColumn:
name: '`color_id`'
referencedColumnname: 'id'
# [..]
uniqueConstraints:
only_one_image_of_same_product_color_idx:
columns:
- 'image_id'
- 'color_id'

Doctrine Relationships - referenced Column name as identifier for entity

So I have these 2 Bundles:
UserBundle
BlogBundle
and these 3 Entities:
UserBundle:User
BlogBundle:User
BlogBundle:Article
BlogBundle:User extends UserBundle:User using a bidirectional one to one relationship. The join column name is user_id and it is an association key:
BlogBundle\Entity\User:
type: entity
table: blog_users
id:
user:
associationKey: true
oneToOne:
user:
targetEntity: UserBundle\Entity\User
inversedBy: blog_user
joinColumn:
name: user_id
referencedColumnName: id
Now I want to create a bidirectional one to Many relationship between BlogBundle:User and BlogBundle:Article.
Currently I'm trying this:
BlogBundle:User
oneToMany:
articles:
targetEntity: Article
mappedBy: author
BlogBundle:Article
manyToOne:
author:
targetEntity: User
inversedBy: Article
joinColumn:
name: author
referencedColumnName: user_id
My Problem is, it works, I can access data from UserBundle:User through an Article object, but in the profiler it shows them as not mapped correctly. My guess is it would be possible to do what I'm trying, but I'm just doing something wrong.
What am I missing?
I think the problem is with the inversedBy, where you have to write the name of the field, not the name of the entity.
Here que documentation:
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-many-bidirectional
Your code will be:
BlogBundle:User
oneToMany:
articles:
targetEntity: Article
mappedBy: author
BlogBundle:Article
manyToOne:
author:
targetEntity: User
inversedBy: articles
joinColumn:
name: author
referencedColumnName: user_id

Doctrine2 + symfony2 Keep history table on delete with FK

I have 2 tables. One let's say organizations and a second one is organization_history. Then I have a table actions_history with concrete action,... But it isn't so important in this case.
In Organization history I keep revision and organizationId. Everything works well until DELETE a table organization. My idea is to keep every action in history table. On INSERT, UPDATE and DELETE action. But problem is when I try to delete organization table. I got this output:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`database`.`base_organizationsHistory`, CONSTRAINT `FK_EAF457A532C8A3DE` FOREIGN KEY (`organization_id`) REFERENCES `base_organizations` (`id`))
1) Is it possible to just delete organization table and keep the history table and ignore foreign key. Or does exist a different and even clear solution?
2) I am thinking also about lifecycle callbacks. In INSERT and UPDATE action I connected tables easily, but when I can create history table on delete action it's not possible to use postRemove callback, because then I don't have the old data for copy to the history. And if I use preRemove callback it's not so clear. Does exist some better idea to do it?
Organization.orm.yml:
BaseBundle\Entity\Organization:
type: entity
table: base_organizations
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
name:
type: string
length: 128
type:
type: string
length: 64
oneToMany:
organizationHistory:
targetEntity: OrganizationHistory
mappedBy: organization
nullable: true
lifecycleCallbacks:
postPersist: [saveInsertHistory]
postUpdate: [saveUpdateHistory]
preRemove: [saveDeleteHistory]
and OrganizationHistory.orm.yml
BaseBundle\Entity\OrganizationHistory:
type: entity
table: base_organizationsHistory
uniqueConstraints:
organization_history_idx:
columns: [ organizationId, revision ]
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
name:
type: string
length: 128
type:
type: string
length: 64
revision:
type: integer
nullable: false
organizationId:
type: integer
nullable: false
createdAt:
type: datetime
nullable: false
updatedAt:
type: datetime
nullable: false
manyToOne:
organization:
targetEntity: Organization
inversedBy: organizationHistory
nullable: true
lifecycleCallbacks:
preUpdate: [ setUpdateTimestamp ]
prePersist: [ setCreationTimestamp, setUpdateTimestamp ]
you cannot delete data from one table and also want to store data related to organization in other table. Thats why relations are made for... But, you are using symfony, do "soft delete" which will just mark entity as deleted, but data still will be in your database.
maybe you can start here https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/softdeleteable.md

Doctrine2 YML Mapping on Symfony2

I am starting with Symfony2, and so do I with Doctrine2.
I want to make an entity bidirectional, but I don't know what I am doing wrong. Here is my two .orm.yml files :
Categorie.orm.yml :
MY\SUPERBundle\Entity\Categorie:
type: entity
fields:
id:
id: true
type: integer
generator:
strategy: AUTO
nomCategorie:
type: text
nullable: true
column: NomCategorie
oneToMany:
SousCategories:
targetEntity: MY\SUPERBundle\Entity\SousCategorie
mappedBy: Categorie
SousCategorie.orm.yml :
MY\SUPERBundle\Entity\SousCategorie:
type: entity
fields:
id:
id: true
type: integer
generator:
strategy: AUTO
nomSousCategorie:
type: text
nullable: true
column: NomSousCategorie
manyToOne:
Categorie:
targetEntity: MY\SUPERBundle\Entity\Categorie
inversedBy: SousCategories
joinColumns:
categorie_id:
referencedColumnName: id
nullable: false
When I want to run the command :
doctrine:schema:update --dump-sql
I am getting this error :
[ReflectionException]
Property MY\SUPERBundle\Entity\Categorie::$SousCategories does not exist
If you guys have any hint on what I am doing wrong, I would be very grateful.
Thanks !
Are you sure you really have a property named "SousCategories" under your "Categorie" class?
Would be nice if you could add your entity class in your first post.
BTW, if it's a property, it shouldn't begin with an uppercase letter.

Symfony 2 - Updating a table based on newly inserted record in another table

I'm trying to create a small forum application using Symfony 2 and Doctrine 2. My ForumTopic entity has a last_post field (oneToOne mapping). Now when I persist my new post with
$em->persist($post);
I want to update my ForumTopic entity so its last_post field would reference this new post. I have just realised that it cannot be done with a Doctrine postPersist Listener, so I decided to use a small hack, and tried:
$em->persist($post);
$em->flush();
$topic->setLastPost($post);
$em->persist($post);
$em->flush();
but it doesn't seem to update my topics table.
I also took a look at http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/working-with-associations.html#transitive-persistence-cascade-operations hoping it will solve the problem by adding cascade: [ 'persist' ] to my Topic.orm.yml file, but it didn't help, either.
Could anyone point me to a solution or an example class?
My ForumTopic is:
FrontBundle\Entity\ForumTopic:
type: entity
table: forum_topics
id:
id:
type: integer
generator:
strategy: AUTO
fields:
title:
type: string(100)
nullable: false
slug:
type: string(100)
nullable: false
created_at:
type: datetime
nullable: false
updated_at:
type: datetime
nullable: true
update_reason:
type: text
nullable: true
oneToMany:
posts:
targetEntity: ForumPost
mappedBy: topic
manyToOne:
created_by:
targetEntity: User
inversedBy: articles
nullable: false
updated_by:
targetEntity: User
nullable: true
default: null
topic_group:
targetEntity: ForumTopicGroup
inversedBy: topics
nullable: false
oneToOne:
last_post:
targetEntity: ForumPost
nullable: true
default: null
cascade: [ persist ]
uniqueConstraint:
uniqueSlugByGroup:
columns: [ topic_group, slug ]
And my ForumPost is:
FrontBundle\Entity\ForumPost:
type: entity
table: forum_posts
id:
id:
type: integer
generator:
strategy: AUTO
fields:
created_at:
type: datetime
nullable: false
updated_at:
type: datetime
nullable: true
update_reason:
type: string
nullable: true
text:
type: text
nullable: false
manyToOne:
created_by:
targetEntity: User
inversedBy: forum_posts
nullable: false
updated_by:
targetEntity: User
nullable: true
default: null
topic:
targetEntity: ForumTopic
inversedBy: posts
I believe that you're making this more difficult for yourself, because you think that you have to flush your post before you can set it to an association on your topic.
Doctrine is smart enough that if you persist an entity and set it to an association without first calling flush, it'll make sure that when you do call flush, it persists that entity first, so that it has an ID which can be used with the association.
What this means is that all you really need to do is this:
// Assume that topic has been fetched from the DB, and post is completely new
$topic = $em->find('TopicModel', $topicId);
$post = new ForumPost();
// Fill in the post info
// Set up the association
$topic->setLastPost($post);
// And finally, persist and flush - no more effort needed
$em->persist($post);
$em->flush();
A nice side effect of this is that you can simply use a prePersist event listener to update the thread's last post - Doctrine will take care of everything else for you.
Alternately, if you want an approach that can be a bit easier to follow logically, you can have your Post model call setLastPost on the topic yourself - for instance, if you set the post's topic in your constructor or in a setTopic() method, add the setLastPost call in there.
It's relatively common practice to have one side of an association take care of both sides like this, to help keep things nicely synchronised - see Working with Associations.

Resources