使用 PHP 创建数据库驱动的应用程序

第 4 课:使用类和对象优化代码

此页上的内容适用于 NetBeans IDE 7.2、7.3、7.4 和 8.0

在本课中,将优化代码以便于将来进行维护。这会影响 createNewWisher.phpwishlist.php 文件。此外,还会创建一个名为 db.php 的新文件。

应用程序代码包含几个类似的块,其中提供了一些数据库查询。为了便于将来阅读和维护代码,您可以提取这些块,将这些块作为名为 WishDB 的单独类的函数进行实现,然后将 WishDB 放在 db.php 中。接下来,您可以将 db.php 文件包含在任何 PHP 文件中,然后使用 WishDB 中的任何函数而无需复制代码。这种方法确保了对查询或函数进行的任何更改是在一个地方完成的,您不必解析整个应用程序代码。

在使用 WishDB 中的函数时,不可更改任何 WishDB 变量的值。而应将 WishDB 类作为模板来创建 WishDB 对象,然后在该对象中更改变量值。在使用完该对象后,将会销毁该对象。由于从未更改 WishDB 类本身的值,您可以不限次数地重复使用该类。在某些情况下,您可能希望同时存在多个类实例,而在其他情况下,您可能希望只存在“单个”类,即,在任何时候都只有一个实例。本教程中的 WishDB 就是单个类。

请注意,创建类对象的术语是“实例化”该类,而有关对象的另一个术语是类“实例”。使用类和对象进行编程的一般术语是面向对象的编程 (OOP)。PHP 5 使用复杂的 OOP 模型。有关详细信息,请参见 php.net

在本教程中,您将数据库调用功能从单独的 PHP 文件移到 WishDB 类。MySQL 用户还会将过程样式的 mysqli 调用替换为面向对象的调用。这是为了与面向对象的新应用程序设计保持一致。

当前文档是“在适用于 PHP 的 NetBeans IDE 中创建 CRUD 应用程序”教程的一部分。


来自上一课的应用程序源代码

MySQL 用户:单击此处以下载源代码,该代码反映了在完成上一课之后的项目状态。

Oracle 数据库用户:单击此处以下载源代码,该代码反映了在完成上一课之后的项目状态。

创建 db.php 文件

在 "Source Files"(源文件)文件夹中创建一个新子文件夹。将该文件夹命名为 Includes。创建一个名为 db.php 的新文件,并将其放在 Includes 中。然后,您可以在该文件夹中添加更多文件,这些文件将包含在其他 PHP 文件中。

在新文件夹中创建 db.php:

  1. 在 "Source Files"(源文件)节点上单击鼠标右键,然后从上下文菜单中选择 "New"(新建)> "Folder"(文件夹)。"New Folder"(新建文件夹)对话框打开。
  2. 在 "Folder Name"(文件夹名称)字段中,键入 Includes。然后,单击 "Finish"(完成)。
  3. 在 "Includes" 节点上单击鼠标右键,然后从上下文菜单中选择 "New"(新建)> "PHP File"(PHP 文件)。"New PHP File"(新建 PHP 文件)对话框打开。
  4. 在 "File Name"(文件名)字段中,键入 db。然后,单击 "Finish"(完成)。

创建 WishDB 类

要创建 WishDB 类,您需要初始化该类的变量,并实现该类的构造函数。MySQL 用户请注意,WishDB 类扩展mysqli。这意味着 WishDB 继承 PHP mysqli 类的函数和其他特性。在类中添加 mysqli 函数时,您会看到这种继承的重要性。

打开文件 db.php 并创建 WishDB 类。在该类中,声明数据库配置变量以存储数据库所有者(用户)的名称和口令、数据库名称和数据库主机。所有这些变量声明都是私有的 ("private"),这意味着无法从 WishDB 类外部访问声明中的初始值(请参见 php.net)。您还可以声明私有的 static $instance 变量以存储 WishDB 实例。"static" 关键字表示,即使没有类实例,类中的函数也可以访问变量。

对于 MySQL 数据库:

class WishDB extends mysqli {


