Yii2: Suggestion: DI container registers classes as singletons by default

Created on 18 Apr 2017  路  31Comments  路  Source: yiisoft/yii2

Hi, I suggest to change current logic of class declaration for DI container. I think it should register singletons by default, it will improve performance and reduce memory consumption for projects which uses dependency injection constantly (like mine).

Most helpful comment

If we're talking about regular container usage (services), not passing every instance creation through it, then singletons are a common use.

Yes, singleton is a default behavior for services, but not for the classes that are stateful and require dependencies e.g. commands, widgets.

Is there another good reason to introduce such a BC break?

All 31 comments

Agree.

there is an option to specify singletons if you want them, what exactly should be changed?

@cebe defaults. i.e. singletons should be preferred (set method), non-singletons should require special method (setNonSingleton or setFactory).

singletons should be preferred (set method), non-singletons should require special method (setNonSingleton or setFactory).

Disagreed: what about widgets registered at DI? It is unlikely they should be registered as singletons.
Also for me it sounds logical that for singletons there is a special method, while regular set() do not register singleton. Any regular class is NOT a singleton, while creating a singleton class require additional explicit efforts.

If we're talking about regular container usage (services), not passing every instance creation through it, then singletons are a common use.

Still, I prefer naming pair set() and setSingleton() insetad of set() and setNotSingleton() - the first one looks better.

If we're talking about regular container usage (services), not passing every instance creation through it, then singletons are a common use.

for services there is the servicelocator at Yii::$app, not the DI container.

Well, we may remove it since these are basically the same thing now.

these are basically the same thing now.

Why is that?
For me DI container take precedence over DI ServiceLocator.
While Container is a singleton - ServiceLocator can appear in multiple instances inside the program.

Let me illustrate the problem:

class Foo {
    public function __construct()
    {
        // Foo will be created twice
        echo "Foo", PHP_EOL;
    }

}

class Bar {
    public function __construct(Foo $foo)
    {

    }
}

class Hoo {
    public function __construct(Foo $foo, Bar $bar)
    {

    }
}

// To solve this issue I have to declare Foo as singleton
// \Yii::$container->setSingleton(Foo::class);

\Yii::$container->get(Hoo::class);

Well, we may remove it since these are basically the same thing now.

no, servicelocator allows you to have multiple instances of the same class, e.g. multiple redis or db connections with different configuration available by different name. A DI container can not provide that.

// To solve this issue I have to declare Foo as singleton

what is the problem with calling setSingleton for the classes which need to be singletons?

So basically, My suggestion is about storing created dependencies as singletons by default

sure, I go that, but why?

what is the problem with calling setSingleton for the classes which need to be singletons?

The problem is that majority of created dependencies usually are singletons, it is much easier to declare not singletons rather then singletons services.

So it is hard for you to type additional 9 letters (setSingleton() instead of set()) during method invocation?
Is this really a challenge?

there is zero difference when you configure container in application config:

'container' => [
    'definitions' => [
        // non-singletons
    ],
    'singletons' => [
        // singletons
    ],
]

So it is hard for you to type additional 9 letters (setSingleton() instead of set()) during method invocation?

I used setSingleton() to solve the issue, if DI container will store dependencies as singletons there won't be a need to use setSingleton() at all.

if DI container will store dependencies as singletons there won't be a need to use setSingleton() at all.

What if I wish to preconfigure all Pagination objects:

Yii::$container->set('yii\data\Pagination', ['defaultPageSize' => 40]);

Converted to singleton Pagination will not function correctly with 2 different GridViews at the same page.
What should I do then?

The problem is that majority of created dependencies usually are singletons

I do not consider this to be true.
If I wish some class to behave as a singleton in my application, I would configure it as an application component and use it via Yii::$app->get().
For me DI is an instrument for quick program adjustment, like re-setting default property values to the widgets or other commonly used classes (like Pagination).

We are talking about different things, I'm talking about dependency resolving&storing them as singletons(if opposite was not declared specifically), you are talking about object creation&configuration. But anyway, let me explain how your example will look like in a reality where my suggestion was accepted. When you declare Yii::$container->set('yii\data\Pagination', ['defaultPageSize' => 40]); yiidataPagination will become non singleton and will be created as much as its needed.

I do not consider this to be true.

It will be great to see examples

When you declare Yii::$container->set('yiidataPagination', ['defaultPageSize' => 40]); yiidataPagination will become non singleton and will be created as much as it needed.

And how this will happen and why? If it would be so, how I can define a singleton with custom configuration?

It will be great to see examples

I have just given you one there:
https://github.com/yiisoft/yii2/issues/14006#issuecomment-295208090

If it would be so, how I can define a singleton with custom configuration?

I think, you will can declare it again via \Yii::$container->setSingleton(...) to preserve singleton behavior and configure it.

@glagola I do not get what you are asking for, can you show some code changes that you think should be made to the framework?

I think, you will can declare it again via \Yii::$container->setSingleton(...) to preserve singleton behavior and configure it.

So, you suggests:

1) following code decalres a singleton:

Yii::$container->set('app\service\YouTube');

2) Following cod does NOT declares singleton:

Yii::$container->set('yii\data\Pagination', ['defaultPageSize' => 40]);

3) So if I wish declare singleton with configuration I should use another method:

Yii::$container->setSingleton('app\service\YouTube', ['apiKey' => 'some-api-key']);

An how this is better? Why there should be 2 different methods, which declares singletons?

no, servicelocator allows you to have multiple instances of the same class, e.g. multiple redis or db connections with different configuration available by different name. A DI container can not provide that.

You can register dependencies not by class name, but by aliases:

Yii::$container->setSingleton('db', [
    'class' => yii\db\Connection::class,
    'dsn' => '...' 
]);

Yii::$container->get('db');

Also you can tag dependencies with a specific interfaces to utilize DI in your project:

namespace app\components;

interface Database
{
}

interface LogDatabase extends Database
{
}

class Connection extends yii\db\Connection implements Database
{
}

class LogsConnection extends yii\db\Connection implements LogDatabase
{
}

Yii::$container->setSingleton(app\components\Database::class, [
    'class' => app\components\Connection::class
    'dsn' => ''
]);

Yii::$container->setSingleton(app\components\LogDatabase::class, [
    'class' => app\components\LogConnection::class
    'dsn' => ''
]);
class Foo
{
    public function __construct(
        app\components\Database $db,
        app\components\LogDatabase $logDb
    ) {
        // ...
    }
}

If we're talking about regular container usage (services), not passing every instance creation through it, then singletons are a common use.

Yes, singleton is a default behavior for services, but not for the classes that are stateful and require dependencies e.g. commands, widgets.

Is there another good reason to introduce such a BC break?

Hi, sorry for long silence, It is not BC break, I just want to reuse created dependencies as singletons if they were not defined explicitly in the container.

Neither valid use case nor additional arguments on why this change should be adopted were provided.
Thank you for your contribution, but this suggestion will not be implemented.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

indicalabs picture indicalabs  路  3Comments

psfpro picture psfpro  路  3Comments

Locustv2 picture Locustv2  路  3Comments

jpodpro picture jpodpro  路  3Comments

skcn022 picture skcn022  路  3Comments