I have relation User to Coupon ManyToMany.
User have many coupons and coupon may belong to many users.
When I call the method $coupon->getUsers(), I get coupon (PersistentCollection).
And when I call the method $user->getCoupon(), I get user (PersistentCollection).
User entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Coupon", inversedBy="users")
*/
private $coupon;
public function __construct()
{
$this->coupon = new ArrayCollection();
}
/**
* #return Collection|Coupon[]
*/
public function getCoupon(): Collection
{
return $this->coupon;
}
public function addCoupon(Coupon $coupon): self
{
if (!$this->coupon->contains($coupon)) {
$this->coupon[] = $coupon;
}
return $this;
}
public function removeCoupon(Coupon $coupon): self
{
if ($this->coupon->contains($coupon)) {
$this->coupon->removeElement($coupon);
}
return $this;
}
Coupon entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="coupon")
*/
private $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
/**
* #return Collection|User[]
*/
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users[] = $user;
$user->addCoupon($this);
}
return $this;
}
public function removeUser(User $user): self
{
if ($this->users->contains($user)) {
$this->users->removeElement($user);
$user->removeCoupon($this);
}
return $this;
}
When I run this code:
namespace App\Controller;
use App\Entity\Coupon;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class TestController extends AbstractController
{
/**
* #Route("/test", name="test")
*/
public function index()
{
$coupon = $this->getDoctrine()->getRepository(Coupon::class)->find(1);
dump($coupon->getUsers());die;
}
}
I get:
screenshot
Why I get a coupon and not a list of users?
On top of what Jakumi wrote, in the controller you can also do
$coupon = $this->getDoctrine()->getRepository(Coupon::class)->find(1);
$users = $coupon->getUsers();
$users->initialize();
Now when you dump($users) the collection should not be empty.
To add to that, I believe you have your mapping wrong. In your Many-To-Many relation the User is the owning side and Coupon is the inversed side, however it is the public function addUser(User $user) in the Coupon entity that does the owning side's job. You should either change the sides (change the mappedBy in Coupon to inversedBy and the other way around in the User) or make sure that User does:
public function addCoupon(Coupon $coupon): self
{
if (!$this->coupon->contains($coupon)) {
$coupon->addUser($this);
$this->coupon[] = $coupon;
}
return $this;
}
and the Coupon does:
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users[] = $user;
}
return $this;
}
Of course the removeUser and removeCoupon methods should be dealth with accordingly.
PersistentCollections conceptually are supposed to work like arrays and are the way of doctrine to realize lazy loading (the default). There are certain operations that will trigger the collection to be loaded from the database (such as iterating over the collection). Before that, it's property initialized will be false (as in your screenshot)
ManyToMany and OneToMany should always be realized as ArrayCollection (or some other collection, such as PersistentCollection) and should not be leaked to the outside. Instead call ->toArray() (or ->asArray(), I always forget) to return them (so, inside getUsers() or getCoupons() respectively). Inside the entity you can just foreach over the PersistentCollection.
If you mark the ManyToMany to fetch as EAGER, it will be loaded immediately, but that might have performance impact...
And the Collection holds a reference to the object it belongs to, so you're not getting a Coupon per se, you get a collection, that still references its owner ;o)
Related
I'm trying to implement a Cart feature in my Symfony app.
The purpose is to allow a User to add some events in a Cart.
So I have created 3 Entities. User, Event and Cart.
A User need to access his Cart to get his events. Like $user->getCart, which will return an ArrayCollection of events.
I have no idea what is the best way to do it with the Doctrine relation.
Everything I have tried does not seems to work.
Here is what I have made so far:
In my User Entity
/**
* #ORM\OneToOne(targetEntity="App\Entity\Cart", mappedBy="user", cascade={"persist", "remove"})
*/
private $cart;
public function getCart(): ?Cart
{
return $this->cart;
}
public function setCart(Cart $cart): self
{
$this->cart = $cart;
// set the owning side of the relation if necessary
if ($this !== $cart->getUser()) {
$cart->setUser($this);
}
return $this;
}
In my User Entity
/**
* #ORM\OneToOne(targetEntity="App\Entity\User", inversedBy="cart", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $user;
I have stopped here, because I feel like I'm not doing the right approach.
May I have your feeling about it?
Although I haven't tested it myself, it will highly likely work.
Relationship: User one-to-many Cart one-to-many Event
Type: Bidirectional Composition
Consider: Cascade "remove" on OneToMany depending on your needs.
Entities:
class User
{
...
/**
* #ORM\OneToMany(targetEntity="Cart", mappedBy="user", cascade={"persist"})
*/
private $carts;
public function __construct()
{
$this->carts = new ArrayCollection();
}
public function addCart(Cart $cart): self
{
$this->carts[] = $cart;
return $this;
}
public function removeCart(Cart $cart): bool
{
return $this->carts->removeElement($cart);
}
public function getCarts(): Collection
{
return $this->carts;
}
}
class Cart
{
...
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="carts", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $user;
/**
* #ORM\OneToMany(targetEntity="Event", mappedBy="cart", cascade={"persist"})
*/
private $events;
public function __construct()
{
$this->events = new ArrayCollection();
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
public function getUser(): User
{
return $this->user;
}
public function addEvent(Event $event): self
{
$this->events[] = $event;
return $this;
}
public function removeEvent(Event $event): bool
{
return $this->events->removeElement($event);
}
public function getEvents(): Collection
{
return $this->events;
}
}
class Event
{
...
/**
* #ORM\ManyToOne(targetEntity="Cart", inversedBy="events", cascade={"persist"})
* #ORM\JoinColumn(name="cart_id", referencedColumnName="id", nullable=false)
*/
private $cart;
public function setCart(Cart $cart): self
{
$this->cart = $cart;
return $this;
}
public function getCart(): User
{
return $this->cart;
}
}
When doing carts and e-commerces you have to keep in mind a lot of things, and need to ask yourself what kind of information you want to persist. Some people develop Cart modules in a Event Sourced way so they don't loose any data. This talk by Greg Young is great on the subject. That's the approach I would use, but is not the easiest one.
But maybe you don't want to persist all that valuable extra state. In that case, you can use the traditional CRUD approach as you are trying to.
When using this, keep in mind two things.
Your events are mutable. Price can change, location can change, time can change.
When the purchase is complete, you will take that cart and generate a Purchase object from it. That Purchase object CAN NOT have any relationship to Event, because of 1.
You don't want a user to have a single Cart, I guess. A User can have many Carts, so maybe you should make that relationship a ManyToOne on the cart side. Otherwise, Cart can be just a value object that you can store in user.
I have Post And PostTag entities. Only custom users are allowed create PostTags. While creating a post user may select post tags.
I have created many to many relationship between Post And PostTag entities using the make:entity command.
The problem is that after creating the post and attaching to it the selected tags the relation table is empty and nothing is returned by post.getPostTags() method.
PostController - create method:
...
$em = $this->getDoctrine()->getManager();
$post = $form->getData();
$post->setAuthor($this->getUser());
$post->setCreatedAt(new DateTime);
foreach ($form->get('post_tags')->getData() as $postTag) {
$post->addPostTag($postTag);
}
$em->persist($post);
$em->flush();
...
Post entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\PostTag", mappedBy="post", cascade={"persist"})
*/
private $postTags;
public function __construct()
{
$this->postTags = new ArrayCollection();
}
/**
* #return Collection|PostTag[]
*/
public function getPostTags(): Collection
{
return $this->postTags;
}
public function addPostTag(PostTag $postTag): self
{
if (!$this->postTags->contains($postTag)) {
$this->postTags[] = $postTag;
$postTag->addPost($this);
}
return $this;
}
public function removePostTag(PostTag $postTag): self
{
if ($this->postTags->contains($postTag)) {
$this->postTags->removeElement($postTag);
$postTag->removePost($this);
}
return $this;
}
PostTag entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Post", inversedBy="postTags", cascade={"persist"})
*/
private $post;
public function __construct()
{
$this->post = new ArrayCollection();
}
/**
* #return Collection|Post[]
*/
public function getPost(): Collection
{
return $this->post;
}
public function addPost(Post $post): self
{
if (!$this->post->contains($post)) {
$this->post[] = $post;
}
return $this;
}
public function removePost(Post $post): self
{
if ($this->post->contains($post)) {
$this->post->removeElement($post);
}
return $this;
}
You have set the PostTag entity as the owning side (by virtue of setting inversedBy parameter on that entity), so you have to persist the PostTag entity, not the Post entity, to make things stick. You can make a simple modification to your Post entity to make this work:
public function addPostTag(PostTag $postTag): self
{
if (!$this->postTags->contains($postTag)) {
$this->postTags[] = $postTag;
$postTag->addPost($this);
// set the owning side of the relationship
$postTag->addPost($this);
}
return $this;
}
I would like to make a Translatable behavior for my entities using metadata.
I have a class Article
class Article implements TranslatableInterface {
/**
* #HeidanTranslatable
*/
private $title;
/**
* #HeidanLocale
*/
private $locale;
public function getTitle() {
return $this->title;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getLocale() {
return $this->locale;
}
public function setLocale($locale) {
$this->locale = $locale;
return $this;
}
I would like to have kind of Gedmo Doctrine Extension behavior which will create in database columns depending on property and allowed locales.
For example, with the entity article, I would like that two columns are created : title_fr, title_en.
I'd like this stuff is bridged to Doctrine behavior and I made a loadClassMetadataListener
class LoadClassMetadataListener {
/**
* #param LoadClassMetadataEventArgs $eventArgs
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$metadata = $eventArgs->getClassMetadata();
$metadata
->mapField(array('fieldName' => 'title_fr', 'type' => 'text'))
;
}
When I run a doctrine:schema:update --force I have the following error :
[ReflectionException]
Property Heidan\CoreBundle\Entity\Article::$title_fr does not exist
So I guess they said that the property title_fr does not exist, and that's right.
I do not want to set manually properties (private $title_fr, private $title_en, private $content_fr, private $content_en) for all my entities.
Is there any way to achieve this behavior so far ?
Thanks a lot for your help.
I'm in the process of creating a trait that I want to insert into a number of my Doctrine entity classes. The trait basically allows for a slug property to be created using the Hashids PHP library based on the entities id (primary key).
I've included the required properties & getters/setters along with the postPersist() method on the trait, but I'm now wondering how I go about re-saving / updating / persisting that change from within the postPersist() method?
Any help or direction would be great.
SlugTrait
trait Slug
{
/**
* #ORM\Column(type="string")
*/
private $slug;
/**
* #ORM\PostPersist
*/
public function postPersist()
{
$this->slug = (new SlugCreator())->encode($this->id);
// Save/persist this newly created slug...?
}
public function getSlug()
{
return $this->slug;
}
public function setSlug($slug)
{
$this->slug = $slug;
}
}
After some trial and error I found out how to persist the update/change. As I'm using Laravel I just resolved the Entity Manager from the IoC container and then used that to persist the updated slug field like so (you could also just new up the Entity Manager manually):
trait Slug
{
/**
* #ORM\Column(type="string")
*/
protected $slug;
/**
* #ORM\PostPersist
*/
public function postPersist()
{
$this->slug = (new SlugCreator())->encode($this->id);
// Save/persist this newly created slug.
// Note: We must add the top level class annotation
// '#ORM\HasLifecycleCallbacks()' to any Entity that
// uses this trait.
$entityManager = App::make('Doctrine\ORM\EntityManagerInterface'); // or new up the em "new EntityManager(...);
$entityManager->persist($this);
$entityManager->flush();
}
public function getSlug()
{
return $this->slug;
}
public function setSlug($slug)
{
$this->slug = $slug;
}
}
I'm trying to learn Doctrine2, and am having some trouble wrapping my brain around Entities and Mappings, and how to pull data from the db.
If I understand correctly, an Entity would be created for data in my db, like a User or a Product.
That's where I'm getting hung up though. I have a simple database with tables like User and such. Do I need to create an Entity called "User"? And does that entity pull data from the User table, and then I get data from the User entity?
Can someone provide me a code example of how I would do this?
Entities are regular PHP classes... First u have to create ur table, then u create your entity. Properties of class should be private with setters and getters and usually with same names as field in table. When U want to put new record in DB, u must make instance of class u want and set values then persist and flush the entity manager. Example code:
<?php
namespace Entity;
/**
* #Entity
* #Table(name="users")
*/
class User {
/** #Id
* #Column(type="integer")
* */
private $id;
/** #Column(type="string") */
private $username;
/** #Column(type="string") */
private $password;
/** #Column(type="boolean") */
private $active;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getUsername() {
return $this->username;
}
public function setUsername($username) {
$this->username = $username;
}
public function getPassword() {
return $this->password;
}
public function setPassword($password) {
$this->password = $password;
}
public function getActive() {
return $this->active;
}
public function setActive($active) {
$this->active = $active;
}
}
and when u want to put a new record:
$user = new Entity\User();
$user->setName('users name');
$user->setPassword('password');
$entityManager->persist($user); // put that entity in queue;
$entityManager->flush(); // execute all pending entities
If u want to get existing record
$found = $entityManager->find('className', $id); // search by id
or $entityManager->getRepository('className')->findOneBy(array('field', 'value'));