Symfony : many-to-many with where clause on child - php

Many users can have many items. But I want to get a list of specific items owned by specifics users.
I have two entities :
User Entity :
class User
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Many Users have many items
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Item", fetch="EAGER")
* #ORM\JoinTable(name="users_items",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="item_id", referencedColumnName="id")}
* )
*/
private $items;
}
Item Entity :
class Item
{
/**
* #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=10)
*/
private $name;
}
Doctrine then created three tables :
items
users
users_items
I want to get all the items named "Pencil" owned by userId 11.
How can I achieve that ?
Thank you.

I hope that you realize that you are using an UNIdirectional many-to-many relation between User and Item. User 'knows' what items belong to him but Item does not know which users use this item. Therefore you can retrieve a single User from the database (by user-id) with a collection of items that are filtered by name. If the user-id does not exist or the user has no items that match the filter, then $user will be NULL. instead of the exception in the controller in my example you could use a if-else statement in twig to verify if user is null or not.
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
/**
* #Route("/someroute", name="some_route")
*/
public function someAction()
{
$em = $this->getDoctrine()->getManager();
// for this example two hardcoded parameters. $user might be NULL !
$user = $em->getRepository('AppBundle:User')->getItemsWithNameForUser(1, 'Pencil');
if(null === $user)
{
throw $this->createNotFoundException('This user does not have the selected items or the user does not exist');
}
return $this->render('default/index.html.twig', [
'user' => $user
]);
}
}
and the UserRepository:
namespace AppBundle\Repository;
class UserRepository extends \Doctrine\ORM\EntityRepository
{
public function getItemsWithNameForUser($userId, $itemName)
{
return $this->getEntityManager()->createQuery(
'SELECT u,i FROM AppBundle:User u JOIN u.items i WHERE u.id=:id AND i.name=:name'
) ->setParameter('id', $userId)
->setParameter('name', $itemName)
->getOneOrNullResult();
}
}

I think it should be something like:
$items= $em->getDoctrine->createQueryBuilder()
->select('[items]')
->from(User::class, 'users')
->join('users.items', 'items')
->where('users.id = :userId')
->setParameter('userId', $theId)
->getQuery()
->getResult();
I am not sure this works (syntax is maybe not perfect), but theoretically it should, the problem in your case is that the owning side only knows about the relation and you want to query from the other side.

Related

"NOT EXISTS" Query with Many to Many Relation Doctrine Symfony3

I would like to build a query that brings me all the games for a logged in user that he has not yet joined. For this I have built these 2 Entities. They are connected by many to many.
class Game
{
public function __construct()
{
$this->users = new ArrayCollection();
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #var Users[]
*
* #ORM\ManyToMany(
* targetEntity="Domain\Entity\User",
* inversedBy="user",
* fetch="EAGER"
* )
*/
private $users;
/**
* #return array
*/
public function getUsers() : array
{
return $this->users->getValues();
}
/**
* #param User $users
*/
public function setUser($users)
{
if(is_array($users)){
/** #var User $user */
foreach ($users as $user){
$this->users->add($user);
}
} else {
$this->users->add($users);
}
}
}
And the User Entity
class User implements AdvancedUserInterface
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
The Entities has more attributes but i think they are not important.
Also I tried these Query, but it doesn't work.
/**
* #param User $user
* #return array
*/
public function fetchAllNonActivatedWhereYouNotJoin(User $user): array
{
$qb = $this->createQueryBuilder('g');
$qb->select('g')
->innerJoin('g.users', 'u')
->where('u.id != :user')
->andWhere('g.activate = 0')
->setParameter('user', $user->getId())
->getQuery()->getResult();
return $qb->getQuery()->getResult();
}
Does anyone know a solution? Its Symfony 3 and Doctrine in PHP 7.1
One way to do it is left join the 2 entities starting from the game repository as you do, with a join condition to the logged in user and have a condition that users is empty:
$qb->leftJoin('g.users', 'u', Join::WITH, 'u.id = :user')
->andWhere('g.activate = 0')
->having('COUNT(u) = 0')
->groupby('g')
->setParameter('user', $user->getId())
->getQuery()->getResult();
This works because of doctrine hydration, which hydrates the users property on the limited joined query(in this case each game will either have the logged in user or not in the users collection).
There are also other ways to achieve this
Be careful with this if you are doing consecutive queries with the query builder, as the entity manager keeps references to the already hydrated relations. Reference of the issue

How to query related entities based on their value

