Xenonite
Xenonite

Reputation: 1965

PHP Using Factory pattern for SDKs

I'm a bit lost here because I want to do something that is very easy in Java but seems a bit complicated in PHP.

We are building an SDK for our product and in Java, we have this one class that must not (!) be instantiated by the user (i.e. the coder), since there are several constraints regarding it's integrity. So we've built that as a nested class "X" inside of the "XFactory" and you will get an instance of X by calling XFactory.buildMeMyX(); - Easy...

Now PHP does not support nested classes at all, and I wonder how to apply the same here. In Java, X's constructor is hidden (private), so only XFactory can call it.

In PHP, it looks like I will have to make __construct() public and move the nested class X out of XFactory. Hence, the user will be able to create an instance without the Factory.

Now - I COULD move the factory functionality to X itself and move all the stuff there, but this would kind of break the design of the SDK. Is there a useful way to do such things in PHP after all?

Upvotes: 4

Views: 173

Answers (3)

thpl
thpl

Reputation: 5910

As the others have said, there currently is no clean way to implement this behavior in PHP. In my opinion, the only valid use case for private constructors are factories inside the class that implement that factories.

Whenever you try to get around that use case it gets messy. No one should ever try to invent clever ways to bypass PHP's language limiations.

I just violated that rule by myself just to prove it is indeed possible. But please refrain from using that in production, or better: using it anywhere. I will try to find some bulletproof arguments for that suggestion and edit the answer afterwards.

<?php

class Dependency {}

class SomeClass {

    protected $dep;

    private function __construct(Dependency $dep) 
    {
        $this->dep = $dep;
    }

    public function doSomething()
    {
        var_dump($this->dep);
        echo "Doing Stuff and even having dependencies";
    }

}

class SomeClassFactory {

    public function buildSomeClass()
    {
        return $this->instantiateSomeClassWith(new Dependency);
    }

    protected function instantiateSomeClassWith()
    {
        $reflectionClass = new ReflectionClass('SomeClass');
        $someClass = $reflectionClass->newInstanceWithoutConstructor();

        $constructor = $reflectionClass->getConstructor();
        $constructorClosure = $constructor->getClosure($someClass);
        call_user_func_array($constructorClosure, func_get_args());
        return $someClass;
    }

}

$factory = new SomeClassFactory();
$someClass = $factory->buildSomeClass();
$someClass->doSomething();

?>

Output: object(Dependency)#2 (0) { } Doing Stuff and even having dependencies

The theory is simple. The constructor of the class that will be built via the Factory is made private. We make use of reflection within the factory to create an instance of the class without invoking the constructor.

Once we have an instance, we grab the closure of the constructor and invoke it via call_user_func_array(). That way you can make use of Dependency Injection just as you would if the constructor was public.

As I said before. That way is a single smell. By creating an object without invoking it's constructor, there is no real way to validate an objects state upon creation

This is a proof of concept, but the concept sucks.

Upvotes: 1

Matiss
Matiss

Reputation: 341

There is no native way to do so, yet. However, if you really want to "enforce" that your class is only created from your factory class, there is a little "hackish" way to do so limiting the instantiation by inistantiating class.

class X
{

    function __construct()
    {
        new Y();
    }
}

class Y
{
    function __construct()
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);

        if (!isset($trace[1]['object']) || !($trace[1]['object'] instanceof X)) {
            throw new \RuntimeException('This is a private class');
        }
    }
}

new X(); // All is fine

new Y(); // Exception

Please note that there is no "real" way to protect the class from being instantiated from elsewhere even using this approach - it still can be done via reflection by bypassing the constructor, or simply modifying your source.

Upvotes: 1

Fabian Schmengler
Fabian Schmengler

Reputation: 24551

For PHP 5.x you already described your options, there are no private/protected classes or inner classes at all, so there is no further way to restrict instantiation.

However, with PHP 7 this is going to change.

There are still no nested classes (although we might get them in the future, see: https://stackoverflow.com/a/31454435/664108), but you could instantiate an anonymous class and only provide the consumer with its interface like this:

class XFactory
{
    public function buildMeMyX()
    {
        return new class() implements XInterface {
            public function doWhatEverAnXCanDo()
            {
                // X X X
            }
            // ...
        };
    }
}
interface XInterface
{
    function doWhatEverAnXCanDo();
}

Upvotes: 3

Related Questions