Convert existing Entity to descendant Entity in Symfony and Doctrine - php

I have a class Employee extends Person.
Over time an already existing Person can become an Employee of the company.
I know it's a bad practice to either change the discriminator type using raw SQL or typecasting with obscure PECL extensions.
But is there a design pattern that manages these kind of complication?
Is the only way to clone all the attributes and relationships (Person has many relationships to other entities) to achieve this? How would I go about cloning the existing person with all its relationships?

Since Person and Employee are two different "things", you should create an Employee entity with a One-to-One relationship with the Person entity. This implies that an Employee is always a Person, but a Person isn't necessarily an Employee.
class Employee
{
/**
* #ORM\OneToOne(targetEntity="Acme\PersonBundle\Entity\Person", cascade={"persist"})
* #ORM\JoinColumn(referencedColumnName="id", nullable=true)
*/
protected $person;
}
This not only allows you to keep the Person entity with all its existing properties, preventing from having to clone data (which is never a good idea), but most importantly separates the two object types.
As #Zeljko mentioned, you could create a type field field, but I believe that my approach of normalizing the data is a lot cleaner. If you need multiple types of employees, then you could create an EmployeeType table and then have a EmployeeType_ID in the Employee entity. This method is much cleaner than using constants, which tend to get messy and hard to maintain.

Cloning data is bad idea. Remember: if you are duplicating something, you are doing it wrong :)
I think the best solution is to not use inheritance at all. Just create new column in Person entity called eg. type which would be a constant.
In that entity, put something like this:
class Person
{
const TYPE_USER = 0 ;
const TYPE_EMPLOYEE = 1 ;
const TYPE_BIG_FAT_BOSS = 2 ;
...
/**
* #ORM\Column(type="integer")
*/
protected $type = self::TYPE_USER ;
This way you only need to update type column and have better functionality.

Related

Doctrine class table inheritance mapping: why a foreign key to parent table?

Short question: can I avoid generating foreign keys for inherited classes to parent classes? Can a discriminator column for inherintance mapping be set in a relation owner instead of a parent class?
Explanation:
I am designing a model for invoices, where invoice subject can be 1 of 3 types:
Contract
Client
Provider
After reading Doctrine inheritance mapping I think Class table inheritance is the one that best fits my needs.
Mapped superclass could better fit my needs if I could draw a relation from Invoice to InvoiceSubject, but I think I can't:
A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all. Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment.
Also, using an Interface could be a solution, but an interface in a relation can only be mapped to one entity.
So, this is my model:
/**
* #ORM\Entity
*/
class Invoice
{
/**
* #ORM\ManyToOne(targetEntity="InvoiceSubject")
* #var InvoiceSubject
*/
protected $subject;
}
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="invoicesubject_type", type="string")
* #ORM\DiscriminatorMap({"invoice-subject" = "InvoiceSubject", "contract" = "Contract", "provider" = "Provider", "client" = "Client"})
*/
class InvoiceSubject
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $id;
}
/**
* #ORM\Entity
*/
class Contract extends InvoiceSubject{}
/**
* #ORM\Entity
*/
class Provider extends InvoiceSubject implements ProviderInterface{}
/**
* #ORM\Entity
*/
class Client extends InvoiceSubject{}
But, when I try to generate the model (bin/console doctrine:schema:update --dump-sql) I see its trying to create foreign keys from child classes to parent (tables already exist and have data):
ALTER TABLE contract ADD CONSTRAINT FK_E9CCE71ABF396750 FOREIGN KEY (id) REFERENCES invoice_subject (id) ON DELETE CASCADE;
ALTER TABLE provider ADD CONSTRAINT FK_B2F1AF1BBF396750 FOREIGN KEY (id) REFERENCES invoice_subject (id) ON DELETE CASCADE;
ALTER TABLE client ADD CONSTRAINT FK_B2F1AF1BBF396750 FOREIGN KEY (id) REFERENCES invoice_subject (id) ON DELETE CASCADE;
This means that there will be collisions between id's coming from different tables or I will need to use different values in every table, none of the solutions are good. So, the question is:
Is there a way to avoid this foreign key and set the discriminator in the relation owner class Invoice? In my case, InvoiceSubject doen't actually need to exist as a table, I'm forced to create it as Class table inheritance forces me to do so.
Or, is this modelling completely wrong and I should use another aproach?
okay, I probably misread part of your question:
yes, the three child entities have a foreign key to the same id field in the parent entity (table), and it's intended. The idea is, that the core entity is the invoice subject. That entity has the id. the child entities inherit that id and gain more attributes (in the child table) by extending the parent entity. That's what inheritance means. You have essentially a core entity, that has different subtypes with extra attributes.
(note: you could do this manually as well, add an association mapping to potential "extra data of the contract/client/provider variety" to some invoicesubject entity)
That also means, you don't really have to take care of collisions, since the parent table entry is always created first by doctrine. When you create a new InvoiceSubject of any subtype, you effectively create an InvoiceSubject (has an id) and extend it. So, your contract/client/provider entities will just not have the same id (unless you manually set it with SQL).
old answer
This is a very opinionated answer. I mean ... technically it's a question of taste. I would always prefer not to do inheritance mapping if it can be reasonably avoided and there are no good reasons to do it.
The questions are: do you have exactly one form to enter them? Do you (already) have single fields that take any of those entities? Do the entities provide the same semantics? is it lazyness that drives you to only wanting to deal with one "type" of entity? Are there so many places, that want to be extremely agnostic to what kind of subject it is and can't that be solved by a well-defined interface? When are they truly treated the same?
Personally, looking at your use case, I probably would stay on the three entities, and Invoice with three fields, one for each entity. It's simple, it's fast, discriminator columns suck (IMHO, semantically, not technically).
Add a function to your Invoice like
function getSubject() {
return $this->contract ?? $this->provider ?? $this->client;
}
setting is a tad more difficult ... but if you don't want three different setters (I honestly doubt that you create a Client and when setting the subject, you forget it's a client and want to treat it as an InvoiceSubject)
function setSubject(InvoiceSubject $subject) {
if($subject instanceof Client) {
$this->client = $subject;
} elseif (...) {} elseif (...) {}
//... technically you should unset the others, if it can only ever be one
}
Almost all concepts that you might want to use via inheritance mapping can be solved in code with little to no overhead, but it will so much simplify lots of other stuff. And most of the time you can probably use the interface in code.
Inheritance mapping IMHO is more trouble than it's worth. Unless you have really strong reasons to actually need it: don't do it. Dealing with uniquely different entities is so much easier than dealing with some abstract entity, where you always have to check which kind it is and pay attention ... it's really annoying. On the other hand, when do you treat the three entities exactly the same? I bet each of those have some unique stuff going on and there are always switch-cases anyway. if not: interface.
keep it simple.

