Doctrine: Object of class User could not be converted to string - php

I keep getting this error with Doctrine:
PHP Catchable fatal error: Object of class User could not be converted to string in vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 1337
In my system users can have many permissions in a One to Many relationship. I have set up a User and Permission entity. They look like this (I removed some annotations, getters and setters to reduce clutter):
class User {
/**
* #ORM\Column(name="user_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
public function getId()
{
return $this->id;
}
/**
* #ORM\OneToMany(targetEntity="Permission", mappedBy="user", cascade={"persist"})
*/
protected $permissions;
public function getPermissions()
{
return $this->permissions;
}
}
class Permission {
/**
* #ORM\Column(name="user_id", type="integer")
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions")
*/
protected $user;
public function getUser()
{
return $this->user;
}
public function setUser( $user )
{
$this->user = $user;
return $this;
}
}
The problem occurs when I add a new Permission to a User:
$permission = new Permission();
$user->getPermissions()->add( $permission );
$em->persist( $user );
$em->flush();
This is the last bit of my stack trace:
PHP 11. Doctrine\ORM\UnitOfWork->persist() vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:565
PHP 12. Doctrine\ORM\UnitOfWork->doPersist() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1555
PHP 13. Doctrine\ORM\UnitOfWork->cascadePersist() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1615
PHP 14. Doctrine\ORM\UnitOfWork->doPersist() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:2169
PHP 15. Doctrine\ORM\UnitOfWork->persistNew() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1597
PHP 16. Doctrine\ORM\UnitOfWork->scheduleForInsert() doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:836
PHP 17. Doctrine\ORM\UnitOfWork->addToIdentityMap() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1157
PHP 18. implode() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1337
Any insight would be greatly appreciated.

OK. I've got it working.
I haven't fully worked out the reason yet but when I add the following to my User entity it works:
class User {
public function __toString()
{
return strval( $this->getId() );
}
}
If I find out more I will post here.

Your solution gave me a clue of what is happening.
Even though you have the entities and the anotations, Doctrine is not being able to understand the relation between entities. When doctrine understands the relation between entities, it knows what methods to call (ie User::getId()) but otherwise, it tries to transform whatever you are sending to a scalar value that it can use to query the database. Thats why it is calling the __toString function of the User, and thats why if you return the id in toString, everything works from here.
This is ok, but its a patch, and probably you dont want to keep it if we can find a better solution, since it could be harder to maintain as your application grows.
What i can see, is that in Permissions you have:
/**
* #ORM\Column(name="user_id", type="integer")
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions")
*/
protected $user;
You should remove the #ORM\Column(type="integer")
About the join columns, it is not mandatory, but you have to be sure that the defauts, are what you want. As we can read here
Before we introduce all the association mappings in detail, you should
note that the #JoinColumn and #JoinTable definitions are usually
optional and have sensible default values. The defaults for a join
column in a one-to-one/many-to-one association is as follows:
name: "<fieldname>_id"
referencedColumnName: "id"
so, they will be the same as an explicit:
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
protected $user;
So it is supposed to look for a column user_id in the Permissions table, and join it with the id column of the User table. We suppose that this is ok.
If this is true, then in your User, the id shouldnt be user_id, but id:
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
Or if the column name is actually user_id, then the User class is ok, but you have to change the join column to #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
That much i can say. I cannot try it know, but i will be glad if you can give it a second.

I think there's a problem with the mapping of user property in permission entity. Try this one:
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;

