Looking towards February - Version 4 (DI-centric design)

Swift Mailer is a fantastic library for sending email with php. Discuss this library or ask any questions about it here.

Moderators: Chris Corbyn, General Moderators

User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Looking towards February - Version 4 (DI-centric design)

Post by Chris Corbyn »

Well, it's probably coming up for 1 year since version 3 was released and all in all it's been a solid workable API so that's not changing a great deal. Version 4 will be PHP 5 and 6 only (sorry PHP4'ers, I made a pledge and I stand by it) and will not be released before February 2008. The main focus is to minimize coupling as much as possible via the use of setter based dependency injection and factories. QP encoding needs a real good re-think too.

I'm starting the initial planning for v4, so as usual I'm looking for some feedback on what you liked or didn't like in the API developed for v3.

Things I liked:

The event-driven plugin system -- although the implementation could be improved
The way messages are composed
The use of exceptions for descriptive errors rather than failing silently (though I hated the PHP4 workaround)

Things I didn't like:

The whole concept of 'Connections' -- a NativeMail class is not a connection!
The type-hinting on class names and the use of instanceof inside Swift_Message
The complexity of the nesting logic in Swift_Message

New features in version 4:

Rather than one GOD encoder, an Encoder interface will allow dedicated Encoders to be encapsulated and used.
Connections are out, DeliveryDrivers are in. Sending behaviour will be abstracted so Swift doesn't "talk SMTP" to a connection, it just issues "send" commands to a Driver.

I've only just started putting this stuff online, but there's a home in the wiki for it now:

http://www.swiftmailer.org/wikidocs/v4/start

Thoughts on my sense of direction greatly appreciated :D
ody
Forum Contributor
Posts: 147
Joined: Sat Mar 27, 2004 4:42 am
Location: ManchesterUK

Post by ody »

Hello Mr Corbyn

I have some wonderings about the use of Swift in a automated mailer (you should remember the emaildirectory ;). The problem, as I see it, is having to instantiate a new swift stack for every message. Will it, in the new version of swift focus on the reuse of the already instantiated objects? Maybe it's possible now, but looking at what you wrote would guess not.


Instead of (very very pseudo):

Code: Select all

 
while(1)
{
 $swift = new Swift();
 $message = new Swift_Message();
 $swift->disconnect();
}
 
this:

Code: Select all

 
$swift = new Swift();
$message = new Swift_Message();
 
while(1)
{
  $message->set('body');
}
$swift->disconnect();
 
Andrew
Last edited by ody on Mon Dec 03, 2007 2:22 pm, edited 1 time in total.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

DeliveryDrivers are in
Maybe Transports?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Looking towards February - Version 4 (DI-centric design)

Post by Chris Corbyn »

Andrew, sorry for the very late reply, for some reason I missed the thread. Swift already supports keep-alive connections so your second example actually works.

~Wierdan, are you just renaming the term 'DeliveryDriver' or asking for a new feature? :) I'm still working on the Mime stuff now so haven't got that far yet but effectively I was planning a really simple API for a 'DeliveryDriver':

Code: Select all

interface DeliveryDriver {
  send(Message $message) : boolean
} 
That's roughly what a transport would do although I can see you may perhaps want to 'chain' one message through a list of transports if that's what you're getting at? Like exim's transport system.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Looking towards February - Version 4 (DI-centric design)

Post by Weirdan »

I was talking about rename... DeliveryDriver sounds odd to me
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Looking towards February - Version 4 (DI-centric design)

Post by Chris Corbyn »

Agreed, Transport sounds better :)
ody
Forum Contributor
Posts: 147
Joined: Sat Mar 27, 2004 4:42 am
Location: ManchesterUK

Re: Looking towards February - Version 4 (DI-centric design)

Post by ody »

Chris Corbyn wrote:Andrew, sorry for the very late reply, for some reason I missed the thread. Swift already supports keep-alive connections so your second example actually works.

Hi Chris

