This question is about Symfony2 table relationships using ORM. I have three tables/entities that are related to each other. The relationship is very similar to Wordpress Posts, Categories and Categories relationship tables.
Table 1 contains posts.
Table 2 contains categories
Table 3 contains relationships between the categories and posts.
I want to be able to have the categories property in the posts table and a posts property in the categories table. So that when I call.
Categories->posts : I should get posts in that category.
Posts->categories : I should get the categories the post belongs to.
I want to have unique categories per table and I want all posts to point to a category without having to create a new entry for the category that already exists which is what ManyToOne or OneToMany is offering this is why the third table I think is necessary.
For example here is the relationships
class Category_relationship
{
/**
* #var integer
*
* #ORM\Column(name="object_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Worksheet", inversedBy="category_relationships")
* #ORM\JoinColumn(name="worksheet_id", referencedColumnName="id", nullable=FALSE)
*/
private $objectId;
/**
* #var integer
*
* #ORM\Column(name="category_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="categories")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=FALSE)
*/
private $categoryId;
}
Here is the Category class:
class Category
{
/**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="categoryId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $posts;
}
Here is the Category class:
class Posts
{ /**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="objectId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $categories;
}
I want to create a system where I can assign posts to a category but the category table can only contain 1 entry about the category. I also want to be able to use expressions link;
Post->categories
Category->posts
or
Post->AddCategory()
Category->AddPost()
Thanks for your help.
It seems that you want a simple many-to-many relationship.
Every post can have multiple categories, and every category have list of related posts. Many to many handles pivot table by itself.
So, in Post entity you have to declare relationship that way:
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="posts")
* #ORM\JoinTable(name="PostsCategories",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")}
* )
**/
protected $categories;
Remember about using Doctrine\ORM\Mapping with ORM alias (you don't have to import all subclasses separately):
use Doctrine\ORM\Mapping as ORM;
After that, you need to create a new ArrayCollection in class constructor:
public function __construct()
{
$this->categories = new ArrayCollection();
}
And add proper methods, like addCategory:
public function addCategory(Category $category)
{
$this->categories[] = $category;
return $this;
}
You can also add them automatically with:
php app/console doctrine:generate:entities BundleName:EntityName
Same thing in Category entity, but with a little different definiton:
/**
* #ORM\ManyToMany(targetEntity="Post", mappedBy="categories")
**/
protected $posts;
You can find all of these information in Doctrine docs
Related
So I have two objects Product and Subcategory with a ManyToMany relationship. The relationship is saved in a third table called ProductSubcategory.
The problem is that if I try to remove a subcategory from the product I get foreign constraint error (I can't remove from ProductSubcategory because it has values mapped to existing objects).
So what I did is added onDelete=SET NULL to my ManyToMany table. Now I can remove the subcategory, but what it does is it just sets the field product to NULL instead of deleting the whole thing.
In product I have:
/**
* #var \Mp\ShopBundle\Entity\ProductSubcategory
* #ORM\OneToMany(targetEntity="\Mp\ShopBundle\Entity\ProductSubcategory", mappedBy="product", cascade={"persist"}, orphanRemoval=true)
*/
private $subcategory;
and a function to remove a subcategory:
/**
* Remove subcategory
*
* #param \Mp\ShopBundle\Entity\ProductSubcategory $subcategory
*/
public function removeSubcategory(\Mp\ShopBundle\Entity\ProductSubcategory $subcategory) {
foreach ($this->subcategory as $k => $s) {
if ($s->getId() == $subcategory->getId()) {
unset($this->subcategory[$k]);
}
}
}
ProductsSubcategory is a ManyToMany table where I save the relationship:
/**
* #var \Mp\ShopBundle\Entity\Product
* #ORM\ManyToOne(targetEntity="\Mp\ShopBundle\Entity\Product", inversedBy="subcategory")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="SET NULL")
* })
*/
private $product;
So if i do this:
$product->removeSubcategory($subcategory);
in database it just makes it null:
id|subcategory_id|product_id|
1 |2 | NULL|
instead of completely removing it.
What am I doing wrong?
I would like to create a notification system. There is a Notification class. A notification can be assigned to more than one users, not just one.
There is a joint table user_notifications, with two columns: user_id and notification_id
The definition of the $notifications in the user class is this:
/**
* #ManyToMany(targetEntity="Notification")
* #JoinTable(name="user_notifications",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="notification_id", referencedColumnName="id", unique=true)}
* )
**/
private $notifications;
Everything works fine. But I would like to add a new column to the user_notifications table, where I would like to store, if the notification is read by the given user, or not. How should I manage it in Doctrine2?
You will have to refactor your entities to introduce a new and transform your user_notifications adjacency table into an entity.
Solution
Transform you table as follows:
Then refactor your associations as follows:
User entity
...
/**
* #OneToMany(targetEntity="UserNotification", mappedBy="notification_id")
**/
private $notifications;
Notification entity
...
/**
* #OneToMany(targetEntity="UserNotification", mappedBy="user_id")
**/
private $users;
UserNotification entity
/** #Entity **/
class UserNotification {
...
/**
* #ManyToOne(targetEntity="User", inversedBy="notifications")
* #JoinColumn(name="user_id", referencedColumnName="id")
**/
private $user_id;
/**
* #ManyToOne(targetEntity="Notification", inversedBy="users")
* #JoinColumn(name="notification_id", referencedColumnName="id")
**/
private $notification_id;
/** #Column(type="boolean") */
private $read;
You'll need to create new entity with this extra column.
You can find details in this answer: https://stackoverflow.com/a/15630665/1348344
I'm trying to create simple getter methods in original entity for specific item in doctrine collection of joined entity.
Main entity looks something like this:
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\OneToMany(targetEntity="File", mappedBy="product")
*/
private $files;
}
And joined entity:
class PrankFile
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="files")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $product;
/**
* #var string
*
* #ORM\Column(name="type", type="string", length=16)
*/
private $type;
...
My DQL in repository class is very simple:
return $this->getEntityManager()
->createQuery('SELECT p, f FROM AppProductBundle:Product p INNER JOIN p.files f ORDER BY p.created DESC')
->setMaxResults($limit)
->getResult();
In files entity type field tells me what kind of a file is it (image, sound, video, demo etc...)
Problem comes when I wish to print out a list of all products and display image next to product details, I would hate to loop through product files for each product displayed.
Is it possible to create some simple getter on product entity to fetch file of certain type?
Or maybe it would be better to create more complex DQL query for this, again how to do this?
I can't just fetch image record from files in DQL because I need all files for certain products.
Any help with this would be most welcome.
You can filter directly on collections using the filter api. If the collection is not loaded already Doctrine will apply your filter on a SQL level, giving you max performance. In case the collection is already eager loaded Doctrine will filter the ArrayCollection in memory.
use Doctrine\Common\Collections\Criteria;
class Product
{
public function getFilesByType($type)
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("type", $type))
return $this->files->matching($criteria);
}
}
I am trying to construct an object with two composite foreign keys pointing out to the same object, but they seem to have the same data, like doing the join only on one column, product_id.
class PostpaidProduct extends Product {
/**
* #ManyToOne(targetEntity="Bundle", fetch="EAGER", cascade={"persist"})
* #JoinColumn(name="bundle_voice_id", referencedColumnName="id")
*/
private $bundleVoice;
/**
* #ManyToOne(targetEntity="Bundle", fetch="EAGER", cascade={"persist"})
* #JoinColumn(name="bundle_data_id", referencedColumnName="id")
*/
private $bundleData;
/**
* #OneToMany(targetEntity="BundlePromo", mappedBy="product", fetch="EAGER", cascade={"persist"})
* #JoinColumns({
* #JoinColumn(name="id", referencedColumnName="product_id"),
* #JoinColumn(name="bundle_voice_id", referencedColumnName="bundle_id")
* })
*/
private $bundleVoicePromos;
/**
* #OneToMany(targetEntity="BundlePromo", mappedBy="product", fetch="EAGER", cascade={"persist"})
* #JoinColumns({
* #JoinColumn(name="id", referencedColumnName="product_id"),
* #JoinColumn(name="bundle_data_id", referencedColumnName="bundle_id")
* })
*/
private $bundleDataPromos;
}
What would be wrong with my mapping?
Is it possible to have composite foreign keys but without being primary keys?
I have talked to one of the developers of Doctrine and he said that the #JoinColumns field in #OneToMany relationships is ignored. The alternative would be having just one foreign key and to use matching criterias in an entity method, filtering for the entries needed based on the other key. Another solution would be having repository methods specific for getting these values.
Also, in OneToMany relationships eager fetching does not work, so it does separate queries for all children. So if you have a product with multiple prices, when fetching a product it will do separate queries for fetching the prices.
I have an Author entity, which is a Class Table Inheritance containing an AuthorUser and an AuthorGroup.
/**
* Author
*
* #ORM\Table
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"user" = "AuthorUser", "group" = "AuthorGroup"})
*/
class Author {
// ...
}
AuthorUser relates to my User entity and AuthorGroup to my Group entity.
class AuthorUser extends Author
{
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="?????")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
class AuthorGroup extends Author
{
/**
* #var Group
*
* #ORM\ManyToOne(targetEntity="Group", inversedBy="?????")
* #ORM\JoinColumn(name="group_id", referencedColumnName="id")
*/
protected $user;
}
I have no idea how to inverse this. Anyway, the problem is that i have to add this CTI to my Article entity field. How can i relate using ManyToOne to this Article entity field?
class Article
{
/**
* #var Author
*
* #ORM\ManyToOne(targetEntity="Author", inversedBy="?????????")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
protected $author;
}
I'm not sure how to make this as transparent as possible. When i create a new Article, i need to provide either an User or Group object to the author field. I followed this behavior, but it doesn't seem to help. It gets even more complicated.
One solution could be to always have AuthorGroups, even when there's only one Author.
Otherwise, take a look at https://github.com/FabienPennequin/DoctrineExtensions-Rateable
You might be able to use that code to provide a similar Authored interface that can discriminate between the AuthorUser and AuthorGroup.