Set Mailable header via a trait? - laravel

I'm creating a Laravel package that would benefit from the use of emails. When a user would use my package they would want to email a file created by the package, but also set some custom headers for the email.
In an ideal solution, I would like to have a trait that the developer could simply use on their mailable class and it would automatically set the header for that email without any additional code. Is this something that is even possible via the use of a trait?
Some solutions have suggested adding headers to mailables by putting this in the build method:
$this->withSwiftMessage(function ($message) {
$headers = $message->getHeaders();
$headers->addTextHeader('mime', 'text/calendar');
});
But is there some way to have my own custom trait piggy-back on the build method of the Mailable that is using it WITHOUT having to write it in the Mailable class itself?

SOLUTION USING TRAITS
The only way to be able to do that with a trait would be defining the build method in your trait and make your user define another function instead of build such that you have the direct manipulation of the function actually used by Mailable class.
So your trait would be:
trait IsMailable {
public function build()
{
$this->withSwiftMessage(function ($message) {
$headers = $message->getHeaders();
$headers->addTextHeader('mime', 'text/calendar');
});
if(!method_exists($this, 'buildMail')) throw \Exception('buildMail is not defined!');
return $this->buildMail();
}
}
So your user will have to define the method buildMail instead of build.
OPTIMAL SOLUTION
The optimal solution, IMHO, would be extending the class Illuminate\Mail\Mailable redefining the method send and making the end user implement this newly defined class instead of Illuminate\Mail\Mailable.
So your class would be:
class Mailable extends \Illuminate\Mail\Mailable {
/**
* Send the message using the given mailer.
*
* #param \Illuminate\Contracts\Mail\Mailer $mailer
* #return void
*/
public function send(MailerContract $mailer)
{
$this->withSwiftMessage(function ($message) {
$headers = $message->getHeaders();
$headers->addTextHeader('mime', 'text/calendar');
});
parent::send($mailer);
}
}
Doing that your user can use the build method as it would do using the standard Illuminate\Mail\Mailable class but the end result would be that your class is piggybacking the additional information that you actually need.

Related

Customize laravel fortify email verification to use a mailjet template

I've been trying to get fortify's native email verification feature to use mailjet so templates can easily be adjusted. I've hit a bit of a roadblock as all tutorials and sources I've found are all about blade templates. Does someone have some pointers in the right direction or perhaps personal experience setting this up?
Thanks in advance!
You need to bind your own implementation of Laravel\Fortify\Contracts\VerifyEmailViewResponse in the FortifyServiceProvider class.
# app/Providers/FortifyServiceProvider.php
namespace App\Providers;
use App\Actions\Fortify\YourClassHere;
use Laravel\Fortify\Contracts\VerifyEmailViewResponse;
class FortifyServiceProvider extends ServiceProvider
{
public function boot()
{
app()->singleton(VerifyEmailViewResponse::class, YourClassHere::class);
}
}
# app/Actions/Fortify/YourClassHere.php
namespace App\Actions\Fortify;
use Laravel\Fortify\Contracts\VerifyEmailViewResponse;
class YourClassHere extends VerifyEmailResponse
{
public function toResponse($request)
{
// your logic here.
// since mailjet uses an api, perhaps return the response of a curl call,
// or a response form Http::get(...)
// or use a third-party package like laravel-mailjet (https://mailjet.github.io/laravel-mailjet/)
}
}
The logic that sends the verification email is part of the Illuminate\Auth\MustVerifyEmail trait. If you want to change it, you need to override it in your User model.
class User extends Authenticatable
{
/**
* Send the email verification notification.
*
* #return void
*/
public function sendEmailVerificationNotification()
{
// your logic here.
}
}

Facades vs. Classes with static methods in Laravel

I was looking around the Laravel framework and some of their products and I noticed that Cashier is using the Casheir class with static methods compared to Socialite, which is used as a facade.
What are the benefits/downsides of building it one or the other way, or is there none at all?
I would like to build something myself, but I don't want to start building it as a class with static methods if building it as a facade is a better solution.
When you may need multiple implementations, a single interface can be defined through facade to simplify the code
Building it as a class with static methods:
When you have multiple classes you have to do something like this:
CashierOne::method, CashierTwo::method ....
Used as a facade:
According to what you bind to the container to switch the implementation
You only need to call through an interface:
// Define a Cashier Facade
class Cashier extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'cashier';
}
}
// In CashServiceProvider
$this->app->singleton('cashier', function ($app) {
return new CashierManager ($app);
});
// In CashierManager
public function gateway($name = null)
{
// get cashier implementation by name
}
public function __call($method, $parameters)
{
return $this->gateway()->$method(...$parameters);
}
// In Controller
Cashier::method
In addition, the facade is easier to test, check:
https://laravel.com/docs/5.8/facades#how-facades-work

