I have a OneToMany Unidirectional relationship between an "Employee" and "Status".
There is then also a ManyToMany biderectional relationship between "Employee" and "Documents".
When I have my a Document, i am trying to find all related employees ($Document->getEmployees()) and then "filter" (using ->matching(Criteria)) by the "Status"
I keep getting the below error:
2018-04-05T14:35:19+00:00 [error] Error thrown while running command "app:expiration-check". Message: "Notice: Undefined index: Status"
In DefaultQuoteStrategy.php line 39:
Notice: Undefined index: Status
Here is the Code i am using:
$Employees = $Document->getEmployees()->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('Status',$this->GlobalSettings->getApprovedEmployeeStatus()))
);
Interestingly enough, the exact same criteria works if i am using the Employee Repository
$Employees = $this->em->getRepository(Employee::class)->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('Status',$this->GlobalSettings->getApprovedEmployeeStatus()))
);
Matching static fields also works fine.
$Employees = $Document->getEmployees()->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('FirstName',"Keven"))
);
Here is the Status Column defintion
/**
* #ORM\ManyToOne(targetEntity="Entity\Accounts\EmployeeStatus")
* #ORM\JoinColumn(name="StatusId", referencedColumnName="id", nullable=false)
*/
private $Status;
Here is the Employees Defintion (on Document Entity)
/**
* #ORM\ManyToMany(targetEntity="Entity\Accounts\Employee", mappedBy="Documents")
*/
private $Employees;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->Employees = new \Doctrine\Common\Collections\ArrayCollection();
}
and Here is the getEmployees() (also on Document)
/**
* Get employees.
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEmployees()
{
return $this->Employees;
}
To manage ManyToMany relations, doctrine uses Doctrine\ORM\Persisters\Collection\ManyToManyPersister class.
You can see it being used here
Unfortunately, currently in the latest release, v2.6.1, method loadCriteria of this class is lacking the feature to use relation fields. Only static fields are supported.
Looking at the master branch currently, this support has been added: Doctrine\ORM\Persisters\Collection\ManyToManyPersister as of today
but it is not part of a release yet. Also having a quick look at 2.7 branch it does not look it will be there.
I am not sure whether you could use the master branch with symfony `s doctrine bundle. I think it will be difficult to get this to work now.
What you could do, is initialize the ManyToMany collection $Document->getEmployees() and then use the matching function, which means that you load all employees and then filter, not lazy load as you would expect.
So do:
$employees = $Document->getEmployees();
$employees->initialize();
$employees->matching(
Criteria::create()
->andWhere(Criteria::expr()->eq('Status',$this->GlobalSettings->getApprovedEmployeeStatus()))
);
and put a note to change the code, when the new changes are released.
Related
I'm actually learning Symfony3 and more precisely the Doctrine2 relation between objects and I was wondering if there is a default value for the cascade parameter when you don't explicite it.
I seen in tutorials when it's necessary to use the remove value that the parameter is not specified, but there is no explanation about this fact.
So I mean is this
/**
* #ORM\ManyToOne(targetEntity="UTM\ForumBundle\Entity\UtmWebsiteTopics")
* #ORM\JoinColumn(nullable=false)
*/
private $topic;
equivalent to that ?
/**
* #ORM\ManyToOne(targetEntity="UTM\ForumBundle\Entity\UtmWebsiteTopics", cascade={"remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $topic;
Thank you for reading and I hope you'll be able to bring me an answer. :D
In short, those two snippets are not the same. If you were to want to delete a specific entity that has relations to others through FK, you would need to explicitly remove() the related entities to avoid Integrity Constraint Violations.
Examples of each
Not defining cascade={"remove"}
public function removeEntityAction($id)
{
// Get entity manager etc....
$myEntity = $em->getRepository("MyEntity")->findBy(["id" => $id]);
foreach($myEntity->getTopics() as $topic) {
$em->remove($topic);
}
$em->remove($myEntity);
}
Defining cascade={"remove"}
public function removeEntityAction($id)
{
// Get entity manager etc....
$myEntity = $em->getRepository("MyEntity")->findBy(["id" => $id]);
$em->remove($myEntity);
}
Doctrine Cascade Operations
Doctrine - Removing Entities
I work with Symfony2 and Doctrine and I have a question regarding entities.
In a performance worries, I'm wondering if it is possible to use an entity without going all the associations?
Currently, I have not found another way to create a model inheriting the class with associations and associations specify NULL in the class that inherits.
thank you in advance
OK, a little detail, it's for a API REST (JSON).
This is my class :
/**
* Offerequipment
*
* #ORM\Table(name="offer_equipment")
* #ORM\Entity(repositoryClass="Charlotte\OfferBundle\Repository\Offerequipment")
*/
class Offerequipment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\OfferBundle\Entity\Offer")
* #ORM\JoinColumn(name="offer_id", referencedColumnName="id")
*/
private $offer;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\ProductBundle\Entity\Equipment")
* #ORM\JoinColumn(name="equipment_id", referencedColumnName="id")
*/
private $equipment;
/**
* #VirtualProperty
*
* #return String
*/
public function getExample()
{
return $something;
}
and with QueryBuilder method, i can't get my virtual properties or getters.
Thanks for your help :)
Look at Serialization.
By serialising your entities, you can choose to exclude or expose a property of an entity when you render it.
Look at the Symfony built-in Serializer and/or JMSSerializer.
Otherwise, you can use QueryBuilder and DQL to choose what fields you want to fetch in your queries.
Like this, you can make your own find method in the Repository of your entities.
// src/AcmeBundle/Repository/FooRepository
class FooRepository extends \Doctrine\ORM\EntityRepository
// ...
public function find($id) {
$queryBuilder = $this->createQueryBuilder('e')
->select('e.fieldA', 'e.fieldB') // selected fields
->where('e.id = :id') // where statement on 'id'
->setParameter('id', $id);
$query = $queryBuilder->getQuery();
$result = $query->getResult();
}
// ...
}
Don't forget define the Repository in the corresponding Entity.
/**
* Foo.
*
* #ORM\Entity(repositoryClass="AcmeBundle\Repository\FooRepository")
*/
class Foo
{
// ...
}
By default Doctrine will not automatically fetch all of the associations in your entities unless you specifically each association as EAGER or unless you are using a OneToOne association. So if you are looking to eliminate JOINs, you can just use Doctrine in its default state and it won't JOIN anything automatically.
However, you this will not alleviate all of your performance concerns. Say, for example, you are displaying a list of 50 products in your application on a single page and you want to show their possible discounts, where discounts are an association on your product entity. Doctrine will create 50 additional queries just to retrieve the discount data unless you explicitly join the discount entity in your query.
Essentially, the Symfony profiler will be your friend and show you when you should be joining entities on your query - don't just think that because you aren't joining associations automatically that your performance will always be better.
Finally, after many days, I've found the solution to select only one entity.
VirtualProperties are found :)
public function findAllByOffer($parameters)
{
$queryBuilder = $this->createQueryBuilder('oe');
$queryBuilder->select('oe, equipment');
$queryBuilder->join('oe.equipment', 'equipment');
$result = $queryBuilder->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)->getResult();
return $result;
}
I am creating some Fixtures in Symfony2 using Doctrine. I get the following error:
Integrity constraint violation: 1062 Duplicate entry '206-411' for key 'PRIMARY'
when I try to persist a many-to-many unidirectional association.
I understand the error, but I'm confused: isn't obvious that some IDs are duplicate in a many-to-many relationship?
If I'm wrong please correct me. I put my code below, any clarification is welcome.
Fixture file:
namespace sociaLecomps\SuperBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class LoadAssociationsData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null){
$this->container = $container;
}
public function load(ObjectManager $manager)
{
$em = $this->container->get('doctrine')->getManager('default');
/*
* COURSE - STUDENT ASSOCIATION
*/
$courses = $em->createQuery('SELECT c FROM sociaLecompsSuperBundle:Course c')->getResult();
$students = $em->createQuery('SELECT s FROM sociaLecompsSuperBundle:Student s')->getResult();
$i=0;
for($j=0; $j<count($courses); $j++){
$course = $courses[$j];
//here I'm adding two different students to the same course
$s = array($students[$i], $students[$i++]);
$course->setSubscribedStudents($s);
$em->persist($course);
$i++;
}
$manager->flush();
}
}
Relationship declaration in Course class:
/**
* #ORM\ManyToMany(targetEntity="Student")
* #ORM\JoinTable(name="relation_course_student",
* joinColumns={#ORM\JoinColumn(name="course_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="student_id", referencedColumnName="id")}
* )
**/
private $subscribed_students;
public function __construct() {
$this->subscribed_students = new ArrayCollection();
}
Entities Student and Course are created, also with Fixtures, before attempting to create the association.
If I try to insert only one student per course it all works smoothly.
I see that your courses entities already exist because you're fetching them directly from the database ($courses = $em->createQuery('SELECT c FROM sociaLecompsSuperBundle:Course c')->getResult();). So you shouldn't be trying to persist the entity a second time. I suggest you use merge() this way:
$em->merge($course);
Note 1:
I see you're using Doctrine fixtures here and that students and courses have already been created. If they have been created through a Doctrine fixture as well consider using the addReference and getReference methods. Example here: https://github.com/doctrine/data-fixtures/blob/master/README.md#sharing-objects-between-fixtures
Note 2: Also you don't have a cascade option set into your subscribed_students association. Since the students already exist should not be an issue. Otherwise you can either set the cascade option or run a merge|persist on the student entity as well.
That was the stupidest thing.
I replaced:
$s = array($students[$i], $students[$i++]);
with
$s = array($students[$i], $students[++$i]);
Since it was a post-increment, the second insertion tried to put the same student into the database, resulting with an exact row duplicate.
Hope this helps someone.
I have the following associations in my database (simplified version):
This is a Many-To-Many association but with an attribute on the joining table, so I have to use One-To-Many/Many-To-One associations.
I have a form where I can add as many relations as I want to one order item and create it at the same time (mainly inspired by the How to Embed a Collection of Forms tutorial from the documentation.
When I post the form, I get the following error:
Entity of type TEST\MyBundle\Entity\Relation has identity through
a foreign entity TEST\MyBundle\Entity\Order, however this entity
has no identity itself. You have to call EntityManager#persist() on
the related entity and make sure that an identifier was generated
before trying to persist 'TEST\MyBundle\Entity\Relation'. In case
of Post Insert ID Generation (such as MySQL Auto-Increment or
PostgreSQL SERIAL) this means you have to call EntityManager#flush()
between both persist operations.
I understand this error because Doctrine tries to persist the Relation object(s) related to the order since I have the cascade={"persist"} option on the OneToMany relation. But how can I avoid this behavior?
I have tried to remove cascade={"persist"} and manually persist the entity, but I get the same error (because I need to flush() order to get the ID and when I do so, I have the same error message).
I also tried to detach() the Relation objects before the flush() but with no luck.
This problem seems unique if 1) you are using a join table with composite keys, 2) forms component, and 3) the join table is an entity that is being built by the form component's 'collection' field. I saw a lot of people having problems but not a lot of solutions, so I thought I'd share mine.
I wanted to keep my composite primary key, as I wanted to ensure that only one instance of the two foreign keys would persist in the database. Using
this entity setup as an example
/** #Entity */
class Order
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
public function __construct(Customer $customer)
{
$this->items = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class Product
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="product") */
private $orders;
.....
public function __construct(Customer $customer)
{
$this->orders = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class OrderItem
{
/** #Id #ManyToOne(targetEntity="Order") */
private $order;
/** #Id #ManyToOne(targetEntity="Product") */
private $product;
/** #Column(type="integer") */
private $amount = 1;
}
The problem I was facing, if I were building an Order object in a form, that had a collection field of OrderItems, I wouldn't be able to save OrderItem entity without having saved the Order Entity first (as doctrine/SQL needs the order id for the composite key), but the Doctrine EntityManager wasn't allowing me to save the Order object that has OrderItem attributes (because it insists on saving them en mass together). You can't turn off cascade as it will complain that you haven't saved the associated entities first, and you cant save the associated entities before saving Order. What a conundrum. My solution was to remove the associated entities, save Order and then reintroduce the associated entities to the Order object and save it again. So first I created a mass assignment function of the ArrayCollection attribute $items
class Order
{
.....
public function setItemsArray(Doctrine\Common\Collections\ArrayCollection $itemsArray = null){
if(null){
$this->items->clear();
}else{
$this->items = $itemsArray;
}
....
}
And then in my Controller where I process the form for Order.
//get entity manager
$em = $this->getDoctrine()->getManager();
//get order information (with items)
$order = $form->getData();
//pull out items array from order
$items = $order->getItems();
//clear the items from the order
$order->setItemsArray(null);
//persist and flush the Order object
$em->persist($order);
$em->flush();
//reintroduce the order items to the order object
$order->setItemsArray($items);
//persist and flush the Order object again ):
$em->persist($order);
$em->flush();
It sucks that you have to persist and flush twice (see more here Persist object with two foreign identities in doctrine). But that is doctrine for you, with all of it's power, it sure can put you in a bind. But thankfully you will only have to do this when creating a new object, not editing, because the object is already in the database.
You need to persist and flush the original before you can persist and flush the relationship records. You are 100% correct in the reason for the error.
I assume from the diagram that you are trying to add and order and the relation to the contact at the same time? If so you need to persist and flush the order before you can persist and flush the relationship. Or you can add a primary key to the Relation table.
I ended up creating a separated primary key on my Relation table (instead of having the composite one).
It looks like it is a dirty fix, and I am sure there is a better way to handle this situation but it works for now.
Here is my Relations entity:
/**
* Relation
*
* #ORM\Entity
*/
class Relation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Contact", inversedBy="relation")
*/
protected $contact;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="relation")
*/
protected $order;
/**
* #var integer
*
* #ORM\Column(name="invoice", type="integer", nullable=true)
*/
private $invoice;
//Rest of the entity...
I then added the cascade={"persist"} option on the OneToMany relation with Order:
/**
* Orders
*
* #ORM\Entity
*/
class Order
{
/**
* #ORM\OneToMany(targetEntity="Relation", mappedBy="order", cascade={"persist"})
*/
protected $relation;
//Rest of the entity...
Et voilĂ !
I don't know why, maybe i am missing some basic logic but I always run again into the same issue. I can't persists ManyToMany collections, and it also faces me with OneToMany collections, though I can work around that.
I read through the doctrine documentation, and I think I do understand the thing with mappedBy and inversedBy (where the last one is always the owner and therefor responsible for persisting the data, please correct me if I am wrong).
So here's a basic example that I have right now, which I can't figure out.
I have an Entity called Site:
#Site.php
...
/**
* #ORM\ManyToMany(targetEntity="Category", mappedBy="sites")
*/
protected $categories;
and another one called Category:
#Category.php
...
/**
* #ORM\ManyToMany(targetEntity="Site", inversedBy="categories")
* #ORM\JoinTable(name="sites_categories")
*/
protected $sites;
Using the Symfony2 entity genenerator it added me some getters and setters to my Entites which look like this.
Site:
#Site.php
...
/**
* Add categories
*
* #param My\MyBundle\Entity\Category $categories
*/
public function addCategory(\My\MyBundle\Entity\Category $categories)
{
$this->categories[] = $categories;
}
/**
* Get categories
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
The same counts for
Category:
#Category.php
...
/**
* Add sites
*
* #param My\MyBundle\Entity\Site $sites
*/
public function addSite(\My\MyBundle\Entity\Site $sites)
{
$this->sites[] = $sites;
}
/**
* Get sites
*
* #return Doctrine\Common\Collections\Collection
*/
public function getSites()
{
return $this->sites;
}
Fair enough.
Now in my controller, I am trying to persist a Site object:
public function newsiteAction() {
$site = new Site();
$form = $this->createFormBuilder($site); // generated with the FormBuilder, so the form includes Category Entity
// ... some more logic, like if(POST), bindRequest() etc.
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($site);
$em->flush();
}
}
The result is always the same. It persists the Site Object, but not the Category entity. And I also know why (I think): Because the Category entity is the owning side.
But, do I always have to do something like this for persisting it? (which is actually my workaround for some OneToMany collections)
$categories = $form->get('categories')->getData();
foreach($categories as $category) {
// persist etc.
}
But I am running into many issues here, like I would have to do the same loop as above for deleting, editing etc.
Any hints? I will really give a cyber hug to the person who can clear my mind about that. Thanks!
.
.
.
UPDATE
I ended up changing around the relationship (owning and inverse side) between the ManyToMany mapping.
If somebody else runs into that problem, you need to be clear about the concept of bidrectional relationships, which took me a while to understand too (and I hope I got it now, see this link).
Basically what anserwed my question is: The object you want to persist must always be the owning site (The owning site is always the entity that has "inversed by" in the annotiation).
Also there is a concept of cascade annotation (see this link, thanks to moonwave99)
So thanks, and I hope that helps somebody for future reference! :)
Regarding OneToMany relationship, you want to know about cascade annotation - from Doctrine docs [8.6]:
The following cascade options exist:
persist : Cascades persist operations to the associated entities.
remove : Cascades remove operations to the associated entities.
merge : Cascades merge operations to the associated entities.
detach : Cascades detach operations to the associated entities.
all : Cascades persist, remove, merge and detach operations to associated entities.
following docs example:
<?php
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* #OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
When you add comments to the author, they get persisted as you save them - when you delete the author, comments say farewell too.
I had same issues when setting up a REST service lately, and cascade annotation got me rid of all the workarounds you mentioned before [which I used at the very beginning] - hope this was helpful.