Archive for the ‘PHP’ Category

Building A Scalable Messaging Solution for PHP

Thursday, April 20th, 2006

Sending notifications to web users is generally taken for granted when writing a PHP application. The excelent PHP mail function is generally used. But what happens when my site becomes popular. Does the mail function grow with my site? Or how about if I wanted to send more types of messages than a simple email. This little article explains how I am currently investigating how I could possibly scale up messaging a user in a web enviroment. A big warning first though, I am not a PHP expert, programmer even, and I am just learning about OOP and PHP and patterns, so please read this at your own risk.

My Problem
I am playing around with PHP at the moment to maybe build a website that *may* get a tad popular (hope is good!:) . My problem here is that I may have to scale the site very quickly and I want to advoid having to rewrite code as much as possible. I also am a big fan of the separation of services where possible. So a database should IMHO sit on a dedicated box, a web server just be a web server etc.

One of the important aspects of this site, is its notification to the user on the occurance of an event. This will initally be an email, but eventually I would like notifications to be delivered to the user via AIM, Google Talk (Or for that matter any Jabber user), Text message (SMS) and maybe even MSN Messenger. In short, whatever way I can get a notification to a user I want to be able to do it. However if the site is popular, do I want the server I am busy serving clients on to be under the burden of having to send mail at the same time, ideally it should not. Now I know the mail function can be used to send the mail to a dedicated mail server, but again I also want to be able to send other types of messages too.

My Solution
First off, I know that this as a solution will only scale so far. I am also aware that my code is really really bad here, but I have just started to play with all this OOP stuff in PHP5, so please any advice you can give me in improving my code, style and method of working, please do so. Of course a lot of error checking has gone out the window here for the purpose of this blog post, along with security too, this solution is presented as way you *may* be able to do things. Its not to be taken as an authorative way to do this.

Right, enough with the disclaimers, lets get cracking. After a wee while playing around and doing a little bit of searching, I decided the best way to implement all this was by heavy use of the new OOP features in PHP5 and using SOAP. The reason I choose SOAP was quite simply, I could direct my code to fire off a soap message faily easily to a SOAP service which could be on the same box (In which case you maybe get twice the overhead of sending a mail using the mail function) or as is most likley the case on another interally acessable box, dedicated to notifications only. So what I really needed at the end of the day, was a Messaging Abstraction layer. Something that would just handle the sending of a message and leave the app to get on with the rest of its processing.

Enter Interfaces

This is when I discovered Interfaces in PHP, below is a cut down version of my Messaging interface.


interface MessengerInterface {
public function validateSender($sender);
public function validateRecipient($recipient);
public function sendMessage ();
}

As you can see its very basic. All I want to do is validate my senders and recipients, and send my message. Nothing colud be simpler… right… :)

I now needed a class that would send an email, but it had to implement the above interface, otherwise what would the point be? So I ran up a rather nasty class to mange this.


class EmailMessenger extends Messenger implements MessengerInterface {

private $sender;
private $recipient;
private $subject;
private $message;
private $messageFormat;

/**
* Method constructor. Validates the recipients and sender and sends the message.
*
* @param string $sender
* @param string $recipient
* @param string $subject
* @param stringe $message
*/
public function __construct($sender, $recipient, $subject, $message) {
$this->sender = $sender;
$this->recipient = $recipient;
$this->message = $message;
// We need a subject for emails
if(!isset($subject)) {
throw new MessengerException("You need to specify a subject for this type of transport");
}
$this->subject = $subject;
// Set message format to true if the message contains HTML
$this->messageFormat = $this->isHtmlMessage($this->message);

// Now we validate the sender address.
self::validateSender($this->sender);
// Now the recipient
self::validateRecipient($this->recipient);
// Now we'll try and send the mail;
self::sendMessage();

}
public function validateSender($sender) {
if (!eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $sender)) {
throw new MessengerException("Invalid Email Sender Address Format: $sender");
return false;
}
return true;
}

public function validateRecipient($recipient) {
if (!eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $recipient)) {
throw new MessengerException("Invalid Email Recipient Address Format: $recipient");
return false;
}
return true;
}

public function sendMessage () {
// Supress an error in case we are on a win32 platform. The exception should handle the error.
if(!@mail("$this->recipient", "$this->subject","$this->message","From: $this->sender \r\n")) {
throw new MessengerException("Mail Server/Service Unavailable. Sorry I Unable To Send Mail At Present.");
return false;
}
return true;
}