I think you got my query confused a bit. Below is a quick example of what I am talking about (you should lookup the pool pattern if you have not already. One important thing to note is one variable storing an object for reuse is the same as an array of them. PHP has no threading so unlikely to need a real pool.). When you create a Swift_Message you set it up via the construct. What I am looking at is not doing it this way but rather reusing the already instantiated object (pool) and using the setters.

Code: Select all

<?php
$iters = 10000;
 
require_once dirname(__FILE__) . "/Swift/ClassLoader.php";
Swift_ClassLoader::load("Swift_Message");
Swift_ClassLoader::load("Swift_Address");
Swift_ClassLoader::load("Swift_Message_Mime");
Swift_ClassLoader::load("Swift_Message_Image");
Swift_ClassLoader::load("Swift_Message_Part");
 
// the way you do it (well at least from what I have seen!)
$time = microtime(TRUE);
for($i=$iters; $i; $i--)
{
        $message = new Swift_Message('subject', 'body');
}
echo 'Without pool took: '.(microtime(TRUE)-$time)."\n";
 
//the way I would do it
$time = microtime(TRUE);
$message = new Swift_Message('subject', 'body'); // subject and body would not be required my way
for($i=$iters; $i; $i--)
{
        //$message->prime(); //get object ready for use, includes resetting old data
        $message->setTo('andrew@test.com');
        $message->setFrom('andrew@test.com');
        $message->setBody('body');
        $message->setSubject('subject');
        $message->setDate(time());
}
echo 'With Pool took: '.(microtime(TRUE)-$time)."\n";
 
Anyway, I'm still familiarising myself with Swift so have probably missed something incredibly obvious ;)

Andrew
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Looking towards February - Version 4 (DI-centric design)

Post by Chris Corbyn »

Ah I see where you're going with that. Most of that stuff is already doable (don't pass anything to the constructor then just use the setters), except for being able to reset the state of the message. Certainly something to think about :)

I'm just about to move onto writing the MimeDocument stuff within the next few days after a little re-assessment of my initial API plans...
ody
Forum Contributor
Posts: 147
Joined: Sat Mar 27, 2004 4:42 am
Location: ManchesterUK

Re: Looking towards February - Version 4 (DI-centric design)

Post by ody »

Glad you see ;)

The way I'm thinking about going about it is to depreciate the construct($subject, $body, ...) in favor of: prepare() and compose(). By sticking everything that is likely to stay the same in prepare() and the main setting up of the message in compose() we will save a lot of cpu cycles which will make Swift a real viable solution to mass unique emailing (sending lots of different message to different people instead of the sending the same message to lots of people way Swift handles really well already).

I've split it up to a degree already and seeing some good speed improvements but splitting the $charset out is difficult as it relies on $body.. a sort of chicken and the egg situation ;)

Anyway, that's just my 2pennies.

Andrew
vanchuck
Forum Newbie
Posts: 6
Joined: Fri Apr 20, 2007 3:56 pm
Location: Vancouver, BC, Canada

Re: Looking towards February - Version 4 (DI-centric design)

Post by vanchuck »

These aren't so much feature requests, as things I had to hack into Swift myself... I'm sharing them here in case they seem like they might be a good idea for ver4.

Enhanced Failure Logging
I was creating a custom Decorator class that would perform replacements. I wanted to force the sending of an email to fail if the requested substitution (say {mailing_address}) did not exist for the recipient. I did get this to work simply by throwing an exception. However, I wanted to log this error properly, with the REASON (like "Sending Failed: Substitution for mailing address not found"), so that the rest of my code could present this message to the user. But IIRC, throwing an exception (in BatchSend) doesn't allow you to log the reason, just that it failed.

What I ended up doing was instantiating my own custom Swift_Log class, and using that to log all these things. Then, if there were any Failed Recipients recorded, I'd have the ability to look up the address, the error message, etc.

The suggestion for this though is allowing plugins to trigger the failing of a recipient, and be able to log the reason why, which would then be accessible in the main error log, united for all plugins as well as core operations.

