Symfony4 如何为 UserPasswordEncoderCommand 构造函数自动装配参数?
How does Symfony4 autowire arguments to UserPasswordEncoderCommand constructor?
这是一个关于 Symfony 4 的问题autowiring
仅使用 'array' 作为构造函数参数类型提示。我给出了一个具体的案例,但这可能对其他人有帮助,因为这种情况发生在几个 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>
vendor/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, array $userClasses = [])
仅使用一个参数调用,因此第二个参数默认为空数组。
这些文件似乎也需要从 vendor/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 发生变化,这可能会发生变化。
在这种特殊情况下,作为配置过程的一部分,数据由 SecurityBundle 组装。当内核启动时,所有的包都被收集起来,它们各自的扩展,通常在 DependencyInjection 子文件夹中,将被注册。构建容器时,它会通过所有这些扩展,因此他们可以修改服务或添加自己的服务。这就是 Symfony 从 bundle 内部收集配置的方式,当主应用程序本身不知道它们存储在哪里,但 bundle 知道。
获取用户 classes 是由 Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension
在处理 security.yml 的 encoders
部分时完成的。如果您查看 security.yaml 的编码器部分,您会注意到每个编码器都分配给了一个 class 名称。然后该方法将使用此数组为该值创建适当的编码器并将 class 名称保留为键。该数组在 createEncoders
中传递给编码器工厂。之后它将从配置中获取相同的数组,使用 array_keys()
获取每个用户的名称 class 并将参数替换为偏移量 1,即 $userClasses
,用于命令。
这个概念称为语义配置,在您自己的应用程序中,这可能有点矫枉过正,因为您必须定义配置,然后手动编写逻辑。相反,您可能只在 services.yaml.
的参数部分定义一个带有 class 名称的数组
如果您想传入其他服务,这些服务可能来自您的捆绑包不知道的其他捆绑包,还有另一种方法称为 CompilerPasses。在 CompilerPass 中,您可以收集所有 classes,例如使用特定标记,然后将它们的引用传递给服务的方法或构造函数。例如,这在 Symfony 内部用于注册 EventListeners 或使命令在控制台应用程序中可用。
这是一个关于 Symfony 4 的问题autowiring 仅使用 'array' 作为构造函数参数类型提示。我给出了一个具体的案例,但这可能对其他人有帮助,因为这种情况发生在几个 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>
vendor/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, array $userClasses = []) 仅使用一个参数调用,因此第二个参数默认为空数组。
这些文件似乎也需要从 vendor/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 发生变化,这可能会发生变化。
在这种特殊情况下,作为配置过程的一部分,数据由 SecurityBundle 组装。当内核启动时,所有的包都被收集起来,它们各自的扩展,通常在 DependencyInjection 子文件夹中,将被注册。构建容器时,它会通过所有这些扩展,因此他们可以修改服务或添加自己的服务。这就是 Symfony 从 bundle 内部收集配置的方式,当主应用程序本身不知道它们存储在哪里,但 bundle 知道。
获取用户 classes 是由 Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension
在处理 security.yml 的 encoders
部分时完成的。如果您查看 security.yaml 的编码器部分,您会注意到每个编码器都分配给了一个 class 名称。然后该方法将使用此数组为该值创建适当的编码器并将 class 名称保留为键。该数组在 createEncoders
中传递给编码器工厂。之后它将从配置中获取相同的数组,使用 array_keys()
获取每个用户的名称 class 并将参数替换为偏移量 1,即 $userClasses
,用于命令。
这个概念称为语义配置,在您自己的应用程序中,这可能有点矫枉过正,因为您必须定义配置,然后手动编写逻辑。相反,您可能只在 services.yaml.
的参数部分定义一个带有 class 名称的数组如果您想传入其他服务,这些服务可能来自您的捆绑包不知道的其他捆绑包,还有另一种方法称为 CompilerPasses。在 CompilerPass 中,您可以收集所有 classes,例如使用特定标记,然后将它们的引用传递给服务的方法或构造函数。例如,这在 Symfony 内部用于注册 EventListeners 或使命令在控制台应用程序中可用。