/**
* Checks a message to see if it contains HTML tags.
*
* @param string $message
* @return bool True if message contains HTML false otherwise.
* @access private
*/
private function isHtmlMessage($sting) {
if(preg_match("/([\< ])([^\>]{1,})*([\>])/i", $string)) {

return true;
}
return false;
}
}

As you can see, all of the work is done when the class is called, I don’t think this is good pratice, but for the purpose of this, I am going to leave well enough alone. I also have made this class a “sub” class of my main Messenger class. The messenger class is where the magic happens, it basically decides what “sub” class should be called depending on the message format.


class Messenger {

/**
* The Senders Address
*
* @var string
*/
private $sender;
/**
* The Recipients Address
*
* @var String
*/
private $recipient;
/**
* The Message Being Seny
*
* @var string
*/
private $message;
/**
* The transport or format of sending the message
*
* @var string
*/
private $transport;

/**
* Contains an instance of our message transport.
*
* @var object
*/
private $messageTransport;

/**
* Contains the message subject if set.
*
* @var string
*/
private $subject;

/**
* Messenger construct, requires that the sender, recipient, message and transport be
* provided otherwise it throws a general MessengerException Error.
*
* @param string $sender The sender of the message.
* @param string $recipient The recipent of the message.
* @param string $message The message that is being sent.
* @param string $subject Optional, only used when sending a mail message.
* @param string $transport Optional, defaults to email
* @throws MessengerException
*/
public function __construct($sender, $recipient, $message, $subject=null, $transport = null) {
if(!$sender) {
throw new MessengerException("You did not specify a sender address.");
}
if(!$recipient) {
throw new MessengerException("You did not specify a recipent address.");
}
if(!$message) {
throw MessengerException("You did not specify a message to send.");
}
if(!$transport) {
throw new MessengerException("You did not specify a transport type.");
}
$this->sender = $sender;
$this->recipient = $recipient;
$this->message = $message;
// If subject is set.
if(isset($subject)) : $this->subject = $subject; endif;
// Convert the trasport argument to lower case. This is case we will pattern match
// on lower case only.
$this->transport = strtolower($transport);
// We'll set our types to be nice.
settype($this->sender, "string");
settype($this->recipient, "string");
settype($this->subject, "string");
settype($this->message, "string");
settype($this->transport, "string");

// Pattern Factory (I think)
switch ($this->transport) {
case "email":
require_once('EmailMessenger.class.php');
try {
$this->messageTransport = new EmailMessenger($this->sender, $this->recipient, $this->subject, $this->message);
}
// Catch the exception and save the message to the message queue if possible.
catch (MessengerException $e) {
require_once('MessageQueue.class.php');
try {
throw new MessengerException("Not Yet Implemented");
//MessageQueue::saveMessage($db, $this->sender, $this->recipient, $this->subject, $this->message, $this->transport);
}
catch (MessengerException $e) {
throw new MessengerException("$e->getMessage()");
}

}
break;
case "jabber":
require_once('JabberMessenger.class.php');
throw new MessengerException("Not Yet Implemented");
//$this->messageTransport = new JabberMessenger($this->sender, $this->recipient, $this->message);
break;
default:
/**
* @todo Can remove this prob, to make class more readable. Also we'll choose email as the default transport.
*/
require_once('EmailMessenger.class.php');
$this->messageTransport = new EmailMessenger($this->sender, $this->recipient, $this->subject, $this->message);
break;
}
}
}

Okay so here you can see, depending on the transport that is being requested, we’ll fire up a new instance of messaging sub classes to send the message.

Washing With SOAP

Soap intergration was quite easy. As I am lazy and could not be arsed finding out the format of a WSDL file, I used the inbuilt wsdl generator included with Zend IDE. This was based around a very simple php file below, called simply MessengerService.

require_once('Messenger.class.php');
/**
* Send a mail message via soap.
*
* @param string $sender
* @param string $recipient
* @param string $subject
* @param string $message
* @return boolean
*/
function sendEmail($sender,$recipient,$subject,$message) {
try {
$Messenger = new Messenger($sender,$recipient,$message,$subject,'EMAIL');
return true;
}
catch (MessengerException $e) {
throw new SoapFault("999", "Fault With Request: $e->getMessage()");
}
}

$SoapServer = new SoapServer('Messenger1.wsdl');
$SoapServer->addFunction('sendEmail');
$SoapServer->handle();

Here I have the EMAIL transport hard coded, as I was just testing this out, but as you can guess, by changing the transport, a different type of message can be sent. Its quite nice this way too as to send a messafe I only have to do the following;


$newSoapClient = new SoapClient('http://notify.dev/Messenger.wsdl');
$newSoapClient->sendEmail("$sender","$recipient","$message", "$subject"); // Real world would include a transport type var.

