如何获取应用程序中可用邮件传输的列表?

How to get list of available mail transports in an application?

我在获取为邮件程序组件定义的所有传输列表时遇到问题。

在我的 config/packages/mailer.yaml 中,我定义了三种传输方式:

framework:
    mailer:
        transports:
            default: '%env(MAILER_DEFAULT_DSN)%'
            first: '%env(MAILER_FIRST_DSN)%'
            second: '%env(MAILER_SECOND_DSN)%'


现在,在我的代码中,我想以关联数组的方式获取已定义传输的列表:

[
    'default' => 'default_mailer_dsn',
    'first' => 'first_mailer_dsn',
    'second' => 'second_mailer_dsn',
]

知道怎么做吗?

像这样在 services.yml 中定义传输参数:

parameters:
    mailDefault: '%env(MAILER_DEFAULT_DSN)%'
    mailFirst: '%env(MAILER_FIRST_DSN)%'
    mailSecond: '%env(MAILER_SECOND_DSN)%'

然后在服务中,在构造函数中获取 ParameterBagInterface 并在变量中实例化以获取参数:

<?php
...
use Symfony\Component\DependencyInjection\ParameterBag;

...
private ParameterBagInterface $parameter;

public function __contructor(ParameterBagInterface $parameter ) {
    $this->parameter = $parameter;
}

private function getTransport() {
    return [
        'default' => $this->parameter->get('mailDefault'),
        'first' => $this->parameter->get('mailFirst'),
        'second' => $this->parameter->get('mailSecond'),
    ];
}
?>

所以这并不能完全回答问题,但会产生如下所示的结果:

array:3 [▼
  "default" => Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport {#401 ▶}
  "first" => Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport {#407 ▶}
  "second" => Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport {#413 ▶}
]

您将获得所需的数组索引以及实际的传输对象。

使用 bin/console debug:container mailer.transports 显示 Transports 对象的存在:

namespace Symfony\Component\Mailer\Transport;
final class Transports implements TransportInterface
{
    private $transports;

$transports 是我们所追求的,但如果没有反射就无法访问它,并且 class 是最终的,所以我们不能真正扩展它。一种蛮力解决方案是将完整的 class 克隆(即复制)到 App 命名空间中并说服邮件程序使用它:

namespace App\Mailer;
final class Transports implements TransportInterface
{
    public $transports; // Change from private to public.  
    the rest stays the same

# config/services.yaml
App\Mailer\Transports: '@mailer.transports' # define alias

# HomeController.php
use App\Mailer\Transports;
class HomeController extends AbstractController
{
    public function index(Transports $transports): Response
    {
        dump($transports->transports);

说服邮件系统使用我们的传输是困难的部分。通常,您只需添加编译器传递并将 mailer.transports 服务的 class 更改为 App 传输。遗憾的是,这不起作用,因为实际的服务定义使用工厂来创建传输,并且似乎没有特定的点来更改传输 class。具体来说:

namespace Symfony\Component\Mailer;
class Transport # This is actually a factory class, poor name
{
    # and the factory method
    public function fromStrings(array $dsns): Transports
    {
        $transports = [];
        foreach ($dsns as $name => $dsn) {
            $transports[$name] = $this->fromString($dsn);
        }

        return new Transports($transports);
    }

如您所见,Transports class 名称是硬编码的。我试图扩展 class 并说服它使用不同的传输 class,但是 return 类型的传输使它变得困难。作为概念证明,我决定将整个 class 复制到 App 命名空间,看看会发生什么。您通常不会这样做,因为 class 中有相当多的代码。随着时间的推移可能会更新的代码。我还将 class 重命名为 TransportFactory,因为 Transport 这个名字似乎有点过时了。然后更改服务定义以使用新的 class.

namespace App\Mailer;
class TransportFactory
{
# src/Kernel.php
class Kernel extends BaseKernel implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $container->getDefinition('mailer.transport_factory')->setClass(TransportFactory::class);
    }

此时你应该有一个工作示例生成我在答案开头给出的数组。对于本来应该是一个简单的请求来说,这是相当多的工作。也许其他人现在会过来说,嘿,就这样做吧。

显然你应该仔细考虑你是否真的需要这个功能。直接从应用程序的扩展中读取 config/packages/mailer.yaml 可能会更好。

我有类似的问题 - 在控制台命令中列出传输,我能够通过查看 debug:config 命令的编写方式来解决这个问题。 我不是通过 Symfony\Component\Console\Command\Command 而是 Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand 来扩展我的命令,并复制私有方法 compileContainer。然后我就可以使用这个方法了:

    private function listTransports(){
      $extension = $this->findExtension('framework');
      $container = $this->compileContainer();
      $extensionAlias = $extension->getAlias();
      $extensionConfig = [];
      foreach ($container->getCompilerPassConfig()->getPasses() as $pass) {
          if ($pass instanceof ValidateEnvPlaceholdersPass) {
              $extensionConfig = $pass->getExtensionConfig();
              break;
          }
      }

      if (!isset($extensionConfig[$extensionAlias])) {
          throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
      }

      $config = $container->resolveEnvPlaceholders($extensionConfig[$extensionAlias]);

      return array_keys($config["mailer"]["transports"]);
    }

如您所料,$config 包含配置树。