Skip to content

[DependencyInjection] Fluent PHP DI Documentation #10824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions components/dependency_injection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -288,17 +288,21 @@ config files:

.. code-block:: php

use Symfony\Component\DependencyInjection\Reference;
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return function(ContainerConfigurator $configurator) {
$configurator->parameters()
->set('mailer.transport', 'sendmail');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in favor of the semi colon on a new line for both reduced diff and avoiding the care of dragging it while editing the files on the long run.
But that is already inconsistent in the docs and you're targeting 4.2 so I would also be in favor of having someone or me take care of this in a dedicated PR if others agree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

semi-colon on newline feels weird to me personally, but the reduced diff argument is a good one. I'd imagine a separate PR to clean that up and make consistent would be advised.


$container = $configurator->services();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not whether we should go for this or:

return function (ContainerConfigurator $container) {
    $container->parameters()
        ->set('mailer.transport', 'sendmail')
        ->set('xxx', 'XXX')
    ;

    $container->services()
        ->set(...)->args(...)
        ->set(...)->call()->public()

        ->instancof()->tags()
        // ...

WDYT?
There are so many paths. Playing with this is a real joy, I guess I won't be able to use any other format now :).
I would like we all agree on a best practice for this to roll it out.
ping @symfony/team-symfony-docs @nicolas-grekas

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HeahDude not sure it matters to much, I think I personally prefer a separate statement per definition just so that it makes the definitions more distinct in the code, but people can also style their stuff however they want.


$container->set('mailer', 'Mailer')
->args(['%mailer.transport%']);

$container->set('newsletter_manager', 'NewsletterManager')
->call('setMailer', [ref('mailer')]);
};

// ...
$container->setParameter('mailer.transport', 'sendmail');
$container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');

$container
->register('newsletter_manager', 'NewsletterManager')
->addMethodCall('setMailer', [new Reference('mailer')]);

Learn More
----------
Expand Down
170 changes: 96 additions & 74 deletions service_container.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,18 @@ each time you ask for it.
.. code-block:: php

// config/services.php
use Symfony\Component\DependencyInjection\Definition;
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

// To use as default template
$definition = new Definition();
return function(ContainerConfigurator $configurator) {
$container = $configurator->services()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, what about:

$container->services()
    ->defaults()->autowire()->autoconfigure()

    ->load('App\\', '../src/*')->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}')

    ->set('x')->arg('$arg', ref('...'))
    ->alias('y', 'x')->public()

    // ...
;

?

->defaults()
->autowire()
->autoconfigure()
->private();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private should be removed every where on 4.2, this could be done in a dedicated PR too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that just because services are now private by default? If so, should we have documentation describing that services are private by default starting in 4.2?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the case since Symfony 4. 4.2 is just now the lowest 4.x branch maintained.


$definition
->setAutowired(true)
->setAutoconfigured(true)
->setPublic(false)
;

// $this is a reference to the current loader
$this->registerClasses($definition, 'App\\', '../src/*', '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
$container->load('App\\', '../src/*')
->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
};

.. tip::

Expand Down Expand Up @@ -396,7 +395,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume
# same as before
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests}'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

# explicitly configure the service
App\Updates\SiteUpdateManager:
Expand All @@ -416,6 +415,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume
<!-- ... -->

<!-- Same as before -->

