Jump to content
iwato

A Flexible PHPMailer Factory Class with Traits

Recommended Posts

BACKGROUND:  After a careful study of the use of traits I have come up with the following schema for PHPMailer factory class to cover a large variety of circumstances.

./global.init.php
<?php
	error_reporting(E_ALL ^ E_STRICT);
	ini_set('error_log', __DIR__ . '/error.log');
	
	include './php_mailer/PHPMailerAutoload.php';
	include './classes/class.phpmailer_factory.php';
	include './classes/trait.smtpserver_config.php';
	include './classes/trait.phpmailer_newsletter_config.php';

	include './classes/trait.phpmailer_verification_config.php':
	include './classes/trait.phpmailer_confirmation_config.php';	
?>

trait.smtpserver_config.php
<?php
	trait SmtpServerConfig {
		static $smtp_server = '';
		static $smtp_port = '';
	}
?>

trait.phpmailer_newsletter_config.php
<?php
	trait PHPMailerNewsletterConfig {
		use  SmtpServerConfig;

		private $email_account_name = '...';
		private $email_account_pswd = '...'}
				
		private $sender_addr = '...';
		private $sender_name = '...';
		private $replyto_addr = '...';
		private $replyto_name = '...';

		private $subject = '...';
		private $html_message = '...';
		private $alt_message = '...';
		
		public function set_letter_contents($subject, $html_message, $alt_message) {
			$this->Subject = $subject;
			$this->msgHTML($html_message);
			$this->AltBody = $alt_message;
		}
	}	
?>

class.phpmailer_factory.php
<?php
    class PHPMailerFactory extends PHPMailer {
    
    	error_reporting(E_ALL ^ E_STRICT);
		ini_set('error_log', __DIR__ . '/error.log');
		
		use SmtpServerConfig;
		private $use = '';
		
		if ($this->use = 'newsletter') {
			use PHPMailerNewsletterConfig;
		} else if ($this->use = 'verification') {
			use PHPMailerVerifyConfig;
		} else if ($this->use = 'confirmation') {
			use PHPMailerConfirmConfig;
		} else {
			die('Please designate an appropriate trait');
		}
		
		private $charset = '';
		
		private $smtp_debug = 0;
		private $smtp_output = 'html';
		private $smtp_auth = 'true';

    	
    	public function __construct($use, $username, $email, $charset='UTF-8', $debug=0) {
			parent::__construct()

			$this->use = $use;
			
			$this->addAddress($email, $username);
			$this->CharSet = $this->charset;

			$this->Host = self::hostserver;
			$this->Port = self::smpt_port;

			$this->isSMTP();

			$this->SMTPDebug = $this->smtp_debug;
			$this->Debugoutput = $this->smtp_output;
			$this->SMTPAuth = $this->smtp_auth;

			$this->Username = $this->email_account_name;
			$this->Password = $this->email_account_pswd;
			
			$this->setFrom($this->sender_addr, $this->sender_name);		
			$this->addReplyTo($this->replyto_addr, $this->replyto_name);

			$this->Subject = $this->subject;
			$this->msgHTML($this->html_message);
			$this->AltBody = $this->alt_message;
		}
		
		public function set_charset($charset) {
			$this->charset = $charset;
		}
		
		public function get_charset() {
			return $this->charset;
		}

	}
?>

Please comment on its efficacy.  Your criticism and praise are both welcome.

Roddy

Share this post


Link to post
Share on other sites

Does that code work when you run it?

Share this post


Link to post
Share on other sites

Certainly not without the required data.  For the moment I am only concerned about the overall concept. With the exception of my if-else statements I have adhered very closely to the manual's restrictions with regard to the use of traits.  There was no example for my use of the if-else statements.  This I invented on my own.

As far as I can tell, the trait is little more than a code-assisted cut-and-paste -- well, this is least how it has been interpreted by several PHP manual contributors.  It is not a precise analogy, because traits have class-like stand alone functionality, as well.

Roddy

Share this post


Link to post
Share on other sites

I've never used traits in that manner before, so I can't be sure if PHP lets these mistakes through, but I am expecting a parse error or syntax error due to there being logic directly inside the class definition. A class definition cannot have any if() statements, loops of any kind or function calls outside of the methods.

The thing with traits and all other object-oriented features is that they have to be clearly established before the code starts running. Before you begin writing your code you clearly define each of the classes and their relationships with other classes, interfaces and traits. You don't choose at runtime which trait your class is going to use, your class was either defined with a specific trait, or defined without the trait. Traits are also not necessary if only one single class is using them, just put the trait's features right into the class definition itself.

Share this post


Link to post
Share on other sites

OK.  So, I have verified what you stated, but I am not very happy with your remedy, for the whole purpose of my design was to eliminate having to create new variable names for each set of mailing uses, on the one hand, and not have to enter each and every value for each and every use as an argument of a constructor function on the other. 

What if I were to enter the values as elements of a unique array -- one for each use -- via the constructor function.  In this way I could still use the same variable names for all uses, but perform the logic within the constructor function.

How then would I access the arrays?  Could I include a path to each via the global.init.php file?  How would this work?

