PHP开发绝对不能违背的安全铁则

没有评论

2010 年 12 月 01 日 at 下午 11:44分类:WEB开发

作为PHP程序员,特别是新手,对于互联网的险恶总是知道的太少,对于外部的入侵有很多时候是素手无策的,他们根本不知道黑客是如何入侵的、提交入侵、上传漏洞、sql 注入、跨脚本攻击等等。作为最基本的防范你需要注意你的外部提交,做好第一面安全机制处理防火墙。

  规则 1:绝不要信任外部数据或输入

  关于Web应用程序安全性,必须认识到的第一件事是不应该信任外部数据。外部数据(outside data) 包括不是由程序员在PHP代码中直接输入的任何数据。在采取措施确保安全之前,来自任何其他来源(比如 GET 变量、表单 POST、数据库、配置文件、会话变量或Cookie)的任何数据都是不可信任的。

  例如,下面的数据元素可以被认为是安全的,因为它们是在PHP中设置的。

  清单 1. 安全无暇的代码

<?php
  $myUsername = ‘tmyer’;
  $arrayarrayUsers = array(‘tmyer’, ‘tom’, ‘tommy’);
  define(“GREETING”, ‘Hello there’ . $myUsername);
  ?>
 

  但是,下面的数据元素都是有瑕疵的。

  清单 2. 不安全、有瑕疵的代码

<?php
  $myUsername = $_POST['username']; //tainted!
  $arrayarrayUsers = array($myUsername, ‘tom’, ‘tommy’); //tainted!
  define(“GREETING”, ‘hello there’ . $myUsername); //tainted!
  ?>
 

  为什么第一个变量$myUsername 是有瑕疵的?因为它直接来自表单 POST。用户可以在这个输入域中输入任何字符串,包括用来清除文件或运行以前上传的文件的恶意命令。您可能会问,“难道不能使用只接受字母 A-Z 的客户端(Javascrīpt)表单检验脚本来避免这种危险吗?”是的,这总是一个有好处的步骤,但是正如在后面会看到的,任何人都可以将任何表单下载到自己的机器上,修改它,然后重新提交他们需要的任何内容。

  解决方案很简单:必须对$_POST['username'] 运行清理代码。如果不这么做,那么在使用$myUsername的任何其他时候(比如在数组或常量中),就可能污染这些对象。对用户输入进行清理的一个简单方法是,使用正则表达式来处理它。在这个示例中,只希望接受字母。将字符串限制为特定数量的字符,或者要求所有字母都是小写的,这可能也是个好主意。

  清单 3. 使用户输入变得安全

<?php
  $myUsername = cleanInput($_POST['username']); //clean!
  $arrayarrayUsers = array($myUsername, ‘tom’, ‘tommy’); //clean!
  define(“GREETING”, ‘hello there’ . $myUsername); //clean!
  function cleanInput($input){   $clean = strtolower($input);
  $clean = preg_replace(“/[^a-z]/”, “”, $clean);
  $clean = substr($clean,0,12);return $clean;
  }
  ?>
 

  规则 2:禁用那些使安全性难以实施的PHP设置

  已经知道了不能信任用户输入,还应该知道不应该信任机器上配置 PHP 的方式。例如,要确保禁用 register_globals。如果启用了 register_globals,就可能做一些粗心的事情,比如使用 $variable 替换同名的 GET 或 POST 字符串。通过禁用这个设置,PHP 强迫您在正确的名称空间中引用正确的变量。要使用来自表单 POST 的变量,应该引用 $_POST['variable']。这样就不会将这个特定变量误会成 cookie、会话或 GET 变量。

  规则 3:如果不能理解它,就不能保护它

  一些开发人员使用奇怪的语法,或者将语句组织得很紧凑,形成简短但是含义模糊的代码。这种方式可能效率高,但是如果您不理解代码正在做什么,那么就无法决定如何保护它。例如,您喜欢下面两段代码中的哪一段?

  清单 4. 使代码容易得到保护