<prototype namespace="App\" resource="../src/*" exclude="../src/{Entity,Migrations,Tests}"/>

<!-- Explicitly configure the service -->
Expand All @@ -428,23 +428,23 @@ pass here. No problem! In your configuration, you can explicitly set this argume
.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Updates\SiteUpdateManager;
use Symfony\Component\DependencyInjection\Definition;

// Same as before
$definition = new Definition();
return function(ContainerConfigurator $configurator) {
$container = $configurator->services()
->defaults()
->autowire()
->autoconfigure()
->private();

$definition
->setAutowired(true)
->setAutoconfigured(true)
->setPublic(false)
;
$container->load('App\\', '../src/*')
->exclude('../src/{Entity,Migrations,Tests}');

$this->registerClasses($definition, 'App\\', '../src/*', '../src/{Entity,Migrations,Tests}');
$container->set(SiteUpdateManager::class)->arg('$adminEmail', 'manager@example.com');
};

// Explicitly configure the service
$container->getDefinition(SiteUpdateManager::class)
->setArgument('$adminEmail', 'manager@example.com');

Thanks to this, the container will pass ``manager@example.com`` to the ``$adminEmail``
argument of ``__construct`` when creating the ``SiteUpdateManager`` service. The
Expand Down Expand Up @@ -503,13 +503,16 @@ parameter and in PHP config use the ``Reference`` class:
.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Service\MessageGenerator;
use Symfony\Component\DependencyInjection\Reference;

$container->autowire(MessageGenerator::class)
->setAutoconfigured(true)
->setPublic(false)
->setArgument(0, new Reference('logger'));
return function(ContainerConfigurator $configurator) {
$container = $configurator->services();
$container->set(MessageGenerator::class)
->autoconfigure()
->args([ref('logger')]]);
};

Working with container parameters is straightforward using the container's
accessor methods for parameters::
Expand Down Expand Up @@ -605,13 +608,18 @@ But, you can control this and pass in a different logger:
.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Service\MessageGenerator;
use Symfony\Component\DependencyInjection\Reference;

$container->autowire(MessageGenerator::class)
->setAutoconfigured(true)
->setPublic(false)
->setArgument('$logger', new Reference('monolog.logger.request'));
return function(ContainerConfigurator $configurator) {
$container = $configurator->services();
$container->set(SiteUpdateManager::class)
->autowire()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add the same comments as other examples and remove needless boilerplate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Y, definitely, not sure why I did it like that.

->autoconfigure()
->private();
->arg('$logger', ref('monolog.logger.request'));
};

This tells the container that the ``$logger`` argument to ``__construct`` should use
service whose id is ``monolog.logger.request``.
Expand Down Expand Up @@ -693,21 +701,22 @@ You can also use the ``bind`` keyword to bind specific arguments by name or type
.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Controller\LuckyController;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Reference;

$container->register(LuckyController::class)
->setPublic(true)
->setBindings([
'$adminEmail' => 'manager@example.com',
'$requestLogger' => new Reference('monolog.logger.request'),
LoggerInterface::class => new Reference('monolog.logger.request'),
// optionally you can define both the name and type of the argument to match
'string $adminEmail' => 'manager@example.com',
LoggerInterface::class.' $requestLogger' => new Reference('monolog.logger.request'),
])
;
return function(ContainerConfigurator $configurator) {
$container = $configurator->services()->defaults()
->bind('$adminEmail', 'manager@example.com')
->bind('$requestLogger', ref('monolog.logger.request'))
->bind(LoggerInterface::class, ref('monolog.logger.request'))
->bind('string $adminEmail', 'manager@example.com')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't remove comments

->bind(LoggerInterface::class.' $requestLogger', ref('monolog.logger.request'));

// ...
};

By putting the ``bind`` key under ``_defaults``, you can specify the value of *any*
argument for *any* service defined in this file! You can bind arguments by name
Expand Down Expand Up @@ -809,6 +818,20 @@ But, if you *do* need to make a service public, override the ``public`` setting:
</services>
</container>

.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Service\MessageGenerator;

return function(ContainerConfigurator $configurator) {
// ... same as code before

$container->set(MessageGenerator::class)
->public();
};

.. _service-psr4-loader:

Importing Many Services at once with resource
Expand All @@ -829,7 +852,7 @@ key. For example, the default Symfony configuration contains this:
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests}'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

.. code-block:: xml

Expand All @@ -850,18 +873,14 @@ key. For example, the default Symfony configuration contains this:
.. code-block:: php

// config/services.php
use Symfony\Component\DependencyInjection\Definition;
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

// To use as default template
$definition = new Definition();

$definition
->setAutowired(true)
->setAutoconfigured(true)
->setPublic(false)
;
return function(ContainerConfigurator $configurator) {
// ...

$this->registerClasses($definition, 'App\\', '../src/*', '../src/{Entity,Migrations,Tests}');
$container->load('App\\', '../src/*')
->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
};

.. tip::

Expand Down Expand Up @@ -998,27 +1017,30 @@ admin email. In this case, each needs to have a unique service id:
.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Service\MessageGenerator;
use App\Updates\SiteUpdateManager;
use Symfony\Component\DependencyInjection\Reference;

$container->register('site_update_manager.superadmin', SiteUpdateManager::class)
->setAutowired(false)
->setArguments([
new Reference(MessageGenerator::class),
new Reference('mailer'),
'superadmin@example.com'
]);

$container->register('site_update_manager.normal_users', SiteUpdateManager::class)
->setAutowired(false)
->setArguments([
new Reference(MessageGenerator::class),
new Reference('mailer'),
'contact@example.com'
]);

$container->setAlias(SiteUpdateManager::class, 'site_update_manager.superadmin')
return function(ContainerConfigurator $configurator) {
// ...

$container->set('site_update_manager.superadmin', SiteUpdateManager::class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$container->services()
    // ...
    
    ->set('site_update_manager.superadmin', SiteUpdateManager::class)->autowire(false)->args([
        ref(MessageGenerator::class),
        ref('mailer'),
        'superadmin@example.com',
    ])

    ->set(...)

?

->autowire(false)
->args([
ref(MessageGenerator::class),
ref('mailer'),
'superadmin@example.com'
]);
$container->set('site_update_manager.normal_users', SiteUpdateManager::class)
->autowire(false)
->args([
ref(MessageGenerator::class),
ref('mailer'),
'contact@example.com'
]);
$container->alias(SiteUpdateManager::class, 'site_update_manager.superadmin');
};

In this case, *two* services are registered: ``site_update_manager.superadmin``
and ``site_update_manager.normal_users``. Thanks to the alias, if you type-hint
Expand Down
60 changes: 57 additions & 3 deletions service_container/3.3-di-changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,32 @@ what the file looks like in Symfony 4):
</services>
</container>

.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return function(ContainerConfigurator $configurator) {
$container = $configurator->services()
->defaults()
->autowire()
->autoconfigure()
->private();

$container->load('App\\', '../src/*')
->exclude('../src/{Entity,Migrations,Tests}');

$container->load('App\\Controller\\', '../src/Controller')
->tag('controller.service_arguments');
};

This small bit of configuration contains a paradigm shift of how services
are configured in Symfony.

.. versionadded:: 3.4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed while targeting 4.2 :).


PHP Fluent DI was introduced in Symfony 3.4.

.. _`service-33-changes-automatic-registration`:

1) Services are Loaded Automatically
Expand Down Expand Up @@ -126,6 +149,18 @@ thanks to the following config:
</services>
</container>

.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return function(ContainerConfigurator $configurator) {
// ...

$container->load('App\\', '../src/*')
->exclude('../src/{Entity,Migrations,Tests}');
};

This means that every class in ``src/`` is *available* to be used as a
service. And thanks to the ``_defaults`` section at the top of the file, all of
these services are **autowired** and **private** (i.e. ``public: false``).
Expand Down Expand Up @@ -319,11 +354,15 @@ The third big change is that, in a new Symfony 3.3 project, your controllers are
.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

// ...
return function(ContainerConfigurator $configurator) {
// ...

$container->load('App\\Controller\\', '../src/Controller')
->tag('controller.service_arguments');
};

$definition->addTag('controller.service_arguments');
$this->registerClasses($definition, 'App\\Controller\\', '../src/Controller/*');

But, you might not even notice this. First, your controllers *can* still extend
the same base controller class (``AbstractController``).
Expand Down Expand Up @@ -464,6 +503,21 @@ inherited from an abstract definition:
</services>
</container>

.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Domain\LoaderInterface;

return function(ContainerConfigurator $configurator) {
// ...

$container->instanceof(LoaderInterface::class
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing parenthesis?

I would go for a :

$container->services()
    // ...

    ->instanceof(LoaderInterface::class)->tag('app.domain_loader')->public()
;

->public()
->tag('app.domain_loader');
};

What about Performance
----------------------

Expand Down
Loading