Roddy

Edited by iwato

Share this post


Link to post
Share on other sites

You should have one class that uses each trait, and your factory should figure out which one you want and return a new instance of that class.  They should be separate classes.

Share this post


Link to post
Share on other sites

I don't get it.  Please, if you would, give me one example as how to get from

	if ($this->use = 'newsletter') {
			use PHPMailerNewsletterConfig;
		} else if ($this->use = 'verification') {
			use PHPMailerVerifyConfig;
		} else if ($this->use = 'confirmation') {
			use PHPMailerConfirmConfig;
		} else {
			die('Please designate an appropriate trait');
		}
	}

to what it is that you are describing.

Roddy

Edited by iwato

Share this post


Link to post
Share on other sites

Make separate classes for each of those traits.  Right now you're trying to make one class use one of many traits.  Instead, define one class that uses each trait.  So, for example, make a PHPMailerNewsletter class which uses the PHPMailerNewsletterConfig trait, and other classes that each use one of the other traits.  Those classes should also extend the PHPMailer class.  Your factory class should not extend PHPMailer, the only purpose of the factory is to return an object of some other class.

A factory class would just have something like this:

<?php
class PHPMailerFactory 
{
    public static function create($type, $username, $email, $charset='UTF-8', $debug=0) {
        switch ($type) {
            case 'newsletter': 
                return new PHPMailerNewsletter($username, $email, $charset, $debug);
            case 'verification':
                return new PHPMailerVerification($username, $email, $charset, $debug);
            case 'confirmation':
                return new PHPMailerConfirmation($username, $email, $charset, $debug);
            default: 
                throw new Exception('Invalid PHPMailer type');
        }
    }
}

That's all a factory does, it creates other objects.  That's why it's called a factory, it wouldn't have any other methods other than what's needed to create objects.  You wouldn't extend the PHPMailer class for the factory, you extend it for the classes that it returns.  You would call it like this:

$obj = PHPMailerFactory::create('newsletter', $username, $email);

You should probably have one abstract class which extends PHPMailer and contains all of the common methods that each of those other classes will have, so you don't need to duplicate code.  That abstract class becomes your base class for all of this.  Then you define each of your other classes to extend the abstract class and implement whatever things are specific to each child class.  You probably don't need to use traits at all this way, but you could put some of the config settings in a trait and then have each child class extend the abstract class and use its own trait.  Then you have the factory to return the appropriate child class instance.

Share this post


Link to post
Share on other sites

So, why must the parent class be abstract?  For each abstract method that I put in the class I must duplicate that method in each of the child classes.  Where is the gain in this?

Roddy

Edited by iwato

Share this post


Link to post
Share on other sites

If that method is different in each child class, then that's how you do it.  Everything uses the same stuff except whatever needs to be different, which you would make abstract methods.  The goal is so that you don't need to duplicate the same code among classes, all of that goes in the parent class and then only the code that changes is in the child classes.

Share this post


Link to post
Share on other sites

OK.  I have tried to follow your factory design model as best as I could.  Does the following look like it might have a chance of successful execution?

Please pay careful attention to the use of the include and require_once commands, the $this variable,  visibility issues, class hierarchy, trait insertion, and the overall effective strategy.  And, please, if I have omitted anything or become redundant in some manner, bring it to my attention.

NOTES:

  1. The three dots indicate that information must be entered.
  2. The double hash marks represent omitted files or lines of code that will be added later.

The CODE

/*********************************************/
./global.init.php
<?php
	error_reporting(E_ALL ^ E_STRICT);
	ini_set('error_log', __DIR__ . '/error.log');
	
	include './php_mailer/PHPMailerAutoload.php';
	include './classes/trait.smtpserver_config.php';
	include './classes/php_mailer/phpmailer_abstract.php';
	include './classes/class.phpmailer_newsletter_config.php';

// 	include './classes/class.phpmailer_verification_config.php':
// 	include './classes/class.phpmailer_confirmation_config.php';	

	include './classes/class.phpmailer_factory.php';
?>

/*********************************************/
trait.smtpserver_config.php
<?php
	trait SmtpServerConfig {
		static $smtp_server = '';
		static $smtp_port = '';
	}
?>

/*********************************************/
class.phpmailer_abstract.php
<?php

    class PHPMailerAbstract extends PHPMailer {
    		
		use SmtpServerConfig;

		protected $charset = '';
		
		protected $smtp_debug = 0;
		protected $smtp_output = 'html';
		protected $smtp_auth = 'true';
   	
    	public function __construct($username, $email, $charset='UTF-8', $debug=0) {
			parent::__construct()
			
			$this->addAddress($email, $username);
			$this->CharSet = $this->charset;

			$this->Host = self::hostserver;
			$this->Port = self::smpt_port;

			$this->isSMTP();

			$this->SMTPDebug = $this->smtp_debug;
			$this->Debugoutput = $this->smtp_output;
			$this->SMTPAuth = $this->smtp_auth;

			$this->Username = $this->email_account_name;
			$this->Password = $this->email_account_pswd;
			
			$this->setFrom($this->sender_addr, $this->sender_name);		
			$this->addReplyTo($this->replyto_addr, $this->replyto_name);

			$this->Subject = $this->subject;
			$this->msgHTML($this->html_message);
			$this->AltBody = $this->alt_message;
		}
		
		public function set_charset($charset) {
			$this->charset = $charset;
		}
		
		public function get_charset() {
			return $this->charset;
		}
	}
