1

How to solve this problem the best way? Maybe any pattern was created for this?

I have class Master, which through the DI manage a Slave object. Instance of the class Slave must have parent Master internal. Something like this:

class Master 
{
  private $slave;

  public function __construct(Slave $slave)
  {
    $slave->registerMaster($this);
    $this->slave = $slave;
  }
}

class Slave
{
  private $master;

  public function registerMaster(Master $master)
  {
    $this->master = $master;
  }
}

Only class Master could call registerMaster method. Is any way, pattern which will help me in this example?

6
  • What is you'r problem now? Commented Jul 25, 2016 at 6:57
  • I would like to disable registerMaster outside in order to user cant do this himself. Commented Jul 25, 2016 at 6:59
  • Your question does't make sense - please clean it up and define it properly. All I'm seeing is that you only want to allow Master the ability to call registerMaster()? Commented Jul 25, 2016 at 7:10
  • 1
    Why do you need a bidirectional association? Commented Jul 25, 2016 at 13:07
  • 1
    I solve the problem with ReflectionMethod and setting accesibility. Commented Jul 25, 2016 at 16:06

2 Answers 2

2

Only class Master could call registerMaster method. Is any way, pattern which will help me in this example?

There is no concept of Friend Classes in PHP, so there is no way to hide a public method from another class.

You could use inheritance and protected visibility

abstract class LinkedEntity {
    protected $master;
    protected $slave;    
}

class Master extends LinkedEntity {
    public function __construct(Slave $slave) {
        $this->slave = $slave;
        $this->slave->master = $this;
    }
}

class Slave extends LinkedEntity {}

$slave = new Slave;
$master = new Master($slave);

Since Master and Slave now extend the same base class, the protected properties are accessible to them, but not to any other classes. The same would be true for protected methods defined in the LinkedEntity class. So you can put a protected registerMaster(Master $master) method and go through that instead of directly assigning the properties.

Personally, I find that not so pretty and I'd rather find out whether you really need the bidirectional association here or whether it's good enough to have a one way association, e.g. from Master to Slave only or vice versa.

Another, even worse option, would be to inspect the callstack:

class Slave {
    private $master;
    public function registerMaster(Master $master)
    {
        if (debug_backtrace(null, 2)[1]["class"] === Master::class) {
            throw new RuntimeException("Only Masters may call Slaves");
        }
        $this->master = $master;
    }
}

The method checks if the previous class in the call stack (the one that did the call to your registerMaster was actually a Master. However, when your methods depend on the information of who the callee was, it's usually a sign of bad design.

A third option would be to break through the visibility of the method in the Slave via Reflection, e.g.

class Master {
    private $slave;
    public function __construct(Slave $slave) {
        $this->slave = $slave;
        $fn = new ReflectionMethod($slave, 'registerMaster');
        $fn->setAccessible(true);
        $fn->invoke($slave, $this);
    }
}

class Slave {
    private $master;
    private function registerMaster(Master $master)
    {
        $this->master = $master;
    }
}

Again, this is not optimal, because a core principle of OOP is Information Hiding and we are breaking it here. We are breaking through the designated private visibility and force it to public for the Master. Also, this will not prevent any other objects from doing the same.

Sign up to request clarification or add additional context in comments.

Comments

0

Another way of avoiding extra methods in the class is to bind a closure to the private scope of another class, so you will be able to access all methods and properties directly:

class Master
{
  private $slave;

  public function __construct(Slave $slave)
  {
    $this->slave = $slave;
    $master      = $this;
    $masterInjector = function (Master $master) {
        $this->master = $master;
    };
    $masterInjector->call($slave, $master);
  }
}

class Slave
{
  private $master;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.