A solution for e-mail sending in CakePHP
Posted by Felix Geisendörfer, on Aug 05, 2006 - in PHP & CakePHP » DataSources, Models & Behaviors
Deprecated post
The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.
Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.
Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).
Warning: This code has been produced in the time between 2am and 6am *without* the influence of coffee. Usage on your own risk ; ).
One of the things when I always felt like I wasn't quite following the DRY(don't repeat yourself)-concept when working with CakePHP was sending out emails. I always ended up with pretty long controller functions for every mail, since I tried to use CakePHP's View class to render my mail templates, and I always had to create the $header and a couple other things.
Now even worse was, that when I finished one of those mail() actions, I felt like it wasn't very reusable, even within the application. Something like reminder messages might need to be send from several Controllers/Actions, which wasn't really confortable to do.
So the last couple of days I've been thinking about the best way to approach mailing in cakephp. I always felt that an E-Mail was something to model, so I felt like implementing it as a Model. On the other Site, I wanted the ability to use the CakePHP render() functionality for my mail templates, which would be easier to do from within a Component (Controller).
Today I finally decided to see if Rails provided a native way for e-mailing and I was pleasently surprised when I found the documention of their ActionMailer class. Their solution to the Problem is to use a kind of hybrid between a Model and a Controller, which provides the base class for one or more app-wide mailer-models. This basically means that you create a new class in your app/models path, and extend the ActionMailer. After that you start putting your own mail (domain) logic into this Model which then can be used from within any Controller to send the kinds of mails you define. One special thing about this mail models is, that they have their own folder named after them inside views, just like a controller, where their mail templates rest.
I liked the concept, so I took it as an inspiration for an ActionMailer for CakePHP. The usage turns out to be as simple as this:
app/models/app_mailer.php
-
vendor('action_mailer');
-
-
class AppMailer extends ActionMailer
-
{
-
var $name = "AppMailer";
-
-
function contact_form($contact_info)
-
{
-
// Standard Mail stuff
-
$this->addRecipient('contact@business.com');
-
$this->setSubject('Web Form Contact');
-
$this->setSender($contact_info['email'], $contact_info['name']);
-
-
// Custom variables for our mail template
-
$this->set('name', $contact_info['name']);
-
$this->set('email', $contact_info['email']);
-
$this->set('phone', $contact_info['phone']);
-
$this->set('message', $contact_info['message']);
-
$this->set('ip', env('REMOTE_ADDR'));
-
$this->set('browser', env('HTTP_USER_AGENT'));
-
}
-
}
app/controllers/mail_controller.php
-
class MailController extends AppController
-
{
-
var $name = "Mail";
-
-
function contact()
-
{
-
// Notice the 'send_' prefix in front of our AppMailer function!
-
if ($this->AppMailer->send_contact_form($this->data['Contact']))
-
{
-
// Mail was sent successfully
-
}
-
else
-
{
-
// Some error occured
-
}
-
}
-
}
app/views/app_mailer/contact_form.thtml
-
<hr>
That's it. Highly resuable and easy to integrate.
Now I do have to admit that the ActionMailer could use a couple additional functions like attachments and multi-part email handling as well as integration with alternative mail engines (not only php's mail() function). But other then that, it might already worth for you to take a look at it.
Download
Download the code (v. 0.7.3) at CakeForge and put it into app/vendors/action_mailer.php.
Note: I just updated the code with some bug fixes, the new version is 0.7.3.
Usage
Have a look at the AppMailer example above. You need to have a folder inside views with the underscored name of your mailer (app_mailer in the example). In this folder you have to put the templates for your Mailer functions (like you do with controller actions). Each function represents one type of e-mail your application sends out.
In your Controller include the Mailer model you've created by using the var $uses; array. After that you can send your e-mails by calling $this->{Mailer}->send_{mail_function}();. Instead of
'send_' as a prefix to all your functions, you can also use 'deliver_'.
Important: In order for the ActionMailer to work you will have to create a new layout in your app/views/layouts folder called mail.thtml. You can change this name by overwriting ActionMailer::layout.
Build-in functions
As of right now, the following functions can be used inside the Mailer model:
ActionMailer::addRecipient($email, $name = null): Add's an email adress (and eventually name) to the list of recipients. Info: I had some problems using $name on my Win32 dev box, you might want to leave it blank in the beginning.
ActionMailer::setRecipient($email, $name = null): Just like addRecipient, the only difference is that it will remove all previously set recipients before adding the new one.
ActionMailer::setHeader($name, $value): Set's an http header for the email that is sent. Use it like this: ActionMailer::setHeader('Content-type', 'text/html');
ActionMailer::set($one, $two = null): Set's a variable to make it available to the mail template. Similiar to Controller::set();
ActionMailer::setSender($email, $name = null): Set's the FROM field of the email. Problems like mentioned with addRecipient do not seem to appear.
ActionMailer::setSubject($subject): Set's the subject of the email.
ActionMailer::reset($subject): Resets header, recipients, sender and data of an email. Automatically called before send_{functions} are being executed.
One more thing
Some people might think this is a horrible solution as it violates some of the fundamental MVC rules. I agree, it does do that, but I also think that it is legitamate in terms of DRY and RAD'ness and doesn't seem to conflict with the other things going on in ones application. So therefor I believe this is one of the things where compromises have to be made for the greater good ; ). Anyway, if you can think of a better solution, let me know, and I'll see what I can do.
--Felix Geisendörfer aka the_undefined