Adding properties to existing doctrine entities - php

I have the following design problem: I have two projects which both use doctrine to manage their entities. Both projects work on similar entities (most of them are in fact equal). I thought now it would be the best to write a library which contains the core entities used by both projects.
But this gives me more headaches than I first thought. Lets give you the following example, I have an entity
class Entity {
... a lot of managed properties used in both projects
}
then in project A i use exactly this entity, in project B my entity looks a little bit different:
class Entity {
... a lot of managed properties used in both projects
... one managed property only used in project B
}
Now if I declare Entity in my library I could extend it in my project B to add the additional property. But then the question is how to do it with doctrine. If I declare the entity in the library managed I can't easily declare the derived class as managed too in project B. On the other hand if I don't declare it managed in the library I won't be able to have associations containing the entity (which is in my case very important, I have a lot of associations across all my entities).
How should I handle such a situation? Writing the whole entity structure two times in both projects? It doesn't seem the right thing to do for me, but I can't think of another solution.

You could consider using traits that would contain sets of annotated properties and their related methods. This would give you the possibility to have a common base on which you can build both sides independently.
The down side is that both objects would not have a common interface, some of their internals would be the same but you would not be able to tell from the outside. A way to go around this would be to write an interface that exposes the methods you need to operate with those entities in your common library. The common trait could then take care of implementing that interface.
Edit: As you pointed out, traits are not very helpful if we are dealing with associations as you cannot override the targetEntity of an association with AssociationOverride annotation. That being said, by searching about that particular topic, I stumbled on the ResolveTargetEntityListener which might be the tool you were searching for.
The official documentation has a detailed page about how to use it (if you are using Symfony, there is this article as well). As explained in the documentation, thanks to that utility, you can have relationships between entities based on abstract classes or interfaces that would be resolved at runtime.
ResolveTargetEntityListener use with abstract classes
Here is a working example with abstract classes with the annotation MappedSuperClass:
AppBundle/Model/AbstractA.php
use Doctrine\ORM\Mapping as ORM;
/** #ORM\MappedSuperclass */
abstract class AbstractA
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="text", type="string")
*/
private $text;
/** #ORM\ManyToOne(targetEntity="AppBundle\Model\AbstractB") */
private $b;
}
AppBundle/Model/AbstractB.php
/** #ORM\MappedSuperclass */
abstract class AbstractB
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** #ORM\Column(name="number", type="integer") */
private $number;
}
AppBundle/Entity/A.php
/** #ORM\Entity() */
class A extends AbstractA {}
AppBundle/Entity/B.php
/** #ORM\Entity() */
class B extends AbstractB {}
And since I tested this with Symfony, I had to add the following in my config:
doctrine:
orm:
resolve_target_entities:
AppBundle\Model\AbstractA: AppBundle\Entity\A
AppBundle\Model\AbstractB: AppBundle\Entity\B
When asking doctrine for a schema update, it outputs:
CREATE TABLE a (id INT AUTO_INCREMENT NOT NULL, b_id INT DEFAULT NULL, text VARCHAR(255) NOT NULL, INDEX IDX_E8B7BE43296BFCB6 (b_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
CREATE TABLE b (id INT AUTO_INCREMENT NOT NULL, number INT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
ALTER TABLE a ADD CONSTRAINT FK_E8B7BE43296BFCB6 FOREIGN KEY (b_id) REFERENCES b (id);

That is a common and actually really good question. Lots of people are in trouble with such issues, and we cannot say that there is one and only one right answer to that.
I experienced various solutions, and the best I could "use" is to duplicate the entity(ies) definition(s) in each project.
Why ?
Because it is indeed easier to maintain, and even if you have some migration strategies (e.g. with Doctrine migrations or whatever), your code, relying on a specific version of the entities, could break.
You can try to deal with that kind of issues with a tool for migration strategies (i'm kinda out of the subject), and I'd then recommend to store every property (even the ones that are project-specific) in your library.
But do this if you really need it. Is it a big deal for you to duplicate ? Sometimes, factoring everything is not a good idea.
If you duplicate the definitions, you'll also be able to only focus on properties that you really care about. This means that some properties that you don't care about can be omitted from your entities definition in each project.
Just ensure that you still have a strategy for your migrations, it's always a good idea.

Related

ZF2 + Doctrine 2 - Child-level discriminators with Class Table Inheritance

Much asked on SO and around the web with regards to ZF2 with Doctrine 2 and using Discriminators is: how do you not declare all child Entities on the parent Entity? Especially when you have multiple modules?
The short answer is: do not declare a discriminatorMap. Doctrine will handle it for you.
The longer answer is below.
A popular article on how to be able to declare your child Entities, on the children Entities, instead of the parent, is this one.
However, Doctrine 2 has changed somewhat since it was written, e.g. the AnnotationWriter no longer exists.
There, however, is a simpler way, as I mentioned in the question: do nothing.
To now use Discriminators using the “Class Table Inheritance” method (as opposed to “Single Table Inheritance”) is to NOT DECLARE a Discriminator Map! (Not sure if this will also work for STI…)
I found an old ticket on Github that explains the same issue as this answer and which many people still have, that declaring on the parent makes no sense. After reading through that, I dove into the code and re-read the docs, carefully.
Also, if you’re reeeeaaally careful when reading the docs, it says this is possible, by not saying it.
Quoting:
Things to note:
The #InheritanceType, #DiscriminatorColumn and #DiscriminatorMap must be specified on the topmost class that is part of the mapped entity hierarchy.
The #DiscriminatorMap specifies which values of the discriminator column identify a row as being of which type. In the case above a value of “person” identifies a row as being of type Person and “employee” identifies a row as being of type Employee.
The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied.
If no discriminator map is provided, then the map is generated automatically. The automatically generated discriminator map contains the lowercase short name of each class as key.
Of course, the above piece of documentation does explicitly state that a map would be generated if none was provided. Though it contradicts the first thing to note, which is that the #DiscriminatorMap must be provided on the topmost class in the hierarchy.
So, if you were to stretch your classes across several modules (as I assume that’s why you would be reading this), do not declare a discriminator map!
I’ll leave you with an example below:
<?php
namespace My\Namespace\Entity;
/**
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* // NOTE: No #DiscriminatorMap!!!
*/
class Person
{
// ...
}
<?php
namespace My\Other\Namespace\Entity;
/** #Entity */
class Employee extends \My\Namespace\Entity\Person
{
// ...
}
When you use the doctrine CLI command to check your entities, you’ll find this is correct.
Also, check that it fully works by using the entity checking command:
./vendor/bin/doctrine-module orm:mapping:describe “\My\Namespace\Entity\Person”
Near the top of the response of that command will be this line:
| Discriminator map | {“person”:”My\\Namespace\\Entity\\Person”,”employee”:”My\\Other\\Namespace\\Entity\\Employee”}

Design pattern Doctrine ORM - Generate unique names based on input

I am working with Symfony2 and Doctrine ORM and want to achieve the following with a clean architecture :
Each time a new Entity is created, I want to save a "display name" chosen by my end-user, then generate a "unique name" based on the "display name".
If my end-user want to create 3 Project called "Drawings",
the first one will have display_name = "drawings"
the second one will have display_name = "drawings2"
the third one will have display_name = "drawings3"
(or something like that, whatever the pattern)
Basic Entity example :
/**
* Project.
*
* #ORM\Entity
*/
class Project
{
//...
/**
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name_display ;
/**
* #ORM\Column(type="string", length=50, nullable=false, unique=true)
*/
protected $name_unique ;
//...
Basic usage example :
$project = new Project();
$project->setDisplayName('Drawings');
//Around here I would like the Unique name to be generated
$this->getDoctrine()->getManager()->persist($project);
I thought about various solutions :
Doing the name generation in the Controller, but it's not re-usable
Doing the unique name generation in the repository. But it seem to be a bad practive (repositories should be read-only)
Using a PrePersist LifecycleCallbacks from doctrine, but it's not a good practice as I need the Entity Manager to do a Database
Doing the name generation in the Entity Setter, injecting the Entity Manager to make requests and look for available names. That looks horrible
Using a service to persist the Entity as explained here : Are Doctrine2 repositories a good place to save my entities? (But it's quite complicated and involve a huge change in my infrastructure if I want to have all my Entity creations to be consistent with this practice)
I would recommend the last options - services. It may need changes in your project, but I find this the best way to manage usual crud operations with entities - create, save, findBySomething ...
It is crystal clear - no black magic. As opposed to events where there is no obvious relation between the executed code and actions with entities (like creating it through new).
It is not dependent on annotations and it is easy to maintain.
Controllers and other services may access this service through Dependency Injection which is a clear way of satisfying dependencies of business objects(objects holding business logic)
Your repositories won't become bigger and bigger
You can use default repositories - fewer issues with back compatibility when upgrading Doctrine
It is much better than the "setter solution", which sounds really horrible - entities should never be that mighty, so they would have references to services (especially services like EntityManager)

Doctrine 2 - Relate from abstract class to abstract class

Alright. This question is kind of hard to describe. But here goes. I'll post some images first, just incase someone gets what I'm doing from this image;
A Block is an element that can be used to fill a webpage or blogpost. This can be images, text or forms. These Blocks are ContentBlocks. Block has a DiscriminatorColumn and DiscriminatorMap properties that are used to join the right Block table and create the underlying Block element. (i.e. an ImageContentBlock)
On the other hand we have Forms. Forms consist of FormBlocks. These are certain common Form elements. (TextField, PhoneField etc).
I want to be able to relate to the Content- or FormBlocks from either Page, Post or Form.
How can I achieve this in Doctrine?
I could add an entityType and entityId field to the Block class. But that would remove the object orientated style of programming. I would rather refer to the owning ContentEntity. But then again. I need to join or relate to the Blocks.
Not every ContentEntity has Blocks. So I cannot add this as an property of ContentEntity.
Now. I could off course use a ManyToMany relationship and go with a JoinTable. I guess that would always work. But I would have to join twice.
I think your problem isn't primarily about the data relations but about the fact that you want to avoid duplicate code. This results in your “Entity” being at the top of your hierarchy, only because it has a few common properties that each entity should have. (By the way, naming an entity “Entity” is a bit confusing.)
Maybe what you're looking for are Traits. So, instead of providing id and active through an entity, it could as well be a trait:
trait CmsEntity
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="boolean")
*/
protected $active;
// add getters/setters and other code as you like
}
Now you can attach this trait to all the entities which should have the given properties.
/**
* #ORM\Entity
*/
class Page
{
use CmsEntity; // import the trait
/**
* #ORM\Column(type="text")
*/
private $header;
// etc.
}
This will make you free from the requirement of deriving all your entities from one master “Entity” which just carries some common properties.
And now you can create a direct relation between between “ContentEntity” and “Block” (1:n I'd guess), which is more logical.
There's also a nice article elaborating on using Doctrine with traits for further reading.

What is a framework? And what is Doctrine 2? [duplicate]

This question already has answers here:
What is a Web Framework? How does it compare with LAMP?
(3 answers)
Closed 8 years ago.
I know some HTML, PHP, CSS and MySQL. Something that I haven't got to grasps with yet is frameworks. I am trying my best to read what they are and what they do, but for the life of me I cannot understand it.
Please could somebody explain frameworks and Doctrine 2 in a very simple way, as I don't know where to start with them, but notice that they are certainly required.
I could tell you here what a framework is, but the answers to the question What is a software framework? already do it.
So, about Doctrine. It's an object relational mapper (ORM). It basically allows you to insert/update/select/delete an object in a relational database, or generate/update tables via classes.
Let's assume a simple Member table:
Usually, you would write a query to, for example, insert something in a table. Such as:
INSERT Member VALUES ('Andy', 'andy#example.com', 30);
What an ORM allows you to do, is to insert a mapped object into the table. The values in the table will be seen just as you would normally see them by inserting them via query.
Let's look at a very simple example of Doctrine in the Symfony framework:
namespace ExampleProject\MemberBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Member
* #ORM\Table()
* #ORM\Entity
*/
class Member {
/**
* #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;
/**
* #var string
* #ORM\Column(name="email", type="string", length=255)
*/
private $email;
/**
* #var string
* #ORM\Column(name="age", type="integer", length=3)
*/
private $age;
/* getters and setters here */
}
The above class is mapped (annotated with DocBlocks) and represents our Member table, describing which parts will be seen as columns in the database. Simply said: the mapped variables inside the class will be visible as columns in the database. And you also can tell by mapping these variables, which datatype you want the columns to have (string/integer etc).
Now in my code, I'm able to call the Doctrine entity manager, create a new Member object, initiate the properties and save it into the database, in a nice object-oriented syntax:
$em = $this->getDoctrine()->getEntityManager();
$member = new Member;
$member->setId($id);
$member->setName($name);
$member->setEmail($email);
$member->setAge($age);
$em->persist($member);
$em->flush();
As you can see, all we need to do is call to save the object in the database. In the background, the ORM also performs a INSERT query (similarly to the one I mentioned above). You even could enable a setting to see the actual query executed.
Now this may look quite unnecessary and lots of work. But it will save you a lot of time. It is more object oriented from your source code point of view, and you will be able to maintain your (medium/large) application much better than without using an ORM. Furthermore, if you currently have MySQL as a database, but you would like to change it to e.g., PostgreSQL, you can do so with minimal changes to your code as your ORM will take care of the underlying queries.
So in essence, the ORM is a database abstraction layer with object-oriented syntax.

How to setup Symfony2 Bundles independently with related Entities

I am trying to figure out a smart way to implement my bundles with following requirements:
I have a Bundle with logic named LogicABundle
I have a Bundle with common things as design and Menus called
AppBundle
I have another Bundle with logic LogicBBundle with some entities
related to LogicABundle entities
I know want to be able to "deploy" two applications from this setup:
Application one uses the LogicABundle and AppBundle
The second one uses LogicABundle, LogicBBundle and AppBundle
The issue is, that for the second application I need to relate some Entities from LogicABundle to LogicBBundle, which causes the first "deploy" option to brake, if I just have an entity in LogicABundle pointing to LogicBBundle.
Is there a smart solution to deploy these two different applications independently?
Here is an example in order to make it easier to understand:
namespace My\LogicABundle\Entity\Game;
use Doctrine\ORM\Mapping as ORM;
/**
* My\LogicABundle\Entity\Game
*
* #ORM\Entity
*
*/
class Game
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string")
*/
private $title;
/**
*
* #var Message
* #ORM\ManyToOne(targetEntity="\My\LogicBBundle\Entity\Message", inversedBy="games")
* #ORM\JoinColumn(name="messag_id", referencedColumnName="id", nullable=false)
* #Assert\NotNull()
*/
private $message;
}
I want to be able to use the Game class in my standalone application only with LogicABundle, and in my second application I need the game Entity with message relation.
I am not sure, but I have the same problem and I just found that : http://symfony.com/en/doc/current/cookbook/doctrine/resolve_target_entity.html
Hope not to late ;)
If you are using Git (or SVN or another source countrol tool) I would recommend to create two separate Symfony2 applications (each in its on repository). Also, every bundle gets its own repository and I would use Composer to set up the dependencies correctly and then install the bundles (LogicABundle, LogicBBundle, AppBundle) as vendors.
Update: Since the different bundles need different entities, one way is to specify the base entity in the bundle that does only need the base entity and extend the entity in the other bundle with additional relations (see Doctrine Inheritance Mapping).
For example, define EntityA in LogicABundle and define EntityA2 in LogicBBundle where EntityA2 extends EntityA and adds additional relations to the entity.
Update: Since you do not have provided additional information why you need to do this, I can only guess now, but one additional idea would be to simply use the same entities in both bundles. The logic in LogicABundle would simply ignore the additional relations. I think that is what most developers would do in your situation.
Consider, for example, bundles that provide common functionality like FOSUserBundle. The bundle defines some models, but not every application that uses FOSUserBundle has to use every field of the entities (in a application I am currently developing I completely ignore the groups functionality of FOSUserBundle).
Please provider further information if it is possible to use a common entity class and ignore these additional relations.

Categories