22.2功能设计和实现
22.2 功能设计和实现
本节开始实现用PHP发送邮件的程序。该程序的基本功能是,通过PHP程序,向SMTP发起请求,待SMTP服务器验证通过后,向PHP程序指定的邮箱发送邮件。使用mail函数也可以发送邮件,但其功能比较弱,不便于调试,而且在Windows下需要配置SMTP服务器,所以本章将通过Socket实现发送邮件的PHP程序。本节将设计一个发送邮件的PHP类,通过这个类完成向SMTP服务器发起请求以及发送邮件的功能。
22.2.1 设计一个发送邮件的类
有了22.1节的理论基础,实现发送邮件的类并不是一件困难的事。首先,确定该类应该有以下成员变量。
·$lastmessage:改变量记录服务器最后返回的响应信息。
·$lastact:最近一次要执行的操作,即是HELLO还是MAIL FROM请求等。
·$welcome:用在HELLO命令后面的字符串。
·$debug:是否显示调试信息。
·$smtp:smtp服务器。
·$port:smtp端口号。
·$fp:socket句柄。
需要实现的函数包括类的构造函数,显示调试信息的函数,处理Socket的函数以及发送邮件的函数。其中发送邮件的函数其实就是用来向Socket发送执行SMTP请求的命令。代码22-1是这个类的完整实现。
代码22-1 发送邮件的PHP类mail.php
01 <?php
02 class smtp_mail
03 {
04 var $lastmessage; //
记录最后返回的响应信息
05 var $lastact; //
最近一次要执行的操作
06 var $welcome; //
用在HELLO
命令后面的字符串
07 var $debug; //
是否显示调试信息
08 var $smtp; //smtp
服务器
09 var $port; //smtp
端口号
10 var $fp; //socket
句柄
11
12 function smtp_mail($smtp, $welcome="", $debug=false)
13 {
14 if(empty($smtp)) die("SMTP cann"t be NULL!");
15
16 $this->smtp=$smtp;
17 if(empty($welcome))
18 {
19 $this->welcome=gethostbyaddr("localhost");
20 }
21 else
22 $this->welcome=$welcome;
23 $this->debug=$debug;
24
25 $this->lastmessage="";
26 $this->lastact="";
27 $this->port="25";
28 }
29
30 function show_debug($message, $inout)
31 {
32 $b = false;
33 if ($this->debug)
34 {
35 if($inout=="in") //
响应信息
36 {
37 $m="<< ";
38 $b = true;
39 }
40 else //
请求指令
41 $m=">> ";
42 if(!ereg("\n$", $message))
43 $message .= "";
44 $message=nl2br($message);
45 if($b)
46 echo "<b>${m}${message}</b>";
47 else
48 echo "${m}${message}";
49 }
50 }
51
52 function do_command($command, $code) //
处理Socket
53 {
54 $this->lastact=$command;
55 $this->show_debug($this->lastact, "out");
56 fputs ( $this->fp, $this->lastact );
57
58 $this->lastmessage = fgets ( $this->fp, 512 );
59 $this->show_debug($this->lastmessage, "in");
60
61 if(!ereg("^$code", $this->lastmessage))
62 {
63 return false;
64 }
65 else
66 return true;
67 }
68
69 function send($from,$to,$subject,$message) //
发送邮件
70 {
71 $this->show_debug("Connect to SMTP server : ".$this->smtp."\n", "out");
72 $this->fp = fsockopen ( $this->smtp, $this->port );
73 if ( $this->fp )
74 {
75 set_socket_blocking( $this->fp, true );
76 $this->lastmessage=fgets($this->fp,512);
77 $this->show_debug($this->lastmessage, "in");
78
79 if (! ereg ( "^220", $this->lastmessage ) )
80 {
81 return false;
82 }
83 else
84 {
85 $this->lastact="HELLO " . $this->welcome . "\n";
86 if(!$this->do_command($this->lastact, "250"))
87 {
88 fclose($this->fp);
89 return false;
90 }
91
92 $this->lastact="MAIL FROM:<$from>". "\n";
93 if(!$this->do_command($this->lastact, "250"))
94 {
95 fclose($this->fp);
96 return false;
97 }
98
99 $this->lastact="RCPT TO:<$to>" . "\n";
100 if(!$this->do_command($this->lastact, "250"))
101 {
102 fclose($this->fp);
103 return false;
104 }
105
106 $this->lastact="DATA\n"; //
发送正文
107 if(!$this->do_command($this->lastact, "354"))
108 {
109 fclose($this->fp);
110 return false;
111 }
112
113 $head="Subject: $subject\n"; //
处理Subject
114 if(!empty($subject) && !ereg($head, $message))
115 {
116 $message = $head.$message;
117 }
118
119
120 $head="From: $from\n"; //
处理From
121 if(!empty($from) && !ereg($head, $message))
122 {
123 $message = $head.$message;
124 }
125
126 $head="To: $to\n"; //
处理To
127 if(!empty($to) && !ereg($head, $message))
128 {
129 $message = $head.$message;
130 }
131
132 if(!ereg("\n\.\n", $message)) //
加上结束串
133 $message .= "\n.\n";
134 $this->show_debug($message, "out");
135 fputs($this->fp, $message);
136
137 $this->lastact="QUIT\n";
138 if(!$this->do_command($this->lastact, "250"))
139 {
140 fclose($this->fp);
141 return false;
142 }
143 }
144 return true;
145 }
146 else
147 {
148 $this->show_debug("Connect failed!", "in");
149 return false;
150 }
151 }
152 }
153 ?>
【代码解析】构造函数smtp_mail()完成一些初始值的判定及设置。参数$welcome用于HELLO指令中,告诉服务器用户的名字或者直接填写服务器的名称,表示向服务器打招呼。如果用户没有给出$welcome,则自动查找本地的机器名,如代码第19行所示。
函数show_debug()用来显示调试信息,可以在参数$inout中指定是请求指令还是返回的响应。如果为请求指令,则使用“out”;如果为返回的响应,则使用“in”。代码第35行判断是否为响应信息,如果是,将信息的前面加上“<<”来区别响应指令,加上“>>”来区别请求指令。同时将响应信息的字符串字体加粗显示,如代码第46行和第48行所示。
函数do_command()用来处理Socket部分,通过Socket处理参数$command所表示的指令,对于HELLO、MAIL FROM、RCPT TO、QUIT和DATA指令,都要求根据是否显示调试信息将相关内容显示出来,并且根据参数$code所表示的服务器响应码,决定是否中断处理。代码第56行使用fputs向服务器发送请求指令。该函数一方面完成指令及信息的发送、显示功能,另一方面根据返回的响应判断是否成功。
函数send()用来发送邮件的函数,它共有4个参数,分别是:$from是发信邮件地址;$to是收信人邮件地址;$subject是邮件的主题;$message是邮件体,也就是邮件正文。如果处理成功,该函数返回true,否则返回false。其中的代码$this->fp=fsockopen($this->smtp,$this->port),作用是使用函数fsockopen()打开一个网络链接,其第一个参数是表示此socket链接的标识符,第2个参数标识在哪个端口打开此链接。
22.2.2 测试发送邮件功能
22.2.1小节实现了发送邮件的PHP类,本小节对这个类的功能加以测试。首先编写一个测试程序,代码22-2是一个测试程序。
代码22-2 测试smtp_mail类的PHP程序testmail.php
01 <?php
02 include "mail.php";
03 echo "<b>start....</b><br/>";
04
05 $email="Hello, this is a test mail!";
06 $sendmail=new smtp_mail("smtp.sina.com", "sina", true);//
实例化一个smtp_mail
类的对象
07 if($sendmail->send("macbooks@domain", " macbooks@domain", "test", $email))
//
发送邮件
08 {
09 echo "
发送成功!";
10 }
11 else
12 {
13 echo "
发送失败!";
14 }
15
16 echo "<br/><b>end....</b><br/>";
17 ?>
【代码解析】这是一个很简单的测试程序。代码第02行将smtp_mail类的定义包含进该程序,然后在第05行定义了一个字符串变量$email表示邮件的正文。在第06行实例化一个smtp_mail类的对象,第07行就是调用该对象的send()方法发送邮件了,其中该函数的发送地址参数和接收地址参数都为macbooks@sina.com,读者调试时,需要使用自己真实的地址。第05行初始化对象时,所使用的SMTP服务器地址smtp.sina.com也仅仅是个示例,读者可以根据实际情况替换成其他的SMTP服务器地址。设定好SMTP服务器地址、发件人地址和收件人地址,执行程序testmail.php,如果一切正常,会看到类似图22-1所示的结果;如果有错误,也可以看到相关的服务器响应信息和错误代码。
22.2.3 发送邮件的表单及程序实现
有了可以发送电子邮件的PHP类之后,最后制作一个发送邮件的简易界面和执行发送的PHP程序。
(1)发送电子邮件的用户界面使用HTML编写,可以参考代码22-3。
代码22-3 发送邮件的用户界面user.htm
01 <html>
02 <head>
03 <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
04 <title>
使用PHP
发送电子邮件</title>
05 <style type="text/css">
06 <!--
07 .STYLE1 {font-size: 12px}
08 .STYLE2 {
09 font-size: 24px;
10 font-weight: bold;
11 }
12 -->
13 </style>
14 </head>
15
16 <body>
17 <p class="STYLE2">
使用PHP
发电子邮件</p>
18 <form name="form1" method="post" action="send_mail.php">
19 <table width="444" height="347" border="0" >
20 <tr>
21 <td width="71" height="23" bgcolor="#D6B1E9">
22 <div class="STYLE1">
23 <div >
收件人</div>
24 </div>
25 </td>
26 <td width="363">
27 <label> <input type="text" name="sendto"></label>
28 </td>
29 </tr>
30 <tr>
31 <td height="27" bgcolor="#D6B1E9">
32 <div class="STYLE1">
33 <div >
邮件标题</div>
34 </div></td>
35 <td>
36 <label> <input type="text" name="subject"></label>
37 </td>
38 </tr>
39 <tr>
40 <td height="23" colspan="2" bgcolor="#D6B1E9">
41 <div class="STYLE1">
42 <div >
邮件正文</div>
43 </div>
44 </td>
45 </tr>
46 <tr>
47 <td colspan="2" bgcolor="#D6B1E9">
48 <div >
49 <label>
50 <div >
51 <textarea name="emailcontent" cols="60" rows="18"></textarea>
52 </div>
53 </label>
54 </div>
55 <div ></div>
56 <div ></div>
57 </td>
58 </tr>
59 <tr>
60 <td colspan="2">
61 <label><input type="submit" name="Submit" value="
提交"></label>
62 </td>
63 </tr>
64 </table>
65 </form>
66
67 </body>
68 </html>
【代码解析】 这是一个常见的邮件发送界面,包含收件人、标题和邮件内容,使用了<table>标签实现界面对齐。
(2)该HTML页面会将收件人、邮件标题等信息提交至程序send_mail.php做处理。该页面显示效果如图22-2所示。

图22-1 发送邮件测试程序的执行结果

图22-2 发送邮件的简单界面
(3)接下来完成发送电子邮件的PHP程序,该程序将使用22.2.1小节创建的smtp_mail类完成发送邮件的功能。这个程序并不复杂,只需对22.2.2小节编写的testmail.php稍做修改即可,如代码22-4所示。
代码22-4 发送邮件的程序send_mail.php
01 <?php
02 include "mail.php"; //
将指定的文件包含进来
03
04 if(isset($_POST["sendto"])) //
判断收信人
05 $sendto = $_POST["sendto"];
06 else
07 $sendto = "";
08
09 if(isset($_POST["subject"])) //
判断主题
10 $subject = $_POST["subject"];
11 else
12 $subject = "";
13
14 if(isset($_POST["emailcontent"])) //
判断邮件内容
15 $emailcontent = $_POST["emailcontent"];
16 else
17 $emailcontent = "";
18
19 if(empty($sendto) || empty($subject) || empty($emailcontent))
20 {
21 echo "<b>
请填写收件人email
地址、邮件主题和邮件正文。</b><br/>";
22 exit;
23 }
24
25 $sendmail=new smtp_mail("smtp.sina.com", "sina", false);
26 if($sendmail->send("macbooks@sina.com", $sendto, $subject, $emailcontent))//
发信
27 {
28 echo "
发送成功!";
29 }
30 else
31 {
32 echo "
发送失败!";
33 }
34 ?>
【代码解析】程序send_mail.php通过使用类smtp_mail完成邮件的发送。代码第26行发送邮件所使用的邮件地址是个例子,读者需要根据实际情况加以替换。
下一篇:22.3小结
