Skip to content

Instantly share code, notes, and snippets.

@MatteoOreficeIT
Last active May 9, 2023 11:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MatteoOreficeIT/2c8f061e81a20fa96b8176efd97dbac5 to your computer and use it in GitHub Desktop.
Save MatteoOreficeIT/2c8f061e81a20fa96b8176efd97dbac5 to your computer and use it in GitHub Desktop.
Laravel: mailing via SMTP with a dynamic configuration determined at Dependency Injection Container resolve-time .
<?php
/**
* Inspired from
* 1. https://github.com/laravel/framework/blob/5.6/src/Illuminate/Mail/TransportManager.php
* 2. https://github.com/laravel/framework/blob/5.6/src/Illuminate/Mail/MailServiceProvider.php
*
* A service provider to register a custom mailer with Dynamic Options ( determined at Dependency Injection Container resolve-time )
*
* 1. resolve-time options: host, port, user, password, secure: [tls,ssl] could be determined at DI container resolve-time
* 2. global options in mail.dynamic config file
*
* Define in mail.php such as structure for global options
*
* 'dynamic' => [
* 'auth_mode' => 'plain',
* 'timeout' => 2,
* 'stream' => [
* 'ssl'=> [
* 'verify_peer' => false,
* 'verify_peer_name' => false,
* ]
* ]
* ],
*/
namespace App\Providers;
use Illuminate\Foundation\Application;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\TransportManager;
use Swift_SmtpTransport as SmtpTransport;
use Swift_Mailer;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
class DynamicMailerServiceProvider extends ServiceProvider
{
/**
* You need to implement this method to return custom options
* allowed key: host, port, user, password, secure: [tls,ssl]
* @return array
*/
abstract protected function getDynamicOptions();
/**
* Indicates if loading of the provider is deferred so the getDynamicOptions() will be called late
* in the code when you request app('myapp.dynamic.mailer')
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerSwiftMailer();
$this->registerIlluminateMailer();
}
/**
* Register the Illuminate mailer instance.
*
* @return void
*/
protected function registerIlluminateMailer()
{
$this->app->singleton('myapp.dynamic.mailer', function ($app) {
// Once we have create the mailer instance, we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$app['view'], $app['myapp.dynamic.swift.mailer'], $app['events']
);
// if ($app->bound('queue')) {
// $mailer->setQueue($app['queue']);
// }
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since they get be sent into a single email address.
foreach (['from', 'reply_to', 'to'] as $type) {
$this->setGlobalAddress($mailer, $app->make('config')->get('mail'), $type);
}
return $mailer;
});
}
/**
* Set a global address on the mailer by type.
*
* @param \Illuminate\Mail\Mailer $mailer
* @param array $config
* @param string $type
* @return void
*/
protected function setGlobalAddress($mailer, array $config, $type)
{
$address = Arr::get($config, $type);
if (is_array($address) && isset($address['address'])) {
$mailer->{'always'.Str::studly($type)}($address['address'], $address['name']);
}
}
/**
* Register the Swift Mailer instance.
*
* @return void
*/
public function registerSwiftMailer()
{
$this->registerSwiftTransport();
// Once we have the transporter registered, we will register the actual Swift
// mailer instance, passing in the transport instances, which allows us to
// override this transporter instances during app start-up if necessary.
$this->app->singleton('myapp.dynamic.swift.mailer', function ($app) {
return new Swift_Mailer($app['myapp.dynamic.swift.transport']->driver('dynamicSmtpSettings'));
});
}
/**
* Register the Swift Transport instance.
*
* @return void
*/
protected function registerSwiftTransport()
{
$this->app->singleton('myapp.dynamic.swift.transport', function ($app) {
return tap(new TransportManager($app),function($tm){
$tm->extend('dynamicSmtpSettings',function(Application $app){
// store global options in config/mail-dynamic.php
$globalOptions = config('mail.dynamic');
// retrieve dynamic options at DI make time
$dynamicConfig = $this->getDynamicOptions();
// The Swift SMTP transport instance will allow us to use any SMTP backend
// for delivering mail such as Sendgrid, Amazon SES, or a custom server
// a developer has available. We will just pass this configured host.
$transport = SmtpTransport::newInstance(
$dynamicConfig['host'],
$dynamicConfig['port']
);
if (!empty($dynamicConfig['secure'])) {
$transport->setEncryption($dynamicConfig['secure']);
}
// Once we have the transport we will check for the presence of a username
// and password. If we have it we will set the credentials on the Swift
// transporter instance so that we'll properly authenticate delivery.
if ($dynamicConfig['mail_autent']) {
$transport->setUsername($dynamicConfig['user']);
$transport->setPassword($dynamicConfig['password']);
}
/**
* @see https://goo.gl/9R13we
*/
if (isset($globalOptions['auth_mode'])) {
$transport->setAuthMode($globalOptions['auth_mode']);
}
if (isset($globalOptions['timeout'])) {
$transport->setTimeout($globalOptions['timeout']);
}
// Next we will set any stream context options specified for the transport
// and then return it. The option is not required any may not be inside
// the configuration array at all so we'll verify that before adding.
if (isset($globalOptions['stream'])) {
$transport->setStreamOptions($globalOptions['stream']);
}
return $transport;
});
});
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
'myapp.dynamic.mailer', 'myapp.dynamic.swift.mailer', 'myapp.dynamic.swift.transport'
];
}
}
@BunnyAlamin03
Copy link

