Eager loading of related entity in Symfony 2 - symfony

There are three entities: Customer, Messages, Attachments.
The relationship between these entities is straight forward: A customer can have many messages and a message can have many attachments. Both relations are "one-to-many".
I told doctrine to be lazy when loading the messages for the Customer entity. So $customer->getMessages() results in an additional SQL statement. That's fine.
But I also defined an "EAGER" loading for the attachments for the Message entity.
Now I would have expected that the messages I get by calling $customer->getMessages() are already loaded with all their attachments. But $message->getAttachments() still causes one SQL statement per message.
Is this behavior expected?
Just for reference, excepts from my classes:
Customer.php
class Customer
{
/**
* #ORM\OneToMany(targetEntity="Message", mappedBy="customer")
* #ORM\OrderBy({"createdOn" = "DESC"})
*/
private $messages;
Message.php
class Message
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="messages")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
**/
private $customer;
/**
* #ORM\OneToMany(targetEntity="Attachment", mappedBy="message", fetch="EAGER")
**/
private $attachments;
Attachment.php:
class Attachment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Message", inversedBy="attachments")
* #ORM\JoinColumn(name="message_id", referencedColumnName="id")
**/
private $message;

It sounds like expected behavior to me. The doctrine documentation seems to imply that eager fetching is only one level deep.
According to the docs:
Whenever you query for an entity that has persistent associations and
these associations are mapped as EAGER, they will automatically be
loaded together with the entity being queried and is thus immediately
available to your application.
http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-eager-loading
The entity being queried in your case is customer and customer has eager on messages so messages are populated. Messages, however are not the object being queried, so attachments do not get loaded.

the correct code example might be like the following:
NOTE: fetch message with lazy loading, i.e. get messages with additional query proactively; as long as the messages are fetched from database, the corresponding attachments referenced to each message will be loaded automatically.
Customer
class Customer
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Message", mappedBy="customer", fetch="LAZY")
* #ORM\OrderBy({"createdOn" = "DESC"})
*/
private $messages;
Message
class Message
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="messages")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
/**
* #ORM\OneToMany(targetEntity="Attchment", mappedBy="message", fetch="EAGER")
*/
private $attachments;
Attachment
class Attachment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToONe(targetEntity="Message", inversedBy="attachments")
* #ORM\JoinColumn(name="message_id", referencedColumnName="id")
*/
private $message;

Related

symfony2-easyadmin bundle blank screen error

i have a strange problem when trying to render entity in symfony2 easyadminBundle i got a blank screen without any errors,
when trying to list entity 'Ads' that has many-to-one relation-ship with entity 'pages' ,where page contains many ads., however if i modified the action parameter in the url to be &action=new instead of &action=list it shows the form but after saving it giving me the same blank screen!
My Pages Entity :
/**
* #var string
*
* #ORM\Column(name="page_name", type="string", length=30, nullable=true)
*/
private $pageName;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/*#ORM\OneToMany(targetEntity="Ads", mappedBy="pages")
*
*/
protected $ads;
public function __construct()
{
$this->ads = new ArrayCollection();
}
}
Ads Entity:
/**
* Ads
*
* #ORM\Table(name="ads")
* #ORM\Entity
*/
class Ads
{
/**
* #var integer
*
* #ORM\Column(name="page_id", type="integer", nullable=false)
*/
private $page_id ;
/**
* #var string
*
* #ORM\Column(name="ad_text", type="text", nullable=false)
*/
private $adText;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Pages", inversedBy="ads")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $page_ad;
}
Your configuration looks "not complicated", so it should work as expected. The "blank page" error is really strange. We render the data with try...catch to avoid any issue when we cannot access the information. Besides, we also catch the exceptions to display customized error pages.
Can you see any error message in your dev.log file?
You could also do the following. Create the simplest possible configuration for the list view of the Ads entity as follows:
easy_admin:
entities:
Ads:
list:
fields: ['id']
If this work, try adding more fields one by one until you find the error.

Doctrine2 relation for non Id column

I'm building a simple web-service using Symfony 3, Doctrine 2.5 and stuck at ORM relations described below in simplified structure.
I have an Action entity containing many actions with ManyToOne relation...
class Action
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Status")
* #ORM\JoinColumn(referencedColumnName="code", nullable=false)
*/
private $status;
and the Status Entity with a few statuses.
class Status
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="integer", unique=true)
*/
private $code;
I cannot get proper way to set referencedColumnName="code" column (not 'Id' as usual) for Action entity.
Configured this way repo throws wxception at persist moment with "Notice: Undefined index: code";
I guess that it is mappedBy or inversedBy annotation parameter... but can't figure out "how".
Unfortunately it's not supported in Doctrine (reference).
You may edit your Status entity like this (ensure that code is set before persist):
class Status
{
/**
* #ORM\Column(name="code", type="integer", unique=true)
* #ORM\Id
*/
private $code;
}
If autoincremented field is your requirement you can take a look on this answer for possible solutions.
Just thought I'd add you can still use the non-primary keys as many to many, by using the entity itself as the join table. This will work but you still need to set your relationship keys correctly.
Example:
/**
* #ORM\Entity
*/
class Car {
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(name="registration_code", type="text", length=128, nullable=false)
* #var string
*/
public $registrationCode;
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\ManyToMany(targetEntity="Registration", mappedBy="Cars")
* #ORM\JoinTable(name="car",
* joinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="registration_code", referencedColumnName="registration_code")}
* )
*/
public $Registrations;
public function __construct() {
$this->Cars = new ArrayCollection();
}
}
/**
* #ORM\Entity
*/
class Registration {
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(name="registration_code", type="text", length=128, nullable=false)
* #var string
*/
public $registrationCode;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="Car", mappedBy="Registrations")
* #ORM\JoinTable(name="car",
* joinColumns={#ORM\JoinColumn(name="registration_code", referencedColumnName="registration_code")},
* inverseJoinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")}
* )
*/
public $Cars;
public function __construct() {
$this->Cars = new ArrayCollection();
}
}
The upside is that it works fine as a workaround.
Keep in mind a few things:
it's a collection not a single instance;
column has to be managed manually on your end;
you must set up constraints correctly (indexes, keys, etc);
check your queries still perform!

