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
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.
I am using symfony2 with the Doctrine entities and I have a problem with the next:
I know I could solve the problem putting an ID to the "club_has_torneo" and turning it into an entity, but to me creating an entity for that looks like something that should not be done. So I wanted to know if there is a way to solve this issue or if I have to do what I think I have to.
Thank you in advance.
I guess I'll submit my own two cents worth.
ORM stands for Object Relational Mapper. The basic idea is to figure out how to map your objects without worrying too much about the database schema. So you have three domain model entities: torneo, club and nadador. Great. Figure out how your application will use these entities. Don't worry about how the relations will end up being stored.
Once you have a working domain model then worry about persistence. The three domain entities clearly map to three doctrine entities. As far as the relations go, I personally am not a big fan of composite primary keys. I think they just complicate things while adding little value. So I would make Doctrine entities for your two has tables and just given them their own primary database id.
Note that these are Doctrine entities not domain entities. Your application code should never need to deal with these relational doctrine entities at all. So in my opinion
creating an entity for that looks like something that should not be
done
does not apply here. It is just a persistence detail.
I think the best solution is indeed to make a entity for your club_has_torneo table. This ClubHasTorneo entity has club_id and torneo_id as composite keys and holds the owning side of a many-to-many relationship between your ClubHasTorneo entity and Nadador entity. This relationship can be done with a join table using the 3 keys. Check the code below on how to do that.
Your database scheme will look exactly like you drew it.
Your ClubHasTorneo entity would look something like this:
<?php
namespace Application\Entity;
use Application\Entity\Club;
use Application\Entity\Torneo;
use Application\Entity\Nadador;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="club_has_torneo")
*/
class ClubHasTorneo
{
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var Club
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Application\Entity\Club", inversedBy="clubHasTorneos", cascade={"persist"})
* #ORM\JoinColumn(name="club_id", referencedColumnName="id")
*/
protected $club;
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var Torneo
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Application\Entity\Torneo", inversedBy="clubHasTorneos")
* #ORM\JoinColumn(name="torneo_id", referencedColumnName="id")
*/
protected $torneo;
/** MANY-TO-MANY BIDIRECTIONAL, OWNING SIDE
* #var Collection
* #ORM\ManyToMany(targetEntity="Application\Entity\Nadador", inversedBy="clubHasTorneos")
* #ORM\JoinTable(name="club_has_torneo_has_nadador",
* joinColumns={
* #ORM\JoinColumn(name="club_id", referencedColumnName="club_id"),
* #ORM\JoinColumn(name="torneo_id", referencedColumnName="torneo_id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="nadador_id", referencedColumnName="id")
* }
* )
*/
protected $natadors;
public function __construct()
{
$this->natadors = new ArrayCollection();
}
// setters and getters
}
my 5 cents
If you want your implementation to match the drawn table structure, then (in my opinion) you need create an entity out of the 'club_has_torneo' table (for 'club_has_torneo_has_matador' you don't need to).
The rationale being that if you try to achieve this without creating the entity, you would need to create the entity associations so, that the 'natador' table references the 'club' and 'torneo' directly - in which case the actual database relations wouldn't match with your drawn table relationship anymore (i.e. the natador wouldn't have relationship to the 'club_has_torneo' table).
Let's say I have a Post entity, and a Comment entity. A comment can be approved or not by an admin (which is a flag in the db). The post entity has:
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="post")
*/
protected $comments;
And I also want a second attribute which will look like:
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="post")
*/
protected $approvedComments;
How is it possible to load only the approved comments here?
Idea #1
You could use Inheritance mapping: https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
The idea would to have separate classes for each type (approved and non-approved), but to store everything in a single table (SINGLE_TABLE inheritance).
You will need to have additional column which will store class type discriminator.
Then, you would have:
/**
* #ORM\OneToMany(targetEntity="ApprovedComment", mappedBy="post")
*/
protected $approvedComments;
/**
* #ORM\OneToMany(targetEntity="NonApprovedComment", mappedBy="post")
*/
protected $nonApprovedComments;
The obvious downside is creation of additional classes.
Idea #2
You could just tweak you Query/QueryBuilder like:
`SELECT p, c FROM AcmeDemoBundle:Post p LEFT JOIN p.comments c WITH c.approved = FALSE`
This idea seems more reasonable.
This can not be achieved through relationships as you describe it. 2 tables can not be related "conditionally" as the relations are based on primary keys.
You have at least solutions here
Leave the "comments" field on the entity with the annotations, remove the annotations from the approved comments field as it is the same as comments as understood by the ORM. You would then have the ->getComments() function to get all comments and you could add a function "getApprovedCommentsForPost($post)" in your repostitory class to retrieve those approved ones.
You could distinguish between comments with single inheritance, so you would have a Comment class and an ApprovedComment class in one table for example, then you could make 2 relations on your entity (read here doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#single-table-inheritance)
You could use doctrine filters to filter out unapproved comments by default when retrieving data from Comments repository
You cannot define that contraint in your entity. Here is the related documentation:
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#annref-onetomany
As you can see there is no option which is related to conditions. You have to define this condition using QueryBuilder.
I'm attempting to set up a many-to-one relationship between a series of sales memos and transaction records.
/**
* #var TransactionInterface
*
* #ORM\ManyToOne(targetEntity="Twb\Common\Model\Broker\TransactionInterface")
* #ORM\JoinColumn(name="FormNoSeller", referencedColumnName="Form")
*/
private $formnoseller;
/**
* #var TransactionInterface
*
* #ORM\ManyToOne(targetEntity="Twb\Common\Model\Broker\TransactionInterface")
* #ORM\JoinColumn(name="FormNoBuyer", referencedColumnName="Form")
*/
private $formnobuyer;
They are split between two different bundles at the moment ('SalesBundle' and 'BrokerBundle'), and with that in mind I am using interfaces from the SalesMemo entity in SalesBundle to the Transaction entity in BrokerBundle.
For some reason, when I reference either or both of $formnoseller and $formnobuyer in my forms, I notice in dev.log that, after selecting all Transaction rows matching the $formnoseller and/or $formnobuyer fields in the SalesMemos, Doctrine tries to SELECT all rows in the Transaction table (the entity for which TransactionInterface references). This is a bit of a problem, since there is an innumerable amount of rows in the DB, which takes up a lot of memory.
Is there any way to have Doctrine avoid selecting all rows with associations? Or am I even understanding properly how Doctrine does associations? Many thanks for any help.
My understanding of your problem is that you're using an Entity Field Type for $formnoseller and $formnobuyer (or you don't specify the type). Giving the choice to select any élément from the underlying table is the expected behaviour for the Entity Field Type (Used by default for OneToMany relationships)
If you don't whant a select list of all the elements of your table for those Fields, you should use an other form field type. You should also have a look at data transformers in the documentation.
If it were me, I would write a stored procedure and do an inner or outer join as appropriate.
Once upon a time, they called this "client server" code. About 15 years ago, it created such a mess the whole industry moved to n-tier development. I'd like to know how the table joins got placed back into the presentation tier again? ORMs and LINQ-to-SQL are a return to client/server".
If you have to do it this way, do the join in LINQ on the Models. Do not do it with the ORM language.
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.