How can I change SMTP details globally at runtime?

I'm using Laravel 5.5. The nature of the website is a 'multisite' architecture where multiple websites/domains are run from the same codebase.
I've come across an issue when sending email. I need to change the from name and address as well as the transport (SMTP, etc) options depending on which website is being viewed. I have these details stored in a config file.
The easiest way is to just pull those details in the Controller before I call Mail::send/Mail::queue and to update them. However, this brings back 2 issues:
There is a heavy reliance on remembering to actually do that every time I send any email in the code. In short, it's not abiding by DRY.
I'd be forced to use Mail::send instead of Mail::queue, because the queue wouldn't have any idea of the config update from the time it was queued only from when it is processed .
How can I achieve what I am looking to do here in a clean way?
I thought about extending all of my 'Mailable' classes with a custom class that updates the SMTP details, but it doesn't look like you can update the SMTP/Transport information after the class is initiated; you can only update the from name and address.
I managed to find a way to do this.
I had my mailable class (ContactFormMailable) extend a custom class, as follows:
<?php
namespace CustomGlobal\Mail;
use CustomGlobal\Mail\CustomMailable;
use CustomGlobal\ContactForm;
class ContactFormMailable extends CustomMailable
{
public $contact_form;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(ContactForm $contact_form)
{
$this->contact_form = $contact_form;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$view = $this->get_custom_mail_view('contact_form', $this->contact_form);
return $this->subject('Contact Form Enquiry')
->view($view);
}
}
You'll notice I'm calling get_custom_mail_view. This is in my extended class and used to calculate the view and template I need to use for my mail, depending on the website being viewed. In here I also set the location of my config folder.
<?php
namespace CustomGlobal\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use Swift_Mailer;
use Swift_SmtpTransport;
use CustomGlobal\Website;
use CustomGlobal\Territory;
class CustomMailable extends Mailable
{
use Queueable, SerializesModels;
public $layout_view_to_serve;
public $host_folder;
/**
* Override Mailable functionality to support per-user mail settings
*
* #param \Illuminate\Contracts\Mail\Mailer $mailer
* #return void
*/
public function send(Mailer $mailer)
{
app()->call([$this, 'build']);
$config = config($this->host_folder .'.mail');
// Set SMTP details for this host
$host = $config['host'];
$port = $config['port'];
$encryption = $config['encryption'];
$transport = new Swift_SmtpTransport( $host, $port, $encryption );
$transport->setUsername($config['username']);
$transport->setPassword($config['password']);
$mailer->setSwiftMailer(new Swift_Mailer($transport));
$mailer->send($this->buildView(), $this->buildViewData(), function ($message) use($config) {
$message->from([$config['from']['address'] => $config['from']['name']]);
$this->buildFrom($message)
->buildRecipients($message)
->buildSubject($message)
->buildAttachments($message)
->runCallbacks($message);
});
}
/**
* Calculate the template we need to serve.
* $entity can be any object but it must contain a
* $website_id and $territory_id, as that is used
* to calculate the path.
*/
public function get_custom_mail_view($view_filename, $entity)
{
if(empty($view_filename)) {
throw new Exception('The get_custom_mail_view method requires a view to be passed as parameter 1.');
}
if(empty($entity->website_id) || empty($entity->territory_id)) {
throw new Exception('The get_custom_mail_view method must be passed an object containing a website_id and territory_id value.');
}
// Get the website and territory
$website = Website::findOrFail($entity->website_id);
$territory = Territory::findOrFail($entity->territory_id);
$view_to_serve = false;
$layout_view_to_serve = false;
// Be sure to replace . with _, as Laravel doesn't play nice with dots in folder names
$host_folder = str_replace('.', '_', $website->website_domain);
$this->host_folder = $host_folder; // Used for mail config later
/***
Truncated for readability. What's in this area isn't really important to this answer.
***/
$this->layout_view_to_serve = $layout_view_to_serve;
return $view_to_serve;
}
}
It's important to remember that mail can be queued. If you do this is another way, such as setting a config at runtime, then you'll find that the process that runs the queue has no visibility/scope of your runtime config changes, and you'll end up firing out email from your default values.
I found a few answers similar to this one, which helped me out, but none of them worked completely, and some are out-dated (Swift_SmtpTransport is changed considerably since those answers).
Hopefully this helps someone else out.

How can I override Laravel Facade methods?

