Doctrine many-to-many association is not resolved - symfony

When i am using a Many-To-Many relationship in Symfony2 using Doctrine ORM i get the problem that my many-to-many relationship is not resolved.
Example:
Class A:
/**
* #ORM\ManyToMany(targetEntity="StoreItem", mappedBy="itemOptions")
*/
protected $storeItems;
Class B:
/**
* #ORM\ManyToMany(targetEntity="StoreItemOption", inversedBy="storeItems")
* #ORM\JoinTable(name="store_item_itemoptions")
*/
protected $itemOptions;
now i store the object in a session, note that i did not called the many to many relationship yet by using
->getItemOptions()
When i get my session object now and do ->getItemOptions() then it is empty.
Anybody has an idea what is causing this?
(PS: I found a hacky solution by saying that when i add an item to my cart i do a empty foreach that calls the method ->getItemOptions())

This is called 'lazy loading', and is a doctrine feature designed to reduce memory overhead.
You can set loading to 'eager' or explicitly add a select for the other field in your DQL to avoid lazy loading: e.g.:
$objectsA=$em->createQueryBuilder('\Class\A', 'a')
->join('a.b', 'b')
->addSelect('b')
->getQuery()
->getResult();

Related

Doctrine select many from the one (many-to-one unidirectional (different bundles))