How to fetch entities without their association mapping in Doctrine 2

If you have a set of association mapped entities in Doctrine, sometimes you might want to retrieve those entities without its mapped associations being fetched and slowing the query down.
For example I have a set of entities which are association mapped in a chain of linked database tables. They are all OnetoMany associations and act as a hierarchy of prices in matrices on product pages. They can be represented as so:
SitePage->SiteMatrix->SiteItems->SiteItemPrices.
The associated mapping works perfectly, and when I use the findBy method to get the root SitePage object it contains arrays which represent the mapped entities down the chain. In other words the SitePage object contains all matrices, which contains all items which contains all prices. So far so good.
My problem is that every time I get a list of pages on my site, doctrine is walking the entire association mapping tree and returning me with the entire datatabase which is very slow. Sometime I want to just get my SitePage entity by ID and not contain all the mapped associations.
I have looked into lazy and extra lazy loading of associations but they only seem to affect certain functions, and not findBy etc. The official documentation is far from helpful:
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
Other similar questions on Stack Overflow have gone unanswered:
Doctrine 2 Association Mapping Overhead?
Is there an easy way to fetch an entity without its mapped associations? The easiest way I can see currently is to create two entities for each database table, one with association mapping and one without for use in the separate situations where they are required. It seems odd to me that you cannot simply fetch an entity and specify whether you want to link to it to other entities or fetch it by itself.
Thanks for any information on the subject.
The JMSSerializer exclusion strategies can help you.
First, exclude all properties by default :
// ...
use JMS\Serializer\Annotation as JMS;
/**
* #ORM\Entity
* #JMS\ExclusionPolicy("all")
*/
class Foo
Then, choose to exclude or expose your properties :
/**
* #ORM\Column(type="string")
* #JMS\Expose
*/
protected $name;
Also, for your associations, you can use the MaxDepth to restrict the associated entries of your results. Example :
// Entity
/** #MaxDepth(1) */
private $selfAssociatedEntries;
// Controller
use JMS\Serializer\SerializationContext;
$serializer->serialize($data, 'json', SerializationContext::create()->enableMaxDepthChecks());
Like this, your entity will contains the $selfAssociatedEntries serialised, but the $selfAssociatedEntries doesn't have the $selfAssociationEntries property.
The serialisation is restricted to the first parent object.
Groups are a powerful feature that allows you to expose properties for some actions and exclude them for another :
/**
* #ORM\Column(type="string", length=255, nullable=true, unique=false)
* #JMS\Groups({"default"}) // Set the group to expose the property
*/
protected $name;
In your controller, set the group used for serialisation :
// Property 'name' is exposed
$serializer->serialize($data, 'json', SerializationContext::create()->setGroups(array('default')));
For more informations, look at Exclusion Strategies chapter of the documentation.