I have Ads on my website. For an Ad to be publicly displayed, it must be paid for. An ad is public or paid for if it has no payment that is unpaid, or all its payments are paid for.
What I want is to retrieve all public ads, but that requires a large number of queries using this method. I'm looking for a better way.
This has more fields, but let's focus on the payments field. It's a OneToMany.
class Ad
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255,nullable=true)
*/
private $adTitle;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255,nullable=true)
*/
private $description;
/**
* #ORM\ManyToOne(targetEntity="Application\FOS\UserBundle\Entity\User")
* #ORM\JoinColumn(name="advertiser")
*/
private $advertiser;
/**
* #ORM\OnetoMany(targetEntity="MyBundle\Entity\Payment",mappedBy="ad",cascade={"persist"})
* #ORM\JoinColumn(name="payments", referencedColumnName="id",nullable=true)
*/
private $payments;
}
For payments, I'm using PayumBundle, so the entity looks like so:
use Payum\Core\Model\Payment as BasePayment;
class Payment extends BasePayment
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*
* #var integer $id
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\Ad",inversedBy="payments",cascade={"persist"})
* #ORM\JoinColumn(name="ad", referencedColumnName="id",nullable=true)
*/
protected $ad;
}
Currently I'm using a service that does something like this:
public function getAllPublicAds()
{
$allAds = $this->em->getRepository('MyBundle:Ad')->findAll();
$publicAds=array();
foreach($allAds as $ad){
if($this->isPublicAd($ad)) array_push($publicAds,$ad);
}
return $publicAds;
}
I'm getting all the ads, and if they are public, I put them into an array which will later go to the twig view.
An Ad is public if all of its payments are paid, therefore:
public function isPublicAd(Ad $ad)
{
$payments = $ad->getPayments();
if (!count($payments)) {
return false;
} else {
foreach ($payments as $payment) {
if (!$this->paidPayment($payment)) return false;
}
return true;
}
}
And finally, a paid Payment is paid if its details field 'paid' in the table is true. This is how PayumBundle works. It creates a token, then after you use that token to pay for your object, it fills that object's payment field details with details about the payment. You can find the class here. My Payment class from above extends it. So I'm checking whether those details have been filled, or if the 'paid' field is true.
public function paidPayment(Payment $payment)
{
$details = $payment->getDetails();
if (array_key_exists("paid", $details) && $details["paid"]) return true;
else return false;
}
The problem is that this creates a very big and unnecessary number of queries to the database. Is it possible to shorten this out? I'm not sure I can use normal queries without Doctrine, because I need to check the details['paid'] field, whcih I can only do in a controller or service, after I've received the database results. If that is even possible to do inside a query, I don't know either nor seem to find a way to do it.
You should use QueryBuilder instead of findAll method. In such case you can specify conditions which need to be match (just like in plain sql).
So:
$paidAds = $this->em->getRepository('MyBundle:Ad')
->createQueryBuilder('a')
->innerJoin('a.Payments', 'p')
->getQuery()
->getResult();
I didn't analyze deep your db structure by in fact innerJoin might be only condition which you need since you will get only those Ads which have relation to payments
To read more about QueryBuilder check doc

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

Filtering on many-to-many association with Doctrine2

