4

Yes, I know dependencies should be passed to the constructor. I'm not asking about coding styles or do's and dont's.

Many of the classes in my application are tied to an instance of a database driver class. For this I've created an abstract Factory class using PHP's late static binding. The only member of this class is a property to hold the driver's reference. It looks like this:

abstract class Factory {

    static private $__instances;
    static private $__default_driver;

    protected $_driver;

    static public function getInstance ( \Database\Driver $driver = null ) {
        if ( ! isset($driver) ) {
            if ( ! isset(self::$__default_driver) ) {
                require ( \Core\Options::$database_driver[ 'path' ] );
                self::$__default_driver = new \Core\Options::$database_driver[ 'class' ]( \Core\Options::$database_options );
            }
            $driver = self::$__default_driver;
        }

        $schema = $driver->getDatabase();
        $class  = get_called_class();

        if ( ! isset(self::$__instances[ $schema ][ $class ]) ) {
            self::$__instances[ $schema ][ $class ] = new $class( $driver );
            self::$__instances[ $schema ][ $class ]->_driver = $driver;
        }

        return self::$__instances[ $schema ][ $class ];
    }
}

As you can see, when I create an instance of the derived class, I pass an instance of the driver to its constructor. THEN set the property. I want to reverse this, if possible, by setting the property first then call the constructor. This way a derived class doesn't need to implement a constructor or worry about calling parent methods if it does.

I've looked into the Reflection API to do this, but I can't seem to find anything that would work. Most Dependency Injection links I found actually use the constructor. This needs to work on PHP 5.3.

For those who are adament that this is not possible, It can easily be done in PHP 5.4 using ReflectionClass::newInstanceWithoutConstructor().

Twifty
  • 3,267
  • 1
  • 29
  • 54
  • 1
    No, you cannot set a property on an object before you have instantiated that object, because there's no object to set anything on before it is instantiated. To be honest, if you're painting yourself into a corner where you're trying to set properties on things before they exist, maybe it's time to take a step back and evaluate whether you should maybe be using different design patterns. :) – deceze May 02 '14 at 12:56

3 Answers3

4
class Foo {
    public function __construct() {
        echo 'foo';
    }
}

$r = new ReflectionClass('Foo');
$o = $r->newInstanceWithoutConstructor();
$o->bar = 'baz';
$o->__construct();

This is possible since PHP 5.4.

But really: no. Just no. Don't. An object's constructor should always be called when the object is instantiated. No sooner (obviously), no later. Constructors and appropriate encapsulation of the rest of the object guarantee that the object is always in a consistent state. Once you start taking it apart using ReflectionClass no guarantees can be made anymore about the state of the object, and sanity and type safety go out the window.

Since you're on 5.3 you're lucky that there's no way to do this there. If you find yourself backed into a corner like this, you're simply doing it wrong. Perfectly complex programs have been written without needing to trick PHP's object model into delaying constructor invocation.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • You still have a constructor here. It is another thing that you reach it through reflection, but at the end of the day you are using a constructor. – Lajos Arpad May 02 '14 at 14:08
  • 1
    You can skip the `$o->__construct()` if you never want to run the constructor. Or are you referring to `ReflectionClass::newInstanceWithoutConstructor`? That's not really a constructor, it explicitly avoids the constructor. Instantiating an object is not the same as running a constructor. – deceze May 02 '14 at 14:10
0

"I know dependencies should be passed to the constructor", not true, in cases where dependencies are inherited by an overridden constructor, using it is a bad idea.

In these cases the class might not even use the constructor, meaning the only state the class holds are its dependencies and the methods are treated as static and thereafter stateless during the lifetime of the object.

This is very common in Symfony2 where some services loaded via DI do not include parameters passed to the constructor, or if they are, then extensions must be compatible.

Flosculus
  • 6,880
  • 3
  • 18
  • 42
  • To be honest, I have no idea what you're saying. But I'm intrigued. A code sample to clarify would really help. – deceze May 02 '14 at 12:59
  • @deceze In short, instantiate the class without any parameters, call your properties, then initialize the data of the object once you actually start using it (this can be invoked internally). – Flosculus May 02 '14 at 13:06
0

You cannot set the value of an object which does not exist yet, you need to instantiate your object, and then initialize it by passing the necessary parameters to the constructor and inside the constructor you have to use the parameters to initialize your object.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • By my understanding, the constructor does not create the object but initializes it with the arguments passed via `new`. Infact, `__construct` is just a method (magic or not). It must be possible to create an object, set a parameter, then call the constructor. I know it's possible with other languages like C++. – Twifty May 02 '14 at 13:17
  • The constructor initializes your object. When you use the new operator you tell PHP to instantiate the class and call the constructor of the newly created object. This is the way to go. You can create object factories to simplify the usage of instantiation, but at the end of the day you are calling either directly or indirectly a constructor using the new operator. And, again, the new operator is not only calling the constructor, but instantiates the class and then calls its constructor. – Lajos Arpad May 02 '14 at 13:22