This messenger service can sit on the same box as your web app, or when things become too busy, moved off to another box to handle notifications.

Finally
There are some questions I have all the same. How do large sites like Yahoo handle mail notifications within their applications? Do the use a similar theory or are the mails sent locally? If you have expeience with such a problem I would love to hear from you. Secondly I think the overhead on a SOAP request migh be higher than a mail function request, but again this is only a theory I am playing with at the moment.

If you did find this long post handy, or indeed if you have suggestions on how I can improve this or my PHP, please drop me a line.

Zend Framework

Monday, March 6th, 2006

The Zend Framework Preview was finally published at the weekend. Only had a quick look at it but it looks good but I think it will be a long road.

XAJAX & Smarty Part Two

Thursday, January 12th, 2006

Okay so I am doing well with this Xajax stuff coupled along with Smarty. All I need now is to have the object response to return my Smarty template with all its glorious data.

Well I must admit to feeling very silly here now, I spent ages doing the $smarty->display() however what I really wanted to do was actually $smarty->fetch in my functions so that I would be sent correctly to the Xajax object. This also means that if you have Smarty defined somewhere else, for example in a config file, you will need to use the Global keyword to allow your xajax function to access your smarty template.

HTH if not at least I will remember. It would be nice to be able to access an Object directy, but then again, not really when I think about it properly.

XAJAX & Smarty

Saturday, December 31st, 2005

Okay so I have been messing around with all this ajax stuff recently. Now being a big fan of SMARTY I was reluctant to go and write my own templating system, and intergrating javascript into some of the existing smarty templates I had was a bit of a pain in the ass. But I really wanted to give this AJAX stuff a go.

After trying various different ajax scripts and implementatons, I settled on XAjax to work with PHP. The main reason was that they seemed to be the easiest to intergrate with Smarty.

So down to the nuts and bolts of it.

require_once(INCLUDESDIR. '/3rdparty/Xajax/xajax.inc.php');
$xajax = new xajax();
// Send our xajax requests to a certain server
$xajax->setRequestURI("form-processor.php");
$xajax->registerFunction("validate-field");
$xajax->processRequests();
$smarty->assign('xajax_javascript', $xajax->getJavascript());
$smarty->display('web/common/header.tpl');

So lets go this bit by bit just so that we are clear.

  • The first line simply includes or Xajax script
  • Begin a new instance of Xajax
  • The next line tells Xajax where to send the data. By default it will use POST and send the data back to the same calling script. However in this example, I am sending the data to another script whihc does all the processing for me and returns the results as an Xajax response.
  • Finally the register functions tells Xajax which function to call based upon what you write and decide it is allowed to call.
  • The process request statement must be called otherwise you won’t get anything back from your scripts
  • Xajax makes it very easy for you, all you do is as normal is assign a smarty var, the Xajax javascript and call it in your template as you would normally call anything else.
  • No an important bit here. If like me you are sending your requests to a separate script, you must include the Xajax library, you must have xajax registered the functions and you must call xajax->processRequests();

    A function, for those that don’t know can be anything you want to do. The validate one looks like this;


    public function validate-field($arg1) {
    $objResponse = new xajaxResponse();
    if(!$arg1, !$arg2) {
    return false;
    }
    $objResponse->addClear("messages","innerHTML");
    $objResponse->addAssign('messages', 'innerHTML', "Form was validated");
    return $objResponse->getXML();
    }

    You must return the $objResponse->getXML(), otherwise, how is XAJAX supposed to know what to do?

    So on my form, I have a on blur event that simply valiades the filed after the user moves away from the field

    This simple gets the value of the field and passes it to xajax whihc then calls my function. Simple right.

    Now I have actually got it to send messages like processing data, and then display an error message, perhaps I will put up an example of those when I get a chance.

    BTW this is a very very simplifed example of how quick it is to add XAJAX into your scripts. However please note that it can be quite complex to do this, and you should sit and plan your application correctly and its data flow. Also remember don’t use AJAX just because its cool to do so, use it because there is actually a need to use it.

    Smarty & Google Maps

    Tuesday, December 27th, 2005

    Well it looks like Monte is at it again. Another great product from the SMARTY guy. This handy little API makes it a piece of piss to intergrate Google maps into you web app. Of course it also supports SMARTY too.

    I have not played with it yet, but I will over the next few days as soon as I return to a dedicated link, instead of this vodafone 3G card which may I add is still over priced, not as good as it should be, and still not working correctly under Linux.

    Anyhow, must dash, have turkey to digest….. :)