are you initializing the collection in your OneToMany side?
and also, the methods to add and remove from the collection?
class User {
/**
* #ORM\OneToMany(targetEntity="Permission", mappedBy="user", cascade={"persist"})
*/
protected $permissions;
public function getPermissions()
{
return $this->permissions;
}
public function __construct()
{
$this->permissions = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addPermissions (Permission $permissions)
{
$this->permissions[] = $permissions;
return $this;
}
public function removePermissions(Permission $permissions)
{
$this->permissions->removeElement($permissions);
}
//...

Related

Symfony Doctrine2 manyToMany relationship not removed - Specific to SQLite

I have several classes using a Taggable trait to set up a tag system common to several doctrine entities (Project, Note, ...).
The relationship between these entities and these tags is a ManyToMany relationship that I can not make multi-directional.
My problem: When I delete a Project entity, it is removed from the project table, but the relationships in the project_tag table between this project and the tags are not deleted. Then, if I create a new Project entity, an exception is thrown.
An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]:
SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id
Entities :
Tag
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
*/
class Tag
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #ORM\Column(name="last_use_at", type="datetime", nullable=false)
* #var \DateTime
*/
private $lastUseAt;
public function __construct()
{
$this->lastUseAt = new \DateTime();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return \DateTime
*/
public function getLastUseAt(): \DateTime
{
return $this->lastUseAt;
}
/**
* #param \DateTime $lastUseAt
*/
public function setLastUseAt(\DateTime $lastUseAt)
{
$this->lastUseAt = $lastUseAt;
}
}
Taggable
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"})
*/
protected $tags;
/**
* Add tag
*
* #param Tag $tag
*
* #return $this
*/
public function addTag(Tag $tag)
{
$tag->setLastUseAt(new \DateTime());
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Project
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository")
*/
class Project
{
use Taggable;
}
Note
class Note
{
use Taggable;
}
Is this the only solution or is my annotation incomplete / incorrect?
I tried with JoinColumns, JoinTable and onDelete = "cascade" but nothing works.
In the meantime, I dodged the problem with this instruction placed before the suppresion.
$project->getTags()->clear();
Full code of the action in the controller :
/**
* #Route("/project/{id}/delete", name="project_delete")
*/
public function deleteAction($id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->find($id);
if(!$project) {
return $this->redirectToRoute('index');
}
$project->getTags()->clear();
$em->remove($project);
$em->flush();
return $this->redirectToRoute('index');
}
I think I found a better solution: you can set the PRAGMA within Doctrine configuration. Like:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_sqlite'
#server_version: '5.7'
#charset: utf8mb4
#default_table_options:
#charset: utf8mb4
#collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
options:
'PRAGMA foreign_keys': 'ON'
I just tried it on my Symfony 4 application, re-created the database and tested using DB Browser for SQLite and it works as I expected.
Hope this helps
I managed to fix the problem. Here's my solution working for SQLite conections.
Create an eventListener listening on the kernel.request event :
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
/**
* #var Registry
*/
private $doctrine;
public function __construct(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON');
}
}
Service declaration
app.event_listener.request_listener:
class: AppBundle\EventListener\RequestListener
arguments:
- '#doctrine'
tags:
- { name: kernel.event_listener, event: kernel.request }
I think the problem is that you have your trait Taggable set as the owning side of the ManyToMany relationship but your are deleting the inverse side and expecting something to happen as a result. Doctrine will only check the owning side of the relationship in order to persist any changes. See here for docs on this.
You can solve by making the Taggable the inverse side of each of your relationships, or by manually telling doctrine to delete the owning side.
The first solution will probably not work for you since you won't (easily) specify multiple inverse sides. (Are you sure a trait is the right way to go for this??)
The second solution is easy. In your entities like Project for your deleteTag($tag) function, call a delete function on the owning side (e.g., deleteProject($project). You will have to create if one does not exist.
class Project
{
use Taggable;
public function deleteTag($tag)
{
$this->tags->removeElement($tag);
// persist on the owning side
$tag->deleteProject($this);
}
}
EDIT:
After seeing full code, it looks like you are deleting correctly. Now you need to tell doctrine to carry that through. See this post for full details, but basically you can change your trait to this:
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(
* targetEntity="AppBundle\Entity\Tag",
* cascade={"persist"},
* onDelete="CASCADE"
* )
*/
protected $tags;
// ...
}

Doctrine and Symfony2 : Association refers to inverse side that doesn't exist error

I have a page address.html.twig , the user can add many addresses in the table UserAddress. when he added his address in the database , the address should be render in the same page that he added his address then he can choose which one he would like to use. Unfortunately the address is not render.
First i thought that i have a problem in my controller action or in my twig page. I even asked a question here about it => here
I verified all my tables in phpmyadmin and all of them are well link but if i'm doing this: php app/console doctrine:schema:validate
i have this error :
[Mapping] FAIL - The entity-class
'FLY\BookingsBundle\Entity\Commandes' mapping is invalid:
* The association FLY\BookingsBundle\Entity\Commandes#user refers to the inverse side field
Application\Sonata\UserBundle\Entity\User#commandes which does not
exist.
[Mapping] FAIL - The entity-class
'FLY\BookingsBundle\Entity\UserAddress' mapping is invalid:
* The association FLY\BookingsBundle\Entity\UserAddress#user refers to the inverse side field
Application\Sonata\UserBundle\Entity\User#address which does not
exist.
Have a look at this picture:
This is my UserAddress.php
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="address")
* #ORM\JoinColumn(nullable=true)
*/
private $user;
Commandes.php
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="commandes")
* #ORM\JoinColumn(nullable=true)
*/
private $user;
User.php
/**
* #ORM\Entity(repositoryClass="FLY\UserBundle\Repository\UserRepository")
* #ORM\Table(name="fos_user_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
$this->commandes = new \Doctrine\Common\Collections\ArrayCollection();
$this->address = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="FLY\BookingsBundle\Entity\Commandes", mappedBy="user", cascade={"remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $commandes;
/**
* #ORM\OneToMany(targetEntity="FLY\BookingsBundle\Entity\UserAddress", mappedBy="user", cascade={"remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $address;
Here you can see my var dump:
User {#124 ▼
#id: 21
-commandes: null
-address: null
}
I've had an issue which has popped up 2-3 times in the last few years, where the mappings were incorrect but the schema update was successful. After the mappings were fixed this wasn't reflected in the schema and symfony assumed it was already up-to-date.
I recommend you try removing the relevent relationships manually from your user, commande and address tables and then run:
php app/console doctrine:schema:update --force
- it may fix your issue.
Heres an example from one of my apps - I've done this for your commandes entity.
You'll be able to piece together your UserAddress Entity from this example yourself!
Here goes:
User.php
/**
* #ORM\OneToMany(targetEntity="FLY\BookingsBundle\Entity\Commandes", mappedBy="commandesUser")
*/
protected $commandes;
User.php - Getters and Setters
/**
* Add commandes
*
* #param FLY\BookingsBundle\Entity\Commandes $commandes
*/
public function addCommandes(\FLY\BookingsBundle\Entity\Commandes $commandes)
{
$this->commandes[] = $commandes;
}
/**
* Get commandes
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCommandes()
{
return $this->commandes;
}
Commandes.php
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="commandes")
* #ORM\JoinColumn(name="user", referencedColumnName="id")
*/
private $commandesUser;
Commandes.php - Getters and Setters
/**
* Set commandesUser
*
* #param Application\Sonata\UserBundle\Entity\User $commandesUser
*/
public function setCommandesUser(\Application\Sonata\UserBundle\Entity\User $commandesUser = null)
{
$this->commandesUser = $commandesUser;
}
/**
* Get $commandesUser
*
* #return Application\Sonata\UserBundle\Entity\User
*/
public function getCommandesUser()
{
return $this->commandesUser;
}
It's quite likely this doesn't happen to anyone else, but there's a chance.
In my case, this error appeared because there was a duplicate. My entity had 2 fields, which are ManyToOne relationships. And they both had the same inversed names, which gave this error.
So this is the relevant bit of code:
class TaskIngredient
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Ingredient", inversedBy="taskIngredients")
* #ORM\JoinColumn(nullable=false)
*/
private $ingredient;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Task", inversedBy="taskIngredients")
* #ORM\JoinColumn(nullable=false)
*/
private $task;
}
The solution was relatively easy. I tried changing the inversedBy name, manually. However this didn't fix it (even after applying php app/console doctrine:schema:update --force and removing the var/cache folder).
So I just:
Removed one of the problematic entities (and it's setter/getter)
Ran the php bin/console make:entity tool and readded the field with a different name
Voilà! Issue fixed.

Doctrine2 ORM OneToOne not working UPDATE changed to ManyToMany but not fully working

I have 2 tables called Advert and User. The User Id gets written into the Advert Table. I have now created a third table called Bookmark. The table got 2 columns, advert_id and user_id. When I see an Advert I like, I can add a Bookmark to be able to find it easier in my Private Section. When I am in my Private Section to see my Bookmarks, I want to see the Advert Details straight away, so in my Repository I want to create a Join to read the information from the Advert Table. I thought this will be a OneToOne Relationship. I also have to make sure that if the Advert gets deleted, then all the Bookmarks need to be deleted, so I thought its a Bi-directional relationship. So I have below:
Entity/Bookmark.php
/**
* Bookmark
*
* #ORM\Table(name="bookmark")
* #ORM\Entity(repositoryClass="Advert\Repository\BookmarkRepository")
*/
class Bookmark
{
/**
* #var integer
* #ORM\Id
* #ORM\Column(name="advert_id", type="integer", nullable=false)
* #ORM\OneToOne(targetEntity="Advert", mappedBy="bookmark")
* #ORM\JoinColumn(name="advert_id", referencedColumnName="id")
*/
private $advertId;
/**
* #var integer
* #ORM\Id
* #ORM\Column(name="user_id", type="integer", nullable=false)
* #ORM\OneToOne(targetEntity="User")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $userId;
public function setAdvertId($advertId)
{
$this->advertId = $advertId;
return $this;
}
public function getAdvertId()
{
return $this->advertId;
}
public function setUserId($userId)
{
$this->userId = $userId;
return $this;
}
public function getUserId()
{
return $this->userId;
}
Entity\Advert.php
/** Advert
*
* #ORM\Table(name="advert")
* #ORM\Entity(repositoryClass="Advert\Repository\AdvertRepository")
*/
class Advert
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="Bookmark", inversedBy="advert")
* #ORM\JoinColumn(name="id", referencedColumnName="advert_id")
**/
private $bookmark;
public function setBookmark($bookmark)
{
$this->bookmark = $bookmark;
return $this;
}
public function getBookmark()
{
return $this->bookmark;
}
public function addBookmark($bookmark)
{
$this->bookmark->add($bookmark);
}
public function removeBookmark($bookmark)
{
$this->bookmark->removeElement($bookmark);
}
Advert\Repository\Advert\Repository.php
class BookmarkRepository extends EntityRepository
{
public function getBookmarksByUserIds($userId)
{
$query =$this->_em->getRepository($this->getEntityName())->createQueryBuilder('b')
->join('b.advertId', 'a')
->andWhere('a.userId=:userid')
->setParameter('userid',$userId)
;
return $query->getQuery()->getResult();
}
What do I do wrong or where is my missunderstanding? I get the Error Message:
Advert\Entity\Bookmark has no association named advertId
As I said, the table Bookmark gets only filled, when I click on "Add Advert to Bookmarks". I need a Join to be able to display the Advert Details when I click on "Show my Bookmarks" and if an Advert or a User gets deleted, all Bookmarks need to be removed from the Bookmark table. Is this a OneToOne Bi-directional relationship and what is wrong?
UPDATE 1 BELOW NOT WORKING
I have updated the 2 Files below, but I do not get any Bookmarks shown. Instead I should see a list of Bookmarked adverts plus the advert details. I have not even tried yet to get my Service to "Bookmark Advert" or the method to check if an Advert is bookmarked working again. I got it working before, but I guess I am just really confused now.
AdvertController.php
public function watchlistAction()
{
$user_id = $this->zfcUserAuthentication()->getIdentity()->getId();
$adverts = $this->getEntityManager()->getRepository('Advert\Entity\User')->findBookmarksByUserId($user_id);
return new ViewModel(array(
'adverts' => $adverts,
));
}
Repository\UserRepository.php
class UserRepository extends EntityRepository
{
public function findBookmarksByUserId($userId)
{
$query =$this->_em->getRepository($this->getEntityName())->createQueryBuilder('b')
->join('b.bookmarks', 'a')
->join('b.adverts', 'c')
->andWhere('a.user=:userid')
->setParameter('userid',$userId)
;
return $query->getQuery()->getResult();
}
UPDATE 2 BELOW WORKING
You were right, I don't need the UserRepository Query see the List of Bookmarked Adverts. I just had to change the
AdvertController.php
public function watchlistAction()
{
$user_id = $this->zfcUserAuthentication()->getIdentity()->getId();
// get User by reference (no queries executed)
$user = $this->getEntityManager()->getReference('Advert\Entity\User' , $user_id);
$adverts = $user->getBookmarks();
return new ViewModel(array(
'adverts' => $adverts,
));
}
Good news also, in the moment I delete an Advert, the Bookmark gets automatically removed in the Bookmark Database table. Now I only have to find out how to add the Bookmark, so I will have to change my Service. As soon I get this working I will update this post for others to see.
UPDATE 3 BELOW NOT WORKING
Unfortunately I do not get the below 3 Methods in my Service working. I obviously have to now pick 1 record, to either check the Status (Bookmarked already or not), remove the Bookmark (defined by advertId) or add a Bookmark (defined by advertId)
public function checkAdvertBookmarkStatus($advertId)
{
$userId = $this->getUserEntity()->getId();
// get User by reference (no queries executed)
$user = $this->getEntityManager()->getReference('Advert\Entity\User' , $userId);
$bookmarkStatus = $this->getEntityManager()->getRepository('Advert\Entity\User')
->findOneBy(array('advert' => $advertId, 'userId' => $userId));
return $bookmarkStatus;
}
public function saveAdvertBookmark($advertId)
{
$bookmark = new UserEntity();
$userId = $this->getUserEntity()->getId();
// $bookmark->addBookmark($advertId);
$bookmark->setAdvertId($advertId);
$bookmark->setUserId($userId);
# write new bookmmark to database tbl bookmark
$this->getEntityManager()->persist($bookmark);
$this->getEntityManager()->flush();
}
public function removeAdvertBookmark($advertId)
{
$bookmark = new UserEntity();
$userId = $this->getUserEntity()->getId();
$bookmark = $this->getEntityManager()->getRepository('Advert\Entity\Bookmark')
->findOneBy(array('advertId' => $advertId, 'userId' => $userId));
# remove bookmmark from tbl bookmark
$this->getEntityManager()->remove($bookmark);
$this->getEntityManager()->flush();
}
I suppose the answer is in the Tutorial, which I keep reading, but I do not understand it fully. I was able to add Bookmarks before, when I was using the BookmarkEntity, but I have no idea how to do it via the UserEntity
A OneToOne relationship would be the wrong choice here, this would mean that a user can bookmark only one advert and that an advert can only be bookmarked by one user. Because a user should be able to bookmark many adverts and an advert should be bookmarked by many users, you need a ManyToMany relationship.
You idea to create a mapping table bookmarks is not wrong if you work with a database. However, you don't need to create it as an entity in Doctrine. You can simply add Adverts in an association called bookmarks in User to display the bookmarked Adverts and vice versa:
User Entity:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Application\Entity\Advert", inversedBy="bookmarks", cascade={"persist"})
* #ORM\JoinTable(name="bookmarks",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="advert_id", referencedColumnName="id")}
* )
*/
private $bookmarks;
Advert Entity
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Application\Entity\User", mappedBy="bookmarks", cascade={"persist"})
* #ORM\JoinTable(name="bookmarks",
* joinColumns={#ORM\JoinColumn(name="advert_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")}
* )
*/
private $bookmarks;
You might want to read this article, too:
http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-associations.html
Edit: How to add and remove bookmarks
Associations in Doctrine are something entirely different from fields, although both are properties in your Entity. To handle bookmarks you add or remove the Advert Entity in your User Entity directly. For example:
$bookmarks = $user->getBookmarks();
$bookmarks[] = $advert;
This would add a bookmark to the user and will be stored as soon as you persist and flush. To make this even easier, you can define remover and adder:
Use statements:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
and the code:
/**
* #param Collection $bookmarks
*/
public function addBookmarks(Collection $bookmarks)
{
foreach ($bookmarks as $bookmark) {
$this->bookmarks->add($bookmark);
}
}
/**
* #param Collection $bookmarks
*/
public function removeBookmarks(Collection $bookmarks)
{
foreach ($bookmarks as $bookmark) {
$this->bookmarks->removeElement($bookmark);
}
}
You can now remove and add adverts given in collection like this:
$user->addBookmarks(new ArrayCollection(array($advert)));
It is always recommended to define adder and remover in toMany relationships, because many Doctrine components will need them, for example the very useful DoctrineObject, a hydrator used by DoctrineModule for Zend 2

Doctrine 2 OneToOne, load owning side only when neccessary

I have two entities - User and UserSettings. In User entity, I want to have UserSettings as an attribute. That would be OK, I would add a OneToOne relation but there's a problem - because UserSettings is an owning side of the relation, every time I load User entity, Doctrine has to load the UserSettings entity too.
Is there a way how to load User but not UserSettings?
I made maybe a weird solution - there's no relation between these entities and the settings are loaded by method of Facade. For example:
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/** #var UserSettings */
private $settings;
public function __construct()
{
$this->settings = new UserSettings();
}
public function setSettings(UserSettings $settings)
{
$this->settings = $settings;
}
}
/**
* #ORM\Entity
*/
class UserSettings
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(name="user_id", type="integer")
*/
private $userId;
}
class UserFacade
{
/**
* #var EntityManager
*/
private $em; // is injected automatically by DI
public function loadSettings(User $user)
{
$settings = $this->em->getRepository("UserSettings")->findOneBy(array("userId" => $user->id));
$user->setSettings($settings);
}
}
$user = $em->find("User", 1);
// if I want user's settings
$userFacade->loadSettings($user); // now I can use $user->getSettings()->something;
Side note: UserFacade is a service class that manipulates with users' data like adding new user, editing, deleting etc. In my MVC application, controller classes communicate with Facades, not with EntityManager directly.
That's OK - settings are loaded only when I want to. However, there are two possible problems:
a) I don't think this is a clear way
b) When I want a list of users, I cannot JOIN a table where settings are, because entities are not associated, so I have to make an extra SQL for each user.
My question is - how to solve the problem with OneToOne relation? I don't have much experience with Doctrine, so it may be a stupid question - sorry for that.
Thanks!

