1

How do I handle multiple inverse relations pointing to the same active record? For example:

class Bicycle extends ActiveRecord {

    public function getFrontWheel() {
        return $this
            ->hasOne(Wheel::class, ['id' => 'front_wheel_id'])
            ->inverseOf('bicycles');
    }

    public function getRearWheel() {
        return $this
            ->hasOne(Wheel::class, ['id' => 'rear_wheel_id'])
            ->inverseOf('bicycles');
    }

}

class Wheel extends ActiveRecord {

    public function getBicycles() {
        return $this
            ->hasMany(Bicycle::class, ['???' => 'id'])
            ->inverseOf('??????');
    }

}

What can I do here? I critically need the inverse relations.

mae
  • 14,947
  • 8
  • 32
  • 47

2 Answers2

0

Here is my own solution. Key points:

  • It all boils down to proper naming.
  • Inverse relations are bijective! In other words, every relation always has to have its own unique mirror relation on the other end.
class Bicycle extends ActiveRecord {

    public function getFrontWheel() {
        return $this
            ->hasOne(Wheel::class, ['id' => 'front_wheel_id'])
            ->inverseOf('frontWheelBicycles');
    }

    public function getRearWheel() {
        return $this
            ->hasOne(Wheel::class, ['id' => 'rear_wheel_id'])
            ->inverseOf('rearWheelBicycles');
    }

}
class Wheel extends ActiveRecord {

    public function getFrontWheelBicycles() {
        return $this
            ->hasMany(Bicycle::class, ['front_wheel_id' => 'id'])
            ->inverseOf('frontWheel');
    }

    public function getRearWheelBicycles() {
        return $this
            ->hasMany(Bicycle::class, ['rear_wheel_id' => 'id'])
            ->inverseOf('rearWheel');
    }

}
mae
  • 14,947
  • 8
  • 32
  • 47
-1

i would suggest to do the following:

create two new classes:

  • class FrontWheel extends Wheel {
  • class RearWheel extends Wheel {

in new classes you can set easily the relation.

How to instantiate the correct class? There is a method in ActiveRecord instantiate() where you can write your logic which wheel class need to be created.

class Wheel extends ActiveRecord {
...

  public static function instantiate ( $row ) {
      if($row['type'] === 'RearWheel') {
           return new RealWheel();
      }
      ...

  }

full code:

class Bicycle extends ActiveRecord
{

    public function getFrontWheel()
    {
        return $this
            ->hasOne(Wheel::class, ['id' => 'front_wheel_id'])
            ->inverseOf('bicycles');
    }

    public function getRearWheel()
    {
        return $this
            ->hasOne(Wheel::class, ['id' => 'rear_wheel_id'])
            ->inverseOf('bicycles');
    }

}

abstract class Wheel extends ActiveRecord
{

    public static function instantiate($row)
    {
        if ($row['type'] === 'RearWheel') {
            return new RealWheel();
        }
        if ($row['type'] === 'FrontWheel') {
            return new FrontWheel();
        }
        
        throw new InvalidConfigException();
    }

    abstract public function getBicycles();
}

class RealWheel extends Wheel
{

    public function getBicycles()
    {
        return $this
            ->hasMany(Bicycle::class, ['rear_wheel_id' => 'id'])
            ->inverseOf('rearWheel');
    }

}

class FrontWheel extends Wheel
{

    public function getBicycles()
    {
        return $this
            ->hasMany(Bicycle::class, ['front_wheel_id' => 'id'])
            ->inverseOf('frontWheel');
    }

}
  • Interesting idea but it won't work. The wheels cannot have a "`type`" property, because they must be usable in either location (rear or front) without any modification. – mae Apr 28 '21 at 07:14
  • Hi, if you cannot use type, you could query your db inside instantiate method where that id belongs, if it belongs to a front_wheel_id then you can instantate the front wheel class. I know it is an extra query, i think that is needed in your case. Think about this: Can you tell from a wheel without context (bike) whether it's a front or rear wheel? – Papp Péter Apr 28 '21 at 07:43
  • Indeed, you cannot and you *should not* be able to. A wheel is a wheel. – mae Apr 28 '21 at 07:45
  • So what do you think about querying for it in instantiate method? – Papp Péter Apr 28 '21 at 07:54
  • That sounds like a performance hit, which defeats the whole purpose of inverse relations. Either way, I've found an elegant solution. I will post my answer in a couple of days unless someone proposes something better. – mae Apr 28 '21 at 08:09