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

947 views Asked by At

I have a problem to get list of all transports defined for mailer component.

In my config/packages/mailer.yaml I have defined three transports:

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


Now, in my code I want to get list of defined transport in associative array manner:

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

Any idea how to do it?

3

There are 3 answers

0
Cerad On BEST ANSWER

So this does not exactly answer the question but will yield a result that looks like:

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 ▶}
]

You get the desired array indexes along with the actual transport objects.

Using bin/console debug:container mailer.transports reveals the existence of a Transports object:

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

$transports is what we are after but it is not accessible without reflection and the class is final so we can't really extend it. A brute force solution is to clone(i.e copy) the complete class into the App namespace and convince the mailer to use it:

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);

Convincing the mailer system to use our Transports is the hard part. Normally you would just add a compiler pass and change the class for the mailer.transports service to the App transport. Sadly, this does not work since the actual service definition uses a factory to create transports and there does not seem to be a specific point for changing the Transports class. Specifically:

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);
    }

As you can see the Transports class name is hardcoded. I fooled around a bit trying to extend the class and convince it to use a different Transports class but the return type of Transports makes it difficult. As a proof of concept I decided to just copy the whole class to the App namespace and see what happens. You would not normally do this because there is quite a bit of code in the class. Code which might get updated as time goes by. I also renamed the class to TransportFactory because the name Transport seems to be a but overused. And then change the service definition to use the new 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);
    }

At this point you should have a working example yielding the array I gave at the beginning of the answer. It's quite a bit of work for what should have been a simple request. Maybe somebody else will now come along and say, hey, just do this.

Obviously you should carefully consider if you really do need this capability. Might be better to just read config/packages/mailer.yaml directly from your app's extension.

1
Eternal Learner On

Define your Transport paramaters in services.yml like that :

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

Then in a service, get ParameterBagInterface in your constructor and instanciate in a variable to get your params :

<?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'),
    ];
}
?>
0
cb1986ster On

I had similar problem - list transports in console command and I was able to solve this by looking how debug:config command was written. I extend my command not by Symfony\Component\Console\Command\Command but Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand and copy private method compileContainer. Then I was able to use this method:

    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"]);
    }

As You can guess $config contains configuration tree.