how could i add this in my project? no documentation. If you can provide this it will be helpful more. Thank you

@Kyslik
Copy link

Kyslik commented Oct 15, 2018

@BunnyAlmin03 I think you need to copy the gist to the providers directory and then in the config/app.php change the stock provider for this one. Just copy the gist to the providers directory and register it in the app.php, after that you can resolve it via
https://gist.github.com/MatteoOreficeIT/2c8f061e81a20fa96b8176efd97dbac5#file-dynamicmailerserviceprovider-php-L197

I'd like to know if this works for queued emails; I can see queue code commented out.

@MarioPerini
Copy link

How should this run, a little bit of documentation would be great.
Classes defined as abstract may not be instantiated, and any class that contains at least one abstract method must also be abstract.
Your class is not abstract but contains an abstract function, so it results in a fatal error.

@MatteoOreficeIT
Copy link
Author

Hi, Sorry but I could not make it before

See an improved demo project, it is not a composer package but you can easily C&P classes from it

https://github.com/MatteoOreficeIT/laravel-dynamic-mailer

@j7550155
Copy link

@MatteoOreficeIT
Hi,I have a question in this code,what is the impact of bind and singleton
https://gist.github.com/MatteoOreficeIT/2c8f061e81a20fa96b8176efd97dbac5#file-dynamicmailerserviceprovider-php-L136
https://github.com/MatteoOreficeIT/laravel-dynamic-mailer/blob/master/app/Providers/AbstractDynamicMailerServiceProvider.php#L131

My understanding is that the bind function will generate multiple class objects in the loop, which may affect the memory

@MatteoOreficeIT
Copy link
Author

Hi @j7550155 , We did it because the base laravel implementation allows to settle only one mailer in a app

  • we want have the chance to obtain a mailer class but with different settings extracted from a domain model ( client db record ), every client has a factory smtp server with different settings
  • see CustomMailer provider in https://github.com/MatteoOreficeIT/laravel-dynamic-mailer
  • in our applications we have scripts that inject only one smtp mail at time, not in a loop
  • if you want you can use the documented-in-source-code-only Container::forgetInstance to deallocate binded instance in a loop
  • remember php needs in some circunstances to call gc_collect_cycles to cleanup memory

@MatteoOreficeIT
Copy link
Author

How should this run, a little bit of documentation would be great.
Classes defined as abstract may not be instantiated, and any class that contains at least one abstract method must also be abstract.
Your class is not abstract but contains an abstract function, so it results in a fatal error.

Sorry @MarioPerini visit https://github.com/MatteoOreficeIT/laravel-dynamic-mailer for a down-to-earth example !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment