Doctrine : Fetch "EAGER" and "Hydrate Array" - symfony

With Doctrine, I have fetch=EAGER in my entity :
class TrainingOrganization
{
/**
* #var TrainingOrganizationVersion[]|ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\TrainingOrganizationVersion",
* mappedBy="trainingOrganization",
* cascade={"persist"},
* fetch="EAGER"
* )
* #ORM\OrderBy({"id" = "ASC"})
* #Assert\Valid()
* #Versionable
*/
private $versions;
Why when i do "hydrate array" it does not work ?
Screen of my dump for same entity (Second is "Hydrate array") :

With Hydration mode Query::HYDRATE_ARRAY, Doctrine will only return information about that 'row'. Since your versions attribute is not a field but a collection, it won't be returned.
If you want to have Collections included, use Objects instead (like your first screenshot).
If you really need your entities serialized (returning a multidimensional array instead of objects), use a serializer. Since you're using Symfony, you can easily use Symfony's Serializer Component. The JMSSerializerBundle is a popular alternative.

Related

Deserialize and persist relationships with JMS Serializer

I'm trying to get the following working:
I've got an entity like:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Contact
*
* #ORM\Table()
* #ORM\Entity()
*/
class Contact
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts")
* #ORM\JoinColumn(name="service_client", referencedColumnName="service_client")
*
* #JMS\Type("AppBundle\Entity\ServiceClient")
* #JMS\SerializedName("serviceClient")
*/
private $serviceClient;
}
I'm sending the following JSON over an HTTP request (Post, it's a new Contact, no ID):
{
"name": "Lorem Ipsum",
"serviceClient": {"service_client": "ipsum"}
}
What I expect is for the JMS Serializer to parse that relationship, and leting me persist the Contact object like this:
<?php
$contact = $this->get('serializer')->deserialize(
$request->getContent(),
Contact::class, 'json'
);
$this->em->persist($contact);
$this->em->flush();
In fact I got that working (I swear it was working) but now it's giving me the follwing error:
A new entity was found through the relationship
'AppBundle\Entity\Contact#serviceClient' that was not configured to
cascade persist operations for entity:
AppBundle\Entity\ServiceClient#000000006fafb93e00007f122bd10320. To
solve this issue: Either explicitly call EntityManager#persist() on
this unknown entity or configure cascade persist this association in
the mapping for example #ManyToOne(..,cascade={\"persist\"}). If you
cannot find out which entity causes the problem implement
'AppBundle\Entity\ServiceClient#__toString()' to get a clue."
So it's tryign to persist the entity... a thing I do not want since the entity already exists. I just want Doctrine to put the reference, the foreign key.
Edit: It seems it's the constructor, if I set it to the doctrine_object_constructor it works like magic, the thing I do not understand is why it stop working in the first place.
Can anyone share any ideas or a cleaner way to do what I did?
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
This problem happens when Doctrine cannot map your relationship to an existing record in the database, so it will try to create a new one with the data from the JSON object.
In your case, the JSON object: {"service_client": "ipsum"} cannot be mapped to an existing ServiceClient instance.
It's because the default JMS object constructor call the unserialize function (will be the one from your Entity if you defined this method) to construct the object, which mean this object will always be treated by Doctrine as new (has never been persisted).
By using doctrine_object_constructor, JMS will get the object from Doctrine. The object came from Doctrine not only have the attributes and methods you define in your entity, but also meta-data about whether it's an existing one, it's corresponding row from the database ( so Doctrine can detect update made on the record later and handle it), therefore Doctrine are able to avoid incorrect persisting.
Doctrine will try to persist the Contact with a reference of a ServiceClient entity given in the deserialization. In the entity definition at the level of the manyToOne definition you need to add :
#ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts", cascade={"persist"})

Symfony QueryBuilder - exclude objects having specified object connected to it?

