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.
Related
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.
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.
I've been thrown a 'can it be done in Doctrine' type question by my supervisor, and while I've been able to do some of it with external 'Mapper' objects, I'm wondering if there might be any way to fake-out the ORM to do it within Doctrine itself.
Essentially, the thing we are interested in doing is cutting down database clutter. We have a number of tables containing distinct properties or item sets in various categories and then a whole bunch of link-tables tying them to other properties or item sets. For example
We have a couple of tables such as 'Materials' and 'PaperTypes' which describe various Material and Paper options for our products. We then group these into 'MaterialCollections' and 'PaperFamilies' respectively. Then there has to be a one-to-many link table between MaterialCollection/Materials and PaperFamilies/PaperTypes respectively. Repeat these types of relationships a couple dozen more times and you see where our DB is starting to clutter up.
The link tables themselves are nothing more than a number of entries including multiple records with the PrimaryId from the parent table (collections/families) and unique-per-parent PrimaryId's from the sub-table (materials/papertypes). Link tables could be named something like MaterialCollectionsMaterials and PaperFamilyPaperTypes for example.
The idea was to get rid of this slew of link tables by using an abstract 3-table structure as follows:
Lists (consisting of a unique ListId, a ListName and a TypeId)
Types (consisting of a unique TypeId, a TypeName and an EntityName)
ListXref (consisting of ListIds and memberId which points to the primaryId from the Entity designated in the list type)
Multiple lists can exist for a given type, but the Xref pairs are unique. Different Types can also point to the same Entity. (e.g. there may be more than one type of Material list defined)
The 'ListName' would be the equivalent of the parent TableNames above and would allow for eliminating two of the tables in those relationships. So the records in 'MaterialCollections' would now instead be records in the List table of TypeName "MaterialCollection". The records that would have been in a link table (such as MaterialCollectionsMaterials) would now instead be pointed to from ListXRef.
As stated, I've gotten a basic mapper to make this work for rather basic list creation. But what I'm wondering is if there is any way to create Entities or things that behave like Doctrine Entities to establish the abstract relationships between a given list/listtype and the table referenced by EntityName and the corresponding memberIds?
In other words, it would be really nice if I had some means by which I could produce something that had the bulk of functionality of a Doctrine Entity which could be retrieved from the Service Manager or something like it that would behave (have similar properties/functions) like a Doctrine Entity.
I tried adding a wrapper object in the Entity tree that my mappers could try to retrieve which was basically an inherited version of the Xref entity with a few alias functions, but it can't be retrieved with the Entity manager.
Any help is appreciated.
P.S. While it is not a priority at the moment, longer term I also want to really throw a wrench into the works by trying to have some lists be capable of pointing back to the records produced by other lists. So, for example, a List "ProductXMaterials" of type "ProductMaterials" might point to some but not all results of "MaterialCollections". But I'll worry about this one later.
Well, Doctrine can't generate "pseudo entity classes" but you can.
First you need to hook into the Doctrine "loadClassMetadata" event.
Inside, you generate your entity classes and configuration from code on the fly and dump them into a specified directory (in the cache for example) maintaining the doctrine convention naming but with a custom namespace. For example : "AutoGeneratedNamespace\Entities"
So let's say you need to have a dynamic entity generated for an entity with name: "TmpUser", and you're using "yml" instead of annotations
<?php
namespace Example;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class DoctrineEventListener
{
/**
* Invoked on Doctrine loadClassMetadata event
*
* #var Doctrine\ORM\Event\LoadClassMetadataEventArgs
**/
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$em = $args->getEntityManager();
$metadata = $args->getClassMetadata();
$factory = $em->getMetadataFactory();
$name = 'AutoGeneratedNamespace\Entities\TmpUser';
$tmpDirectory = '/path/to/cache/generated-classes';
// current class metadata is the same as the one we need. this means we already generated it
if($metadata->getName() == $name || $factory->hasClassMetadata($name)) {
return;
}
/**
Generate your class here and dump it into the cache directory with the name: TmpUser.php
(If you're using "yml" instead of annotations also create the TmpUser.orm.yml configuration and dump it in the same directory)
**/
// create a metadata for the newly created class
$metadata = $factory->newClassMetadataInstance($name);
// Register metadata so doctrine knows about it
$factory->setMetadataFor($name, $metadata);
}
}
Finally, you tell Doctrine to look for entities inside the cache directory as well using the configuration: (This is taken from the Symfony documentation so if you're not using Symfony it may vary a bit but you can find the precise configuration in the Doctrine docs)
doctrine:
orm:
mappings:
MyGeneratedEntitiesUniqueMappingsKey:
type: yml
dir: '/path/to/cache/generated-classes'
is_bundle: false
prefix: "AutoGeneratedNamespace\Entities"
alias: GeneratedEntities
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.
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