Soflomo / Mail

Module to to ease the use of sending e-mail messages in Zend Framework 2
9 stars 5 forks source link

Email plugin manager for distinct email tasks #1

Open juriansluiman opened 10 years ago

juriansluiman commented 10 years ago

From juriansluiman/SlmMail#72:

At you have to:

Create an array of options Optionally: Create an array of variables Optionally: Create a default Message and attach params So the creation of above stuff needs to be done somewhere in a controller or service. The idea is to go a bit further and create an API for the creation part.

Let's say: I have a platform with multiple tenants who have users. When a new user registers, the from address should be the one of the tenant.

What I suggest:

Create a UserRegisteredEmail class which is a MailInterface or whatever you name it. In this mailerclass you can add dependencies that are only needed to create the e-mail. In this case: tenant, user, and a service that is called: SuperMegaGreeter which is only used for the e-mail. This way it is possible to get the from adresses from the tenant object, the to addres from the user object, and a custom variable for the SuperMegaGreeter. The MailInterface will have methods like: getOptions() , getVariables(), getMessage(). Now the mail is fully configurable from a class with dependencies that are only needed to send the e-mail. (Note: I don't like the getOptions() in this case, but like to use getTemplate(), getSubject(), ...) Now on the MailSender service I want to send this class instead of calling the three methods. Extra: An instance of the UserRegisteredEmail can be fetched from a MailManager (like the service manager, with invokables, factories, ...) I know it CAN be done allready, but it is a lot faster for RAD + easier to use because you don't need to know how the MailSender works exactly.

The idea is to create an email plugin manager which holds "email tasks" for distinct emails. This prevents the caller of MailService#send() to have knowledge of the email composition (subject, template and so on) and helps to reuse the email in different parts. An example is the user can request a password-reset email but the admin can trigger one as well.

Possible API:

// $emailManager is the abstract plugin manager
// $user is an instance of a User entity

$email = $emailManager->get('password-reset');
$email->setOption('user', $user);
$email->send();

The email task itself:

EmailInterface
{
    public function setOptions(array $options);
    public function setOption($key, $value);
    public function send();
}

abstract class AbstractEmail implements EmailInterface
{
   protected $service;
   protected $options = array();

    public function __construct(EmailService $service)
    {
        $this->service = $service;
        $this->init();
    }

    protected function init()
    {
    }

    public function setOption($key, $value)
    {
        $this->options[$key] = $value;
    }

    public function setOptions(array $options)
    {
        $this->options = $options;
    }

    protected function getService()
    {
        return $this->sevice;
    }

    protected function getOptions()
    {
        return $this->options;
    }

    protected function getOption($key)
    {
        if (!array_key_exists($key, $this->options)) {
            return;
        }

        return $this->options[$key];
    }

    protected function setTemplate($template)
    {
        $this->setOption('template', $template);
    }

    protected function setSubject($subject)
    {
        $this->setOption('subject', $subject);
    }
}

A concrete class could look like this:

class PasswordResetEmail extends AbstractEmail
{
    protected function init()
    {
        $this->setTemplate('email/password-reset');
        $this->setSubject('Your password reset');
    }

    public function send()
    {
        $user       = $this->getOption($user);
        $options   = $this->getOptions();
        $variables = array('user' => $user);

        $options['to'] = $user->getEmailAddress();
        $options['to_name'] = $user->getName();
        $this->getService()->send($options, $variables);
    }
}

But @veewee I am still not quite happy with this, as you have to know the API from Soflomo\Mail\Service\MailService for these email tasks as well. Perhaps we can come up with a better alternative for this. Anyhow, I think the idea is pretty good but it needs some work to finalize.

PS. For these kind of features I tend to think about the public API from the users perspective, how we do the internals is most times easier when the API is set.

veewee commented 10 years ago

Some extra thoughts:

The AbstractPluginManager has $creationOptions (MutableCreationOptionsInterface), so you can use:

$email = $emailManager->get('password-reset', ['subject' => 'new subject']);
$emailService->send();
// Or maybe a bit faster, but not so good for SRP:
$emailManager->send(); 

Maybe it is better to create a class MailOptions which extend StdLib\AbstractOptions in strict mode. This way it is possible to use the options as an array, but also with getters and setters. So you can choose if you add an array or MailOptions to the send method.

I would not add a send() method to the MailInterface. In the real world I still haven't found a letter which posts itself :) This way there is no dependency to the EmailService. The MailService should be smart enough to handle EmailInterface.

Maybe the MailInterface should look like:

interface EmailInterface
{
    /**
     * @return MailOptions
     */
    public getMailOptions();

    /**
     * @return \Zend\View\Variables|array|Traversable|null
     */
    public getViewVariables();

    /**
     * @return \Zend\Mail\Message|null
     */
    public getDefaultMessage();

}

This way you can work as followed:

What do you think?