Use Doctrine to merge entity with different subclass - symfony

I have a mapped superclass AbstractQuestion with single-table-inheritance.
/**
* #ORM\Entity
* #ORM\MappedSuperclass
* #ORM\Table(name="Question")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="dtype", type="string")
* #ORM\DiscriminatorMap({
* "simple": "SimpleQuestion",
* "dropdown": "DropdownQuestion"
* })
*/
abstract class AbstractQuestion
SimpleQuestion and DropdownQuestion inherit from this superclass.
/**
* Class SimpleQuestion.
* #ORM\Entity()
*/
class SimpleQuestion extends AbstractQuestion
I want to modify an existing SimpleQuestion and make it a DropdownQuestion.
When saving a question, I deserialise and merge the question, which contains an ID and the 'dtype' and other properties.
$dquestion = $this->serial->fromJson($request->getContent(), AbstractQuestion::class);
$question = $this->em->merge($dquestion);
$this->em->flush();
So I submit something like:
{ id: 12, dtype: 'dropdown', 'text': 'What is my favourite animal?'}
After the deserialisation, $dquestion is a DropdownQuestion object as I desired, but after the merge $question is a SimpleQuestion object as it was in the database previously, so any unique properties of DropdownQuestion are lost and the question is saved as a SimpleQuestion. Is there any way to work around this?

You will first have to delete the existing record (SimpleQuestion) and then insert the new record (DropdownQuestion). Type casting is not supported in Doctrine 2.
Note.
You can probably change the discriminator column with a pure SQL query, but this is absolutely not recommended and will for sure give you problems...
Check also the answers here and here since they might be interesting for you.

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

making unique Constraint on doctorine2

I would like to make uniqueConstraint user and lesson.
/**
* #ORM\Table(name="ReviewSchool",uniqueConstraints={
* #ORM\UniqueConstraint(name="lessonid", columns={"lesson", "user"})})
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class ReviewSchool
{
* #ORM\ManyToOne(targetEntity="Lesson",inversedBy="reviewschool")
* #ORM\JoinColumn(name="review_lesson", referencedColumnName="id",onDelete="cascade")
*/
private $lesson;
/**
*
* #ORM\ManyToOne(targetEntity="User",inversedBy="reviewschool")
* #ORM\JoinColumn(name="review_user",referencedColumnName="id",onDelete="cascade")
*/
private $user;
However it shows
[Doctrine\DBAL\Schema\SchemaException]
There is no column with name 'lesson' on table 'ReviewSchool'.
Surely I have 'lesson' column, how can I solve this?
I have misunderstood something??
It allows to hint the SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool schema generation context.
So you have to use column names. In your case:
#ORM\UniqueConstraint(columns={"review_lesson", "review_user"})}

Symfony2 - One ManyToOne relation on one field referencing two entities

I have an entity which stores "removal requests" to either studios or models. An object (Studio or model can have many requests).
Entity RemovalRequest has a field named : object.
I would like to know if it's possible to do something like this in RemovalRequest entity:
/**
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Studio", inversedBy="requests")
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Model", inversedBy="requests")
*/
private $object;
I can't find anything about this special case over Internet..
If it's not possible, I'm open to any suggestions you might have !
Do you realy need a new entity to store information about removal? Maybe just add a flag to Studio and Model:
/**
* #ORM\Column(name="is_to_remove", type="boolean")
*/
$isToRemove = false;
If you need RemovalRequest entity you should add two properties fore each type like this:
/**
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Model", inversedBy="requests")
*/
$model;
/**
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Studio", inversedBy="requests")
*/
$studio;
It is bed idea to store two different classes in one property

copy object Inheritance Mapping doctrine2

i have the following entity:
/**
* #ORM\Table(name="event")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="eventtype", type="integer")
* #ORM\DiscriminatorMap({1 = "eventClub", 2 = "eventLive", 3 = "eventBar", 4 = "eventGeneric" })
*/
class P1event extends AbstractEntity {
/**
*
* #var List[] $lists
*
* #ORM\OneToMany(targetEntity="List", mappedBy="fkevent", cascade={"persist", "merge"})"
*/
private $lists;
A user should have the possibility to change the eventtype via a form. By changing the evetntype, i must create a new Object becaus of my table inheritance (doctrine doc).
I have no idea how i can change the lists of the copied event to the new event inside one transaction. Has anybody an idea how to handle it correctly? Thank you very much.
In a similar situation, I just passed GET parameter type, then in the controller created an object of the desired type and transferred it to the form. If I understand your question correctly.
After created a new event object, call
$eventY->setLists($eventX->getLists());
doen't work ?

Resources