如何获取应用程序中可用邮件传输的列表?
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
包含配置树。
我在获取为邮件程序组件定义的所有传输列表时遇到问题。
在我的 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
包含配置树。