<?php
  //obfuscated code
  $input = (isset($_POST['username']) ? $_POST['username']:”);
  //unobfuscated code
  $input = ”;
  if (isset($_POST['username'])){
  $input = $_POST['username'];
  }else{
  $input = ”;
  }
 

  在第二个比较清晰的代码段中,很容易看出 $input 是有瑕疵的,需要进行清理,然后才能安全地处理。

  规则 4:“纵深防御” 是新的法宝

  本教程将用示例来说明如何保护在线表单,同时在处理表单的 PHP 代码中采用必要的措施。同样,即使使用 PHP regex 来确保 GET 变量完全是数字的,仍然可以采取措施确保 SQL 查询使用转义的用户输入。纵深防御不只是一种好思想,它可以确保您不会陷入严重的麻烦。既然已经讨论了基本规则,现在就来研究第一种威胁:SQL 注入攻击。

  ◆防止SQL注入攻击

  在SQL注入攻击中,用户通过操纵表单或 GET 查询字符串,将信息添加到数据库查询中。例如,假设有一个简单的登录数据库。这个数据库中的每个记录都有一个用户名字段和一个密码字段。构建一个登录表单,让用户能够登录。

  <html>
  <head>
  <title>Login</title>
  </head>
  <body>
  <form action=”verify.php” method=”post”>
  <p><label for=’user’>Username</label>
  <input type=’text’ name=’user’ id=’user’/>
  </p> <p><label for=’pw’>Password</label>
  <input type=’password’ name=’pw’ id=’pw’/>
  </p> <p><input type=’submit’ value=’login’/></p>
  </form>
  </body>
  </html>
 

  这个表单接受用户输入的用户名和密码,并将用户输入提交给名为verify.php的文件。在这个文件中,PHP处理来自登录表单的数据,如下所示:

  清单 5. 不安全的 PHP 表单处理代码

  <?php
  $okay = 0;
  $username = $_POST['user'];
  $pw = $_POST['pw'];
  $sql = “select count(*) as ctr from users where username=’
  ”.$username.”‘ and password=’”. $pw.”‘ limit 1″;
  $result = MySQL_query($sql);
  while ($data = mysql_fetch_object($result)){
  if ($data->ctr == 1){
  //they’re okay to enter The application!
  $okay = 1;
  }
  }
  if ($okay){
  $_SESSION['loginokay'] = true;
  header(“index.php”);
  }else{
  header(“login.php”);
  }
  ?>
 

  这段代码看起来没问题,对吗?世界各地成百(甚至成千)的 PHP/MySQL 站点都在使用这样的代码。它错在哪里?好,记住 “不能信任用户输入”。这里没有对来自用户的任何信息进行转义,因此使应用程序容易受到攻击。具体来说,可能会出现任何类型的SQL注入攻击。例如,如果用户输入 foo 作为用户名,输入 ‘ or ’1′=’1 作为密码,那么实际上会将以下字符串传递给 PHP,然后将查询传递给 MySQL:

  <?php
  $sql = “select count(*) as ctr from users where username=
  ’foo’ and password=” or ’1′=’1′ limit 1″;
  ?>
 

  这个查询总是返回计数值 1,因此 PHP 会允许进行访问。通过在密码字符串的末尾注入某些恶意 SQL,黑客就能装扮成合法的用户。解决这个问题的办法是,将 PHP 的内置 mysql_real_escape_string() 函数用作任何用户输入的包装器。这个函数对字符串中的字符进行转义,使字符串不可能传递撇号等特殊字符并让 MySQL 根据特殊字符进行操作。清单7展示了带转义处理的代码。

  清单7展示了带转义处理的代码

  <?php
  $okay = 0;
  $username = $_POST['user'];
  $pw = $_POST['pw'];
  $sql = "select count(*) as ctr from users where username='".mysql_real_
  _string($username)."' and password='". mysql_real_escape_string($pw)."'
  limit 1";
  $result = mysql_query($sql);
  while ($data = mysql_fetch_object($result)){
  if ($data->ctr == 1){          //they're okay to enter the
  application!
  $okay = 1;
  }
  }
  if ($okay){
  $_SESSION['loginokay'] = true;
  header("index.php");
  }
  else{
  header("login.php");
  }
  ?>
 

  使用 mysql_real_escape_string() 作为用户输入的包装器,就可以避免用户输入中的任何恶意 SQL 注入。如果用户尝试通过 SQL 注入传递畸形的密码,那么会将以下查询传递给数据库:

  select count(*) as ctr from users where username=’foo’ and password=
  ’\’ or \’1\’=\’1′ limit 1″

简述MVC思想与PHP如何实现MVC

没有评论

2010 年 12 月 01 日 at 下午 11:37分类:WEB开发

简单的说就是将网站源码分类、分层。
MVC三个字母的含义:
M:Model 模型,负责数据库操作。
V:View 视图,负责调用Model调取数据,再调用模板,展示出最终效果。
C:Controller 控制器,程序的入口,决定改调用哪个View,并告诉View该做什么。
如此说来,程序的执行顺序是C-V-M 或 C-M ,和MVC的名字正好相反。
为什么要MVC?
1.能使网站程序物理结构更合理。
当用PHP建设一个网站的时候,最笨的方法,你可能把每个页面建成一个PHP文件。如果你的网站只有index.php,menu.php.article.php 三个页面,那你可以不用MVC,但我们做一般的网站的时候,动辄几十个页面,把所有页面放在根目录显然不是我们所能接受的,于是你需要一个合理的思想去将你的代码分类,按功能把他们分成不同的目录,且由程序智能的载入调用,这就是MVC要帮助你做的。
2.使代码更容易维护。
我们再来看单个页面,最笨的方法,就是PHP代码与HTML代码混合,这显然不够好,你在维护网站的时候不得不区分哪里是PHP,哪里是HTML,这对于一个程序员来说,简直只灾难。于是很多人就使用Smarty,这样就可以将“数据处理”与“页面展示”分开来,这样做的确不错,也有很多人正在这么做,但这还不是MVC,MVC要做的就是将“数据处理”再分为“逻辑处理”与“数据库操作”,这就是所说的分层。
这样当你的程序错误或想要修改的时候,就变得很轻松了,当页面显示错误的时候,你就去检查V或模板文件;当逻辑有问题的时候,你就去检查C和V;当你数据库操作错误就去检查M。
其实MVC一般要把PHP的一个页面分割为4个页面,分别是C,V,M,模板。各司其职,方便管理。
3.有利于代码复用。
MVC会把一般会把一个大的功能放在一个目录下,也就是由一个C去管理。
例如要做一个含有会员系统的网站,我们就可以把会员相关的代码都放到user目录里,由User_Controller统一管理,当我们另一个网站也需要会员系统的时候,我们就可以直接把这个目录复制过去,修改一下接口就可以了。
PHP实现MVC的思路
我们需要三个基类:Controller、View、Model ,然后不同的C、V、M分别继承他们就有对应的属性与方法了,如果这里你不理解,可以去看看面向对象的书。
我给大家提供一种MVC基类的设计思路,仅供参考:
1. Controller类的设计
一个main()方法,供程序调用,主要是通过get和post变量决定该如何处理。
一个getModel($model)方法,在需要调用数据库的时候,调用对应目录的M。
一个display($view)方法,在main()方法中调用,载入对应的V,并掉应V的main()方法;
2.View类的设计与Controller很相似
一个main()方法,当C载入V的时候调用这个方法,使程序能继续执行下去。
一个getModel($model)方法,在需要调用数据库的时候,调用对应目录的M。
一个display($template),调用对应的模板文件,并把数据传递给模板。
3.Model类的设计
可以定义一些属性,例如要操作那些表,操作那些字段等。
一个getDB()方法,获得一个数据库类的实例,(数据库类一般都是用单件模式设计的)
一个load()方法,载入一个数据。
一个add()方法,可以根据定义好的属性自动构造SQL语句,并执行插入操作。
一个eidt()方法,同上,但执行修改操作。
一个del()方法,同上,但执行删除操作。
为了能使新手更好的理解我这个思路的工作原理,我们现在模拟一个用户登录的场景,看看MVC是如何工作的。
现在假设,所有的数据都提交给index.php,
第一步:
我们提交各get变量,告诉index.php该用哪个C,例如可以这样index.php?controller=user
然后index接收到get变量,什么也不需要做,直接找到/user/controller.php,把所有数据丢给他,本来GET和POST就是全局的,所以index.php也不需要做什么,直接调用C的main函数就可以了,到此index.php的任务完成。
第二步:
C的main函数开始执行,检查变量,发现用户要执行的登录操作(很简单,你post个变量do=login就可以了),于是调用getModel,载入对应的M类(例如/user/models/model.php),并实例化, 调用实例的load方法,载入该用户的数据资料,判断是否与用户提交的密码一致,如果提交的数据不正确header跳转到出错页面,如果正确,调用display()方法,载入对应的V(例如/user/views/details.php),并实例化,调用其main()函数,进入第三步。到此C的任务已完成,第二不操作均在main函数中进行。
第三步:
你可以选择调用getModel()载入M,重写调取数据,也可以在C实例化V的时候,把参数传过来(例如SESSION),当V已经确定得到数据以后,display(),载入模板,MVC执行完毕。
当然,由于字数与精力限制,这里写的只是非常简要的概括,实际实施的时候要考虑很多细节,但我设计MVC的时候,大概思路就是这样,也用到了实际中,感觉良好。

PHP发邮件函数 sendmail() ,附源码下载!

没有评论

2010 年 12 月 01 日 at 下午 11:32分类:PHP | WEB开发

不需要邮件服务器,不使用mail内置函数,一个类就搞定,利用PHPMailer类我写了一个自定义函数 sendmail() ,VERY实用!
以前也在几个PHP论坛上发表过这个发邮件的函数,今天再发,因为today要附上使用例子,如果你还不会用,那就要补补PHP基础课了。
1.下载相关文件sendmail.rar,包含 index.php文件、PHPMailer类库文件夹 下载
2.然后解压 sendmail.rar 到服务器的任何目录下
3.打开 index.php ,如下(修改浅黄色标记部分,表单部分随便。)

<?php
include_once "phpmailer/class.phpmailer.php"; 
function sendmail($to,$subject,$content) {
 $mail = new PHPMailer();
 // 以下设置 - 不要更改!
 $mail->IsSMTP();
 $mail->SMTPAuth = true;
 $mail->IsHTML(true);
 $mail->CharSet ="GB2312";   
 $mail->Encoding = "base64";
 // 以下设置 - 不要更改!    
 $mail->AddAddress($to, ""); 
 $mail->Subject = $subject;   
 $mail->Body    = $content;    
 // 以下 5 个变量值必须据实修改
 $host     = '61.183.41.172';
 $username = 'admin@php95.com';
 $password = '******';         
 $from     = 'admin@php95.com';   
 $fromname = '天马博客';   
 //$mail->AddReplyTo("", "");      
 //$mail->AddAttachment("ok.jpg"); //附件 
 // 以下设置 - 不要更改!   
 $mail->Host     = $host;                               
 $mail->Username = $username;     
 $mail->Password = $password;                
 $mail->From     = $from;           
 $mail->FromName = $fromname;                   
 // 发送并返回相应信息
 if(!$mail->Send()){ return 0; exit(); }
 return 1;
}
?>
<form action="" method="post" name="f_sendmail" id="f_sendmail">
<input name="t_from" type="text" disabled="disabled" id="t_from" value="admin@php95.com" />
发件人
<p>
  <input name="t_to" type="text" id="t_to" />
收件人<p><input name="b_sendmail" type="submit" id="b_sendmail" value=" 发 送 " />
</form>
<?php
/**先设置 $to $subject $content 这三个变量的值
   再调用 sendmail 函数来发送邮件
**/
if($_POST[b_sendmail]){
 $to = htmlspecialchars(addslashes($_POST[t_to]));
 $subject = '天马博客:一封测试邮件↖(^ω^)↗';
 $content = '恭喜你,你已看到了PHP发送邮件的效果。 -- <a href="http://www.php95.com" target="_blank">天马博客</a>';
 if(sendmail($to,$subject,$content)) {
  echo "发送到 $to 的邮件已成功!";
 }else{
  echo '发送失败!';
 } 
}
?>

4.最后一步,运行index.php
你的邮件是否发送成功?
天马测试本代码,IS OK,
贴上测试地址:http://www.php95.com/test/sendmail/

一个程序员,你的代码为谁写

没有评论

2010 年 12 月 01 日 at 下午 11:27分类:乱七八糟

几周前,布莱斯在网上发帖,漫谈自己对编程工作的看法。在Reddit上引起了广泛讨论。讨论的焦点集中在程序员的等级——“优秀”、“良好”、“糟 糕”和“极差”。我发现,讨论中一些用语十分不妥。“好”与“坏”都是道德评价,评价之后似乎便给人贴上了永久不变的标签。

可以肯定的说,我曾被另一个程序员称作是 “极差”的程序员。我也承认,我确实写过一些极差的代码;但我也自认为曾写过相当多的“好”代码。

要评判很久以前写出的代码是优是劣很不容易,因为现在已经不知道当时为什么编写这些代码,也不知道为谁编写了这些代码。

问问自己,现在正为谁编写代码?

为了按时交付任务

也许最常见的原因就是为了按时交付任务。走走捷径,宁可复制粘贴删掉几行代码也不愿意重构代码,然后匆匆交工。我们都这么做过,也都知道这是不妥的。

为了突出的考核结果 [至尊版]

当管理者本身不懂代码,却有一套程序员“好坏”评价标准时,会出现什么情况?程序员要理清这套标准并不困难,因为他们的特长就是解决难题,然后他们会努力完善自己,从而迎合评价标准。代码行数、已解决Bug数量、注释的密度、代码深度等都可能是衡量编码人员的指标,但这些又都是相对标准,而不是绝对标准。也有些新颖的衡量手段(比如“已删除代码的行数”)。

为计算机编写

从某种意义上来说,所有的程序都是为计算机编写的,但计算机应当程序员最后才考虑的。计算机只注重语法,不注重注释和变量名称。大多数程序语言也不注 重间 距与代码格式化。当然,你还是要选择正确的算法,但不要想着通过微小的优化来加速算法。在for循环中,使用i++还是++i并不重要,编译器和JITs 会解决这些问题。在考虑优化算法之前,还是应该先把代码写的清晰易懂。要知道编码在使用通用模式时,计算机和编译器运行的更快。

为了自己

虽然学习一门新的程序语言很有趣,不过如果你将整个公司架构都建立兴趣之上是不切实际的。Hacker News上曾有一则相关故事,Lambda the Ultimate网站上还有更糟糕的案例。如果你是为自己写代码,你可以不加注释,可以随意使用糟糕的变量名,甚至使用其他“怪癖”,但这样写出来的怪异 代码别人很难看明白。不过没关系,因为每个人都会时不时想在某些事上找点漏洞出来。

为后来者编程

编程是把抽象观念转换成计算机可以理解的形式。即使是细微的抽象观念,转换成代码也是很不简单。因此很多软件项目都衍生出了成千上万甚至是上百万行的代码,相当于一本代码书。通过有限的语法与其他人交流这些概念,大多数时候都注定失败。

我所写的最出色代码就是我愿意花时间来添加注释、列出代码流、甚至附上一些ASCII文字图的代码。编写过程专注于如何把自己抽象概念,与今后将有可 能读到这些程序的、不幸的程序员进行传递和交流。我认为专注于这种交流,代码会变得越来越好,因为你会更深入地思考抽象概念以及如何对正在做的事情分层, 而不是一味的编写代码和转到下一个程序块。

注释使代码变得更好理解。每当你再次做某事的时候,总会比上一次更好。当你在编写代码和注释时,就是将抽象概念向读者解释了两遍。这会迫使你思考更 多。很多次我写完一个代码以后都会对它写一个注释。然后从头修订代码,甚至改变了一些小地方,例如选择更好的变量名称,来更好的交流想法。

评价代码/程序员

综合前文所述,可以看出,编程人员孰优孰劣确实难以定夺。因为难以明确他们编写代码目的。你可以考评代码,但你无法得知代码编写者当时的心理状况。或 许那天是星期五,他急着要赶去维加斯度周末;也许是程序出了问题,他不得不采取紧急补救措施,但这些补救措施一用就是5年;也可能他原本就是个不合格的程 序员。

也许编程真是一门艺术?

我不知道如何公正地考核编程人员,我想也没几个公司能做到。看看程序员的面试流程就清楚了,他们只不过坐在桌前被问几个问题而已;根本没有什么标准测试能让计算机科学专业的学生证明自己已经掌握了必要的技能。

编程工作带有太多艺术色彩,所以不可能通过测试手段或者固定的考核标准来评价。

你知道还有哪个领域也是通过视觉媒介将抽象的概念传达给其他人?美术和绘画作品。今天,我们或许会说梵高是个大人物(其画作闻名于世),但是仍然有人不喜欢他的作品。类似表达抽象概念的事物不应该用“好”或“坏”来评价。

程序员可以做到的就是时刻提醒自己,编程的目的要正确。不能仅仅要求编译器能识别就行,不能为了迎合某种考核标准,也不能为了按时交工而编程。相反,应该适时注解或写文档,解释或记录代码功能。只要用心,你就能编写出优秀代码。如此一来,以后就会有人夸你是个优秀程序员,而不会因你那一万行的代码文件而“咒骂”你是“极品”程序员。欢迎在评论或微博中分享你的观点。

StBlog中的类似TP的标签库功能的小插件

没有评论

2010 年 12 月 01 日 at 下午 10:47分类:PHP | WEB开发

在一般博客的程序中基本的样式好像就是一边是内容板块,一边是什么分类的板块!像这样子的样式的话,我们在切换整个页面的时候其实变换的是内容板块的内容,而另一边的分类板块是不变的!这样子就要求我们在写程序的时候要注意点了,要使用恰当的方法了!之前在TP中我用的是TP的标签库功能实现了这么一个功能,现在在CI 中,今天看到了StBlog实现的方法。过程如下:
首先我们打开右边板块的试图文件,一下是部分代码:

<h5>日志分类</h5>
 <ul>
     <?php $this->plugin->trigger('Widget::Categories', '<li><a href="{permalink}" title="{description}">{title} [{count}]</a></li>');?>
 </ul>
    
    <h5>最新日志</h5>
 <ul class="post_list">
        <?php $this->plugin->trigger('Widget::Posts::Recent', '<li><a href="{permalink}" title="{title}">{title}</a></li>');?>
 </ul>
    
    <h5>最新评论</h5>
 <ul class="recent_comments">
     <?php $this->plugin->trigger('Widget::Comments::Recent', '<li><a href="{permalink}" title="{parent_post_desc}">{title}: </a><p>{content}</p></li>', 50, '...');?>
 </ul>
 
 <h5>日志归档</h5>
 <ul>
     <?php $this->plugin->trigger('Widget::Posts::Archive', '<li><a href="{permalink}">{title} [{count}]</a></li>', 'month', 'Y年m月');?>
 </ul>

看着这些代码,我们能够直接看懂的好像就是

$this->plugin->trigger()

很明显这是调用了plugin这么一个类文件,我们先找到这个类文件。
其实一看这个类就知道了,一旦这个被加载,是要执行很多操作的,我们先来看构造函数:

public function __construct()
    {
        /** 获取CI句柄 **/
		$this->_CI = & get_instance();
		
		$plugins = $this->_CI->utility->get_active_plugins();
		//print_r($plugins);
		if($plugins && is_array($plugins))
		{
			foreach($plugins as $plugin)
			{
				$plugin_dir = $plugin['directory'] . '/' . ucfirst($plugin['directory']) . '.php';
				
				$path = FCPATH . ST_PLUGINS_DIR . '/' . $plugin_dir;
				
				/** 仅能识别"插件目录/插件/插件.php"目录下的插件 */
				if (preg_match("/^[\w\-\/]+\.php$/", $plugin_dir) && file_exists($path))
				{	
					//加载每一个小功能插件
					include_once($path);

					$class = ucfirst($plugin['directory']);
					
					if (class_exists($class)) 
					{
						/** 初始化插件 */
						//初始化时要调用相应类文件的初始函数(也就是条用了本文件的register函数)
						new $class($this);//传递当前对象
					}
				}
			}
		}

先来分析这个构造函数,

$plugins = $this->_CI->utility->get_active_plugins();

这句话的功能是:从数据库中读取激活的插件名称,因为所有的插件是否激活都是通过后台写进数据库的!通过这个函数可以获取数据库中所有已经激活的插件,至于这个函数 可以去看看utility类文件里面是怎么处理的!
获取的变量$plugins是一个二维数组!接下来遍历这个二维数组,通过索引directory获取的插件的名称,然后再生成相关的路径和文件名,此后在通过PHP的系统函数include_once()来加载这些插件,这些插件放在系统根目录中的st_plugins文件夹下面。其实这个目录路径是可以更改的!这个目录下面每一个文件夹里面就是一个插件,文件夹的名字就是插件的名字!加载完插件之后,就执行了

new $class($this);//传递当前对象

初始化插件!这个初始化是在循环里面,因此只要是在数据库中被激活的插件在这里都会被初始化操作!其实所有的插件的初始化函数都是一样的,只是插件的钩子(即register函数的第一个参数)不一样罢了,代码如下:

public function __construct(&$plugin)
	{
		$plugin->register('Widget::Comments::Recent', $this, 'show_recent_comments');
		
		$this->_CI = &get_instance();
	}

看到该构造函数里面的变量了吗?其实他是个对象!其实这种写法和下面的这种写法是等效的:

$ci = &get_instance();
		$ci->load->libarary('plugin');
		$ci->plugin->register('Widget::Posts::Recent', $this, 'show_recent_posts');

还要注意的一点就是其中的第二个参数和滴三个参数,第二个参数是当前类的一个对象,第三个参数是当前类中的一个方法,这个方法在后面的操作中会被自动的调用!
这里调用了plugin类中的register方法,我们在返回找到这个方法:

[]public function register($hook, &$reference, $method)
	{
		//通过对象获取类的名称
		$key = get_class($reference).'->'.$method;		//返回一个字符串如:Related_posts->show_related_posts
		//$reference为对应的对象,$method为该对象内的一个方法
		$this->_listeners[$hook][$key] = array(&$reference, $method);
		
		log_message('debug', "$hook Registered: $key");
	}

这个方法的功能是将要使用的插件放在一个指定索引和格式的数组中($this->_listeners[$hook][$key]);
上面讲的就是在系统初始化的时候执行的
接下来我们看视图文件,它是调用了plugin类中trigger方法:

public function trigger($hook)
	{
		$result = '';
		//判断钩子是否存在
		if($this->check_hook_exist($hook))
		{
			foreach ($this->_listeners[$hook] as $listener)
			{
				$class  = & $listener[0];		//对象名
				$method = $listener[1];			//方法名
				
				if(method_exists($class, $method))
				{
					$args = array_slice(func_get_args(), 1);//取从第一个开始(除第一个)到最后一个之间的变量
					//调用用户自定义的方法/函数
					//print_r($args);
					$result = call_user_func_array(array($class, $method), $args);
				}
			}
		}
		
		log_message('debug', "Hook Triggerred: $hook");
		
		return $result;
	}

	/**
	 * 检查钩子是否存在
	 * @param string $hook 钩子的名称
	 * @return array
	 */
	public function check_hook_exist($hook)
	{
		if(isset($this->_listeners[$hook]) && is_array($this->_listeners[$hook]) && count($this->_listeners[$hook]) > 0)
		{
			return TRUE;
		}
		
		return FALSE;
	}

这个函数是在加载页面的时候被执行的。其他的就不用多说了,我们只要来看看循环里面的判断部分:
首先判断对象中是否存在该方法 这是肯定的 因为这两个都是从插件类文件中传过来的。之后的获取参数并处理参数部分,
通过程序来看我们只需要获取除了第一个参数以外的全部参数。这是为什么呢?因为第一个参数是插件的钩子,没有任何意义的!因此$args变量是一个数组!我们在通过call_user_func_array()函数来使插件类的对象调用指定的方法(show_recent_posts()),
这个方法在每一个插件中都有,是用来处理数据,我们哪一个插件来说明:
如显示最新日志的:

public function show_recent_posts($format)
	{
		//变量$format是一个html的字符串,其中的一些需要更改的值用{}扩起来了
		/** 输出格式为空?*/
		if(empty($format)) return;
		
		/** 输出多少条? */
		$list_size = setting_item('posts_list_size');
		$list_size = ($list_size && is_numeric($list_size)) ? intval($list_size) : 10;
		
		$posts = $this->_CI->stcache->get('Widget::Posts::Recent');	//判断是否有相应的缓存数据
		
		if(FALSE == $posts)
		{
			$posts = $posts = $this->_CI->db->select('slug, title')
				 ->from('posts')
        		 ->where('type', 'post')
        		 ->where('status', 'publish')
        		 ->orderby('created', 'DESC')
        		 ->limit($list_size)
        		 ->offset(0)
        		 ->get()
        		 ->result();
        
        	$this->_CI->stcache->set('Widget::Posts::Recent', $posts);	//放入缓存
		}
				
		if($posts)
		{
			foreach($posts as $post)
			{
				$wildcards = array('{permalink}', '{title}');
				
				$replaces = array(site_url('posts/'. $post->slug), $post->title);
				
				echo str_replace($wildcards, $replaces, $format) . "\r\n";   //将指定的字符进行替换成我们需要的数据
			}	
		
		}		
		
	}

通过这样子调用,到最后echo一下 就能把数据库中相应的数据输出到我们调用plugin中的trigger方法的地方

($this->plugin->trigger())

!而且不管在哪个页面都是可以直接调用的

STBLOG学习笔记3

没有评论

2010 年 12 月 01 日 at 下午 3:41分类:PHP | WEB开发

在StBlog中的控制器文件comment.php中用到一个函数go_back();我们在扩展的辅助函数文件里面找到了这个函数代码如下:

/**
 * 返回来路
 *
 * @access public
 * @param string $anchor 附加地址
 * @param string $default 默认来路
 * @return void
 */
function go_back($suffix = NULL, $default = NULL)
	{
	    //获取来源
	    $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
	
	    //判断来源
	    if (!empty($referer)) 
	    {
	        // 来自Typecho
	        if (!empty($suffix)) 
	        {
	            $parts = parse_url($referer);    //解析URL
	            $myParts = parse_url($suffix);
	            
	            if (isset($myParts['fragment'])) 
	            {
	                $parts['fragment'] = $myParts['fragment'];
	            }
	            
	            if (isset($myParts['query'])) 
	            {
	                $args = array();
	                if (isset($parts['query'])) 
	                {
	                    parse_str($parts['query'], $args);
	                }
	            
	                parse_str($myParts['query'], $currentArgs);
	                $args = array_merge($args, $currentArgs);
	                $parts['query'] = http_build_query($args);
	            }
	            
	            $referer = build_url($parts);   //建立一个新的URL
	        }
	        
	        redirect($referer);        //跳转到指定的路径
	    } 
	    else if (!empty($default)) 
	    {
	        redirect($default);         // 跳转到原来的路径
	    }
	}

这个函数主要防止那些外部注入操作的,也就是所谓的外链传输数据的!
通过这个函数,可以防止非本站提交的数据!以提高本站的安全性!

STBLOG学习笔记2

没有评论

2010 年 12 月 01 日 at 下午 3:14分类:PHP | WEB开发

之前讲了STBLOG的模型和一些基本的东西!今天来看看STBLOG的控制器里面的操作方法!
在配置文件的路由配置文件中设置了默认的控制器为home,我们在controller里面找到home.php文件,纵观整个页面发现人家的代码写的很规范!
首先也许是为了安全考虑吧?除了index方法之外,所有的内部处理数据的和类内部的操作方法名称的前面都加了一个下划线“_”,这样子做的目的是:在CI中如果方法名前面有一个下划线的话,那么该方法不能同过URL来访问的!只能 通过内部方法的调用!也就是将方法私有化!其实我们在一些类中我们有些代码是公用的,如:分页的代码,我们可以把分页的呆经过处理成一个方法!然后在这个方法的最后返回给一个OOP的对象!,我们在要使用分页的时候只要调用这个似有方法就行了!
关于变量,我们之前在类中写变量,都是喜欢像面向过程一样的使用,如:[code[$var,$this;[/code]这样子!,其实这样子写很不好,具体怎么不好,我也不是很清楚!我们在面向对象中使用的变量尽量在类的初始化之前先定义一下!然后之后要调用的时候,直接只用面向对象的方式来调用!如:

$this->var = XXX,$this->bin = XXX;

,在StBlog中,所有的类文件中的变量都是使用面向对象的方式来使用的!这点非常的好!
不管是在控制器中还是在模型中我们对所有的变量都需要类型的强制操作!如整型的我们要用

$month = intval($month);

,数据在传输的过程中使用的字符串类型的数据!我们在使用的时候必须对数据进行强制转换,这也是考虑到安全吧!
为了使模型文件看起来比较简单点!我们可以在我们的控制器中我们模型中方法,而且还可以使用级联的写法!如以下:

$this->_posts = $this->posts_mdl->get_posts_by_author($uid, 'post', 'publish', $this->_limit, $this->_offset)->result();
$this->_total_count = $this->posts_mdl->get_posts_by_author($uid, 'post', 'publish', 10000, 0)->num_rows();

这样子我们模型中的方法get_posts_by_author()只要用

$this->db->get('XXX');

就行了,当然前面肯定有很多的查询条件

stblog学习笔记1

没有评论

2010 年 12 月 01 日 at 下午 1:23分类:PHP | WEB开发

最近在学习CI 的开源产品StBlog,初次学习感觉这个产品写的很不错 对于我这样子的一个CI 新手来说是一个很不错的学习机会。在大致的理解了一下整个的博客系统的架构之后,感觉这个里面有好多东西都是我在之前一段时间里面学习CI没有接触到的东西,就比如说自定义模版的路径,这样子以来 使得整个网站有了一个新的功能 那就是可以选择主题和皮肤了!主要的实现过程就是自己在项目的类库文件夹中首先扩展Loader.php文件(class MY_Loader extends CI_Loader),然后在这个类里面定义两个方法(一个是项目的前台的重写路径,一个是后台的重写路径,后台的路径可以说不变还是原来的那个),该方法的功能是用来重写视图的路径的,详见代码:

 public function __construct() 
    {
        parent::CI_Loader();
    }

	 /**
	 * 打开皮肤功能
	 * 
	 * @access public
	 * @return void
	 */ 
    public function switch_theme_on()
    {
    	$this-&gt;_ci_view_path = FCPATH . ST_THEMES_DIR . DIRECTORY_SEPARATOR . $this-&gt;theme . DIRECTORY_SEPARATOR;
    }

	 /**
	 * 关闭皮肤功能
	 * 
	 * @access public
	 * @return void
	 */ 
    public function switch_theme_off()
    {
    	//just do nothing
    }

接着我们要扩展系统的控制器类(class QT_Controller extends Controller和class HT_Controller extends Controller),然后继承系统的控制器类,在这个类文件中实际上是有两个类的 一个是前台的(QT_Controller),只要是控制前台要操作的一些操作的,然后在之后的写前台的操作代码是只要继承这个类(QT_Controller)就可以了,别忘了在这个类中写上

/** 前台页面均使用主题皮肤功能 */
	    $this-&gt;load-&gt;switch_theme_on();

功能是开启主题皮肤功能我们很多的加载什么类啊 辅助函数啊等都可以在这个前台主控制器里面加载。再来说后台(HT_Controller)其实是跟前台一样的功能的,只不过不同的是使用了

 /** 后台管理页面,不使用皮肤 */
	    $this-&gt;load-&gt;switch_theme_off();

不开启主题皮肤功能,如果要开启的只要写成

/** 前台页面均使用主题皮肤功能 */
	    $this-&gt;load-&gt;switch_theme_on();

就OK了,这样子就描述完了实现前后台主题皮肤的功能!
接下来研究了主要的前台模型的类文件,感觉自己还是很有相当大的差距的,人家写的就是不一样。首先为了避免出现直接的数据库的数据表名,我们可以在模型文件类里面用const来定义表常量,就是把表名赋值给一个自定义的常量,之后在条用表名的时候用这个常量就可以了!其次也许是为了方便检查错误把,在初始化函数里面使用

log_message('debug', "STBLOG: Posts Model Class Initialized");

这样子通过查看日志文件就可以知道这个类已经被加载了。
看到stblog整个模型文件里面函数时,感到都有一个特征 而且我感到这样子蛮好的,就是先把所有各种各样带条件查询相关的函数写在一块,然后把所有的CURD的操作写在一块,这样子感觉整个模型文件蛮清晰的!要找一个函数能够快速找到!
再来说说单个函数的写法,其实要是我自己写的话,也无非就是整理查询条件等东西:首先,所有的查询方法中都有默认的参数项,在生成条件($this->db->where(XXXX))之前我们可以先判断这个参数值的一些情况如:是否为空 是否是数字类型的等等情况,然后在生成条件语句,再者我们可以用链式的写法如:

$this-&gt;db-&gt;select('uid')-&gt;from(self::TBL_USERS)-&gt;where($key, $value);

,在这之后我们还可以不断的写我们需要的各种条件语句 如order_By啊等,在这之后我们只要用

$this-&gt;db-&gt;select('uid')-&gt;from(self::TBL_USERS)-&gt;where($key, $value);

l来返回结果集或者:

$this-&gt;db-&gt;get(self::TBL_USERS)-&gt;row();

l来返回第一行数据,或者

$this-&gt;db-&gt;get(self::TBL_USERS)-&gt;num_rows();

l返回结果数据的数目等等
因为stblog在他的autoload.php文件中默认加载了一些类和辅助函数!因此在整个项目的书写时只要随时拿来用就可以了!
此外 要注意的是有个函数叫做setting_item()的,这个函数的作用是用来获取数据表setting的一些值的,一次获取一个指定name的值,这个函数定义在library的common.php文件中,虽然这个是个类,但是setting_item()函数是定义在类之外的,也就相当于我们一般的辅助函数之类的用法,这个函数用到了stblog自定义的缓存类stcache.php文件,这个文件里面的东西看起来很简单,这个文件中使用文件的辅助函数(file),只要利用文件辅助函数来将数据写入文件实现数据的定时缓存的!
stblog的url都是使用路由来实现的,一开始我也纳闷了半天,后天突然想到可能是设置了路由,打开routes.php一看 果不其然,里面设置了很多的路由规则!看的时候一定要注意哦!
一般情况下我们从网上下载下来CI系统,所有的东西都是放在system文件夹中的,而我们只要开发的目录是在application目录中,其实我个人也不是很喜欢这个样子的目录结果,我实现类似于TP那样子的,把我们要开发的项目目录文件夹APP放在框架同级目录 ,而不是在框架目录里面,其实在CI中要做到这点很简单的,只要把整个application文件剪切出来放在与system文件夹同级目录就行了,并不需要修改任何地方!这样子就实现了类似TP那样子的外围目录效果了!在CI中我们一般的如js啊css啊图片啊等都是在与system文件夹同级目录的目录上建立一个或者几个文件夹来存放相应的东西的!我们的上传文件的目录也是定义在这个地方,当然这个目录是自己定义的,可以放在自己想要的任意位置!我们甚至可以将我们的模版目录定义到入口文件的地方,定义的方法上面已经讲过了!不过要记住的一点就是所有的目录都是相对于index.php文件的,为什么呢?因为stblog是采用单一入口流程来运行的!