Une alternative à la fonction mail() pour de l'envoi en masse

Publié le 05 juillet 2009 par Methylbro

Pour commencer, notre classe devra pouvoir se connecter à un serveur SMTP, lui envoyer et recevoir des informations de sa part. Pour cela nous devront donc implémenter des méthodes open(), read(), write() et close(). Qui correspondront respectivement à l'ouverture d'un socket, la lecture et l'écriture d'informations sur ce dernier ainsi que sa fermeture.

Pour ce faire nous allons bien évidement avoir besoin de certaines informations sur le serveur SMTP auquel nous souhaitons nous connecter. Comme par exemple son adresse, le port à utiliser si ce dernier est différent du port par défaut. Ou, comme nous l'avons vu au sujet du protocole SMTP, le nom à utiliser lors de l'identification avec la commande EHLO.

Toutes ces informations seront stockées dans les propriétés $server_name, $server_port et $used_domain. Ces valeurs seront à fournir lors de l'instanciation d'un objet et serviront donc de paramètres au constructeur.

Nous devront également utiliser une méthode escape_string() pour formater les chaînes de caractères à envoyer au serveur SMTP ainsi qu'une propriété $socket_smtp contenant la ressource correspondant au flux de notre socket.

Je vais également utiliser des accesseurs pour modifier les propriétés $server_name, $server_port et $used_domain. Ne soyez donc pas étonnés de voir mes méthodes setServerName(), setServerPort() et setUsedDomain().

Enfin, dernière étape : proposer une méthode send() qui servira à l'utilisateur d'interface pour envoyer des emails via notre petite connexion sur le serveur SMTP.

Notre classe va donc prendre la forme suivante :

class Mail_SMTP {
const CRLF = "\r\n";
const DEFAULT_UDNM = 'unknown';
private $server_name;
private $server_port;
private $used_domain;
private $socket_smtp;
static public function escape_string($str) {}
private function setServerName($name) {}
private function setServerPort($port) {}
private function setUsedDomain($domain) {}
private function open() {}
private function read($length=1024) {}
private function write($data, $wait=true) {}
private function close() {}
public function __construct($name, $port=25, $udn=self::DEFAULT_UDNM) {}
public function __destruct() {}
public function send($to, $message, $from) {}
}

La méthode open()

Pour commencer nous allons devoir ouvrir notre connexion vers le serveur SMTP. Rappelez vous, sous windows (beurk, honte à vous !) nous utilisions telnet pour discuter avec notre serveur. En PHP, nous pouvons utiliser un socket pour échanger des informations avec une autre machine.

En PHP un socket est considéré comme un flux. Tout comme le sont par exemple les fichiers. Donc, pour manipuler ce flux nous allons utiliser les fonctions fgets(), fputs() et fclose(). Cependant à la différence des fichiers la ressource que nous allons manipuler - notre socket - devra être créer avec la fonction fsockopen() et non pas avec fopen() comme vous pouviez en avoir l'habitude avec les fichiers.

Une fois notre socket ouvert, nous pouvons envoyer au serveur SMTP la commande EHLO pour nous identifier.

private function open() {
$this->socket_smtp = fsockopen($this->server_name, $this->server_port);
$this->write('EHLO '.$this->used_domain);
}

La méthode close()

Comme je viens de le dire, un socket est un flux au même titre qu'un fichier en PHP. Donc, pour fermer notre ressource nous utiliserons la fonction close().

Mais avant de fermer la connexion, n'oublions pas de dire correctement au revoir à notre serveur SMTP. Pour cela, nous utiliserons la commande QUIT. Pour envoyer une commande à notre serveur SMTP, nous utilisons à nouveau la méthode write() que je vais développer un peu plus loin.

private function close() {
$this->write('QUIT');
fclose($this->socket_smtp);
}

La méthode read()

Cette méthode va nous permettre de lire les informations renvoyées par le serveur SMTP. Les messages de succès (250 OK par exemple) ou encore les messages d'erreurs.

Vous remarquerez d'ailleurs que pour simplifier mon exemple je ne prends pas en charge les messages d'erreurs que peut me retourner le serveur SMTP.

private function read($length=1024) {
return fgets($this->socket_smtp, $length);
}

La méthode write()

Maintenant nous allons voir comment envoyer nos commandes SMTP à notre serveur. Nous utiliserons pour cela cette méthode write().

Comme je l'ai expliqué plus haut, en PHP un socket se manipule comme un fichier. Car pour le langage sockets et fichier sont des flux. Pour écrire dans notre flux ; nous allons donc utiliser fputs().

