2

UPDATE 2017/04/19

Another view is using wrappers for late binding.

Introduction

I believe that objects should be immutable, so I only set properties via the constructor. In that case, the object state never change.

Problem

There are cases the required parameters for the constructor aren't known at that moment. So I need:

  • a setter (not my preference)
  • a 'hard coded' instance in my method (no Depency Injection, not flexible) (not my preference)
  • a deferred binding (possible solution, but the disadvantage (I think) is that the container contains too many logic)

Case

Let me give a simplified code example to illustrate my problem (PHP).

interface Personable
{
    public function __toString();
}

final class Person implements Personable
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function __toString(): string
    {
        return 'Person ' . $this->name;
    }
}

final class Foo
{
    private $personable;

    public function __construct(Personable $personable)
    {
        $this->personable = $personable;
    }
}

Question

Let's say that I default bind Personable to Person in my container. At that moment I haven't the required name parameter. I want to pass the instance of Person via constructor. What's the best practice?

3 Answers3

4

I think that your Foo class should not depend on a Personable instance. Your Foo class probably has a number of methods that you can call to make it perform some actions using the Personable object, however I would argue that objects like Personable should be passed as an argument to those functions.

Class Foo
{
    public function printName(Personable $personable) 
    {
        echo (string)$personable;
    } 
} 

Try to inject stateless dependencies (services, repositories, factories, etc.) via the constructor and inject these stateful objects (database models and such) via the function that you are calling.

1

First off:

There are cases the required parameters for the constructor aren't known at that moment.

I dont really believe that. And even if so, there might be a more fundamental flaw to your applications architecture.

But assuming that nothing can be done about that core issue:


Say you want to Inject a dependency like this:

interface MyDependecy {
    function doStuff(): void;
}

class MyDependencyImpl1 implements MyDependency {
    function doStuff(): void { /* stuff */ }
}

class MyDependecyImpl2 implements MyDependency {
    function doStuff(): void { /* stuff */ }
}

You cannot decide between MyDependencyImpl1 and MyDependencyImpl2 at the time when __construct of the dependent object executes.

This is how i'd resolve it:

class MyDependencyDelegator implements MyDependency {
    /** @var ?MyDependency */ private $target;

    function __construct(?MyDependency $target) { $this->setTarget($target); }

    function doStuff(): void { $target->doStuff(); }

    function setTarget(MyDependecy $target) { $this->target = $target; }
}

In the DI config:

$delegator = new MyDependencyDelegator();
$this->when(MyDependentObject::class)->needs(MyDependecy::class)->give($delegator); // i dont remember the exact syntax right now, forgive me

// call $delegator->setTarget when the desicion can be made

Now, this boils down to setter injection. It does not resolve the fact that information is not present when it should be (at construction time of the dependent object). This approach just keeps the "dirty" part out of your core object and moves it closer to the issue: the DI config.

marstato
  • 4,638
1

Since argument for creating an instance of Personable is unknown during creation of Foo - you need create it later.

You can introduce PersonableFactory abstraction, which will be responsible for creation of Personable.

interface PersonableFactory
{
    public function create();
}

final class PersonFactory implements PersonableFactory
{
    public function create(string $name): string
    {
        return new Person($name);
    }
}

final class Foo
{
    private $personableFactory;

    public function __construct(PersonableFactory$personableFactory)
    {
        $this->personableFactory = $personableFactory;
    }
}

In case when Person is just a data object, I think you don't need factory or dependency injection - just create new instance when you name will be known

Fabio
  • 3,166