Is there a better way to handle the Doctrine proxy object

I have two classes linked with a one-to-one relation.
class Client {
...
/**
* #ORM\OneToOne(targetEntity="ClientInfo")
* #ORM\JoinColumn(name="id", referencedColumnName="client_id")
*/
private $info;
...
public function doSomething() {
if (!$this->getInfo() instanceof ClientInfo) {
return false;
}
return $this->getInfo()->doSomething();
}
...
}
class ClientInfo {
...
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
...
public function doSomething() {
return 'something';
}
...
}
Those classes are loaded with database content with Doctrine. It is working perfectly when there is data in the database. But if there is not ClientInfo data, I have a \Doctrine\ORM\EntityNotFoundException raised.
So I changed the doSomething() method to take this into account.
public function doSomething() {
if (!$this->getInfo() instanceof ClientInfo) {
return false;
}
try {
return $this->getInfo()->doSomething();
} catch (\Doctrine\ORM\EntityNotFoundException $e) {
return false;
}
}
But it does not feel right to me since it is tied with Doctrine. I am trying to modify my unit tests to add a mock of the proxy object but it does not feel right either.
Is there a better way of doing that?
EDIT 1
I followed Nico Kaag suggestion but it does not change anything.
My constructor in my Client class look like this:
public function __construct() {
$this->info = new ClientInfo();
}
If I do a var_dump of $this->info after retrieving my object with Doctrine, this is what I get.
object(Proxies\__CG__\MyBundle\Entity\ClientInfo)[444]
public '__initializer__' =>
object(Closure)[461]
public '__cloner__' =>
object(Closure)[462]
public '__isInitialized__' => boolean false
private 'client' (MyBundle\Entity\ClientInfo) => string '21055' (length=5)
...
EDIT 2
I finally changed what I have done. I removed the try..catch block and change the query to retrieve objects from database. Now I force the query to retrieve the ClientInfo object at the same time as the Client object.
This way, I can trust my test and if I forget to query both objects simultaneously, I will have an exception to remind it to me.
See I have made classes for you.
use Doctrine\ORM\Mapping AS ORM;
/**
* #ORM\Entity
*/
class client
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="Entities\client_info", inversedBy="client")
* #ORM\JoinColumn(name="client_info_id", referencedColumnName="id", unique=true)
*/
private $clientInfo;
}
use Doctrine\ORM\Mapping AS ORM;
/**
* #ORM\Entity
*/
class client_info
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="Entities\client", mappedBy="clientInfo")
*/
private $client;
}
Try this, you will not get such issue.
Also I have used bi-directional relation with cardinality one-to-one, parent connection 0:1*- (parent optional), please see the diagram.
Suggetion : Use ORM designer tool for designing and extracting entity classes.

Categories