ZF-Commons / RFC

Placeholder repo for RFCs wiki -- Add new proposals for ZF-Commons modules in the wiki.
10 stars 1 forks source link

RFC: ZfcMail #2

Open EvanDotPro opened 12 years ago

EvanDotPro commented 12 years ago

Comments and discussion for: RFC: ZfcMail

EvanDotPro commented 12 years ago

It's possible this could be split up into two modules -- one for additional mail adapters and one for an email template system, as both modules would be useful on their own without the other.

yanickrochon commented 12 years ago

I don't see why it should be split into two modules. An email module would always require a template portion. Or else, what would be the other use for a ZfcTemplate module?

ZfcMail should provide mail adapter and template configurations, all overridable and extendable.

Danielss89 commented 12 years ago

I think voting should start on this. I think that the most needed functions is covered: Different mail adapters and mail templates, i think especially that templates are needed, ASAP :D This module would be used for ZfcUser to send email.

yanickrochon commented 11 years ago

The core functionalities of such module should include, but not limited to :

juriansluiman commented 11 years ago

Two modules

I would split the two modules. Transportation is something different than 3rd party email service providers. I have now basic integration with various services in SlmMail and it does not focus on a single transportation system, but integration with the full API. You can check for bounces, get statistics, use tags etcetera.

Modules are small and very lightweight: there is no need to put everything into one module. I assume for most users a templating system is good enough since often you only use SMTP or the standard mail() feature. If you use 3rd party email service providers, it is more likely you test some different services to test them for delivery speed for example.

That said, it is a community vote and if everybody else agree transportation and email template rendering should be put into one module, you can integrate the services from SlmMail easily.

Implementation

Then about @yanickrochon suggested features. I would never send emails in a controller. Forcing developers to extend from a base controller just because you need some email service, is also not really a good example of using abstract classes.

Instead, I would suggest that email rendering should happen in a service layer. You get an email service, where you can inject a transport and basic message object. Then you specify some variables (subject, to address), a view template and view variables. The service is registered in the service manager and you can make other services aware, so with an initializer the mail service is injected.

This method is used in Soflomo\Mail and Soflomo\Log and Soflomo\Cache. I do not provide templating (yet) but it's fairly simple:

namespace MyModule\Service;

use Soflomo\Mail\MessageAwareInterface;
use Soflomo\Mail\TransportAwareInterface;

use Zend\Mail\Message;
use Zend\Mail\Transport\TransportInterface as Transport;

class MyService implements MessageAwareInterface, TransportAwareInterface
{
    protected $message;
    protected $transport;

    public function setMessage(Message $message)
    {
        $this->message = $message;
    }

    public function setTransport(Transport $transport)
    {
        $this->transport = $transport;
    }

    public function doSomething()
    {
        $message = $this->message;
        $message->setTo('john@doe.com', 'John Doe');
        $message->setSubject('Hello you');
        $message->setBody('Hi there!');

        $this->transport->send($message);
    }
}

Your service is aware of Mail, Cache or Log and you get it injected. Then you consume it.

You initiate this kind of action from the controller or another service. I can imagine there is a controller plugin invoking the email service to make it easy to send an email from a controller, but this should never happen inside the controller itself:

$this->zfcMail()
     ->mail('john@doe.com', 'John Doe')
     ->template('my/email/say-hi', array('name' => 'John'))
     ->send();

Features

Some suggestion for features:

  1. Use the normal view resolvers, so the service can simply render zfc-user/email/activation and that just resolves as normally: no specific template configuration since every zf2 user knows how template names might resolve. Additional logic blurs the picture.
  2. Split the work in three distinctive steps: a. Configure message object b. Render view script c. Send message. The three are consecutively called with a wrapper method. This way you can do various stuff with each result: add an attachment to the mail message object, for example.
  3. Good point for the events. I would suggest a .pre and .post for every above action. That will make a message.pre, message.post, render.pre, render.post, send.pre, send.post for example.
yanickrochon commented 11 years ago