Symfony 2.8. I have ExternalReference, which has its type and can be attached to some objects. Like this:
Entity\ExternalReferenceType.php:
- $id
- $name (string)
Entity\ExternalReference.php:
- $id
- $value
- $type (ManyToOne to ExternalReferenceType)
Now, something I attach references to, Entity\ObjectWithReferences.php has the following, apart from $id and other fields:
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\ExternalReference")
* #ORM\JoinTable(name="obj_references",
* joinColumns={
* #ORM\JoinColumn(name="obj_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="reference_id", referencedColumnName="id")
* }
* )
*/
private $references;
(OneToMany unidirectional assotiation, as far as I remember, I found it in Doctrine's documentation):
Now user will pick one of reference types (ExternalReferenceType object) and I want to list all ObjectWithReferences objects which don't have any reference of that type. I'm a bit lost, I probably could do it in plain SQL, but I need it in QueryBuilder format. Can you help me?

Symfony entities without relational

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;
}

Symfony2, Sonata : Customize title of collection

I've been able to translate most of the titles but i still have some non-friendly titles on collections (Table of relation)
Aire\AppBundle\Entity\ProjectSupported:000000002d1a645a000000015441bb1f
How could i custom them?
At best it could be the name of the related object ($investor->getName() and $project->getName() for exemple), at worst just a string.
In that case i'm using en entity with 2 relations
/**
* Owning Side
*
* #ORM\ManyToOne(targetEntity="Investor", inversedBy="supportedProject")
* #ORM\JoinColumn(name="investor_id", referencedColumnName="id")
**/
private $investor;
/**
* Owning Side
*
* #ORM\ManyToOne(targetEntity="Project", inversedBy="supportedProject")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
**/
private $project;
Any hints or solutions?
Sonata is using the __toString method for text representation of objects.

Stop Doctrine / Symfony from loading associated entities during foreach? (lazy loading not happening)

I'm seeing doctrine generating additional queries to load entities that I'm not directly accessing. I thought that lazy loading meant these associate entities wouldn't get loaded. Can you help me figure out why the queries are happening and how to stop them?
Here's the entities in question:
class Invoice
{
/**
* #ORM\OneToMany(targetEntity="InvoiceCard", mappedBy="invoice")
*/
protected $cards;
...
}
class BaseInvoiceCard
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Invoice", inversedBy="cards")
* #ORM\JoinColumn(name="invoice_id", referencedColumnName="id")
*/
protected $invoice;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Printing")
* #ORM\JoinColumn(name="printing_id", referencedColumnName="id")
*/
protected $printing;
...
}
class InvoiceCard extends BaseInvoiceCard{ ... }
class Printing{ ... }
This line of code doesn't cause any queries to the InvoiceCards table:
$cards = $invoice->getCards();
Once I do this:
foreach($cards as $card){
//do nothing in this loop
}
I get a "SELECT ... FROM invoicecard", which is expected.
However, I'm also getting a "SELECT ... FROM printing" for every $card in $cards. I never call $card->getPrinting(). This happens even if I do nothing at all inside the loop; just running it causes doctrine to run these queries.
Why this happening and how can I prevent it?
EDIT: This is the code for getCards().
/**
* Get cards
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCards()
{
return $this->cards;
}
EDIT 2:
I've found a workaround to this problem, though it's not going to solve things in the long run. I fetch the InvoiceCards as an array, rather than having doctrine hydrate them as entities.
$query->getArrayResult();
In my current situation, this technique is better anyways, since I don't require the overhead of full hydration.
However, the application will be working with InvoiceCards in many places, and the original issue will be still be a problem then. I feel like either I've misunderstood Doctrine's lazy loading, or it isn't working as expected.
The entities were getting eagerly loaded because Printing is a parent class whose children use single table inheritance.
If I change $printing to point to leaf entity, lazy loading works as desired.
From the Doctrine docs, 7.2.2. Performance impact
There is a general performance consideration with Single Table Inheritance: If the target-entity of a many-to-one or one-to-one association is an STI entity, it is preferable for performance reasons that it be a leaf entity in the inheritance hierarchy, (ie. have no subclasses). Otherwise Doctrine CANNOT create proxy instances of this entity and will ALWAYS load the entity eagerly.

Resources