利用PHP的OOP特性实现数据保护

  在PHP 4中,声明变量通常使用var,而在PHP 5中,可使用面向对象编程(OOP)的特性来自定义数据的可见性--即可访问性,可见性在此与变量作用域非常类似,但提供了更好的控制机制,有以下三种类型的可见性修饰符:
  Public(默认)--变量可在全局范围内访问或修改。
  Protected--变量只能在类本身及直接派生(使用extends语句)类内访问或修改。
  Private--变量只能在类内部访问或修改。
  与接口实现类似,在程序中违反这些规则将会导致严重的错误;且与接口类似的是,它们的存在纯粹是为了方便程序员。但这并不意味着可以忽略它们,指定某个类成员变量的可见性,可保护对象内的数据免受外界影响。
  假设有一个MySqlDB类,一个$link变量在其中声明为private,这意味着这个变量只能从对象内部使用$this变量访问,这防止了类外其他对象或函数的意外覆盖,在此,我们将使用可见性特性帮助我们创建一个query对象。
  你可以把query当作一个单独的实体,它可以执行,并且返回结果。一些数据库系统也具有存储过程,存储过程与函数很相似,它们存储查询语句,并在调用时接受相应的参数,但MySQL在5.1版本之前并没有提供类似功能,某些其他类型的数据库管理系统也没有。
  在本文中,将把上述两个特性结合进示例的query对象中,示例将模拟一个基本的存储过程,并在内部保存结果指针。目前,重点是从对象中执行query,在此可以调用MySqlDB对象的query()函数。
  可在query对象中定义如下的public函数:
  __construct()--构造函数接受一个包含了实现DB接口对象实例引用的参数。
  prepare()--函数prepare()初始化query的存储过程。它可能包含一个或多个有限的占位符,而其将会作为参数传递给execute()函数。占位符定义为与参数个数有关的一个冒号紧跟一个整数及与参数类型有关的一个字母。
  包含占位符的一个简单的query看起来像以下这样:
  SELECT col1,col2 FROM table_name WHERE col1=:1I
  execute()--函数execute()将执行query。如果它被prepare()函数过早地初始化为一个存储过程,任何传递进来的参数都会被作为存储过程的执行参数,否则,第一个参数只会被作为查询文本。函数execute()将返回执行查询后的结果。
  compile()--函数compile()与函数execute()类似,实际上,query并没有执行,而是替换查询字符串中所有占位符,接受存储过程的参数,并返回query的编译版本。
  受保护的成员
  正如上面所提到的,可见性的概念可用于隐藏对象的内部工作,保护内部工作所需的数据完整性。前面已经解释,query返回的结果指针将会保存为protected属性,在此使用保护成员是因为从query对象派生出来的特定数据库query对象可能会重载某些核心功能。
  深掘代码
  理论说够了,现在开始编写代码,首先,创建一个例1所示的模板:
  例1:数据库query类的一个模板
  class DBQuery
  {
  /**
  *保存一个实现了DB接口对象的引用。
  */
  protected$db;
  /**
  *如果是一个存储过程,设为true。
  */
  protected$stored_procedure=false;
  /**
  *保存一个删除了所有字符串的query。
  */
  private$query;
  /**
  *用于在SQL中匹配引号。
  */
  private static$QUOTE_MATCH="/(".*(?db=$db;
  }
  public function prepare($query)
  {
  $this->stored_procedure=true;
  }
  public function compile($args)
  {}
  public function execute($query)
  {}
  }
  函数prepare
  为使用例1中的模板,你要做的第一件事是构建好prepare()函数,为确保无带引号的字符被偶然解析为占位符,函数应该移除query内所有字符串,并把它们临时存储在一个数组内。而字符串本身也会被占位符取代,其通常被识别为不应该在SQL语句中出现的的字符串序列。在query的编译期间,过程占位符会首先被替换,接着把字符串放回query中,这是通过preg_replace()函数,和另一个用作preg_replace()函数的helper回调函数完成的。
  例2:prepare()函数
  /**
  *把query准备为一个存储过程。
  * param string$query Prepared query text
  * return void
  */
  public function prepare($query)
  {
  $this->stored_procedure=true;
  $this->quote_store=array();//清除引号
  $this->query=preg_replace(self::$QUOTE_MATCH,'$this->sql_quote_replace("1"?"1":'2')',$query);
  }
  private function sql_quote_replace($match)
  {
  $number=count($this->query_strings);
  $this->query_strings[]=$match;
  return"$||$$number";
  }
  在此留意对静态QUOTE_MATCH属性private的使用,还有quote_store属性和sql_quote_replace()函数。相比protected,在此定义为private更能确保任何重载query类prepare()方法的子类使用其自身的机制来剔除引号。
  函数compile
  下一步是构建compile()与execute()函数。
  函数compile()如例3中所示,功能如下:
  ·接受的参数数目可变(即可变参数),其将匹配query中的占位符。
  ·检查占位符是否为正确的数据类型,并把它替换为参数中的值。
  ·把query作为字符串返回,但不执行它。
  ·如果query对象没有使用prepare()函数初始化为一个存储过程,将抛出一个异常。
  例3:compile()函数
  /**
  *返回编译的query,但并不执行它。
  * param mixed$args,...Query Parameters
  * return string Compiled Query
  */
  public function compile($params)
  {
  if(!$this->stored_procedure){
  throw new Exception("存储过程未被初始化!");
  }
  /*替代参数*/
  $params=func_get_args();//取得函数参数
  $query=preg_replace("/(?query);
  return$this->add_strings($query);//把字符串放回query中
  }
  /**
  *重新插入被prepare()函数移除的字符串。
  */
  private function add_strings($string)
  {
  $numbers=array_keys($this->query_strings);
  $count=count($numbers);
  $searches=array();
  for($x=0;$x<$count;$x){
  $searches[$x]="$||${$numbers[$x]}";
  }
  return str_replace($searches,$this->query_strings,$string);
  }
  /**
  *每次执行,存储过程中都有一个占位符被替换。
  */
  protected function compile_callback($params,$index,$type)
  {
  --$index;
  /*抛出一个异常*/
  if(!isset($params[$index])){
  throw new Exception("存储过程未收到所需的参数数目!");
  }
  /*可以在此添加别的类型,如日期和时间。*/
  switch($type){
  case'S':
  return'"'.$this->db->escape_string($params[$index]).'"';
  break;
  case'I':
  return(int)$params[$index];
  break;
  case'N':
  return(float)$params[$index];
  default:
  throw new Exception("存储过程中指定的数据类型'$type'无法识别。");
  }
  }
  函数compile()中使用了两个额外的函数,其中compile_callback()函数是作为在preg_replace()函数调用中的回调函数,每一次在query中查找到占位符,并把它替换为传给compile函数的值时,都会执行它。
  函数execute
  最后,还需要构建函数execute(),函数execute()编译query并且使用DB对象执行它,而DB对象在此是用于初始化DBQuery对象的。请注意在例4中,是怎样运用函数call_user_func_array()来得到编译后的query的,而这样做的原因是,函数execute()要直到运行时,才能确定传递给它的参数数目。
  例4:execute()函数
  /**
  *
  *执行当前query,并把占位符替换为所提供的参数。
  *
  * param mixed$queryParams,...Query parameter
  * return resource A reference to the resource representing the executed query.
  */
  public function execute($queryParams='')
  {
  //例如:SELECT*FROM table WHERE name=:1S AND type=:2I AND level=:3N
  $args=func_get_args();
  if($this->stored_procedure){
  /*调用函数compile以取得query*/
  $query=call_user_func_array(array($this,'compile'),$args);
  }else{
  /*如果存储过程未被初始化,就把它作为标准query执行。*/
  $query=$queryParams;
  }
  $this->result=$this->db->query($query);
  return$this->result;
  }
  全部整合起来
  为演示怎样使用query对象,下面构造了一个小例子,其将把DBQuery对象作为存储过程使用,并检查是否输入了正确的用户名与密码,请看例5:
  例5:
  require'mysql_db.php5';
  require_once'query2.php5';
  $db=new MySqlDb;
  $db->connect('host','username','pass');
  $db->query('use content_management_system');
  $query=new DBQuery($db);
  $query->prepare('SELECT fname,sname FROM users WHERE username=:1S AND pword=:2S AND expire_time<:3I');
  if($result=$query->execute("visualad","apron",time())){
  if($db->num_rows($result)==1){
  echo('凭证正确。');
  }else{
  echo('凭证不正确,会话已过期。');
  }
  }else{
  echo('执行query时发生错误:'.$db->error());
  }
  在本文中,你已看到了如何在声明类变量时,利用访问修饰符private、protected和public,保护数据和限制数据对象的可见性,同时,在PHP 5中,这些概念也可用于其他的数据类,保护其重要的内部数据。