    // single instance of self shared among all instances
    private static $instance = null;


    // db connection config vars
    private $user = "phpuser";
    private $pass = "phpuserpw";
    private $dbName = "wishlist";
    private $dbHost = "localhost";
}

对于 Oracle 数据库:

class WishDB {

// single instance of self shared among all instances private static $instance = null;

// db connection config vars private $user = "phpuser"; private $pass = "phpuserpw"; private $dbName = "wishlist"; private $dbHost = "localhost/XE"; private $con = null;

}

实例化 WishDB 类

如果其他 PHP 文件要使用 WishDB 类中的函数,这些 PHP 文件需要调用一个函数以创建 WishDB 类的对象(“实例化”)。WishDB 设计为单个类,这意味着在任何时候都只有一个类实例。因此,这对防止任何外部 WishDB 实例化是非常有用的,这种实例化可能会创建重复的实例。

在 WishDB 类中,键入或粘贴以下代码:

 //This method must be static, and must return an instance of the object if the object
 //does not already exist.
 public static function getInstance() {
   if (!self::$instance instanceof self) {
     self::$instance = new self;
   }
   return self::$instance;
 }

 // The clone and wakeup methods prevents external instantiation of copies of the Singleton class,
 // thus eliminating the possibility of duplicate objects.
 public function __clone() {
   trigger_error('Clone is not allowed.', E_USER_ERROR);
 }
 public function __wakeup() {
   trigger_error('Deserializing is not allowed.', E_USER_ERROR);
 }

getInstance 函数为 "public" 和 "static"。"Public" 表示可以从类外部任意访问该函数。"Static" 表示即使未实例化类,也可以使用该函数。在调用 getInstance 函数以实例化类时,该函数必须是静态的。请注意,该函数访问静态 $instance 变量,并将其值设置为类实例。

称为作用域解析运算符的双冒号 (::) 和 self 关键字用于访问静态函数。Self 从类定义中使用以引用类本身。从类定义外部使用双冒号时,将使用类名而不是 self。请参见 php.net 上的作用域解析运算符

在 WishDB 类中添加构造函数

类可以包含一个称为“构造函数”的特殊方法,每次创建该类的实例时,都会自动处理该方法。在本教程中,将在 WishDB 中添加一个构造函数;每次实例化 WishDB 时,它都会连接到数据库。

在 WishDB 中添加以下代码:

对于 MySQL 数据库:

// private constructor
private function __construct() {
parent::__construct($this->dbHost, $this->user, $this->pass, $this->dbName);
if (mysqli_connect_error()) {
exit('Connect Error (' . mysqli_connect_errno() . ') '
. mysqli_connect_error());
}
parent::set_charset('utf-8');
}

对于 Oracle 数据库:

// private constructor
private function __construct() {
    $this->con = oci_connect($this->user, $this->pass, $this->dbHost);
    if (!$this->con) {
        $m = oci_error();
        echo $m['message'], "\n";
        exit;
    }
}

请注意,使用了伪变量 $this,而不是使用 $con$dbHost$user$pass 变量。从对象上下文中调用方法时,将使用伪变量 $this。它引用该对象中的变量值。

WishDB 类中的函数

在本课中,将实现 WishDB 类的以下函数:

函数 get_wisher_id_by_name

该函数要求将许愿者名字作为输入参数,并返回许愿者的 ID。

在 WishDB 类中,在 WishDB 函数后面键入或粘贴以下函数:

对于 MySQL 数据库:

public function get_wisher_id_by_name($name) {
$name = $this->real_escape_string($name);
$wisher = $this->query("SELECT id FROM wishers WHERE name = '"
. $name . "'"); if ($wisher->num_rows > 0){
$row = $wisher->fetch_row();
return $row[0];
} else
return null; }

对于 Oracle 数据库:

public function get_wisher_id_by_name($name) {
    $query = "SELECT id FROM wishers WHERE name = :user_bv";
    $stid = oci_parse($this->con, $query);
    oci_bind_by_name($stid, ':user_bv', $name);
    oci_execute($stid);
//Because user is a unique value I only expect one row
    $row = oci_fetch_array($stid, OCI_ASSOC);
if ($row)
return $row["ID"];
else
return null; }
该代码块执行 SELECT ID FROM wishers WHERE name = [variable for name of the wisher] 查询。查询结果是一个数组,其中包含符合查询条件的记录中的 ID。如果该数组不为空,则自动表示它包含一个元素,这是因为在创建表期间将字段名称指定为 UNIQUE。在本示例中,该函数返回 $result 数组的第一个元素(编号为零的元素)。如果数组为空,该函数将返回空值。

安全注意事项:对于 MySQL 数据库,将转义 $name 字符串以防止 SQL 注入攻击。请参见有关 SQL 注入的维基百科mysql_real_escape_string 文档。虽然在本教程的上下文中,您不会遇到有害 SQL 注入的风险,但最佳做法是转义存在此类攻击风险的 MySQL 查询中的字符串。Oracle 数据库通过使用绑定变量来避免该问题。

函数 get_wishes_by_wisher_id

该函数要求将许愿者 ID 作为输入参数,并返回为许愿者注册的愿望。

请输入以下代码块:

对于 MySQL 数据库:

public function get_wishes_by_wisher_id($wisherID) {
return $this->query("SELECT id, description, due_date FROM wishes WHERE wisher_id=" . $wisherID);
}

对于 Oracle 数据库:

public function get_wishes_by_wisher_id($wisherID) {
    $query = "SELECT id, description, due_date FROM wishes WHERE wisher_id = :id_bv";
    $stid = oci_parse($this->con, $query);
    oci_bind_by_name($stid, ":id_bv", $wisherID);
    oci_execute($stid);
    return $stid;
}

该代码块执行 "SELECT id, description, due_date FROM wishes WHERE wisherID=" . $wisherID 查询并返回一个结果集,这是一个符合查询条件的记录数组。(出于数据库性能和安全考虑,Oracle 数据库使用绑定变量。)数据选择是按 wisherID 执行的,这是 wishes 表的外键。

注:在第 7 课之前,您不需要使用 id 值。

函数 create_wisher

该函数在 wishers 表中创建一个新记录。该函数要求将新许愿者的名字和口令作为输入参数,并且不返回任何数据。

请输入以下代码块:

对于 MySQL 数据库:

public function create_wisher ($name, $password){
    $name = $this->real_escape_string($name);
$password = $this->real_escape_string($password);
$this->query("INSERT INTO wishers (name, password) VALUES ('" . $name . "', '" . $password . "')"); }

对于 Oracle 数据库:

public function create_wisher($name, $password) {
    $query = "INSERT INTO wishers (name, password) VALUES (:user_bv, :pwd_bv)";
    $stid = oci_parse($this->con, $query);
    oci_bind_by_name($stid, ':user_bv', $name);
    oci_bind_by_name($stid, ':pwd_bv', $password);
    oci_execute($stid);
}
该代码块执行 "INSERT wishers (Name, Password) VALUES ([variables representing name and password of new wisher]) 查询。该查询在 "wishers" 表中添加一个新记录,并分别使用 $name$password 值填充 "name" 和 "password" 字段。

重构应用程序代码

现在,您已创建了一个单独的类以使用数据库,接下来便可将重复的块替换为对该类中的相关函数的调用。这有助于避免将来出现拼写错误和不一致的情况。不影响功能的代码优化称为“重构”。

重构 wishlist.php 文件