@juriansluiman, I believe you misunderstood (or perhaps I wasn't clear enough about it) what I meant by having controllers and actions within the module.

As I did mention services and events, the controllers would only serve as built-in and ready-to-use-out-of-the-box module functionality with pre-configured routes that would make use of the declared services and functionalities. Of course, these routes and module core functionality could very well be overridden and disabled in favor of an application specific usage.

Less the mail and template module separation, I actually agree with your feature suggestions. The example you gave does not contradict how I see this module.

Though I am proposing another core feature, to use pre-configured named templates (see my last comment), that could be used as :

// using your last example :
$this->zfcMail()
     ->mail('john@doe.com', 'John Doe')
     ->body('my/email/say-hi', array('name' => 'John'))   // or bodyHtml(...) and bodyText(...)
     ->send();

// using a pre-configured named template (overridable via configurations)
$this->zfcMail()
     /// ->mail('john@doe.com', 'John Doe')    // optional override
     ->template('template_name', array('name' => 'John'))
     ->send();

And that template could have access to the actual message to setup subject, cc fields, etc.

<?php
// specify extra parameters
$this->message
    ->subject('Message subject')   // could be ignored if already set externally to the template, for example
    ->addCc('admin@mydomain.com');

?>
<p>This is a template message for <?= $this->name ?> !</p>
<p>Some other text.</p>
Thinkscape commented 11 years ago

@juriansluiman I don't like how you're injecting a message into a service. MessageAwareInterface is very odd.

A transport represents a configurable service - once selected and injected into MyService it stays the same.

A message on the other hand, is volatile, passable object that should be created on the fly (new Message()) or with a factory (MessageFactory::factory). It must not be injected from the outside. What if my service wants to send 10 messages?

juriansluiman commented 11 years ago

@yanickrochon I still don't get why you would want a controller with a route. This is typical business logic a developer should implement him/herself. An publicly available controller action through which every user could send a message sounds like a ContactForm module or something. And contact forms do not necessarily use email as transport and emails are not sent solely for contact forms.

@Thinkscape we've thought about this for a while. We sometimes clone the message after injection. In that case, my example would look like this:

namespace MyModule\Service;

use Soflomo\Mail\MessageAwareInterface;

class MyService implements MessageAwareInterface
{
    // implementation here

    public function doSomething()
    {
        $message = clone $this->getMessage();

        // Rest here
    }
}

I agree messages are an edge case. However, this was our reasoning:

In 1, 5, or perhaps 10 classes we consume email message. In our case, 100% of the messages across all cases comes from the same sender. There are three variables which differ: subject, to address and body message. In the services consuming mail messages, you can easily set those properties. To set the from address in the message, you have either three options:

  1. Inject the from email address + from name in the service. This will be done either by a controller or a factory;
  2. Inject a ModuleOptions class in the service, where you specify a method to get the default from address;
  3. Inject a pre-configured message class.

In case no1 and no2 you can create the message yourself. But no1 is tedious to work with, if you choose no2 you have to depend on a specific module with an options class and pull it there.

With no3 you just can ignore all configuration and assume the message (including encoding, for example) is configured correctly. There is no need to clone if you only send one message (again, in our case also many times), but if you need a loop and send messages, clone the object. Mind that email messages are not shared in the service manager, so each time you inject a message, it is a new class.

yanickrochon commented 11 years ago

@juriansluiman, perhaps. This I wrote before I had an idea about a general purpose API web service module for other modules to register to (I talked about it last time I logged on IRC with EvanDotPro) that would totally replace controllers here and would only offer services as you mentioned. The rationale was that ZfcUser had controllers that could be extended or overridden, and I thought ZfcMail could offer the same basic functionality out of the box.

BTW: this API module (ex: ZfcAPI or ZfcExternalAPI, etc.) is an idea from WHMCS External API feature. Which would make it very useful for third party (existing?) applications to communicate with applications built on top of ZFC modules.

Thinkscape commented 11 years ago

@juriansluiman Thx for the explanation, now I get where you came from with this.

In ZF1 and similar fw, basic message info was pre-populated usually in the transport itself or somewhere "nearby".

Bottom line is - the MessageAwareInterface and the injection is basically used for instance templating which reduces boilerplate code and centralizes the configuration of sender address (might also be used for things like reply-to, priority, default subject, etc.)

ghost commented 11 years ago

Hi there what about zfcmail ? Everybody seems to agreee with 2 distinct modules. Everybody seems to agreee that SlmMail is great to manage different transports. But most of people only need smtp transport so the most important fonctionality is to send mail with html rendering. @juriansluiman you described a good way to do this with service layer. Do you provide it now into Soflomo/Mail ?

juriansluiman commented 11 years ago

@booradleys we use Soflomo\Mail now for SMTP integration and message templating. The html rendering is done outside that module, because it is fairly simple to inject the php renderer and render some view script in a service layer.

In the case we used a 3rd party email service provider, we hooked SlmMail into it so we had a pre-configured mail transport for that as well.

ghost commented 11 years ago

@juriansluiman ok thank you so i'm going to use Soflomo\Mail because i can't wait that zfcMail be ready. Thank you very much!