Progress Tracking
I have progress bars in my app for anything that typically takes more than 10 seconds. Uploading, generating large pdfs, etc... and sending emails. If using Swift in a command-line environment, that's fine, the web front-end could just go happily on it's way while the backend churns things out. In my case, I wanted to allow people to cancel the process, so had a progress bar with a cancel button. But progress bars mean you need to be able to see what's going on!

In my case, I update the progressbar by writing JavaScript to an iframe. So the email sending is done by the script that is loading in the iframe. Here's what I did:

Code: Select all

 
                $swift->attachPlugin(new Swift_Plugin_Progress($num_recipients, $initial_offset), 'progress');
                apache_setenv('no-gzip', '1');
 
Here's the custom plugin:

Code: Select all

class Swift_Plugin_Progress implements Swift_Events_SendListener
{
    protected $total;
    protected $progress;
    protected $starttime;
 
    public function __construct($total, $progress = 0)
    {
        $this->total = $total;
        $this->progress = $progress;
        $this->initial = $progress;
        $this->starttime = time();
    }
 
    public function sendPerformed(Swift_Events_SendEvent $e)
    {
        $this->progress ++;
        $recipients = $e->getRecipients();
        $recipients = $recipients->getTo();
        $recipient = reset($recipients);
 
        $percent = round(100 * $this->progress / $this->total);
 
        $timesofar = time() - $this->starttime;
        $rate = $timesofar / ($this->progress - $this->initial);
        $remaining = round($rate * ($this->total - $this->progress)); //seconds
 
        $statustext = 'Sent to '.$recipient->getName().'<br/>'.
                      '<em>Sent '.($this->progress - 1).' of '.$this->total.'</em>';
 
        echo '<script type="text/javascript">'.
             "parent.progressbarUpdate('".$statustext."',".$percent.",".$remaining.",false);".
             '</script>';
        flush();
    }
}
 
Easy and simple. Luckily the event-driven design of Swift allows for plugins like this to be easily written (I love finding libraries that are well-thought out!). I'm including this second one here just in case they can be useful to anyone else, but also to see if it helps to spark any new ideas for the upcoming release, in terms of a progress monitor :-).

Dave

PS, can't wait to see the php4 compatibility crutches gone! Death to php4!
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Looking towards February - Version 4 (DI-centric design)

Post by Chris Corbyn »

I just wanted to paste some code I was "brainstorming" a minute ago and gather some thoughts. I know, I know, I drastically changed the API when I moved from v2 to v3, but do we all agree it was a change for the better? :) I'm expecting some negative remarks about the fact I'm thinking of changing again (the constructors are only used for dependency injection in v4).

I'm not generally one for following "fashions", but fluid interfaces seem to work really well for this DI-driven system where factories will be used to create complex components, rather than the use of the "new" keyword.

I haven't written the code for this yet, but v4 is very much lots of glue code. I don't want end-users to have to worry about how everything goes together in the new API (and I don't want to force people to adopt a new API so I 100% promise there will be lightweight compat wrappers to expose a v3 API).

But what does code like this feel like to people who are happy to adopt a change in API? I see the benefits as:

* It's fluid... the order you do things in doesn't matter
* You can build an entire message and all it's subparts etc in a logical manner
* Remembering what order constructor parameters go in is not an issue
* It's descriptive (fields are set with setters, not with constructor parameters)

Code: Select all

<?php
 
require_once 'lib/swift-required.php';
 
//Transports are the sendmail, smtp, nativemail, rotation etc "connections"
$smtp = Swift_TransportFactory::create('smtp') 
  ->setHostName('localhost')
  ->setPort(25);
 
//Speaks for itself
$mailer = Swift_Mailer::newInstance($smtp);
 
//Fields can be set fluidly (but don't have to be)
$message = Swift_MessageFactory::create('message')
  ->setSubject('some subject')
  ->setTo('someone@somewhere.com')
  ->setFrom('your@address.com')
  ->attach( //Attachments are set fluidly
    Swift_MessageFactory::create('part')
      ->setContentType('text/html')
      ->setCharset('utf-8')
    )
  ->attach(
    Swift_MessageFactory::create('attachment')
      ->setFilename('textbook.pdf')
      ->setContentType('application/pdf')
      ->setContentAsByteStream(
        new Swift_ByteStream_FileByteStream('the-actual.pdf') //Some utility classes have no dependencies anyway
        )
    );
 
//Recipients etc are read from the message object.... no need for a RecipientList
if ($mailer->send($message))
{
  echo "Message sent";
}
else
{
  echo "Failed";
}
Who'd just be ignoring this style of API and working with the v3 compat wrappers?

Half the reason this use of factories is needed is because of the dependency injection (I'm not going to use some bulky container (or phemto cos it won't work for me)) so I've chosen to write factories and have simple declarative dependency maps which allow end-users to swap out implementations.