Notre méthode write() accepte deux paramètres. Le premier, obligatoire contiendra les information à envoyer au serveur SMTP sous la forme d'une chaîne de caractères. Le second est un booléen facultatif qui permet de ne pas demander à notre méthode write() de lire le résultat retourné par le serveur SMTP.

Avant d'écrire dans le flux, je rajoute également un CRLF (Carriage Return Line Fine soit Retour de Chariot Fin de Ligne) à la fin de ma requête SMTP. Rappelez-vous ; c'est ce que nous faisions dans notre exemple sur le protocole SMTP.

Une fois l'écriture faite, je vais retourner le résultat envoyé par le serveur SMTP si notre booléen en paramètre est à vrai. Sinon je retourne true. Pour aller plus vite, j'utilise ici l'opérateur de comparaison ternaire de PHP.

private function write($data, $wait=true) {
$data.= self::CRLF;
fputs($this->socket_smtp, $data);
return ($wait) ? $this->read() : true;
}

Le constructeur

Lors de la création d'une nouvelle instance de notre classe, il faudra non seulement penser à spécifier l'adresse du serveur SMTP que nous souhaitons utiliser mais également à préciser le port à employer si celui-ci est différent du port par défaut ainsi que le nom à utiliser sur le serveur SMTP si ce dernier le requiert.

C'est pourquoi tous ces éléments sont à indiquer comme paramètres du constructeur de notre classe. Une fois tous ces éléments renseignés et passés comme paramètres de classe via nos accesseurs, nous pouvons tout de suite ouvrir le socket en faisant appel à la méthode open().

public function __construct($name, $port=25, $udn=self::DEFAULT_UDNM) {
$this->setServerName($name)->setServerPort($port)->setUsedDomain($udn)->open();
}

Le destructeur

Lors de la destruction d'une instance de notre classe, nous prendrons soin de bien clore la connexion au serveur SMTP. En effet cette dernière ne sera plus utile. Pour cela nous devons faire appel à notre méthode close() au sein du destructeur.

public function __destruct() {
$this->close();
}

La méthode send()

Maintenant que nous disposons des éléments techniques nécessaires pour dialoguer avec notre serveur SMTP, nous pouvons lui envoyer les informations nécessaires pour l'envoi d'un message.

La méthode send() devra accepter trois paramètres :

Pour commencer ; la liste des destinataires de notre message. Cette dernière doit être une liste d'adresse email stockée dans un tableau. Avec une adresse pour chaque élément du tableau. Dans notre exemple, peut importe la méthode utilisée pour indexé ce tableau. Nous l'appellerons $to.

Ensuite, nous devront bien évidement fournir le contenu du message à envoyer à chacun de ces destinataires. Il s'agit tout simplement d'une chaîne de caractères que nous appellerons $message.

Et enfin l'adresse email de l'émetteur de ce message que nous appellerons $from.

public function send($to, $message, $from) {
$this->write('MAIL FROM: <'.$from.'>');
foreach($to as $recipient) {
$this->write('RCPT TO: <'.$recipient.'>');
}
$this->write('DATA');
$this->write(self::escape_string($message), false);
$this->write('.');
$this->write('RSET');
return true;
}

Si vous avez bien suivi mon tutoriel précédant sur le protocole SMTP vous vous demanderez sans doutes à quoi peut bien servir la commande RSET.
Cette commande spécifie au serveur de messagerie que la transaction en cours va être interrompue. Tout envoyeur, receveur, et données de messagerie mémorisés seront alors éliminés. Cela permettra alors de réutiliser la même instance de notre classe pour un nouvel envoi avec des paramètres ($from, $to et $message) différents.

Exemple d'utilisation

Maintenant que notre classe est prête voyons comment l'utiliser dans un exemple simple. Nous allons pour commencer nous connecter à un serveur SMTP avant d'envoyer le message « Hello World » à deux adresses différentes :

$SMTP = new Mail_SMTP('smtp.orange.fr');
$to = array('mail_1@domain.tld', 'mail_2@domain.tld');
$message = 'Hello World !';
$from = 'mail_3@domain.tld';
$SMTP->send($to, $message, $from);

Conclusion

Bien évidement cette classe n'est qu'un exemple. Comme je l'ai abordé au cours de ce billet, bien des choses ne sont pas prises en compte dans cette classe. On aurait pu faire beaucoup mieux.

Le principe ici était de vous faire toucher du doigts les limites de la fonction mail() pour de l'emailing mais également de montrer sur un plan technique qu'il est facile de créer des clients en tout genre avec PHP pour peut que l'on maîtrise un minimum le protocole en question.