请从 wishlist.php 文件入手,因为该文件很短,改进更能说明问题。
  1. 在 <?php ?> 块的顶部,输入以下行以允许使用 db.php 文件:
    require_once("Includes/db.php");
  2. 将连接到数据库并获取许愿者 ID 的代码替换为 get_wisher_id_by_name 函数调用。

    对于 MySQL 数据库,替换的代码为:

    $con = mysqli_connect("localhost", "phpuser", "phpuserpw");
    if (!$con) {
        exit('Connect Error (' . mysqli_connect_errno() . ') '
                . mysqli_connect_error());
    }
    //set the default client character set 
    mysqli_set_charset($con, 'utf-8');
    
    mysqli_select_db($con, "wishlist");
    $user = mysqli_real_escape_string($con, $_GET['user']);
    $wisher = mysqli_query($con, "SELECT id FROM wishers WHERE name='" . $user . "'");
    if (mysqli_num_rows($wisher) < 1) {
        exit("The person " . $_GET['user'] . " is not found. Please check the spelling and try again");
    }
    $row = mysqli_fetch_row($wisher);
    $wisherID = $row[0]; mysqli_free_result($wisher);


    $wisherID = WishDB::getInstance()->get_wisher_id_by_name($_GET["user"]); if (!$wisherID) { exit("The person " .$_GET["user"]. " is not found. Please check the spelling and try again" ); }

    对于 Oracle 数据库,替换的代码为:

    $con = oci_connect("phpuser", "phpuserpw", "localhost/XE", "AL32UTF8");
    if (!$con) {
       $m = oci_error();
       echo $m['message'], "\n";
       exit;
    }
    $query = "SELECT id FROM wishers WHERE name = :user_bv";
    $stid = oci_parse($con, $query);
    $user = $_GET["user"];
    
    oci_bind_by_name($stid, ':user_bv', $user);
    oci_execute($stid);
    
    //Because user is a unique value I only expect one row
    $row = oci_fetch_array($stid, OCI_ASSOC); if (!$row) { echo("The person " . $user . " is not found. Please check the spelling and try again" );
    exit;
    } $wisherID = $row["ID"];

    $wisherID = WishDB::getInstance()->get_wisher_id_by_name($_GET["user"]); if (!$wisherID) { exit("The person " .$_GET["user"]. " is not found. Please check the spelling and try again" ); }

    新代码先调用 WishDB 中的 getInstance 函数。getInstance 函数返回一个 WishDB 实例,然后代码在该实例中调用 get_wisher_id_by_name 函数。如果在数据库中找不到请求的许愿者,代码将终止该进程,然后显示一条错误消息。

    此处不需要用于打开数据库连接的代码。连接是通过 WishDB 类的构造函数打开的。如果名字和/或口令发生变化,您只需要更新 WishDB 类的相关变量即可。

  3. 将获取按 ID 标识的许愿者的愿望的代码替换为调用 get_wishes_by_wisher_id 函数的代码。

    对于 MySQL 数据库,替换的代码为:

    $result = mysqli_query($con, "SELECT description, due_date FROM wishes WHERE wisher_id=". $wisherID);
                    
    $result = WishDB::getInstance()->get_wishes_by_wisher_id($wisherID);

    对于 Oracle 数据库,替换的代码为:

    $query = "select * from wishes where wisher_id = :id_bv";
    $stid = oci_parse($con, $query);
    oci_bind_by_name($stid, ":id_bv", $wisherID);
    oci_execute($stid);
    $stid = WishDB::getInstance()->get_wishes_by_wisher_id($wisherID);
  4. 删除关闭数据库连接的行。
     mysqli_close($con);
                        or
     oci_close($con);                
    不需要该代码,因为在销毁 WishDB 对象时自动关闭数据库连接。不过,保留了释放资源的代码。即使您调用了 close 函数或销毁了使用数据库连接的实例,也需要释放使用连接的所有资源以确保正确关闭连接。

 

重构 createNewWisher.php 文件