?>

/*********************************************/
class.phpmailer_newsletter.php
<?php
	class PHPMailerNewsletter extends PHPMailerAbstract {
		use  SmtpServerConfig;

		private $email_account_name = '...';
		private $email_account_pswd = '...'}
				
		private $sender_addr = '...';
		private $sender_name = '...';
		private $replyto_addr = '...';
		private $replyto_name = '...';

		private $subject = '...';
		private $html_message = '...';
		private $alt_message = '...';
		
		public function set_letter_contents($subject, $html_message, $alt_message) {
			$this->Subject = $subject;
			$this->msgHTML($html_message);
			$this->AltBody = $alt_message;
		}
	}	
?>

/*********************************************/
class.phpmailer_factory.php';
<?php
	class PHPMailerFactory {
		public static function create($use_type, $username, $email, $charset='UTF-8', $debug=0) {
			switch ($use_type) {
				case 'newsletter': 
				return new PHPMailerNewsletter($username, $email, $charset, $debug);
// 				case 'verification':
// 				return new PHPMailerVerification($username, $email, $charset, $debug);
// 				case 'confirmation':
// 				return new PHPMailerConfirmation($username, $email, $charset, $debug);
				default: 
				throw new Exception('Invalid PHPMailer type.');
			}
		}
	}
?>

 SAMPLE IMPLEMENTATION

<?php
	$username = '...';
	$email = '...';
	$subject = '...';
	$html_message = '...';
	$alt_message = '...'; 

	require_once = global.init.php
	$newsletter = new PHPMailerFactory('newsletter', $username, $email);
	$newsletter->set_letter_contents($subject, $html_message, $alt_message); 
	$newsletter->send();
?>

Roddy

Share this post


Link to post
Share on other sites

Hi there Roddy,

I'm going to poke some holes in the implementation.

  1. require_once shouldn't have an `=` in it, semi-colon at the end.
  2. You're calling your PHPMailerFactory as if it had a constructor function, from your latest post it seems like it doesn't. Instead call it like:
     
    <?php
    $newsletter = PHPMailerFactory::create('newsletter', $username, $email);
    ?>

     

Share this post


Link to post
Share on other sites

Thank you, Funce.

Both of those were silly errors on my part.

I have been a little overwhelmed with this entire task, and if this is all the errors  that can be found, then I will be most elated.

Already you have earned a trophy.  Simply I have not added it yet out of fear that everyone will stop looking for more holes to poke. :-)

Roddy

  • Like 1

Share this post


Link to post
Share on other sites

Personally I feel like this whole thing is overcomplicated. Is there a reason why you can't instantiate an ordinary PHPMailer, set a subject and body and send the mail? It's about 5 lines of code.

Share this post


Link to post
Share on other sites
20 minutes ago, iwato said:

Thank you, Funce.

Both of those were silly errors on my part.

I have been a little overwhelmed with this entire task, and if this is all the errors  that can be found, then I will be most elated.

Already you have earned a trophy.  Simply I have not added it yet out of fear that everyone will stop looking for more holes to poke. 🙂

Roddy

No worries Roddy, I'm not too familiar with PHPMailer myself, (I went hunting viciously for what msgHTML() was) but from my cursory evaluation (without a PHP parser) it otherwise looks okay.

Share this post


Link to post
Share on other sites

Ingolme,  I hope to correspond with my visitors in a variety of ways, and I find the language of PHPMailer awkward and confusing.  I am trying to rewrite it in a way that I can understand quickly what is going on and make changes without a lot of research each and every time that I need to modify it.

If the above can eventually be made to work, then I am very comfortable with the new structure.

Where for example do you think the trait

use  SmtpServerConfig;

should be entered? I have entered it in two places, and it likely needs to be entered only once.

Roddy

 

Share this post


Link to post
Share on other sites

If you are going to include retrieving of input, validation, sanitization of those inputs and able to include a html layout tmplate that included those inputs which usually comes with sending email through a form etc that would be more useful. What you are doing seems to me is putting the PHPmailer class within another class that fills the required data required using parent class variables which you can do manually filling 5 lines.

Share this post


Link to post
Share on other sites
Quote

If you are going to include retrieving of input, validation, sanitization of those inputs and able to include a html layout tmplate that included those inputs which usually comes with sending email through a form etc that would be more useful.

 

This has already been achieved elsewhere, separately and differently, for each case.

 

Quote

What you are doing seems to me is putting the PHPmailer class within another class that fills the required data required using parent class variables which you can do manually filling 5 lines.

I am sure that the five lines of code of which you speak are included in the code that I have described above, else the code would not perform its most essential task.  These lines of code, however, are hardly the purpose of my having gone through the trouble of writing the code.

This said, do you find any errors in that what I have presented beyond those which Funce has already addressed?

Roddy

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×