Doctrine ORM on EAV Tables

I was planning on creating my application and use an ORM for the models, but the thing is, there's a part of the database which uses Entity-Attribute-Value Tables.
I pretty liked Doctrine ORM but I don't know if it is possible to maybe create classes that would look like any ordinary doctrine entity, when the table actually hooked up to is of EAV style.
Would it be possible to use Doctrine on this, and if so, how?
definitely possible:
Have relationships like this:
Object (one to many) -> AttributeValue -> Many to One -> AttributeType
In view of EAV it seems to be obvious how to build a relation between entity and attribute using doctrine. In the most complicated case we deal with a Many to Many relation.
So lets say we want to map an attribute Name to an entity User. Assuming a user has exactly one name and each name belongs to exactly one user this link can be archived using One to One relation
But how to model the relation between attribute and value? The problem is that values can be of different types or even need different numbers of fields in order to save their information.
Consider the attributes name and phone_number. While a name might be represented by a string, an integer could be needed for the phone number. Or it is even necessary to not only the number but also the area code in a separate filed.
Because EAV requires very flexible value representation, it is not possible to store all of them within the same field in a database table (disregard blobs, data serialization an the like). Therefore most EAV implementations using different tables representing different value types.
In order to reach such flexibility, doctrine features Inheritance Mapping. It basically allows you to extend doctrine entities. Doing so you specify a discriminator for each sub-type of your entity:
/**
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="value_type", type="string")
* #DiscriminatorMap({"name" = "Name", "phone" = "PhoneNumber"})
*/
class Value
{
// ...
}
/** #Entity */
class Name extends Value
{
// ...
}
/** #Entity */
class PhoneNumber extends Value
{
// ...
}
The Value class provides common implementation for all values, i.e. an id. Each subclass (i.e. Name and PhoneNumber) extend those common values by their specific ones, for example additional fields.
The #DiscriminatorColumn defines a column in the parent relation which stores the type of the value.
The #DiscriminatorMap is used by doctrine to map the type from the #DiscriminatorColumn to one of those classes.
The relation between attribute and value can be specified to the parent class. Calling values from the attribute then will fetch all types of values which can be filtered (and dealt with) during runtime using for example instanceof.

Doctrine2 - using a foreign key column as a normal field

