Some input desired

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

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

Post by Chris Corbyn »

Yeah, I was thinking the same thing about caching the email body since some it shouldn't need to build a completely new email each time.... I'll work on that before adding authentication (I was reading the RFC for SMTP authentication and got a bit confused :P).

Perhaps template stuff should kept to extensions.... I want this class to basically handle the things that are needed to send email via an SMTP server.... not a lot else :)

If you set $to as an array it will send the same email to each address in the array....

The flush() method is used when sending *different* multipart emails through the same connection.... it would need to be called by the user at present, and since the class does not include a wrapper for the task of sending batches of *different* emails I hadn't thought of calling it from inside.... but I can see, it would make sense to call flush() imeediately after a user calls send(). I'm not sure it might have an adverse effect on a user that was intending on running some additional commads after the mail has been sent however (you can call the command() method externally).
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

d11wtq wrote:Perhaps template stuff should kept to extensions.... I want this class to basically handle the things that are needed to send email via an SMTP server.... not a lot else :)
Yes maybe. I was thinking that if 'to', 'from' and 'subject' were replaced in the template in the base version (by embedding {to}, {from} and {subject} in the built template) then adding other fields would require only a trivial addition of a set() method.
d11wtq wrote:The flush() method is used when sending *different* multipart emails through the same connection.... it would need to be called by the user at present, and since the class does not include a wrapper for the task of sending batches of *different* emails I hadn't thought of calling it from inside.... but I can see, it would make sense to call flush() imeediately after a user calls send(). I'm not sure it might have an adverse effect on a user that was intending on running some additional commads after the mail has been sent however (you can call the command() method externally).
I think there are a couple of use cases: send single emails, send the same email to a list of addresses, do crazy custom stuff. The single and mail list cases should be streamlined at the expense of the crazy customization capabilities.
(#10850)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

I see... something simple like that would be easy to include I guess.... I wonder if it might cause problems with the tiny possibility of those strings occuring elsewhere... a better approach may just be to cache the full email (in a property, or myabe just locally in the send() method) but keep the "To: " header out of it, then throw that header in each time a mail is sent.

I've also got it working with binaries on the server now (such as sendmail, qmail, or exim). To use it this way you call $mail->initilalizeSendmail( [command] ) and $mail->closeSendmail() where you'd normal call connect() and disconnect()... the rest is the same.

I've also moved the smtp server name out of the construct and in to the connect() method since it makes more sense (especially with sendmail being used).

So if I'm right in thinking it's just authentication to add, caching, and perhaps SSL.

Code: Select all

<?php

/*
 dMail: A Flexible PHP Mailer Class.
 Author: Chris Corbyn
 Date: 29th April 2006
 
 Current functionality:
  
  * Send uses one single connection to the SMTP server
  * Doesn't rely on mail()
  * Custom Headers
  * Sends Multipart messages, handles encoding
  * Sends Plain-text single-part emails
  * Batch emailing
  * Support for attachments
  * Sendmail (or other binary) support
 
 Things to add:
 
  * Basic SMTP Authentication
  * SSL (Not sure?)
 
 -----------------------------------------------------------------------

	This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc.,
    51 Franklin Street,
    Fifth Floor,
    Boston,
    MA  02110-1301  USA
    
    "Chris Corbyn" <chris@w3style.co.uk>

 */

class mailer
{
	private
	
	//Standard properties
	$server,
	$socket,
	$port,
	$connectTimeout = 30,
	$headers = "X-Mailer: dMail by Chris Corbyn\r\n",
	$domain = 'dMailUser',
	//Sendmail support
	$usingSendmail = false,
	$sendmailCommand = "/usr/sbin/sendmail -bs",
	$handle,
	$pipes,
	//MIME support
	$mimeBoundary,
	$mimeWarning,
	$parts = array(),
	$attachments = array();
	
	public
	
	//Just logging stuff
	$errors = array(),
	$transactions = array(),
	$lastResponse;
	
	public function __construct()
	{
		$this->mimeWarning = "This part of the E-mail should never be seen. If\r\n".
		"you are reading this, consider upgrading your e-mail\r\n".
		"client to a MIME-compatible client.";
	}

	private function getMimeBoundary($string=false)
	{
		$force = true;
		
		if (!$string)
		{
			$force = false;
			$string = implode('', $this->parts);
			$string .= implode('', $this->attachments);
		}
		
		if ($this->mimeBoundary && !$force) return $this->mimeBoundary;
		else
		{ //Make sure we don't (as if it would ever happen!) -
		  // produce a hash that's actually in the email already
			do
			{
				$this->mimeBoundary = 'dMail-'.strtoupper(md5($string));
			} while(strpos($string, $this->mimeBoundary));
		}
		return $this->mimeBoundary;
	}

	public function setDomain($domain)
	{
		$this->domain = $domain;
	}

	public function addHeaders($string)
	{
		$this->headers .= $string;
	}

	public function setMimeBoundary($string)
	{
		$this->mimeBoundary = $string;
	}

	public function setMimeWarning($warning)
	{
		$this->mimeWarning = $warning;
	}

	public function setConnectTimeout($secs)
	{
		$this->connectTimeout = $secs;
	}

	//When a multipart message has been sent, and the user wants to
	// send a different message through the same connection
	public function flush($clear_headers=false)
	{
		$this->parts = array();
		$this->attachments = array();
		$this->mimeBoundary = null;
		if ($clear_headers) $this->headers = "X-Mailer: dMail by Chris Corbyn\r\n";
	}

	public function connect($server, $port=25)
	{
		$this->server = $server;
		$this->port = $port;
		
		$this->socket = @fsockopen($this->server, $this->port, $errno, $errstr, $this->connectTimeout);
		if (!$this->socket)
		{
			$this->logError($errstr, $errno);
			return false;
		}
		//What did the server greet us with on connect?
		$this->logTransaction();
		//Just being polite
		$this->command("HELO {$this->domain}\r\n");
		return true;
	}

	public function initializeSendmail($command=false)
	{
		if ($command) $this->sendmailCommand = $command;
		
		$pipes_spec = array(
			array("pipe", "r"),
			array("pipe", "w"),
			array("pipe", "w")
		);
		$this->handle = proc_open($this->sendmailCommand, $pipes_spec, $this->pipes);
		
		if (!$this->handle)
		{
			$this->logError('Unable to start '.$this->sendmailCommand, 0);
			return false;
		}
		
		$this->socket =& $this->pipes[0];
		$this->usingSendmail = true;
		
		//What did the server greet us with on connect?
		$this->logTransaction();
		//Just being polite
		$this->command("HELO {$this->domain}\r\n");
		return true;
	}

	//Dish out the appropriate commands to send an email through the server
	public function send($to, $from, $subject, $body=false, $type='text/plain')
	{
		if (!is_array($to)) $to = array($to);
		
		foreach ($to as $address)
		{
			$data = $this->buildMail($address, $from, $subject, $body, $type);
			
			foreach ($data as $command)
			{
				if (!$this->command($command)) return false;
			}
		}
		
		$this->flush(true);
		
		return true;
	}

	//Connect errors, timeouts, anything that went wrong but
	// wasn't brought up by SMTP itself
	private function logError($errstr, $errno=0)
	{
		$this->errors[] = array(
			'num' => $errno,
			'time' => microtime(),
			'message' => $errstr
		);
	}

	//Keep track of everything that gets said
	private function logTransaction($command='')
	{
		$this->transactions[] = array(
			'command' => $command,
			'time' => microtime(),
			'response' => $this->getResponse()
		);
	}

	//Read back the data from our socket
	private function getResponse()
	{
		if (!$this->socket) return false;
		
		$ret = '';
		
		if (!$this->usingSendmail) $ret .= fgets($this->socket);
		else $ret .= fgets($this->pipes[1]);
		
		$this->lastResponse = $ret;
		return $ret;
	}

	public function command($comm)
	{
		if (!$this->socket) return false;
		
		//SMTP commands must end with a return
		if (substr($comm, -2) != "\r\n") $comm .= "\r\n";
		
		if (@fwrite($this->socket, $comm))
		{
			$this->logTransaction($comm);
			return $this->lastResponse;
		}
		else return false;
	}

	//Add a part to a multipart message
	public function addPart($string, $type='text/plain', $encoding='7bit')
	{
		$ret = "Content-Type: $type\r\n".
				"Content-Transfer-Encoding: $encoding\r\n\r\n".
				$this->encode($string, $encoding);
		$this->parts[] = $ret;
	}

	//Attachments are added as base64 encoded data
	public function addAttachment($data, $filename, $type)
	{
		$ret = "Content-Type: $type; ".
				"name=\"$filename\";\r\n".
				"Content-Transfer-Encoding: base64\r\n".
				"Content-Disposition: attachment;\r\n\r\n".
				chunk_split($this->encode($data, 'base64'));
		$this->attachments[] = $ret;
	}

	public function disconnect()
	{
		if ($this->socket)
		{
			$this->command("quit\r\n");
			fclose($this->socket);
		}
	}

	public function closeSendmail()
	{
		$this->command("quit\r\n");
		
		foreach ($this->pipes as $pipe) fclose($pipe);
		if ($this->handle) proc_close($this->handle);
	}

	private function encode($string, $type)
	{
		$type = strtolower($type);
		
		switch ($type)
		{
			case 'base64':
			$string = base64_encode($string);
			break;
			//
			case 'quoted-printable':
			$string = $this->quotedPrintableEncode($string);
			//
			case '7bit':
			default:
			break;
		}
		
		return $string;
	}

	//From php.net by user bendi at interia dot pl
	private function quotedPrintableEncode($string)
	{
		$string = preg_replace('/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', 'sprintf( "=%02x", ord ( "$0" ) ) ;', $string);
		preg_match_all('/.{1,73}([^=]{0,3})?/', $string, $matches);
		return implode("=\r\n", $matches[0]);
	}

	//Builds the list of commands to send the email
	private function buildMail($to, $from, $subject, $body, $type='text/plain')
	{
		$date = date('r'); //RFC 2822

		if ($body) //Ignore multipart messaging
		{
			$data = "Subject: $subject\r\n".
				"From: $from\r\n".
				"Reply-To: $from\r\n".
				"To: $to\r\n".
				"Date: $date\r\n".
				"{$this->headers}\r\n".
				"Content-Type: $type\r\n".
				"Content-Transfer-Encoding: 7bit\r\n\r\n".
				"$body";
		}
		else //Build a multipart message
		{
			$boundary = $this->getMimeBoundary(); //Overall MIME boundary
			
			$message_body = implode("\r\n\r\n--$boundary\r\n", $this->parts);

			if (!empty($this->attachments)) //Make a sub-message that contains attachment data
			{
				$attachment_boundary = $this->getMimeBoundary(implode('', $this->attachments));
				
				$attachments = implode("\r\n\r\n--$attachment_boundary\r\n", $this->attachments);
				
				$attachments = "\r\n\r\n--$boundary\r\n".
				"Content-Type: multipart/alternative;\r\n".
				"	boundary=\"$attachment_boundary\"\r\n".
				"Content-Transfer-Encoding: 7bit\r\n\r\n".
				"\r\n\r\n--$attachment_boundary\r\n".
				$attachments.
				"\r\n--$attachment_boundary--\r\n";
			}
			
			$data = "From: $from\r\n".
				"Reply-To: $from\r\n".
				"To: $to\r\n".
				"Subject: $subject\r\n".
				"Date: $date\r\n".
				"{$this->headers}".
				"MIME-Version: 1.0\r\n".
				"Content-Type: multipart/mixed;\r\n".
				"	boundary=\"{$boundary}\"\r\n".
				"Content-Transfer-Encoding: 7bit\r\n\r\n";

			$data .= $this->mimeWarning;
			
			if (isset($attachments)) $message_body .= $attachments;
			
			$data .= "\r\n\r\n--$boundary\r\n".
				"$message_body\r\n".
				"\r\n--$boundary--";
		}

		$data .= "\r\n.\r\n";
		
		$ret = array(
			//Can be spoofed but that's not my problem
			"mail from: $from\r\n",
			//Recipient
			"rcpt to: $to\r\n",
			//Inform that we're about the give our message
			"data\r\n",
			//Message body
			$data
		);

		return $ret;
	}
}

?>
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I am wondering if you should treat SMTP and Sendmail as separate connection classes that you pass to the mailer.

Code: Select all

class Mailer_SMTPConnection {
        public function connect($server, $port=25) {
            $this->server = $server;
            $this->port = $port;
        }

        public function connect()
        {
                $this->socket = @fsockopen($this->server, $this->port, $errno, $errstr, $this->connectTimeout);
                if (!$this->socket)
                {
                        $this->logError($errstr, $errno);
                        return false;
                }
                //What did the server greet us with on connect?
                $this->logTransaction();
                //Just being polite
                $this->command("HELO {$this->domain}\r\n");
                return true;
        }
}


class Mailer_SendmailConnection {
        public function connect($command=false) {
                if ($command) $this->sendmailCommand = $command;
        }

        public function connect()
        {
                $pipes_spec = array(
                        array("pipe", "r"),
                        array("pipe", "w"),
                        array("pipe", "w")
                );
                $this->handle = proc_open($this->sendmailCommand, $pipes_spec, $this->pipes);
               
                if (!$this->handle)
                {
                        $this->logError('Unable to start '.$this->sendmailCommand, 0);
                        return false;
                }
               
                $this->socket =& $this->pipes[0];
                $this->usingSendmail = true;
               
                //What did the server greet us with on connect?
                $this->logTransaction();
                //Just being polite
                $this->command("HELO {$this->domain}\r\n");
                return true;
        }
}
And then do:

Code: Select all

$mailer = new Mailer(new SendmailConnection('mycommand'));
(#10850)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Yes I like that.... this also gives more flexibility to interface with different services too :) Your codes looks a little wrong though but I know what you're getting at. $this->domain is not ready by this point but it can be set in the construct of the mailer class I guess. I need to head home today but I'll post an update later tonight :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

d11wtq wrote:Your codes looks a little wrong though but I know what you're getting at.
Yeah, I figured the code wouldn't work. It was just to show the idea. I was hoping that I didn't totally miss something and they were totally different. :)
(#10850)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

OK how does this look? The API is pretty simple.... the connection handler object must contain at least a (public) start() method and a stop() method which cleanly handle the opening and closing of connections internally. They must also contain a property named readHook and writeHook which allow the mailer object to read and write to the open processes. That's all for now :)

Main class:

Code: Select all

<?php

/*
 dMail: A Flexible PHP Mailer Class.
 Author: Chris Corbyn
 Date: 1st May 2006
 
 Current functionality:
  
  * Send uses one single connection to the SMTP server
  * Doesn't rely on mail()
  * Custom Headers
  * Sends Multipart messages, handles encoding
  * Sends Plain-text single-part emails
  * Batch emailing
  * Support for attachments
  * Sendmail (or other binary) support
 
 Things to add:
 
  * Basic SMTP Authentication
  * SSL (Not sure?)
 
 -----------------------------------------------------------------------

	This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc.,
    51 Franklin Street,
    Fifth Floor,
    Boston,
    MA  02110-1301  USA
    
    "Chris Corbyn" <chris@w3style.co.uk>

 */

class mailer
{
	private
	
	//Standard properties
	$mailObject,
	$headers = "X-Mailer: dMail by Chris Corbyn\r\n",
	$domain = 'dMailUser',
	//MIME support
	$mimeBoundary,
	$mimeWarning,
	$parts = array(),
	$attachments = array();
	
	public
	
	//Just logging stuff
	$errors = array(),
	$transactions = array(),
	$lastResponse;
	
	public function __construct($object, $domain)
	{
		$this->domain = $domain;
		$this->mailObject =& $object;
		
		if (!$this->mailObject->start())
		{
			$this->logError('Unable to open a connection in the mail object', 0);
		}
		else
		{
			//What did the server greet us with on connect?
			$this->logTransaction();
			//Just being polite
			$this->command("HELO {$this->domain}\r\n");
		}
		
		$this->mimeWarning = "This part of the E-mail should never be seen. If\r\n".
		"you are reading this, consider upgrading your e-mail\r\n".
		"client to a MIME-compatible client.";
	}

	private function getMimeBoundary($string=false)
	{
		$force = true;
		
		if (!$string)
		{
			$force = false;
			$string = implode('', $this->parts);
			$string .= implode('', $this->attachments);
		}
		
		if ($this->mimeBoundary && !$force) return $this->mimeBoundary;
		else
		{ //Make sure we don't (as if it would ever happen!) -
		  // produce a hash that's actually in the email already
			do
			{
				$this->mimeBoundary = 'dMail-'.strtoupper(md5($string));
			} while(strpos($string, $this->mimeBoundary));
		}
		return $this->mimeBoundary;
	}

	public function addHeaders($string)
	{
		$this->headers .= $string;
	}

	public function setMimeBoundary($string)
	{
		$this->mimeBoundary = $string;
	}

	public function setMimeWarning($warning)
	{
		$this->mimeWarning = $warning;
	}

	//When a multipart message has been sent, and the user wants to
	// send a different message through the same connection
	public function flush($clear_headers=false)
	{
		$this->parts = array();
		$this->attachments = array();
		$this->mimeBoundary = null;
		if ($clear_headers) $this->headers = "X-Mailer: dMail by Chris Corbyn\r\n";
	}

	//Dish out the appropriate commands to send an email through the server
	public function send($to, $from, $subject, $body=false, $type='text/plain')
	{
		if (!is_array($to)) $to = array($to);
		
		foreach ($to as $address)
		{
			$data = $this->buildMail($address, $from, $subject, $body, $type);
			
			foreach ($data as $command)
			{
				if (!$this->command($command)) return false;
			}
		}
		
		$this->flush(true);
		
		return true;
	}

	//Connect errors, timeouts, anything that went wrong but
	// wasn't brought up by SMTP itself
	private function logError($errstr, $errno=0)
	{
		$this->errors[] = array(
			'num' => $errno,
			'time' => microtime(),
			'message' => $errstr
		);
	}

	//Keep track of everything that gets said
	public function logTransaction($command='')
	{
		$this->transactions[] = array(
			'command' => $command,
			'time' => microtime(),
			'response' => $this->getResponse()
		);
	}

	//Read back the data from our socket
	private function getResponse()
	{
		if (!$this->mailObject->readHook) return false;
		
		$ret = fgets($this->mailObject->readHook);
		
		$this->lastResponse = $ret;
		return $ret;
	}

	public function command($comm)
	{
		if (!$this->mailObject->writeHook) return false;
		
		//SMTP commands must end with a return
		if (substr($comm, -2) != "\r\n") $comm .= "\r\n";
		
		if (@fwrite($this->mailObject->writeHook, $comm))
		{
			$this->logTransaction($comm);
			return $this->lastResponse;
		}
		else return false;
	}

	//Add a part to a multipart message
	public function addPart($string, $type='text/plain', $encoding='7bit')
	{
		$ret = "Content-Type: $type\r\n".
				"Content-Transfer-Encoding: $encoding\r\n\r\n".
				$this->encode($string, $encoding);
		$this->parts[] = $ret;
	}

	//Attachments are added as base64 encoded data
	public function addAttachment($data, $filename, $type)
	{
		$ret = "Content-Type: $type; ".
				"name=\"$filename\";\r\n".
				"Content-Transfer-Encoding: base64\r\n".
				"Content-Disposition: attachment;\r\n\r\n".
				chunk_split($this->encode($data, 'base64'));
		$this->attachments[] = $ret;
	}

	public function close()
	{
		if ($this->mailObject->writeHook)
		{
			$this->command("quit\r\n");
			$this->mailObject->stop();
		}
	}

	private function encode($string, $type)
	{
		$type = strtolower($type);
		
		switch ($type)
		{
			case 'base64':
			$string = base64_encode($string);
			break;
			//
			case 'quoted-printable':
			$string = $this->quotedPrintableEncode($string);
			case '7bit':
			default:
			break;
		}
		
		return $string;
	}

	//From php.net by user bendi at interia dot pl
	private function quotedPrintableEncode($string)
	{
		$string = preg_replace('/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', 'sprintf( "=%02x", ord ( "$0" ) ) ;', $string);
		preg_match_all('/.{1,73}([^=]{0,3})?/', $string, $matches);
		return implode("=\r\n", $matches[0]);
	}

	//Builds the list of commands to send the email
	private function buildMail($to, $from, $subject, $body, $type='text/plain')
	{
		$date = date('r'); //RFC 2822

		if ($body) //Ignore multipart messaging
		{
			$data = "Subject: $subject\r\n".
				"From: $from\r\n".
				"Reply-To: $from\r\n".
				"To: $to\r\n".
				"Date: $date\r\n".
				"{$this->headers}\r\n".
				"Content-Type: $type\r\n".
				"Content-Transfer-Encoding: 7bit\r\n\r\n".
				"$body";
		}
		else //Build a multipart message
		{
			$boundary = $this->getMimeBoundary(); //Overall MIME boundary
			
			$message_body = implode("\r\n\r\n--$boundary\r\n", $this->parts);

			if (!empty($this->attachments)) //Make a sub-message that contains attachment data
			{
				$attachment_boundary = $this->getMimeBoundary(implode('', $this->attachments));
				
				$attachments = implode("\r\n\r\n--$attachment_boundary\r\n", $this->attachments);
				
				$attachments = "\r\n\r\n--$boundary\r\n".
				"Content-Type: multipart/alternative;\r\n".
				"	boundary=\"$attachment_boundary\"\r\n".
				"Content-Transfer-Encoding: 7bit\r\n\r\n".
				"\r\n\r\n--$attachment_boundary\r\n".
				$attachments.
				"\r\n--$attachment_boundary--\r\n";
			}
			
			$data = "From: $from\r\n".
				"Reply-To: $from\r\n".
				"To: $to\r\n".
				"Subject: $subject\r\n".
				"Date: $date\r\n".
				"{$this->headers}".
				"MIME-Version: 1.0\r\n".
				"Content-Type: multipart/mixed;\r\n".
				"	boundary=\"{$boundary}\"\r\n".
				"Content-Transfer-Encoding: 7bit\r\n\r\n";

			$data .= $this->mimeWarning;
			
			if (isset($attachments)) $message_body .= $attachments;
			
			$data .= "\r\n\r\n--$boundary\r\n".
				"$message_body\r\n".
				"\r\n--$boundary--";
		}

		$data .= "\r\n.\r\n";
		
		$ret = array(
			//Can be spoofed but that's not my problem
			"mail from: $from\r\n",
			//Recipient
			"rcpt to: $to\r\n",
			//Inform that we're about the give our message
			"data\r\n",
			//Message body
			$data
		);

		return $ret;
	}
}

?>
SMTP Connection:

Code: Select all

<?php

/*
 This is the SMTP handler for dMail, a PHP Mailer class.
 
 Author: Chris Corbyn
 Date: 1st May 2006
 
 $mailer = new mailer(new SMTPConnection('smtp-server.com'), 'mydomain.com');
 
 -----------------------------------------------------------------------

	This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc.,
    51 Franklin Street,
    Fifth Floor,
    Boston,
    MA  02110-1301  USA
    
    "Chris Corbyn" <chris@w3style.co.uk>

 */

class SMTPConnection
{
	public
	
	$socket,
	$readHook,
	$writeHook,
	$connectTimeout = 30,
	$server,
	$port = 25;

	public function __construct($server, $port=25)
	{
		$this->server = $server;
		$this->port = $port;
	}

	public function start()
	{
		$this->connect();
	}

	private function connect()
	{
		$this->socket = @fsockopen($this->server, $this->port, $errno, $errstr, $this->connectTimeout);
		
		$this->readHook =& $this->socket;
		$this->writeHook =& $this->socket;

		if (!$this->socket) return false;
		else return true;
	}

	public function stop()
	{
		$this->disconnect();
	}

	private function disconnect()
	{
		if ($this->socket) fclose($this->socket);
	}
}

?>
Sendmail Connection:

Code: Select all

<?php

/*
 This is the sendmail handler for dMail, a PHP Mailer class.
 
 Author: Chris Corbyn
 Date: 1st May 2006
 
 $mailer = new mailer(new sendmailConnection(), 'mydomain.com');
 
 -----------------------------------------------------------------------

	This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc.,
    51 Franklin Street,
    Fifth Floor,
    Boston,
    MA  02110-1301  USA
    
    "Chris Corbyn" <chris@w3style.co.uk>

 */

class sendmailConnection
{
	public
	
	$command = "/usr/sbin/sendmail -bs",
	$handle,
	$pipes,
	$readHook,
	$writeHook;
	
	public function __construct($command=false)
	{
		if ($command) $this->command = $command;
	}

	public function start()
	{
		$this->initializeProcess();
	}

	public function initializeProcess()
	{
		$pipes_spec = array(
			array("pipe", "r"),
			array("pipe", "w"),
			array("pipe", "w")
		);
		$this->handle = @proc_open($this->command, $pipes_spec, $this->pipes);
		
		$this->writeHook =& $this->pipes[0];
		$this->readHook =& $this->pipes[1];
		
		if ($this->handle) return false;
		else return true;
	}

	public function stop()
	{
		$this->closeProcess();
	}

	private function closeProcess()
	{
		foreach ($this->pipes as $pipe) fclose($pipe);
		if ($this->handle) proc_close($this->handle);
	}
}

?>
Quick demo:

Code: Select all

//Using SMTP
$mailer = new mailer(new SMTPConnection('your-smtp-server.tld'), 'the-server-the-script-is-on.tld');
$mailer->send('to-address@domain.tld', 'from-address@domain.tld', 'Subject', 'Body');
$mailer->close();

//Using sendmail
$mailer = new mailer(new sendmailConnection('/usr/sbin/sendmail -bs'), 'the-server-the-script-is-on.tld');
$mailer->send('to-address@domain.tld', 'from-address@domain.tld', 'Subject', 'Body');
$mailer->close();
Last edited by Chris Corbyn on Tue May 02, 2006 12:06 pm, edited 1 time in total.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Looks good. I will need to actually use it and look through the code to give any real comments.

One thing I would recommend is to rename you classes in PEAR / Zend Framework style naming. It is the standard these days -- like it or not -- and it makes autoload easy.

class Dmail
class Dmail_SMTPConnection
class Dmail_SendmailConnection

Files:

Dmail.php
Dmail/SMTPConnection.php
Dmail/SendmailConnection.php
(#10850)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

arborint wrote:Looks good. I will need to actually use it and look through the code to give any real comments.

One thing I would recommend is to rename you classes in PEAR / Zend Framework style naming. It is the standard these days -- like it or not -- and it makes autoload easy.

class Dmail
class Dmail_SMTPConnection
class Dmail_SendmailConnection

Files:

Dmail.php
Dmail/SMTPConnection.php
Dmail/SendmailConnection.php
I've always had my own naming convention of classname.class.php :( Class names themselves use the camelback style wording. I'll name them like you say if/when I SF this. Before I ever stick this on SF I'd like to add some of the extensions that were mentioned such as templating for emails and command-response logging in various formats.

Doh! Just realised I've completely neglected to sort out the email caching thingy.... :oops:
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

d11wtq wrote:I've always had my own naming convention of classname.class.php
I have my own naming conventions too -- I just don't use them. ;)
(#10850)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

d11wtq wrote:
arborint wrote:Looks good. I will need to actually use it and look through the code to give any real comments.

One thing I would recommend is to rename you classes in PEAR / Zend Framework style naming. It is the standard these days -- like it or not -- and it makes autoload easy.

class Dmail
class Dmail_SMTPConnection
class Dmail_SendmailConnection

Files:

Dmail.php
Dmail/SMTPConnection.php
Dmail/SendmailConnection.php
I've always had my own naming convention of classname.class.php :( Class names themselves use the camelback style wording. I'll name them like you say if/when I SF this. Before I ever stick this on SF I'd like to add some of the extensions that were mentioned such as templating for emails and command-response logging in various formats.

Doh! Just realised I've completely neglected to sort out the email caching thingy.... :oops:
I personally say PHP/Zend naming convention isn't as clear as it could be and for a few reasons not optimal...

I always do the following:

class.myname.php
module.myname.php
html.myname.php
...

File names are all lowercase with no characters but 0-9a-z and '_'

A hard lesson learned when I switched from classic ASP years ago to PHP and uploaded my files to a *nix server...

My file names were in windows style MyDocument.pdf but in HTML the links followed a more *nix feel in that mydocument.pdf where all file names were lower case...

When I loaded my web page and tried it only to find none of the pages worked as they did on my desktop...I became irate...only later to finally learn Apache was case-sensitive...

Also I avoid spaces as they cause some funny looking characters to appear in URL's so my dad once said...expressing his concern...

So I figured I would forever avoid that by simply using a '_' instead SPACE...

I also prefix every *module* with it's type so you get:

class.myname.php
html.myname.php

I do this because when I work with classes and templates and languages files, etc...

I can quickly dertermine which file is which in my IDE by looking at the first prefix on any file...

And the order...it's obvious...

class = Because thats what it is
name = Name of the class or module, etc
ext = Always PHP :)

As for class naming...

Thats easy as well...

If your going to develop software in a language derived from the mother of all languages C++ you might as well follow it's convention as it makes sense...

CMyClass - The C indicates class whereas
IMyClass - Indicates an interface
TMyClass - Indicates it's a template and so on...

I personally throw in a lower case x between the C and the name just to clearly seperate what is what...

This is handy when your working in VS IDE where your class browser can be listing 2,000 classes, templates, Interfaces, etc...the ability to quickly find the *class* as opposed to the Template is always nice :)

As for members, I use Hungarian notation, minus basic types and prefix everything with 'm_' which indicates this variable is without a dought a member and *not* a local variable or global...

So:

m_arrNames says this "member" is an array of names...

In PHP some might argue 'm_' is unnessecary because you *must* explicitly use the this pointer to use members, which tells you right away it's an member of the object not a local or global...

I argue...ok fine...but at least using 'm_' distinguishes without a dought that you are using a member variable NOT a member function.

Besides...C++ developers know what they are doing, so you might as well follow those in the know as opposed to those that don't know :P

Global variables I use the same notation but instead of a 'm_' I use 'g_'

Only in local parameters do I use the *nix style of naming variables...where I use small case seperated by '_' but still follow a type_description convention...

$arr_names <= nice and easy on the eyes...I choose this because you spend most of your time writting function code, so I figured local variable names should be easiest to write and read... :)

This way I can always tell where data is coming from, from inside a function without having to determine if it's a global, member, etc...

I never understood not do I like PEAR style of naming...

HTML_Crypt

That tells me nothing...is it a class or a function or perhaps even a strange define/constant...

As it's indutry standard to use all UPPERCASE_NAMES for constants...

This has been my experience in every language I have spent time learning... :)