Symfony + Doctrine - UnitOfWork commit order

I have a form that shows entity:
class Event
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\OneToMany(targetEntity="EventAttendee", mappedBy="event", cascade={"all"})
*/
private $attendees;
}
and a collection within it:
class EventAttendee
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="Event", inversedBy="attendees")
* #ORM\JoinColumn(name="event_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $event;
/**
*
* #ORM\OneToOne(targetEntity="Employee")
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $employee;
}
If I delete an employee from the collection and add it again, I'm getting integrity constraint violation. This is because Doctrine's UnitOfWork first executes Inserts and then Deletes. Therefore, when it inserts a new record db still has the old one with the same employee.
Doctrine2 developers did not provide any working solution for Symfony2 users (here is the thread: http://www.doctrine-project.org/jira/browse/DDC-601).
And thus, I'm asking the question here: is it anyhow possible to avoid this issue?
EDIT:
My current workaround is:
find all not-persisted colletion items ready to insert
remove them from the collection and save to a variable
remove all the items that were really deleted in the form
call flush()
add all the items for insert back to the collection
call flush()
This works for me, however doesn't look good. Maybe someone has a better solution.

One join table for two Many-to-Many relations

I'm working on a solution for adding tags to two differente entities.
in order to get data easily in the frontend i created a joinTable named Tag_Mapping like this :
class TagMapping
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Tag", inversedBy="tags")
*/
private $tag;
/**
* #ORM\ManyToOne(targetEntity="Feed", inversedBy="tags")
*/
private $feed;
/**
* #ORM\ManyToOne(targetEntity="Question", inversedBy="tags")
*/
private $question;
...
}
The Tag Entity :
class Tag
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255, unique=true)
*/
private $name;
/**
* #Gedmo\Slug(fields={"name"})
* #ORM\Column(unique=true)
*/
private $slug;
/**
* #ORM\OneToMany(targetEntity="TagMapping", mappedBy="tag", cascade="remove")
*/
private $tags;
...
}
and in both other entities (Feed and Question) I made reference to TagMapping entity like this
...
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #JoinTable(name="tag_mapping",
* joinColumns={#JoinColumn(name="feed_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="id")}
* )
*/
private $tags;
...
the problem I'm facing is that it's not a valid way to do it, as it shows an error when i'm trying to execute :
php app/console doctrine:schema:update --force
saying that tag_mapping table already exists.
do you have any idea how can i get it done using only one joinTable instead of one for each relation ?
Thanks.

Doctrine OneToMany relationship error

I am trying to set up some ManyToOne/OneToMany relationships on objects in my database using Doctrine (2.2.3+) via Symfony2 (2.3.0) and am getting a strange error. Here are the relevant parts of the objects (many attributes to one product):
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
...
/**
*
* #OneToMany(targetEntity="ProductAttributes", mappedBy="product")
*/
protected $product_attributes;
public function __construct() {
$this->product_attributes = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/**
* ProductAttributes
*
* #ORM\Table(name="product_attributes")
* #ORM\Entity
*/
class ProductAttributes
{
/**
* #var integer
*
* #ORM\Column(name="pa_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $pa_id;
/**
* #var integer
*
* #ORM\Column(name="product_id", type="integer")
*/
protected $product_id;
...
/**
*
* #ManyToOne(targetEntity="Product", inversedBy="product_attributes")
* #JoinColumn(name="product_id", referencedColumnName="id")
*/
protected $product;
}
When I run the
php app/console doctrine:generate:entities BundleName
command I get the following error:
[Doctrine\Common\Annotations\AnnotationException]
[Semantical Error] The annotation "#OneToMany" in property LVMount\LVMBundle\Entity\Product::$product_attributes was never imported. Did you maybe forget to add a "use" statement for this annotation?
I have looked through the Doctrine docs and don't see any reference to a "use" statement for the ManyToOne/OneToMany pairings. What is going on?
Your annotations' syntax aren't complete.
You can see the proper syntax for any doctrine annotation below.
/**
* #ORM\********
*/
So, in your case it should look like the following.
/**
* #ORM\OneToMany(targetEntity="ProductAttributes", mappedBy="product")
*/
You will also want to fix the annotations in the ProductAttributes entity.

Resources