Working on a legacy project which restricts the options available, has left me in a situation where I need to solve the following problem, ideally with doctrine.
I have two entities in different bundles that have a unidirectional many-to-one link.
BundleA has dependency on BundleB and the entities are linked similar to this:
BundleA/Entity/TheMany:
/**
* #var TheOne $theOne
* #ORM\ManyToOne(targetEntity="BundleB\Entity\TheOne")
* #ORM\JoinColumn(name="theone_id", referencedColumnName="id", onDelete="SET NULL")
*
*/
private $theOne;
From BundleB I now need to select all TheOne entities, and for each I need all of the TheMany entities.
The query also needs to be sortable on any property of TheOne entity, or the count of related TheMany entities.
It is fairly simple in Doctrine to build a query which brings back all TheOne entities and one of TheMany for each... however I am having some difficulty coming up with a Doctrine query that will bring back all of the related TheMany entities rather than just one.
I was hoping someone might have encountered a similar issue and therefore have some insight?
This may not have been explained clearly enough, in which case please direct me to explain further.
In the end I was able to achieve what I needed by using GROUP_CONCAT (which required inclusion of https://github.com/beberlei/DoctrineExtensions).
The query looks something like this:
$queryBuilder->select(
'to,
GROUP_CONCAT(DISTINCT tm.id SEPARATOR \',\') as theManyIds,
COUNT(DISTINCT tm.id) as HIDDEN theManyCount'
)
->from('BundleB\Entity\TheOne', 'to')
->leftJoin(
'BundleA\Entity\TheMany',
'tm',
Join::WITH,
'to.id = tm.theOne'
)
->groupBy('to.id')
->orderBy($sortString, $direction)
->setFirstResult($start)
->setMaxResults($limit);
I compromised by accepting the consequences of linking the two bundles - however that could have been avoided by making use of Native SQL and Result Set Mapping (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html).
So what you are trying to do is to get all the ones and for each one find all the many. But you want to put all the many in one array or you want to create an array of array for the entities ? (what i did here)
$em = $this->getDoctrine()->getManager();
$theones = $em->getRepository('BundleA:theOne')
->createQueryBuilder('p')
->OrderBy(//your ordering)
->getQuery()
->getArrayResult()
$theManies = [];
for($theones as $theOne){
$theManies [] = $em->getRepository('BunbleB:theMany')
->createQueryBuilder('p')
->Where('p.theOne = :theOne')
->setParameter('theOne', $theOne)
->getQuery()
->getArrayResult();
$finalOnes[$theOne->getId()] = sizeof($theManies)
}
asort($finalOnes);
return array_keys($finalOnes);

Doctrine ManyToMany, find related and no related results

I have an entity called tournament to which users can register to participate in.
The relationship between the two entities is ManyToMany and need to create a view of Symfony2 in which list all tournaments, with or without registered users so that they can join.
This is my DoctrineQueryBuilder
$em->createQueryBuilder('d')
->select('d, i, u')
->leftJoin('d.item','i')
->leftJoin('d.users','u')
->where('d.active = 1')
->andWhere('d.state = 1')
->orderBy('d.dateStart', 'ASC');
I also need to get the number of users who have joined the tournament.
Preamble
There are various ways to achieve what you want. You can create a sub-query to do the count, however a simpler solution is to let doctrine handle this for you.
The solution described below is based on Doctrine lazy/eager loading capability. When doctrine loads an entity, it will also populate it's associations, either lazily or eagerly (default is lazy).
Solution
Assuming your Tournament entity maps the users association as a ManyToMany relation. You can create a new method which counts your Tournament->users.
ManyToMany associations would populate the entity property (in this cases $users) with an ArrayCollection.
A method called countUsers would do the trick, example implementation below:
...
class Tournament {
...
/**
* #ManyToMany(targetEntity="User")
* #JoinTable(name="tournament_users",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="tournament_id", referencedColumnName="id"}
* )
**/
private $users;
...
public function countUsers(){
return $this->users->count();
}
In the view when iterating over the collection of tournaments, simply call the $tournament->countUsers() method to display the count.
References:
ArrayCollection: http://www.doctrine-project.org/api/common/2.1/class-Doctrine.Common.Collections.ArrayCollection.html

Doctrine "A new entity was found through the relationship" error

First off I want to say I've read through all the docs and googled this plenty before posting this question. I know what that error means (un-persisted entity in a relationship)
I'm getting this error where I think I shouldn't be getting it.
I have a OneToMany Bi-Directional relationship as follow:
Class Channel
{
/**
* #ORM\OneToMany(targetEntity="Step", mappedBy="channel", cascade={"all"}, orphanRemoval=true)
* #ORM\OrderBy({"sequence" = "ASC"})
*/
protected $steps;
}
Class Step
{
/**
* #ORM\ManyToOne(targetEntity="Channel", inversedBy="steps")
*/
protected $channel;
}
One Channel can have many Steps and the owning side is Channel. After I upgraded from Doctrine 2.4 to 2.5 I'm getting this error:
Doctrine\ORM\ORMInvalidArgumentException: A new entity was found
through the relationship 'Company\MyBundle\Entity\Step#channel' that
was not configured to cascade persist operations for entity
why is it even finding new relationships from the inverse side? Here's my code:
$channel = new Channel();
$step = new Step();
$channel->addStep($step);
$em->persist($channel);
$em->flush();
Thanks!
You're right: Doctrine looks only for changes into owning side but you're wrong: owning side of your relationship is Step, not Channel.
Why is step the owning side? Because is the entity that has foreign key. Even Doctrine documentation says to you
The owning side has to use the inversedBy attribute of the OneToOne,
ManyToOne, or ManyToMany mapping declaration. The inversedBy attribute
contains the name of the association-field on the inverse-side.
Possible solutions:
Try to invert cascade operations by putting cascade={"all"} into Step entity (are you sure that all is the correct choice?)
Persist explicitly both entities:
$channel = new Channel();
$step = new Step();
$channel->addStep($step);
$em->persist($channel);
$em->persist($step);
$em->flush();
here you can read why second way provided here is fine too
You try to persist $channel, but it has Step entity inside. So in Doctrine now you have 2 entities that are queued for inserting. Then Doctrine order entities in the order where first is Step because it has channel_id foreign key (that is empty now). Doctrine try persist this entity and when it understands that channel_id is empty it sees for cascade rules for persisting. It doesn't see any cascade rules and throw you this exception.

How to handle SQL triggers in Symfony2?

I have a User entity in my Symfony2/Doctrine2 webapp. This user has an attribute last_updated to identify the latest time, anything has changed. I set this attribute to NOT NULL in my database. So far, so good.
I would consider it to be good practice to create a SQL trigger in the database, that sets this last_updated to NOW() on every INSERT or UPDATE. So you don't have to care about this in your application. So that's what I did, I implemented this trigger in my database.
But if I now create a user in my app
$user = new User();
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
I get an error message by Symfony:
An exception occurred while executing 'INSERT INTO User (username, ..., last_updated) VALUES (?, ..., ?)'
with params ["johndoe", ..., null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'last_updated' cannot be null
The problem is clear: Symfony is trying to fire an INSERT-statement to the database with the parameter null for last_updated, which is not allowed - as this attribute may not be null.
I could quickly think of two workarounds:
One workaround would be to take the last_updated field out of the entity description. Then Symfony would not try to pass anything to the database for this column, and the trigger would set the appropriate value. But I don't think this is a good way, because as soon as I would try to update the db schema (doctrine:schema:update --force) I would loose my last_updated-column.
Another workaround: Simply do $user->setLastUpdated(new \DateTime()) before I persist() and flush(). But this would minimize the advantage of using a trigger on my database to avoid having to care about it in my application.
Is there any way to let Symfony/Doctrine know that there is a trigger running on my database? If not, (how) can I hook into Symfony/Doctrine to implement a proper workaround?
To quote a response to this question from a google group:
Database side code (such as Triggers and Functions) tend to break the benefits of developing software using an ORM like Propel or Doctrine as one of the biggest advantages of using ORM's is to be database agnostic. By having database side Triggers and Functions you are tying yourself to the database and therefore gain little to no benefit using an ORM. -GarethMc
https://groups.google.com/forum/#!topic/symfony-users/MH_ML9Dy0Rw
For this it is best to use the Life Cycle Callbacks as Faery suggests. One simple function will handle updating that field so that you dont have to worry about it if you decide to change databases in the future.
//In Your Entity File EX: SomeClass.php
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
*/
class SomeClass
{
....
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function prePersistPreUpdate()
{
$this->last_modified = new \DateTime();
}
}
See also references for lifecycle callbacks
Symfony reference
Doctrine reference
In your case you would add the lifecycle call back function and annotation to your User entity class. SomeClass is simply an example class showing that lifecycle callbacks are good for more than just your User entity.
Another (easier and more generalized) option would be to use the Timestampable Doctrine extension by Gedmo. In this way, you could simply annotate your entity fields to be timestamped on create or on update.
Example:
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
class MyEntity
{
...
/**
* #var \DateTime $lastUpdated
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="last_updated", type="datetime")
*/
private $lastUpdated;
...
}
https://packagist.org/packages/gedmo/doctrine-extensions

Doctrine one-to-many situation: how to easily fetch related entities

To simplify, two entities are defined: User and Comment. User can post many comments and every comment has only one user assigned, thus Comment entity has:
/**
* #var \Frontuser
*
* #ORM\ManyToOne(targetEntity="Frontuser")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ownerUserID", referencedColumnName="id")
* })
*/
private $owneruserid;
However, when in action:
$orm = $this->getDoctrine()->getManager();
$repo = $orm->getRepository('CompDBBundle:Comment');
$repo->findBy(array('owneruserid' => $uid);
Error occured, that there's no such field like owneruserid.
How can I fetch all the user's comments then? The same happens to similar relations in my DB - looks likes you cannot run find() with foreign keys as parameters. I believe a function $user->getComments() should be automatically generated/recognised by Doctrine to allow efficient, quick access to related entities.
The example's simple but, what if there are more entities related to my User in the same way? Do I have to declare repositories for each and try to fetch them by it's owneruserid foreign keys?
Using doctrine, when you define a related entity it's type is the entity class (in this case FrontUser). Therefore firstly your related entity variable name is misleading. It should be e.g.
private $ownerUser;
Then, in order to do a findBy on a related entity field you must supply an entity instance e.g.
$orm = $this->getDoctrine()->getManager();
$userRepo = $orm->getRepository('CompDBBundle:FrontUser');
$user = $userRepo->findById($uid);
$commentRepo = $orm->getRepository('CompDBBundle:Comment');
$userComments = $commentRepo->findByOwnerUser($user);
If you don't have or want to retrieve the user entity you could use a DQL query with the 'uid' as a parameter instead.

Resources