Although I agree with capitalizing acronyms, I feel those are best left for class name prefixes, ideally seperate by underscore :)

CxMySexyClass_HTML

You camel case the name and it's clearly a class not an interface and the confusion of uppercase letters is removed using the '_' to clearly seperate from a constant...as the very first character tells you it ain't a constant but a class...

Anyways, I've never understood some naming conventions other than...it's personal taste...which is fine, because programming is as much an art as it is a science...

You should have seen my conventions a few years ago...with VB, C++, Pascal, Assembler and COBOL experience...my code always looked like 40 people wrote it when in reality it was only me :)

But these are my arguments towards a naming convention...with quantifiable reasons to do so...

In the end though, dude...you write how you wanna write...thats part of the art behind programming...artistic creativity...take that away and you just might loose your interest in programming...

I also wanted to note...I use the 'x' between the C and the class name because it helps keep names clear, especially when the class name also starts with a C

Like: CClown looks more practical as CxClown

Cheers :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I don't think I could have given a better example of why not to use you own naming conventions than that.
(#10850)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

arborint wrote:I don't think I could have given a better example of why not to use you own naming conventions than that.
Please explain... :?

And be pragmatic, as I have given a *reason* for the conventions I use...

I'd like to hear yours, other than just because...or I'm following suit/trends...

Mine are consequences of 10 years of development in C++ not to mention experience in a dozen other languages and almost 20 years programming experience in general...

So where sir, do my reasons go wrong?

Just because...or it feels wrong - doesn't cut it...I struggled with the convention at first as well...but years of experience told me it would work better in the long wrong...

And indeed it has...as i've standardized the way I program I waste zero time thinking about how or why I should name a variable what I do...

Granted sometimes I slip, but nobody is perfect :P

Cheers :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I'll be quite blunt. Every set of naming conventions that an individual programmer invents for his/her own code is quite simply crap. Yours is no exception. I have had conventions just as bad in the past -- and I still have plenty of cruft I am trying to unlearn.

The artistic part of programming is the peer reviewed results of very smart people solving very difficult problems -- results that we all benefit from every time we program -- if we are open to them.
(#10850)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

arborint wrote:I'll be quite blunt. Every set of naming conventions that an individual programmer invents for his/her own code is quite simply crap. Yours is no exception. I have had conventions just as bad in the past -- and I still have plenty of cruft I am trying to unlearn.

The artistic part of programming is the peer reviewed results of very smart people solving very difficult problems -- results that we all benefit from every time we program -- if we are open to them.
Good cuz I'm a straight shooter... 8)

...so I gave pragmatic reasons why I used the conventions I do...built over years of experience, just like those experts you mention ;)

Modeling after some well known conventions...hungarian, etc...

and you choose some random convention the PEAR/PHP team has thrown at you, just because (may I remind you almost anyone can write PHP code)?

If Rasmus Lerdorf asked you to jump off a bridge because he thought it was cool or the PHP way of doing things would you?

Honestly...conventions...reflecting more personal choice...are not *crap* however, conventions which have proven the test of time or are built from experience...are better suited in an environment where "close" doesn't count...

Basically I'm saying, you can code anyway you like...dude you can name your variables backwards for all I care...

But to answer my question with...dude it's crap...and *not* being able to quantify why like that other guy on here always when he argues with me...

Really makes me wonder where you both stand as profesisonal developers...

Granted you both have taught me or at least introduced a new idea to me, but still...

Your answer screams: "I'm frustrated cuz I want to argue with you and prove I'm right...but I can't..."

It's good of you to hold well known people in high regards (these brilliant people you refer to all the time) but I think it's time you start developing your own opinions...and ideals...learn from others mistakes or existing technologies and make them better...

Cheers :)
Post Reply