I have an Account entity which has a collection of Section entities. Each Section entity has a collection of Element entities (OneToMany association). My problem is that instead of fetching all elements belonging to a section, I want to fetch all elements that belong to a section and are associated with a specific account. Below is my database model.
Thus, when I fetch an account, I want to be able to loop through its associated sections (this part is no problem), and for each section, I want to loop through its elements that are associated with the fetched account. Right now I have the following code.
$repository = $this->objectManager->getRepository('MyModule\Entity\Account');
$account = $repository->find(1);
foreach ($account->getSections() as $section) {
foreach ($section->getElements() as $element) {
echo $element->getName() . PHP_EOL;
}
}
The problem is that it fetches all elements belonging to a given section, regardless of which account they are associated with. The generated SQL for fetching a section's elements is as follows.
SELECT t0.id AS id1, t0.name AS name2, t0.section_id AS section_id3
FROM mydb.element t0
WHERE t0.section_id = ?
What I need it to do is something like the below (could be any other approach). It is important that the filtering is done with SQL.
SELECT e.id, e.name, e.section_id
FROM element AS e
INNER JOIN account_element AS ae ON (ae.element_id = e.id)
WHERE ae.account_id = ?
AND e.section_id = ?
I do know that I can write a method getElementsBySection($accountId) or similar in a custom repository and use DQL. If I can do that and somehow override the getElements() method on the Section entity, then that would be perfect. I would just very much prefer if there would be a way to do this through association mappings or at least by using existing getter methods. Ideally, when using an account object, I would like to be able to loop like in the code snippet above so that the "account constraint" is abstracted when using the object. That is, the user of the object does not need to call getElementsByAccount() or similar on a Section object, because it seems less intuitive.
I looked into the Criteria object, but as far as I remember, it cannot be used for filtering on associations.
So, what is the best way to accomplish this? Is it possible without "manually" assembling the Section entity with elements through the use of DQL queries? My current (and shortened) source code can be seen below. Thanks a lot in advance!
/**
* #ORM\Entity
*/
class Account
{
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Section")
* #ORM\JoinTable(name="account_section",
* joinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="section_id", referencedColumnName="id")}
* )
*/
protected $sections;
public function __construct()
{
$this->sections = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Section
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="MyModule\Entity\Element", mappedBy="section")
*/
protected $elements;
public function __construct()
{
$this->elements = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Element
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var Section
* #ORM\ManyToOne(targetEntity="MyModule\Entity\Section", inversedBy="elements")
* #ORM\JoinColumn(name="section_id", referencedColumnName="id")
*/
protected $section;
/**
* #var \MyModule\Entity\Account
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Account")
* #ORM\JoinTable(name="account_element",
* joinColumns={#ORM\JoinColumn(name="element_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")}
* )
*/
protected $account;
// Getters and setters
}
If I understand correctly, you want to be able to retrieve all Elements of all Sections of an Account, but only if those Elements are associated with that Account, and this from a getter in Account.
First off: An entity should never know of repositories. This breaks a design principle that helps you swap out the persistence layer. That's why you cannot simple access a repository from within an entity.
Getters only
If you only want to use getters in the entities, you can solve this by adding to following 2 methods:
class Section
{
/**
* #param Account $accout
* #return Element[]
*/
public function getElementsByAccount(Account $accout)
{
$elements = array();
foreach ($this->getElements() as $element) {
if ($element->getAccount() === $account) {
$elements[] = $element->getAccount();
}
}
return $elements;
}
}
class Account
{
/**
* #return Element[]
*/
public function getMyElements()
{
$elements = array()
foreach ($this->getSections() as $section) {
foreach ($section->getElementsByAccount($this) as $element) {
$elements[] = $element;
}
}
return $elements;
}
}
Repository
The solution above is likely to perform several queries, the exact amount depending on how many Sections and Elements are associated to the Account.
You're likely to get a performance boost when you do use a Repository method, so you can optimize the query/queries used to retrieve what you want.
An example:
class ElementRepository extends EntityRepository
{
/**
* #param Account $account [description]
* #return Element[]
*/
public function findElementsByAccount(Account $account)
{
$dql = <<< 'EOQ'
SELECT e FROM Element e
JOIN e.section s
JOIN s.accounts a
WHERE e.account = ?1 AND a.id = ?2
EOQ;
$q = $this->getEntityManager()->createQuery($dql);
$q->setParameters(array(
1 => $account->getId(),
2 => $account->getId()
));
return $q->getResult();
}
}
PS: For this query to work, you'll need to define the ManyToMany association between Section and Account as a bidirectional one.
Proxy method
A hybrid solution would be to add a proxy method to Account, that forwards the call to the repository you pass to it.
class Account
{
/**
* #param ElementRepository $repository
* #return Element[]
*/
public function getMyElements(ElementRepository $repository)
{
return $repository->findElementsByAccount($this);
}
}
This way the entity still doesn't know of repositories, but you allow one to be passed to it.
When implementing this, don't have ElementRepository extend EntityRepository, but inject the EntityRepository upon creation. This way you can still swap out the persistence layer without altering your entities.

Doctrine2 Class Table Inheritance

I am a little bit confused by Doctrine's documentation so maybe you can help me. I have the following class inheritance:
<?php
class User
{
/**
* #var int
*/
private $_id;
/**
* #var Role
*/
private $_role;
}
class Company extends User
{
}
class Customer extends User
{
...
}
class Role
{
/**
* #var int
*/
private $_id;
}
?>
I want to store each class in a separate table. The role defines the type of user by an id. How would I solve this problem? I tried this:
<?php
/**
* #Entity
* #Table(name="user")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="role_id", type="integer")
* #DiscriminatorMap({"1" = "User", "2" = "Customer"})
*/
class User
{
...
}
?>
I am not sure how to handle the role class in this scenario.
Thank you for your answer. Now I tried this and got following error:
[Doctrine\DBAL\Schema\SchemaException]
There is no column with name '_id' on table 'customer'.
I have following code:
<?php
/**
* Class Sb_User
*
* #Entity
* #Table(name="user")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="role_id", type="integer")
* #DiscriminatorMap({"2" = "Sb_Customer", "8" = "Sb_Pos"})
*/
class Sb_User implements Sb_User_Interface
{
/**
* #Id
* #GeneratedValue
* #Column(name="id", type="integer")
* #var int
*/
protected $_id;
...
}
/**
* Class Sb_Customer
*
* #Entity
* #EntityResult(discriminatorColumn="role_id")
* #Table(name="customer")
*
*/
class Sb_Customer extends Sb_User implements Sb_Customer_Interface
{
....
}
I do not know what I am doing wrong. Can you help me?
?>
Your question is a bit confusing to me.
You want to use joined table inheritance to define the role of a user, but you also want to add a role attribute to your user that, as I understand, does exactly the same. Seems like you're trying to do the same thing twice in a different way.
Anyway, I will try to give you an answer.
If you want to use separate tables for each type (Customer, Company, etc) you should have a look at mapped superclasses:
http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html#mapped-superclasses
That way you can define basic class attributes and relations that will be used by all the entities that extend it. All data will be saved in separate tables for each entity.
If you want to define the role of a user using the Role entity you should define a many-to-one relation between user and role.
Good luck!

Categories