这是关于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更改,则可能会更改。
答案 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可用。