Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding inversedBy mapping in Doctrine 2 inheritance

Tags:

doctrine-orm

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.

like image 982
BenMorel Avatar asked Mar 30 '14 15:03

BenMorel


2 Answers

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
{
}
like image 102
PowerKiKi Avatar answered Oct 26 '22 11:10

PowerKiKi


Unidirectional / bidirectional

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).

Principle of inheritance

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.

Value Object

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).

like image 8
Jasper N. Brouwer Avatar answered Oct 26 '22 10:10

Jasper N. Brouwer