I have tough skin... if I'm going crazy and this looks awful, go ahead and criticise it, I'll listen :)

NOTE: I can't stress enough for all the people who are gonna be like "omg, not another API change" there will be compat wrapper since they'll be incredibly easy to write due to the modular nature of v3.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Looking towards February - Version 4 (DI-centric design)

Post by Chris Corbyn »

I went ahead and did this. Here's what it looks like. This example creates a greek message with an attached PDF. A big focus has been on ensuring as many charsets as possible work. You'll also notice there's no explicit need to use a mime part when you want an attachment. It's handled internally now.

Code: Select all

//Require the injector
require_once dirname(__FILE__) . '/../../lib/swift_required.php';
 
Swift_MimeFactory::setCharset('utf-8'); //Optional (can be set on a per-part basis)
 
$message = Swift_MimeFactory::create('message')
  ->setSubject('??? ??????? ??? ?? ?????????????')
  ->setTo(array('rob@site.com' => 'Rob'))
  ->setFrom(array('chris@w3style.co.uk' => '???????????'))
  ->setBody("?? ??? ??????? ??? ?? ????? ????? ???????")
  ->attach(
    Swift_MimeFactory::create('attachment')
      ->setContentType('application/pdf')
      ->setFilename('??? ????? ???????.pdf')
      ->setBody(file_get_contents('files/stifado_recipe.pdf')) //Haven't implemented FileStream yet.
    )
  ;
xdecock
Forum Commoner
Posts: 37
Joined: Tue Mar 18, 2008 8:16 am

Re: Looking towards February - Version 4 (DI-centric design)

Post by xdecock »

Hello,

I was wondering if the version 4 would be able to make use of the stream_select function as it permits asynchronous sending of emails.

The only drawback of this system will be the use of callback for success / error notification. But it can be more efficient than the LoadBalancedTransport without needing to use multiple sending processes.

this system will be able to have multiple mails in sending state. and would optimize throughput (when using multi core smtp server, a single php process is too slow and the fork() is far more memory hungry than a select oriented approach).

(I'll try to implement an example of this method)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Looking towards February - Version 4 (DI-centric design)

Post by Chris Corbyn »

xdecock wrote:Hello,

I was wondering if the version 4 would be able to make use of the stream_select function as it permits asynchronous sending of emails.

The only drawback of this system will be the use of callback for success / error notification. But it can be more efficient than the LoadBalancedTransport without needing to use multiple sending processes.

this system will be able to have multiple mails in sending state. and would optimize throughput (when using multi core smtp server, a single php process is too slow and the fork() is far more memory hungry than a select oriented approach).

(I'll try to implement an example of this method)
Yikes, it actually sounds like you've had a good old nosey in the svn repository :) Kudos.

It's not something I've looked into, but thanks for the heads-up, I'll take a look.
xdecock
Forum Commoner
Posts: 37
Joined: Tue Mar 18, 2008 8:16 am

Re: Looking towards February - Version 4 (DI-centric design)

Post by xdecock »

Would it be possible to create a Swift_Transport_Dummy ? to allow application benchmarking without introducing the smtp / mail() / /usr/bin/sendmail delay in the test? (and will be a good "troubleshoot" tool when getting SwiftMailer is too slow topics)
Post Reply