3

I have two classes A and B, both inheriting from the same parent. In PHP, is there a way to make sure that class B cannot be instantiated except from within class A?

(Class B is not a child of A.)

JDelage
  • 13,036
  • 23
  • 78
  • 112

3 Answers3

4

Using debug_backtrace:

class Ancestor{}

class A extends Ancestor{

    public function buildB()
    {

        return new B;

    }

}

class B extends Ancestor{

    public function __construct(){

        $backtrace = debug_backtrace();
        if( $backtrace[1]['class'] !== 'A' )
            throw new Exception("Don't you dare!");

        echo "Built successful!\n";

    }

}

Try it:

//Everything ok this way:
$a = new A;
$a -> buildB();

// You will have an exception in any other case:
try{

    $b = new B; 

}catch(Exception $e){

    echo $e -> getMessage();

}

EDIT: if you want to be sure to create B just inside A's code, you can do as well - just make buildBprivate ^^.

moonwave99
  • 21,957
  • 3
  • 43
  • 64
  • 1
    Oh, nice. I wouldn't have thought of that. – JDelage Sep 13 '12 at 19:00
  • I'd do some testing to see if debug_backtrace() performs well enough for this to be a viable solution. Otherwise you might be better off not having this requirement. From a general point of view, de-coupled code is better than tightly coupled code and enforcing this requirement goes against that basic principle. Having said that, your specific case might warrant this requirement, but if I were architecting this, I'd look into a different class structure. – Tim G Sep 13 '12 at 19:37
  • @moonwave99, this is a clever approach though - +1 – Tim G Sep 13 '12 at 19:38
  • @TimG I do agree with you - by my choice, I won't never provide such enforcements in code: if creating objects outside certain kind of objects makes it go boom, just don't to it :D Thank you anyway! – moonwave99 Sep 13 '12 at 20:12
1

I'm not thinking of this from a php perspective, but more from the oop side...think the only way you could accomplish it is if you made B's constructor private, then exposed a static method accepting a parameter of A and an out parameter of B, then that method could privately instantiate B and return it to A through the out parameter.

//Pseudocode, language-indifferent

    class A{
       var _B;

       public B GetMeAnInstanceOfB(){
          _B=B.CreateInstanceOfB(this);
       }

       //alternate
       public B GetMeAnotherInstanceOfB(){
          _B=new B(this);
       }
    }

    class B{
       private B();
       //alternate
       private B(A);
       static B CreateInstanceOfB(A){
          return new(b);
       }
    }

That's really crude and probably full of potholes, but there's a stab at it. Technically, subclasses of A could still get a B, so if you sealed the class (prevented subclasses), that would close that door.

Interesting question, to be sure...

EDIT: This mod really doesn't fix the problem, but maybe(?) it's better - I've created a public constructor for B that takes an A as a parameter, and the instantiation of B now takes place only in A. The only problem is that it persists with the same problem JDelage pointed out - if I instantiate A, I can build a B...

David W
  • 10,062
  • 34
  • 60
  • Yes, I'm working on PHP, but the question is really language agnostic. That's a cool idea you have here. I like it. – JDelage Sep 13 '12 at 18:44
  • If its language agnostic, then define nested private class `B` in `A`. Unfortunately, this [cannot be accomplished with PHP](http://stackoverflow.com/questions/1548286/can-you-have-nested-classes-in-php). There are some workarounds posted on SO, however. – Josh Sep 13 '12 at 18:45
  • @Josh Very valid point, but the OP did state that B is not a child of A, so I took a bit of a liberty with that definition so as not to nest them. I know that nesting doesn't make them children in an OO hierarchy sense, but separating them was valid in the context the OP offered. – David W Sep 13 '12 at 18:52
  • @DavidW Sorry, I wasn't attempting to take away from your answer. Just offering another answer/suggestion in light of his comment. – Josh Sep 13 '12 at 18:56
  • @JDelage - Thanks - forced me to think about it before I just posted something :) – David W Sep 13 '12 at 18:57
  • @Josh - No apologies necessary! All here to learn and toss ideas. I think your point was very well taken re nesting. – David W Sep 13 '12 at 18:59
  • @David W - what prevents another part of the code (than `A`) to call `CreateInstanceOfB` as long as they have an instance of `A` to inject? – JDelage Sep 13 '12 at 19:12
  • @JDelage - Hmmm... well, nothing, I suppose. Guess I was thinking that if *only* instances of A could create B's, then it was implied that *any* instance of A could create B's. Is that a bad inference on my part? Are you thinking of controlled (for lack of a better term) instances of A? – David W Sep 13 '12 at 19:18
  • @David W - What I am saying is that it seems to me that with your method, an instance of `B` could be created from code outside of `A` as long as this code has an instance of `A` to inject. If I'm not missing anything, your code requires that an instance of `A` be available, but not that the instantiation of `B` take place within `A`. – JDelage Sep 13 '12 at 19:27
  • @JDelage - Ahhh, I think I'm starting to see where you're coming from. May hit a roadblock here, because the first step is to make B's constructor private so arbitrary code cannot execute it. But if we must instantiate B from A (literally within its confines), and the classes are not otherwise related (excepting the commonancestor), the only way I see to do it is through the constructor - but that's private. I think we may be hitting the boundary where nested classes are needed, unfortunately. Lemme keep stewing on it a bit. Great mental exercise!! :) – David W Sep 13 '12 at 19:36
  • @JDelage I'm thinking that, barring nested classes, or using some type of code-cognizance (reflection), this is as far as my model can take it. No matter how I attack it, making B's constructor private kills things because A isn't any more special than any other class, and can't see B's constructor to perform the direct instantiation. I thought about making B's ctor public and requiring an A parameter, but in reality that's no different than what I have now, excepting I suppose that you can literally do new b(this) from A.... – David W Sep 13 '12 at 19:54
1

Yes.

How you can go about achieving it is, make the class B's constructor accept one argument. And define a method for class A that makes objects of B.

Die or throw execption if $argument == null || !($argument instanceof A).

Example code:

class X {
    public $i = 0;
    public function getI() {
        return $i;
    }
    public function setI($x) {
        $i = $x;
    }
}

class A extends X {
    public function setI($x) {
        $i = $x * 2;
    }
    public function makeB($var){
        $b = new B($var);
    }
}

class B extends X {
    public function __construct($a) {
        if (null == $a) {
            echo "no arguments given!\r\n";
            //exit;
        }else if (!($a instanceof A)) {
            echo "disallowed\r\n";
            //exit;
        }else{
            echo "initialized b\r\n";
        }
    }
    public function setI($x) {
        $i = $x * 3;
    }
}

$a = new A();

$a->makeB();
$a->makeB(new X());
$a->makeB(&$a);

Output:

Warning: Missing argument 1 for A::makeB(), called in file.php
no arguments given!
disallowed
initialized b

You can see a demo here.

Prasanth
  • 5,230
  • 2
  • 29
  • 61