重构不影响 HTML 输入窗体或显示相关错误消息的代码。

  1. 在 <?php?> 块的顶部,输入以下代码以允许使用 db.php 文件:
    require_once("Includes/db.php");
  2. 删除数据库连接凭证($dbHost 等)。这些凭证现在包含在 db.php 中。
  3. 将连接到数据库并获取许愿者 ID 的代码替换为 get_wisher_id_by_name 函数调用。

    对于 MySQL 数据库,替换的代码为:

    
    $con = mysqli_connect("localhost", "phpuser", "phpuserpw");
    if (!$con) {
        exit('Connect Error (' . mysqli_connect_errno() . ') '
                . mysqli_connect_error());
    }
    //set the default client character set 
    mysqli_set_charset($con, 'utf-8');
    
    
    
    /** Check whether a user whose name matches the "user" field already exists */ mysqli_select_db($con, "wishlist"); $user = mysqli_real_escape_string($con, $_POST['user']); $wisher = mysqli_query($con, "SELECT id FROM wishers WHERE name='".$user."'"); $wisherIDnum=mysqli_num_rows($wisher); if ($wisherIDnum) { $userNameIsUnique = false; }

    $wisherID = WishDB::getInstance()->get_wisher_id_by_name($_POST["user"]);
    if ($wisherID) {
    $userNameIsUnique = false;
    }

    对于 Oracle 数据库,替换的代码为:

    
    $con = oci_connect("phpuser", "phpuserpw", "localhost");
    if (!$con) {
        $m = oci_error();
        echo $m['message'], "\n";
        exit;
    }
    $query = "select ID from wishers where name = :user_bv";
    $stid = oci_parse($con, $query);
    $user = $_POST['user'];
    $wisherID = null;
    oci_bind_by_name($stid, ':user_bv', $user);
    oci_execute($stid);
    
    //Each user name should be unique. Check if the submitted user already exists.
    $row = oci_fetch_array($stid, OCI_ASSOC);
    if ($row) {
    $wisherID = $row["ID"];
    }
    if ($wisherID != null) {
    $userNameIsUnique = false;
    }
    $wisherID = WishDB::getInstance()->get_wisher_id_by_name($_POST["user"]);
    if ($wisherID) {
    $userNameIsUnique = false;
    }
    只要处理当前页面,WishDB 对象就会存在。在处理完成或中断后,将销毁该对象。不需要用于打开数据库连接的代码,因为该操作是由 WishDB 函数完成的。不需要用于关闭连接的代码,因为在销毁 WishDB 对象后,将立即关闭连接。
  4. 将在数据库中插入新许愿者的代码替换为调用 create_wisher 函数的代码。

    对于 MySQL 数据库,替换的代码为:

    if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {
        $password = mysqli_real_escape_string($con, $_POST["password"]);
    mysqli_select_db($con, "wishlist");
    mysqli_query($con, "INSERT wishers (name, password) VALUES ('" . $user . "', '" . $password . "')");
    mysqli_free_result($wisher);
    mysqli_close($con);
    header('Location: editWishList.php');
    exit;
    }
    if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {
    WishDB::getInstance()->create_wisher($_POST["user"], $_POST["password"]);
    header('Location: editWishList.php' );
    exit;
    }

    对于 Oracle 数据库,替换的代码为:

    
    if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {
        $query = "INSERT INTO wishers (name, password) VALUES (:user_bv, :pwd_bv)";
        $stid = oci_parse($con, $query);
        $pwd = $_POST['password'];
        oci_bind_by_name($stid, ':user_bv', $user);
        oci_bind_by_name($stid, ':pwd_bv', $pwd);
        oci_execute($stid);
        oci_close($con);
        header('Location: editWishList.php');
        exit;
    }
    
    
    if (!$userIsEmpty && $userNameIsUnique && !$passwordIsEmpty && !$password2IsEmpty && $passwordIsValid) {
    WishDB::getInstance()->create_wisher($_POST["user"], $_POST["password"]);
    header('Location: editWishList.php' );
    exit;
    }

完成当前课程后的应用程序源代码

MySQL 用户:单击此处以下载源代码,该代码反映了在完成课程后的项目状态。

Oracle 数据库用户:单击此处以下载源代码,该代码反映了在完成课程后的项目状态。

后续步骤

<< 上一课

下一课 >>

返回到教程主页

有用链接

了解在 PHP 中使用类的详细信息:

了解重构 PHP 代码的详细信息:



要发送意见和建议、获得支持以及随时了解 NetBeans IDE PHP 开发功能的最新开发情况,请加入 邮件列表

返回至 PHP 学习资源

get support for the NetBeans

Support


By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2013, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo