I have the following entity:
class Restaurant
{
/**
* @OneToMany(targetEntity="CollectionTime", mappedBy="restaurant")
*/
protected $collectionTimes;
/**
* @OneToMany(targetEntity="DeliveryTime", mappedBy="restaurant")
*/
protected $deliveryTimes;
}
Mapping to two subclasses of the same entity:
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorMap({
* "CollectionTime" = "CollectionTime",
* "DeliveryTime" = "DeliveryTime"
* })
*/
abstract class OrderTime
{
/**
* @ManyToOne(targetEntity="Restaurant")
*/
protected $restaurant;
}
/**
* @Entity
*/
class CollectionTime extends OrderTime
{
}
/**
* @Entity
*/
class DeliveryTime extends OrderTime
{
}
Now the problem is, doctrine orm:validate-schema
reports the following errors:
The field Restaurant#collectionTimes is on the inverse side of a bi-directional relationship, but the specified mappedBy association on the target-entity CollectionTime#restaurant does not contain the required 'inversedBy=collectionTimes' attribute.
The field Restaurant#deliveryTimes is on the inverse side of a bi-directional relationship, but the specified mappedBy association on the target-entity DeliveryTime#restaurant does not contain the required 'inversedBy=deliveryTimes' attribute.
In short, Doctrine expects every mappedBy
to have an inversedBy
on the other side.
The only solution I can see so far is to move the OrderTime::$restaurant
property and mapping to CollectionTime
and DeliveryTime
, just to be able to add the proper inversedBy
mapping:
abstract class OrderTime
{
}
/**
* @Entity
*/
class CollectionTime extends OrderTime
{
/**
* @ManyToOne(targetEntity="Restaurant", inversedBy="collectionTimes")
*/
protected $restaurant;
}
/**
* @Entity
*/
class DeliveryTime extends OrderTime
{
/**
* @ManyToOne(targetEntity="Restaurant", inversedBy="deliveryTimes")
*/
protected $restaurant;
}
But it is cumbersome and goes against the principle of inheritance.
Is there a way to just override the inversedBy
attribute in the subclasses, without having to (re)declare the whole property in the subclass?
I've looked into @AssociationOverrides and @AttributeOverrides, but they don't seem to be designed for this purpose.
You can override inversedBy
since Doctrine 2.6. That would look like that:
/**
* @Entity
* @ORM\AssociationOverrides({
* @ORM\AssociationOverride(name="restaurant", inversedBy="collectionTimes")
* })
*/
class CollectionTime extends OrderTime
{
}
/**
* @Entity
* @ORM\AssociationOverrides({
* @ORM\AssociationOverride(name="restaurant", inversedBy="deliveryTimes")
* })
*/
class DeliveryTime extends OrderTime
{
}
Doctrine 2 uses the concept of unidirectional associations and bidirectional associations.
When defining a unidirectional association you have to omit the inversedBy
parameter, because the association isn't inversed.
When defining a bidirectional association you have to use the inversedBy
parameter on the owning side (that's the side that actually contains all metadata for Doctrine to properly map the association), and you have to use the mappedBy
parameter on the inversed side (so that Doctrine knows where to look for the actual mapping metadata for the association).
So you either use both inversedBy
and mappedBy
(bidirectional) or you don't use them at all (unidirectional).
But it is cumbersome and goes against the principle of inheritance.
I think that depends on how you look at it:
If you only look at the code (not the mapping), you are correct. Both concrete implementations of OrderTime
share a property $restaurant
(and probably getters, setters, and maybe other logic), so the principle dictates that you define that in OrderTime
.
But when you look at the mapping, you have 2 different associations: One that ties Restaurant::$collectionTimes
and CollectionTime::$restaurant
together, and one that ties Restaurant::$deliveryTimes
and DeliveryTime::$restaurant
together.
Because these are 2 different associations, it's only fair that Doctrine wants you to properly define them both.
You can still stick to principle of inheritance in the following way: Define all shared logic you need in OrderTime
, even the property $restaurant
, just don't add the mapping metadata. In the concrete implementations you can redeclare the property $restaurant
with the proper mapping metadata.
The only reason you have to redeclare the property $restaurant
in those concretes is that you're using Annotations for mapping metadata. When using Yaml or XML, you don't have to redeclare the property because the mapping metadata will be in separate files.
So in fact it's not code/logic you're defining in those concrete implementations, it's the mapping metadata.
Those OrderTime
classes look more like Value Objects to me (not Entities): A small simple object, like money or a date range, whose equality isn't based on identity.
Doctrine will support Value Objects starting with version 2.5 (see here).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With