5

In a base class for all the models in our MVC system, I created a factory method BaseCLass::getNew() that returns an instance of the requested child class when called via SomeChildClass::getNew().

Now, I'm looking for a way to force the programmer to use this factory. I.e., idially I'd like that any class created directly, like this:

 new SomeChildClass

will throw an exception upon creation, and only classes created by the factory will be usable.

Any ideas how can this be achieved?

Our code is written in PHP, but good chance that your idea will be valuable even if you think on a different language.

edit: I cannot make my constructor private, as the framework constructor in the class that I inherit is public, and php would not allow me this.

shealtiel
  • 8,020
  • 18
  • 50
  • 82

5 Answers5

2

By making the constructor of the child class protected. The parent class will have access to all protected methods of the child. Any attempt to directly create the child (ie: new child) will cause a fatal error.

<?php

class factory
{

    static public function create()
    {
        return new child;
    }

}

class child extends factory
{

    protected function __construct()
    {
        echo 'Ok';
    }

}

$c = factory::create(); // Ok
$c2 = new child; // fatal error

?>

Though this method won't let you throw an exception instead :(

If then absolutely necessary, only debug_backtrace() function comes to mind (besides using singleton for the child itself, or forced object pool patterns using and passing GUID's generated by factory and verified by child). Within the child constructor, look at the 2nd array value to make sure "function" === "create" and "class" === "factory. Throw exception if not matching. I didn't suggest this initially, only because I suspect using debug_backtrace may give a performance hit.

bob-the-destroyer
  • 3,164
  • 2
  • 23
  • 30
  • This will not work for me, as the constructor of *my* parent is already public and I can't change it. In your example this means that factory extend ParentOfFactoryAFrameworkClass – shealtiel Mar 27 '11 at 03:27
  • @gidireich: sorry, missed that part. Attempting to raise the visibility of a child method would indeed cause a fatal error. I'm not aware of any way to overcome that, except what I just added to my answer. – bob-the-destroyer Mar 27 '11 at 03:51
  • Actually, the debug_trace solution suits in this contexts. The code calling for it can be marked to run only on development and not on production, which is good enough for any practical need. – shealtiel Mar 27 '11 at 05:11
1

By making the class have a private constructor.

Update -- solution that covers your stated requirements

class Base {
    private static $constructorToken = null;

    protected static function getConstructorToken() {
        if (self::$constructorToken === null) {
            self::$constructorToken = new stdClass;
        }

        return self::$constructorToken;
    }
}

class Derived extends Base {
    public function __construct($token) {
        if ($token !== parent::getConstructorToken()) {
            die ("Attempted to construct manually");
        }
    }

    public static function makeMeOne() {
        return new Derived(parent::getConstructorToken());
    }
}

This solution takes advantage of the object equality rules for stdClass by storing a "magic password" object on the base class which only derived classes can access. You can tweak it to taste.

I wouldn't call it horrible like the debug_backtrace idea, but still I have the impression that things should be done differently.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • Any way to enforce this from the level of the base class? I'd like to avoid defining this private constructors to all subClasses – shealtiel Mar 27 '11 at 02:54
  • I cannot make my constructor protected, as the constructor in the framework class that I inherit is public, and php would not allow me this. – shealtiel Mar 27 '11 at 03:28
  • 2
    @gidireich: Then you should rethink if you are going the right way. I can think of voodoo to achieve your stated goal, but it's the "you shouldn't be caught dead doing this" kind. Maybe you can satisfy your business requirements without needing all construction to go through the factory methods? – Jon Mar 27 '11 at 03:34
  • Yeah, go ahead, give me your vodoo! I'm currently thinking of a debug_backtrace vodoo by the way, but let's hear yours – shealtiel Mar 27 '11 at 03:38
  • @gidireich: That was the one I was thinking of as well ;) – Jon Mar 27 '11 at 03:40
  • Can you educate me why *exactly* it's a vodoo? – shealtiel Mar 27 '11 at 03:42
  • 2
    @gidireich: Because it's just *bad* on many levels. In all respect, if I was asked this question in an interview I 'd interpret it as "this guy is willing to do anything, no matter how wrong, to get the code to run" and *immediately* recommend no hire. On the bright side, I came up with something not as bad; check it out. – Jon Mar 27 '11 at 03:56
  • At this point, I'd either pull the framework library file and edit the visibility myself, or just tell my boss "sorry, not a feature of the language yet", and just wait for traits. – bob-the-destroyer Mar 27 '11 at 04:17
  • The constructor parameter thing is a good direction, and I didn't even find a need in using the stdClass magic. Just passing an agreed value from the factory method, is in practice 100% safe that no developer will call the constructor directly by accident. – shealtiel Mar 27 '11 at 05:09
0

Declare the class's constructor private, and it can only be called from within the class's own methods like getNew().

user229044
  • 232,980
  • 40
  • 330
  • 338
  • I cannot make my constructor private/protected, as the constructor in the framework class that I inherit, is public, and php would not allow me this. – shealtiel Mar 27 '11 at 03:28
  • @gid Then write a wrapper class which contains an instance of the framework class, rather than inheritng from the class directly. Choose **composition** instead of **inheritance**. There is no external way to prevent a class from being instantiated with `new`; you necessarily have to be able to change the functionality of the class from within the class definition. – user229044 Mar 27 '11 at 15:34
0

there are couple of ways to implement it

  • make parent class private use magic
  • user magic function __autoload; check the type of class and through error with not allowed message

http://php.net/manual/en/function.is-a.php

Pramendra Gupta
  • 14,667
  • 4
  • 33
  • 34
  • "make parent class private use magic" - do not understand what it means, can you clarify? – shealtiel Mar 27 '11 at 03:15
  • as for the second suggestion, what do you mean by checking the class? It'll be invariably some sublcass of my class, but how this information will help me? – shealtiel Mar 27 '11 at 03:17
0

The best way is to define constructor of the class private or protected. But if you cannot do it, you can control where an object of the class is created in the constructor:

trait FactoryChecking
{
    protected function checkFactory(string $factoryClass): void
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        foreach($trace as $traceItem) {
            if ($traceItem['class'] == $factoryClass) {
                return;
            }
        }   
        throw new Exception('Cannot create class ' . static::class . ' outside of factory');
    }
}

class ClassA
{
    use FactoryChecking;

    public function __construct()
    {
        $this->checkFactory(Factory::class);
    }
}

class Factory
{
    public function create(): ClassA
    {
        return new ClassA();
    }
}

Details I described in the article "Forbidding of creating objects outside factory in PHP"

  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/31351852) – txyoji Mar 24 '22 at 20:46