views:

118

answers:

5

I got two classes, "A" and "B". In the application logic no one is allowed to create an object of class "B", except for class "A". But, since I dont want to have the two classes in the same file I cant restrict it with the "private" properity.

Is it possible to create this kind of restriction? If someone other then "A" tries to create an object of class "B", you say piss off!?

A: 

In the constructor of B, require that A be passed in. When you then want to get B from A, just create a B and pass in A. When new B is called, it will require A be passed in.

class A
{
    private $b;

    private function getB()
    {
        if (null === $this->b)
        {
            $this->b    = new B($this);
        }

        return $this->b;
    }
}

class B
{
    public function __construct(A $a)
    {

    }
}
MANCHUCK
@MANCHUCK This could technically be tricked with `$a = new A(); $b = new B($a);`...
steven_desu
Actually thinking about it that is correct. Let me test out what happens if A follows a singleton pattern.
MANCHUCK
You could also clone an existing instance of B. And since the class will get instantiated by those who write the code anyway, there is very little point in restricting access this way. Class A and B will also be a pain to test this way, since A has a hardcoded dependency and B always requires an A Mock. Making B a Singleton will throw you into hell completely. IMO the value you get for restricting access this is not worth the penalty.
Gordon
Maybe a better solution would be to use PHPcs. Creating your own sniffer to detect weather B was called from A. Every time you want to deploy the code, just run the sniffer and see if it catches it
MANCHUCK
+4  A: 

You could inspect the backtrace:

class B
{
    public function __construct()
    {
        $chain = debug_backtrace();
        $caller = $chain[1]['class'];

        if ('A' != $caller) {
            throw new Exception('Illegal instantiation');
        }
    }
}
Nev Stokes
If you go this route, you should profile how it performs. The function is called **debug** _backtrace for a reason.
Gordon
Oh, more than agreed Gordon, but it is a solution!
Nev Stokes
+3  A: 

This is as hacky as it get's and you should not use it. I only post it, because I like hacky things ;) Furthermore this will throw an error if E_STRICT error reporting is enabled:

class B
{
    private function __construct() {}

    public function getInstance() {
        if (!isset($this) || !$this instanceof A) {
            throw new LogicException('Construction of B from a class other than A is not permitted.');
        }

        return new self;
    }
}

class A
{
    function someMethod() {
        $b = B::getInstance(); // note that I'm calling the non-static method statically!
    }
}

The reason why this works is a "feature" which may be seen in the second example of this manual page.

nikic
Bear in mind that while this works, it does cause PHP to spit an `E_STRICT`.
BoltClock
Yes, I already say that in the first sentence ;)
nikic
A: 

Maybe you want to use something like this:

class A
{
        protected function __construct ()
        {
        }
}

class B extends A
{
        public function __construct ()
        {
                $a = new A();
        }
}

$b = new B();
matthiasz
A: 

Use get_called_class to find out which class tries to instantiate an object:

class B
{
        public function __construct ()
        {
                if(get_called_class() != 'A') {
                    //booboo
                }
        }
}
w13531
This would only work if `A` would extend `B` and `B` were constructed, not `A`.
nikic