I want to override the Laravels' Mail's classes facade method send (just intercept it forcing some checks and then if it passes triggering parent::send())
What is the best way to do this?
A Facade doesn't work like that. It's essentially kind of like a wrapper class that calls the underlying class that it represents.
The Mail facade doesn't actually have a send method. When you do Mail::send(), under the hood, the "facade accessor" is used to reference an instance of the Illuminate\Mail\Mailer class bound in the IoC container. It's on that object the send method is called.
The way in which you can achieve what you're after is actually a little bit trickier than it seems. What you can do is:
Write your own implementation of Mailer, extending Illuminate\Mail\Mailer, in which you can override the send method, implement your checks and call parent::send().
Write your own service provider (Extending Illuminate\Mail\MailServiceProvider), in particular re-implement the register method. It should create an instance of your own Mailer in place of Laravel's own. (You can copy most of the code from Laravel's register method).
Now, in your config/app.php file, in the providers array, replace Illuminate\Mail\MailServiceProvider::class, with your own provider.
That should let you hook into Laravel's Mail functionality.
For more information, you can take a look at the following question/answer which achieves a similar thing. It extends the Mail functionality to add a new transport driver, but it takes a similar approach in that it provides its own Mailer implementation and service provider.
Add a new transport driver to Laravel's Mailer
app/MyMailer/Mailer.php
<?php
namespace App\MyMailer;
class Mailer extends \Illuminate\Mail\Mailer
{
public function send($view, array $data = [], $callback = null)
{
// Do your checks
return parent::send($view, $data, $callback);
}
}
app/MyMailer/MailServiceProvider.php (Most of the code copied from Laravel's MailServiceProvider class)
<?php
namespace App\MyMailer;
class MailServiceProvider extends \Illuminate\Mail\MailServiceProvider
{
public function register()
{
$this->registerSwiftMailer();
$this->app->singleton('mailer', function ($app) {
// This is YOUR mailer - notice there are no `use`s at the top which
// Looks for a Mailer class in this namespace
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
$this->setMailerDependencies($mailer, $app);
$from = $app['config']['mail.from'];
if (is_array($from) && isset($from['address'])) {
$mailer->alwaysFrom($from['address'], $from['name']);
}
$to = $app['config']['mail.to'];
if (is_array($to) && isset($to['address'])) {
$mailer->alwaysTo($to['address'], $to['name']);
}
return $mailer;
});
}
}
config/app.php (In the providers array)
//...
// Illuminate\Mail\MailServiceProvider::class,
App\MyMailer\MailServiceProvider::class,
//...

How to override the native zf2 view helpers with a custom helper

I wanted to create a custom basepath helper to replace the original zf2 basepath view helper.
So if i call $this->basepath, it will use my custom basepath instead of the original one. I am not sure if this is can be done. I want my custom basepath extends the original basepath class too.
I have found some answers on how to create custom helpers and how to register them in module.php or module.config.php
But i can't find any similar questions on how to override the original helpers!
Factory definition of the basepath view helper is declared as a hardcoded invokable in HelperPluginManager (on line 45) however this definition also overridden in ViewHelperManagerFactory (line 80 to 93) because BasePath view helper requires the Request instance from ServiceLocator:
$plugins->setFactory('basepath', function () use ($serviceLocator) {
// ...
})
I strongly recommend extending the built-in basepath helper with a different name (MyBasePath for example) instead of trying to override the existing one. Overriding that native helper may produce some unexpected headaches later (think about 3rd party modules which uses that helper to work).
For your question; yes, it is possible.
Create the Application\View\Helper\BasePath.php helper class like below:
namespace Application\View\Helper;
use Zend\View\Helper\BasePath as BaseBasePath; // This is not a typo
/**
* Custom basepath helper
*/
class BasePath extends BaseBasePath
{
/**
* Returns site's base path, or file with base path prepended.
*/
public function __invoke($file = null)
{
var_dump('This is custom helper');
}
}
And override the factory in the onBootstrap() method of the Module.php file like below:
namespace Application;
use Zend\Mvc\MvcEvent;
use Application\View\Helper\BasePath; // Your basepath helper.
use Zend\View\HelperPluginManager;
class Module
{
/**
* On bootstrap for application module.
*
* #param MvcEvent $event
* #return void
*/
public function onBootstrap(MvcEvent $event)
{
$services = $event->getApplication()->getServiceManager();
// The magic happens here
$services->get('ViewHelperManager')->setFactory('basepath', function (HelperPluginManager $manager) {
$helper = new BasePath();
// Here you can do whatever you want with the instance before returning
return $helper;
});
}
}
Now you can try in any view like this:
echo $this->basePath('Bar');
This is not a perfect solution but it should work.

Resources