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.
Related
I am using Symfony 4 with EasyAdminBundle to create a simple administrative interface. I'm having a few problems trying to automatically set the value for createdAt and updatedAt columns. When creating/updating an entity within EasyAdmin, the configured Doctrine lifecycle callbacks are not called. For example, here's a simple entity that is managed with EasyAdmin, note the lifecycle callback hooks:
<?php
namespace App\Entity;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductRepository")
* #ORM\HasLifecycleCallbacks
*/
class Product
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
// Additional column configuration removed for brevity
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $updatedAt;
// Additional getters/setters removed for brevity
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(?\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* #ORM\PrePersist
*/
public function onPrePersist(): void
{
$this->createdAt = new \DateTime();
}
/**
* #ORM\PreUpdate
*/
public function onPreUpdate(): void
{
$this->updatedAt = new \DateTime();
}
}
When I create a new product within EasyAdmin, onPrePersist() is not called, and when I edit an existing product with EasyAdmin, onPreUpdate() is not called.
If I create a new product the "traditional" way, the lifecycle callbacks work exactly as expected. For example:
<?php
$product = new Product();
$product->setTitle('Test Product');
$product->setDescription('Test description');
// Doctrine lifecycle callbacks work as expected
$this->getDoctrine()->getManager()->persist($product);
Is EasyAdminBundle bypassing Doctrine lifecycle callbacks? If so, why? How do I utilize Doctrine lifecycle callbacks within Doctrine entities managed by EasyAdminBundle?
I understand there is documentation to do something similar with an AdminController:
https://symfony.com/doc/master/bundles/EasyAdminBundle/book/complex-dynamic-backends.html#update-some-properties-for-all-entities
But again, why do I need to do this when we already have Doctrine lifecycle callbacks. My other problem with using an AdminController and extending various methods, persistEntity() within AdminController is never called either.
What am I missing?
Any help would be greatly appreciated! Cheers!
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)
I have a doctrine setup where i cant use the many side of collections.
The objects used:
User.php:
class User extends \App\Entity
{
/**
* #Id #Column(type="integer")
* #GeneratedValue
*/
private $id;
/**
* #ManyToOne(targetEntity="Company", inversedBy="users")
*/
private $company;
public function getCompany()
{
return $this->company;
}
public function setCompany($company)
{
$this->company = $company;
}
}
Company.php:
class Company extends \App\Entity
{
/**
* #Id #Column(type="integer", nullable=false)
* #GeneratedValue
*/
private $id;
/**
* #OneToMany(targetEntity="User", mappedBy="company")
*/
private $users;
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getUsers()
{
return $this->users;
}
}
When i create the relation seems to be working. Get no errors. Then i tried the following:
$company = $this->em->getRepository('App\Entity\Company')->findOneByName("Walmart");
$user = $this->em->getRepository('App\Entity\User')->findOneByName("Niek");
$user->setCompany($company);
$company2 = $this->em->getRepository('App\Entity\Company')->findOneByName("Ford");
$user2 = $this->em->getRepository('App\Entity\User')->findOneByName("Henk");
$company2->getUsers()->add($user2);
$this->em->flush();
When i inspect the database for the first user the company is set. Relation is there. The seconds does not persists. and when i do this:
print_r(\Doctrine\Common\Util\Debug::dump($company->getUsers(),$doctrineDepth));
print_r(\Doctrine\Common\Util\Debug::dump($company->getUsers2(),$doctrineDepth));
i get 2 empty arrays.
So it seems that the array isnt connected. It only behaves like this on OneToMany ore ManyToOne relationships. Got one ManyToMany and that one works perfect in the same project
Any ideas?
You still need to set both sides of the relationship with a one-to-many or a many-to-one relationship:
$company2 = $this->em->getRepository('App\Entity\Company')->findOneByName("Ford");
$user2 = $this->em->getRepository('App\Entity\User')->findOneByName("Henk");
$company2->getUsers()->add($user2);
$user2->setCompany($company2);
$this->em->flush();
The reason your many-to-many works is because you don't need to set both sides of the relationship.
I am working on a project that utilizes Zend Framework 1.12 integrated with doctrine 2. I am having trouble with a Bidirectional One-to-Many relation in said project.
The two entities concerning my problem are Team and Player; a team can have many players.
The Team Entity:
namespace Entities;
use Doctrine\Common\Collections\Collection,
Doctrine\Common\Collections\ArrayCollection;
/**
* #Entity(repositoryClass="Repositories\TeamRepository")
* #Table(name="team")
*/
class Team extends Entity{
/**
* #Column(type="string", length=255)
*/
protected $name;
/**
* #OneToMany(targetEntity="Player", mappedBy="team")
*/
protected $players;
public function __construct() {
$this->players = new ArrayCollection();
}
public function getName(){
return $this->name;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getPlayers() {
return $this->players;
}
And the Player Entity:
namespace Entities;
/**
* #Entity(repositoryClass="Repositories\PlayerRepository")
* #Table(name="player")
*/
class Player extends Entity{
public function __construct() {
}
/**
* #Column(type="string", length=255)
*/
protected $name;
/**
* #ManyToOne(targetEntity="Team", inversedBy="players")
* #JoinColumn(name="team_id", referencedColumnName="id")
*/
protected $team;
public function getName(){
return $this->name;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getTeam() {
return $this->team;
}
public function setTeam($team) {
$this->team = $team;
return $this;
}
}
Now in my player controller for example I can retrieve a player and get the team name
$oPlayer = $this->_em->find('Entities\Player', $player_id);
$teamname = $oPlayer->getTeam()->getName();
This works as expected and I successfully obtain the name of the players team.
However the other way around does not work. I can not retrieve all the players given a team
$oTeam = $this->_em->find('Entities\Team', $team_id);
$oPlayers = $oTeam->getPlayers();
When I var_dump this the result looks like
object(Doctrine\ORM\PersistentCollection)#238 (9) {
["snapshot":"Doctrine\ORM\PersistentCollection":private]=>
array(0) {
}
["owner":"Doctrine\ORM\PersistentCollection":private]=>
object(Entities\Team)#195 (7) {
...
}
Note that a persistenCollection seems to be build, however the array is empty.
I have read the doctrine manual extensively and googled my behind off and am now at a loss.
Also the fact that there is no error message, I am having a hard time solving this problem.
Any pointers would be more than welcome.
The problem has been resolved. I have been trying to puzzle the solution together for posterity but have come to the conclusion that I no longer have the files where I now suspect the original error was located. I managed to get a working copy derived from another project. By brute force 'diff'-ing and replacing code I traced the error of the empty array of the persistent collection, to my bootstrap and resources/doctrine.php config file, which unfortunately I do not have any longer and therefore can not confirm this. With string(4) "team" still being returned (as discussed in the comments) embarrassingly I finally found out that it was just due to a die() I put in the doctrine library file and forgot about.
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'));