I'm using Symfony with Doctrine.
To resolve my problem I want to:
or When I'm extending Entity class I want the doctrine to ignore parent's class #entity annotation (to see it as #MappedSuperclass)
or (this one is more preferable) When I'm extending Entity class add to child class something like #MappedChildclass just to know that this class is an Entity, but actual implementation and mappings in the parent class
Lets see concrete problem:
I have 3 bundles:
AppBridgeBundle
UserBundle
ProfileBundle
UserBundle and UserProfile must be decoupled. AppBridgeBundle is a bridge between 2 bundles, it will couple both.
UserBundle has UserEntity:
/**
* #ORM\Entity
**/
class UserEntity {
/**
* #var \Acme\UserBundle\Interfaces\ProfileData
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Interfaces\ProfileData",
cascade={"persist"})
* )
*/
private $profileData;
// ...
}
UserBundle has its own interface ProfileData (decoupled, we only need to inject implementation of this interface).
ProfileBundle has ProfileEntity:
/**
* #ORM\Entity
**/
class ProfileEntity implements Acme\ProfileEntity\Interfaces\Profile {
// properties ...
}
ProfileBundle has its own interface Profile (decoupled).
Basically ProfileData and Profile interfaces are the same.
Now AppBridgeBundle introduces Adapter of Profile and ProfileData interfaces to adapt UserBundle with ProfileBundle.
class ProfileAdapter extends \Acme\ProfileBundle\Entity\ProfileEntity
implements \Acme\UserBundle\Interfaces\ProfileData,
\Acme\ProfileBundle\Interfaces\Profile {
}
Next, we inject our interface implementation in the config.yml app configuration:
orm:
resolve_target_entities:
Acme\UserBundle\Interfaces\ProfileData: Acme\AppBridgeBundle\Entity\ProfileAdapter
Now, the problem is that when I update doctrine schema it throws me an error that Acme\AppBridgeBundle\Entity\ProfileAdapter is not Entity.
If I mark ProfileAdapter with #Entity then it will create 2 separate tables - I don't need it
If I mark ProfileAdapter with #Entity with the same name as ProfileEntity - #Table('profileentity') then it throws me an error that table profile already exists
If I mark ProfileEntity with #ORM\MappedSuperclass and remove #Entity annotation from it - I'll loose default implementation of the ProfileEntity class (so it cant work without bridge anymore).
if I mark ProfileEntity with #InheritanceType("SINGLE_TABLE") it will add to the tables unnecessary discriminator field to my table.
Any suggestions?
I think the trick is to use the dispatched doctrine "loadClassMetadata" event.
You can track this event in a subscriber which is aware of ProfileEntity inheritance, and inject the "#Entity" or the "#MappedSuperclass" annotation.
You can see an implementation here : https://github.com/victoire/CmsBundle/blob/master/EventSubscriber/LoadMetadataSubscriber.php
In this example, I dynamically inject enabled widgets to the discriminatorMap of my Widget entity.
I let you adapt this code to your needs.
Had the same problem, what I did using your example:
move \Acme\ProfileBundle\Entity\ProfileEntity to \Acme\ProfileBundle\Model\ProfileEntity
redeclare ProfileEntity class to abstract but leave all the ORM instructions intact
redeclare ProfileAdapter to extend new ProfileEntity model
mark ProfileAdapter as #ORM\Entity only, all remaining #ORM* instructions leave in the model class
Pros:
no additional tables
no additional fields
can work without the bridge
Cons:
it is necessary to create entity extending abstract model for each new Bundle installation
Related
I got a question. I have been waiting on the DiscriminatorColumn annotation in Doctrine 2 but now that I got it via Update of Doctrine I am not able to find the Hibernate's DiscriminatorValue annotation equivalent in Doctrine. FYI, my Doctrine version is "doctrine/orm": "^2.5.6" and "doctrine/doctrine-bundle": "~1.6" but I cannot find such annotation.
My basic desire here is to set the Discriminator Column value per child class not in the main class in the DiscriminatorMap.
Like my comment stated, I had this issue a while back as well, where I wanted to "declare" new DiscriminatorMap entries on the child-classes. The short answer is: do not declare a map at all. Doctrine takes care of it.
Have a read of my full answer. This works for me using Class Table Inheritance (CTI), the docs state that it should work the same for Single Table Inheritance (STI).
The basic code setup to let Doctrine handle it for you is:
<?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
{
// ...
}
I would like to retrieve a record from another entity (or record from the DB) within a entity.
They there are no relationship between the two entities.
I am using #ORM\HasLifecycleCallbacks() and #ORM\PrePersist so when the main entity is created it will also create another entity (save a record to another table)
The above is working fine, there are no issues with this.
What I am having an issue with is I would like to link that entity with another table but I need to retrieve the object based on the value of the first entity.
Usually I would write a function in the entity repository but I am not calling the entity manager within the entity.
An Entity in Doctrine is an object representation of a concept, with attributes and methods. It is meant to be lightweight, a POPO (plain old php object). It must not know anything about its persistence. Therefore if you see reference to the EntityManager in a model, it probably stinks.
Solutions? You could use an entity listener called on entity creation and then use a service dedicated only to properly compose your object(s), maybe something like a Factory. In this way, your entity stays lightweight, the lifecycle management is satisfied and the entity composing is responsibility only of your service.
Entity manager is accessible in an entity repository. You can legally use it to fetch data from other entities and to compose your business logic. This is what entity repositories are made for: Doctrine Custom Repositories, Symfony Custom Repository Classes.
/**
* #ORM\Entity
*/
class Beta {}
/**
* #ORM\Entity
*/
class Alpha {}
class AlphaRepository extends EntityRepository
{
public function getDataFromAnotherEntity($something)
{
$query = 'select * from MyBundle\Entity\Alpha alpha where alpha.id = :something';
return $this->getEntityManager()
->createQuery($query)
->setParameter('something', $something)
->getResult();
}
}
In Symfony 3.1 you can use the entityManager to set a reference. This is still lightweight as it does not instance a complete Doctrine Record.
Example: I have an entity Status which has some states, and it's referenced in another entity. On create i use this method inside EventSubscriber:
public function preAction(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
if (method_exists($entity, 'setStatus')) {
if ($entity->getStatus() === null) {
$entity->setStatus($entityManager->getReference('AppBundle\Entity\Status', Status::STATUS_REGULAR));
}
}
}
How do you share an entity between multiple bundles with different relationships?
For example both the ZooAnimalBundle and FarmAnimalBundle need a User Entity. A third Bundle AccountUserBundle has the User Entity.
In both the Zoo and Farm AnimalBundles I create a User Entity like so:
use Account\UserBundle\Entity\User as BaseUser;
class User extends BaseUser
{
}
I then have a Hospital entity in Zoo:
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_zoo_animals")
*/
protected $users;
And a Room entity in Farm:
class Room {
/**
* #ORM\ManyToMany(targetEntity="Farm\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_farm_animals")
*/
protected $users;
Everything works so far in that I can call Zoo.Room->getUsers() or Farm.Hospital->getUsers()
However the problem is I'm not sure on how to set up the inverse relationship in their respective User entities.
If for example I update the FarmAnimal User Entity and run doctrine:generate:entities
/**
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\ManyToMany(targetEntity="Room", mappedBy="users", cascade={"persist"})
*/
protected $rooms;
}
It will copy the protected $properties from BaseUser and create all the set and get methods which is not what I want. What is the correct way of setting up these relationships?
Update
If you don't setup the inverse relationship, how would you select all users where hospital.id = 1
$qb = $this->getEntityManager()->createQueryBuilder()
->select(
'u'
)
->from('Account\UserBundle\Entity\User','u')
->leftJoin('u.hospitals', 'h')
->andWhere('h.id = :hospital_id')
->setParameter('hospital_id',$hospital_id);
This gives the error:
Class Account\UserBundle\Entity\User has no association named hospitals
I know I could select from hospital and join user because that relationship does exist but I need to select users because I am using them with Doctrine\ORM\Tools\Pagination\Paginator
The query would be
$qb = $this->createQueryBuilder('a')
->select(
'h', 'u'
)
->leftJoin('h.users', 'u')
The problem with this is Paginator only sees one result Hospital because the Users are attached to it.
You can define abstract entity dependencies and implement them with other bundles.
First, each of the bundles depending on a user entity should define a User interface. For example:
namespace Foo\BarBundle\Entity;
interface UserInterface
{
public function getId();
public function getEmail();
// other getters
}
Then, in each entity depending on the user, define the relationship, e.g.:
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class Something
{
/**
* #ORM\ManyToOne(targetEntity="UserInterface")
* #Assert\NotNull
*/
protected $User;
// add other fields as required
}
Now you need to register the User entity as an implementation of the UserInterfaces:
namespace Foo\UserBundle\Entity;
use Foo\BarBundle\Entity\UserInterface as BarUserInterface;
use Foo\FoobarBundle\Entity\UserInterface as FoobarUserInterface;
/**
* #ORM\Entity
*/
class User implements BarUserInterface, FoobarUserInterface
{
// implement the demanded methods
}
Then add the following to app/config/config.yml:
doctrine:
orm:
resolve_target_entities:
Foo\BarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
Foo\FooarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
(Heads up: there will usually already be a doctrine.orm node which you'll have to extend.)
This is not a perfect solution, because you cannot really say which fields the user entity should have. On the other hand, it's strictly OOP, as you don't have to know about internals of the User implementation – you just need it to return the right values.
Creating multiple definitions of the account is the wrong way to do it, unless you want to create 3 seperate user tables (even then it's better not to do it this way).
Really you want your other entities to map to the your user entity in the account bundle.
I.e.,
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AccountBundle\Entity\User")
*/
protected $users;
Now, there is no need to create the inverse relationship. In fact, this is a bad practice since you have a bi-directional dependency. Users don't know about hospitals, but hospital knows about it's users. Now, any bundle can map to the user entity and reuse it.
This is my base/parent entity, setup so its children are using their own tables.
/**
* #ORM\Entity
* #ORM\Table(name="layer_object")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"service"="Service", "aircraft"="Aircraft", ...})
*/
class LayerObject {}
Aircraft entity. A simple child that is doing well
/**
* #ORM\Entity
* #ORM\Table(name="aircraft")
*/
class Aircraft extends LayerObject
Service entity. A complex child, that itself is using single table inheritance.
/**
* #ORM\Entity
* #ORM\Table(name="service")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"ground"="Ground", "onboard"="Onboard"})
*/
class Service extends LayerObject {}
A child of the Service entity
/**
* #ORM\Entity
*/
class Ground extends Service {}
app/console doctrine:schema:validate finds no errors but app/console doctrine:schema:update --force just won't generate the 'service' table, the one that should use single table inheritance. Seems like the service entity definition is simply ignored.
Sure I could create the SQL for this table by hand, but the application will grow and at some point I will need to use migrations.
Could anyone point me in some direction? Thanks.
Found a duplicate, but there are no answers so far, see: Doctrine 2 multiple level inheritance
Edit:
When I use class table inheritance for the 2nd level too (#ORM\InheritanceType("JOINED") for the Service entity) it works pretty well. See: Doctrine2 Multiple level inheritance
What you're trying to achieve is not possible with pure mapping.
The documentation for Class Table Inheritance and Single Table Inheritance clearly state:
The #InheritanceType, #DiscriminatorColumn and #DiscriminatorMap must
be specified on the topmost class that is part of the mapped entity
hierarchy.
You might be able to make this work by implementing a subscriber to the loadClassMetaData event that changes the inheritance-type dynamically (i.e. based on annotations on of the child entities).
Some further inspiration can be found in this article.
I'm having trouble finding a way to appropriately extend an Entity across bundles in Symfony2 using Doctrine2 as the ORM.
Currently there are three methods that I've found to extending entities in Symfony2 using Doctrine2 as the ORM. Mapped Superclass, Single Table Inheritance and Class Table Inheritance. None of these work for what I'm looking to do.
I have two Bundles: UserBundle and BlogBundle. I want to be able to use the UserBundle in projects that do not have the BlogBundle, but the BlogBundle will always be used in projects that have the User Bundle. It's ok if the BlogBundle has dependencies on the UserBundle, but not the other way around.
I have two entities:
BlogBundle\Entity\Post and
UserBundle\Entity\User
Relationship:
There needs to be a One to Many relationship between Users and Blog Posts. This is achieved through a Author_ID property (column) on the BlogBundle\Entity\Post object (table) which is mapped to UserBundle\Entity\User.id
The Problem:
I can call the UserBundle\Entity\User entity directly from within the BlogBundle and achieve what I'm looking for using a Uni-Directional mapping. This does not allow me to access all posts by a user from within a User object. I can access the data via custom queries but this is not as clean as accessing posts by a user through the user object.
What I'd like to do is extend the UserBundle\Entity\User object from within the BlogBundle, and add the methods and properties to this object that establish the One to Many mapping used within the BlogBundle. None of this is persisted, it simply defines the relationship and allows me to logically access all posts created by a user in an application that implements both the BlogBundle and UserBundle by adding needed functionality to the User object within the blog bundle (thus avoiding a dependency from the UserBundle to the BlogBundle).
When I create a BlogBundle\Entity\User object and extend UserBundle\Entity\User I must declare #ORM\Table(name="usertablename"). If I don't, any attempt to access the BlogBundle\Entity\User object will fail to access the database. Since none of the additions in the extended object persist, this works fine across bundles. The issue with this is when I call "php app/console doctrine:schema:update --force", there is a conflict since two entities try to map to & create the same table. I have tried using the ResolveTargetEntityListener feature that was recently implemented but this, along with Mapped Superclas, STI and CTI all force a dependency on the BlogBundle from the UserBundle.
Below are my objects to help illustrate my my setup. They have been abbreviated for clarity. I realize some of the semantics aren't correct but it's intended to communicate the ideas & configuration.
UserBundle\Entity\User
#ORM\Table(name="app_user")
#ORM\Entity
class User implements UserInterface
{
...
}
BlogBundle\Entity\Post
#ORM\Table(name="app_post")
#ORM\Entity
class Post
{
...
#ORM\Column(name="author_id", type="integer")
protected $author_id;
#ORM\ManyToOne(targetEntity="\App\BlogBundle\Entity\User", inversedBy="posts")
#ORM\JoinColumn(name="author_id", referencedColumnName="id")
protected $author;
}
BlogBundle\Entity\User
use App\UserBundle\Entity\User as BaseUser
#ORM\Entity
#ORM\table(name="app_user")
class User extends BaseUser
{
....
#ORM\OneToMany(targetEntity="App\BlogBundle\Entity\Post", mappedBy="author")
protected $posts;
public function __construct()
{
parent::_construct();
$this->posts = new \Doctrine\Common\Collections\ArrayCollection();
}
....
/* Getters & Setters, nothing that defines #ORM\Column, nothing persisted */
}
This works but the problem is that I'm mapping two entities in the project to the same table. The extended object doesn't grab the #ORM\Table(name="app_user") from it's parent so it must be defined in BlogBundle\Entity\User. If not any reference to this object from a controller will not access the database. Since nothing is persisted from the extended object nothing is broken except for when I try to update the database schema from the console.
I can use a unidirectional relationship, but this limits how I can access the data from within a controller.
You can see in this link to know about inheritance: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html#single-table-inheritance
You must declare in UserBundle\Entity\User:
/**
* #Entity
* #InheritanceType("SINGLE_TABLE")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"baseuser" = "UserBundle\Entity\User", "blogUser" = "BlogBundle\Entity\User"})
*/
class User implements UserInterface
{
...
}
And BlogBundle\Entity\User
use App\UserBundle\Entity\User as BaseUser;
/**
* #ORM\Entity
*/
class User extends BaseUser
{
....
}
Goodluck!
I think you could find this Bundle interesting:
https://github.com/mmoreram/SimpleDoctrineMapping
It allows you to define by parameters what files are mapping your entities, allowing to override every entity of your generic bundles.
For example:
parameters:
#
# Mapping information
#
test_bundle.entity.user.class: "TestBundle\Entity\User"
test_bundle.entity.user.mapping_file_path: "#TestBundle/Mapping/Class.orm.yml"
test_bundle.entity.user.entity_manager: default
test_bundle.entity.user.enable: true
The only contra I see is that you have to define all the next entities the same way cause you disabled auto_mapping...