Symfony4如何将参数自动关联到UserPasswordEncoderCommand构造函数?

时间:2019-02-06 21:55:30

标签: symfony symfony4

这是关于Symfony 4 autowiring的问题 仅使用“数组”作为构造函数参数类型提示。我给出一个具体案例,但这可能对其他人有帮助,因为这种情况发生在多个Symfony捆绑软件中。

此Symfony命令要求输入密码并对其进行编码:
php bin/console security:encode-password

此命令的代码在vendor / symfony / security-bundle / Command / UserPasswordEncoderCommand.php中 在Symfony 4.2.3中,这是UserPasswordEncoderCommand的构造函数:

class UserPasswordEncoderCommand extends Command
{
    protected static $defaultName = 'security:encode-password';

    private $encoderFactory;
    private $userClasses;

    public function __construct(EncoderFactoryInterface $encoderFactory, array $userClasses = [])
    {
        $this->encoderFactory = $encoderFactory;
        $this->userClasses = $userClasses;

        parent::__construct();
    }

Symfony使用依赖注入来调用构造函数,并传入自动确定的参数。上面的构造函数第一个参数$ encoderFactory是使用类型提示EncoderFactoryInterface自动连接的。 我的问题是:第二个参数$ userClasses如何自动关联? Symfony如何知道数组应包含什么?调试打印语句显示该数组包含单个值“ App \ Entity \ User”。

这是我的config / packages / security.yaml

security:
    encoders:
        App\Entity\User:
            algorithm: argon2i

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: true
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator

            # activate different ways to authenticate

            # http_basic: true
            # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate

            # form_login: true
            # https://symfony.com/doc/current/security/form_login_setup.html

            logout:
                path:   app_logout
                # Where to redirect after logout
                target: app_login

我认为以下三个文件对于回答这个问题很重要。
vendor / symfony / security-bundle / Resources / config / console.xml包含:     

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <defaults public="false" />

        <service id="security.command.user_password_encoder" class="Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand">
            <argument type="service" id="security.encoder_factory"/>
            <argument type="collection" /> <!-- encoders' user classes -->
            <tag name="console.command" command="security:encode-password" />
        </service>
    </services>
</container>

供应商/symfony/security-bundle/Resources/config/security.xml的一部分包含:                               

    <service id="security.encoder_factory" alias="security.encoder_factory.generic" />
    <service id="Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface" alias="security.encoder_factory" />

    <service id="security.user_password_encoder.generic" class="Symfony\Component\Security\Core\Encoder\UserPasswordEncoder">
        <argument type="service" id="security.encoder_factory"></argument>
    </service>

    <service id="security.password_encoder" alias="security.user_password_encoder.generic" public="true" />
    <service id="Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface" alias="security.password_encoder" />

vendor / symfony / security-bundle / DependencyInjection / SecurityExtension.php的一部分包含:

function load(array $configs, ContainerBuilder $container)
...
    if (class_exists(Application::class)) {
        $loader->load('console.xml');
        $container->getDefinition('security.command.user_password_encoder')->replaceArgument(1, array_keys($config['encoders']));
    }

我问这个问题是因为我想使用UserPasswordEncoderCommand.php作为在数据库中创建管理用户的命令的起点。这将授予第一位管理员用户使用网络浏览器登录的权限,以 创建其他用户。

我尝试将vendor / symfony / security-bundle / Command / UserPasswordEncoderCommand.php复制到src / Command / AddUserCommand.php并更改以下行:

< namespace Symfony\Bundle\SecurityBundle\Command;
---
> namespace App\Command;

< class UserPasswordEncoderCommand extends Command
---
> class AddUserCommand extends Command

<     protected static $defaultName = 'security:encode-password';
---
>     protected static $defaultName = 'app:add-user';

当我运行此命令时:php bin/console app:add-user 出现此错误:

There are no configured encoders for the "security" extension.

发生这种情况是因为构造函数__construct(EncoderFactoryInterface $ encoderFactory,数组$ userClasses = []) 仅使用一个参数调用,因此第二个参数默认为空数组。

似乎这些文件也需要从供应商/ symfony / security-bundle复制到src /

下的某个位置。
DependencyInjection/SecurityExtension.php
Resources/config/console.xml

,并以某种方式进行修改。 还需要将其他文件复制到src /吗? https://symfony.com下的Symfony文档非常好,但是找不到这种情况的描述。

请注意,此命令有效:

php bin/console app:add-user mypassword "App\Entity\User"

但我不希望用户键入此内容。如果security.yaml更改,则可能会更改。

1 个答案:

答案 0 :(得分:0)

在这种特殊情况下,SecurityBundle会在配置过程中组装数据。引导内核时,将收集所有捆绑软件,并且通常在DependencyInjection子文件夹中将注册各自的扩展。构建容器时,将通过所有这些扩展传递容器,以便它们可以修改服务或添加自己的服务。当主应用程序本身不知道它们在哪里存储,但捆绑软件知道时,Symfony就是从捆绑软件内部收集配置的方式。

在处理security.yml的encoders部分时,由Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension完成用户类的获取。如果查看security.yaml的编码器部分,则会注意到每个编码器都分配有一个类名。然后,该方法将使用此数组为该值创建适当的编码器,并将类名保留为键。该数组将传递到createEncoders中的编码器工厂。然后,它将从配置中获取相同的数组,使用array_keys()获取每个用户类的名称,并将该参数替换为偏移量1,即该命令的$userClasses

此概念称为语义配置,在您自己的应用程序内部,这可能会过分地用上,因为您必须定义配置,然后手动编写逻辑。相反,您可能只需要在services.yaml的parameters部分中使用类名定义一个数组。

如果您要传递其他服务,这些服务可能来自您的捆绑软件不知道的其他捆绑软件,则还有另一种方法称为CompilerPasses。在CompilerPass中,您可以收集所有类,例如使用特定标记,然后将其引用传递给服务的方法或构造函数。例如,这在Symfony内部用于注册EventListeners或在Console应用程序中使Commands可用。

相关问题