Is there a way to have something like this in doctrine:
class Entity {
/**
* #Column(name="related_entity_id")
*/
private $relatedEntityId;
/**
* #ManyToOne(targetEntity="RelatedEntitiy")
* #JoinColumn(name="related_entity_id", referencedColumnName="id")
*/
private $relatedEntity;
}
What I want to do I do something like this:
call Entity::setRelatedEntityId($someId), and persist the entity,
and have the entity return the related entity by calling Entity::getRelatedEntity().
The related entity is selected from a table which will be strictly limited and it will never dynamically grow at runtime, so there is a finite number of related entity ids.
At the time of creating a new Entity, I'd like to set the related entity id, but without having to fetch the whole related entity from the database.
As far as I could test this, it does not work, because if I set the relatedEntityId but not the relatedEntity, Doctrine automatically sets the related_entity_id column to null, since basically no relationship has been established.
I've tried to do something like this also:
remove the relatedEntityId property, and use
Entity::setRelatedEntity(new RelatedEntity($relEntId))
the constructor of the RelatedEntity will set the id, but not other values.
I do not want to persist the RelatedEntity (it's values are already set in the DB for the given $relEntId), but this time Doctrine signals an error at flush, because it has an unpersisted entity.
Basically, what I want to do is create a relationship without knowing anyhing but the Id of the related entity. If there is some other way this can be done, please share.
Thanks in advance
EDIT:
I've found a workaround. Since the RelatedEntities will be a limited set of immutable objects, I've done the following:
use the entityManager to find all RelatedEntities;
inject the list to the object that will be creating new Entities
when creating a new Entity, select one of the RelatedEntities from the list as its RelatedEntity
I'll leave the question open for a day or two, just in case somebody comes up with something better.
Use the entity proxy:
Entity::setRelatedEntity($entityManager->getReference('RelatedEntity', $relEntId))
I don't think this is supposed to work like how you described :)
The entity you add must be a Doctrine managed object, so that means you have to load it yourself first using the find() family of methods.
Based on my experience with Doctrine 2 further elaborated here http://ssmusoke.wordpress.com/2012/03/25/doctrine2-day-3-proxies-associations-relationships/
My approach is as follows:
a) Have only the $relatedEntity property
b) Add a getRelatedEntityId() function which returns the id value from $relatedEntity
c) Add a setRelatedEntityId() which sets the $relatedEntity object - you may need to load it from the database, saves you from polluting other layers when u only have the id of the related entity
d) Add getRelatedEntity() and setRelatedEntity() functions
BOTTOM LINE: You cannot have a property for the foreign key column and the mapped property as Doctrine gets confused

Doctrine2: association mapping for a table with multiple foreign keys

A friend of mine and me are working on a multilingual web-application for storing recipes. We use Doctrine 2.1.1 for persistence. At the moment I stuck generating models for an entity and hope someone can give me a hint, or show up a best practice for our case.
I'll try to explain our ERD broadly. Feel free to ask, if there is something unintelligible. Our application has the Entities recipe, ingredients, units (measure units for ingredients) and categories. Each of these entities is stored in an own table. Each entity can be translated in multiple languages. Storage of the translations is intended in a table called (you name it) translations.
Now it will be a bit tricky… we have an extra table called mnemonic. We use it for identifying recipes, ingredients, categories and units of measure in a global context… best analogy to this is a GUID I think. The mnemonic also helps us to map between recipes, ingredients etc. and their translations.
The mnemonic table consists of five rows: id (the primary key) and four additional meta-data rows: ingredient_id, recipe_id, category_id and unit_id referencing a 1:1 relationships to primary key of their relatives.
I'm asking myself how cam I map the relationship between the mnemonic entity and recipes, ingredients an so on.
Or (to be more specific) how can the category_id in the mnemonic table be automatically populated with the value of the the primary key of the category table?
I tried first this kind of association mapping in my Category model:
class Category
{
/**
* #var integer $id
*
* #Column(name="id", type="bigint", nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
* #OneToOne(targetEntity="Mnemonic", mappedBy="category")
* #JoinColumn(name="id", referencedColumnName="category_id")
*/
private $id;
and in the Mnemonic model:
class Mnemonic
{
/**
*
* #OneToOne(targetEntity="Category", inversedBy="mnemonic")
* #JoinColumn(name="category_id", referencedColumnName="id")
*/
private $categoryId;
That didn't work out — Doctrine dont' produce any errors, but populates only the category table.
So I thought I could write some code in the __construct method of the Category model, where I would create a Mnemonic object, set his categoryId according to the ID of Category model and bind it to doctrine's entity manager. But that's kind of ugly — I think model classes should not be responsible for persistence.
During the writing of my question I thought, that the solution could be a kind of a Factory class. It would take all the persistence logic out of the model and could handle special cases.
What do you think? What is the best solution?
Best regards in advice
Paul
Have you thought about adding an event listener for onFlush? so essentially on flush, you check what object you are persisting, then execute some code such as creating the Mnemonic object.
Info about registering onFlush events can be found on the doctrine website at the following link:
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/events.html#onflush

Categories