i try to set up a "following" feature for users, so users can follow users.
I tried doing this by adding a Many-to-many Relation:
#ORM\ManyToMany(targetEntity="User",inversedBy="User",fetch="LAZY",cascade={"persist"})
#ORM\JoinTable=(name="user_followers",
joinColumns={
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
},
inverseJoinColumns={#JoinColumn(name="follower_id",referencedColumnName="id")}
)
Now doctrine creates a table user_user with just one field user_id.
I really have no idea how to declare this.
Any thoughts?
I found the Solution myself.
You can actually use a self refferencing Many-to-Many Solution. From the Doctrine Website:
<?php
/** #Entity */
class User
{
// ...
/**
* #ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
private $friendsWithMe;
/**
* #ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
private $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/association-mapping.html#many-to-many-self-referencing
Related
I have two fields : account_type_id and account_id.
How can i manually map doctrine TargetEntity to join Company Entity if accountTypeId = 1 OR join User Entity if account_type_id = 2 ?
<?php
/** #Entity */
class Accounts
{
// 1= Company, 2 = User
private $accountType;
/**
* #ManyToOne(targetEntity="Companies")
*/
private $company;
/**
* #ManyToOne(targetEntity="Users")
*/
private $user;
//...
}
Unfortunately, joining different columns on the fly cannot be done automatically, but you can have both fields set as nullable and only set the correct one when persisting the Account entity.
This would be the annotation:
/**
* #ORM\ManyToOne(targetEntity="Users", inversedBy="users")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
Keep in mind that nullable=true is the default anyway, I'm just being specific here.
If you want to go defensively about this, you can have an additional check in getter
/**
* #return User
* #throws \Exception
*/
public function getUser()
{
if ($this->accountType !== 2) {
throw new \Exception("Entity is not of type 'user'");
}
return $this->user;
}
Just example:
/**
* #ORM\Entity
*/
class Menu
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="MenuDish", mappedBy="menu", fetch="EAGER")
*/
private $menu_dishes;
public function __construct()
{
$this->menu_dishes = new ArrayCollection();
}
}
/**
* #ORM\Entity
*/
class MenuDish
{
/**
* #ORM\ManyToOne(targetEntity="Dish", inversedBy="menu_dishes", fetch="EAGER")
*/
private $dish;
}
Then i trying to get menu:
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository(Menu::class);
$menu = $repo->find(1);
Then I look into XDebug and see that Menu::$menu_dishes is Collection of Entities (not proxies) all is OK, EAGER working.
BUT Menu::$menu_dishes::$dish contains Proxy ! There is a bug? $dish marked with fetch=EAGER.
When I mark some property as FETCH=EAGER I expect that this property will not contain Proxy class.
I need it for fighting with SoftDelete, the real issue is that $dish is actually soft deleted, and fetch eager may fix it by setting $dish as null, but it is not working.
Ho to make EAGER working on dish property?
I have set up a many to one relation and want to add a new product-object linked to its category. Related to this I have 2 questions:
I get stuck on saving the category object to the new product. Tried different options and read related questions here. At this moment I am getting the error:
Attempted to call an undefined method named "getCategory" of class "AppBundle\Controller\ProductController".
I do have the method getCategory in my Product class.
What am I missing here?
Another thing I would like to know is do I need to pass the category-id in the url to get the related products for that category?
I have a category class:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="category")
*/
class Category
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $cat_id;
...
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
private $products; ...
public function __construct()
{
$this->products = new ArrayCollection();
}
and product class:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Category;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="product")
*/
class Product
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $prd_id;
/**
* #var Category
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="cat_id", referencedColumnName="cat_id", nullable=false)
*/
private $category;
....
/**
* Set category
*
* #param \AppBundle\Entity\Category $category
*
* #return Product
*/
public function setCategory(\AppBundle\Entity\Category $category)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return \AppBundle\Entity\Category
*/
public function getCategory()
{
return $this->category;
}
From my list of categories "/categories" I link the categories to the productlist "/cat1/product" (<-- do I need to pass the category-id here?). There I want to add a new product and call the following action in my ProductController:
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Product;
use AppBundle\Form\ProductType;
use Symfony\Component\HttpFoundation\Request;
class ProductController extends Controller
{
/**
* #Route("/cat{cat_id}/product/new", name="newproduct")
*/
public function newAction(Request $request, $cat_id)
{
$product = new Product();
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$category = $this->getCategory();
$product->setCategory($category);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('productlist');
}
return $this->render('product/new.html.twig', array(
'form' => $form->createView(),
'cat_id' => $cat_id,
));
}
Suggestions appreciated!
when you do :
$category = $this->getCategory();
$this represent your productController, it is the reason of the undefined method error. to get the category object you must do :
$categoryRepository = $this->getDoctrine()->getRepository('AppBundle:Category');
$product->setCategory($categoryRepository->find($cat_id));
hope that may help you.
Entity User
class User {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
...
/**
* #ORM\ManyToMany(targetEntity="Controleitor\Model\Entity\Account", mappedBy="user")
*/
protected $userAccount;
public function __construct() {
$this->userAccount = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getUserAccount() {
return $this->userAccount;
}
...
}
Entity Account
class Account{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
*
* #ORM\ManyToMany(targetEntity="JasUser\Model\Entity\User", inversedBy="userAccount")
* #ORM\JoinTable(name="user_has_account",
* joinColumns={#ORM\JoinColumn(name="idAccount", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="idUser", referencedColumnName="id")}
* )
*/
private $user;
public function __construct(array $options = null) {
$this->user = new \Doctrine\Common\Collections\ArrayCollection();
}
...
}
Test:
$user = $this->entityManager->getRepository('User')->findOneById($id);
$user->getUserAccount()->add($account);
$this->entityManager->persist($account);
$this->entityManager->flush();
$id = $entity->getId();
It gets the user, the userAccount and also inserts the new record account in the db, but it doesn't adds the record in the user_has_account table for the manytomany association between user and account with the add method as I was expecting..
Looks like you've got owning/inverse sides of your relation backwards.
The owning side has to use the inversedBy attribute of the OneToOne, ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute contains the name of the association-field on the inverse-side.
Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine’s point of view)
This can be somewhat annoying.
The preferred solution is to only touch the Collections internally. So instead of having your calling code do $user->getUserAccount()->add($account), implement it like this:
<?php
class Account {
// ...
public function addUser($user){
$this->users->add($user);
}
}
class User {
// ...
public function addAccount($account){
// relation must be added from the owning side
$account->addUser($this);
}
}
I'm looking for a suggestion on how to map a OneToMany/ManyToOne relationship that uses a join table. The mapping I have is not taking, and I get an error that article_id is not set in the media table.
class Media
{
// ...
/**
* #ManyToOne(targetEntity="Document", inversedBy="media")
* #JoinTable(name="articles_x_media", referencedColumnName="id")
* joinColumns={#JoinColumn(name="media_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="bid_id", referencedColumnName="id")})
* )
*/
protected $document;
}
class Document
{
// ...
/**
* #OneToMany(targetEntity="Media", mappedBy="document"))
* #JoinTable(name="articles_x_media", referencedColumnName="id")
* joinColumns={#JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="media_id", referencedColumnName="id")}
* )
*/
protected $media;
}
There's a specific paragraph in the documentation about OneToMany mapping with join table.
Anyway, what you probably want is an uni-directional ManyToMany association.
Also, #OneToMany does not come with a #JoinTable, and the same for #ManyToOne either.