PHPを使用するデータベース駆動型アプリケーションの作成

レッスン4: クラスとオブジェクトを持つコードの最適化

このページの内容は、NetBeans IDE 7.2、7.3および7.4に適用されます

このレッスンでは、コードを最適化し、将来のコードの保守を容易にします。これは、createNewWisher.phpファイルおよびwishlist.phpファイルに影響します。また、db.phpというファイルが新しく作成されます。

アプリケーションのコードには、データベースへの問合せを持つ、類似するブロックがいくつか含まれています。コードを将来読みやすく、保守しやすくするために、これらのブロックを抽出してWishDBと呼ばれる個別クラスの関数として実装し、WishDBdb.php内に置くことができます。後でdb.phpファイルを任意のPHPファイルに含め、コードの複製を行わずにWishDBから任意の関数を使用できます。このような方法によって、問合せまたは関数への変更を1箇所で行うことができ、アプリケーション・コード全体を解析する必要はありません。

WishDBから関数を使用する場合、WishDBの変数の値は変更しません。かわりに、WishDBのオブジェクト作成用の設計図としてWishDBクラスを使用して、そのオブジェクトの変数の値を変更します。そのオブジェクトの操作が終了すると、そのオブジェクトは破棄されます。WishDBクラス自体の値は変わらないので、クラスを何度も再利用できます。場合によっては、クラスのインスタンスが複数同時に存在したり、同時に存在するインスタンスが1つのみ、つまり「単一」クラスにする場合もあります。このチュートリアルのWishDBは単一クラスです。

クラスのオブジェクトの作成を示す用語はクラスの「インスタンス化」で、オブジェクトを別の言葉で言うと、クラスの「インスタンス」です。クラスとオブジェクトを使用するプログラミングを示す一般的な用語は、「オブジェクト指向プログラミング」、またはOOPです。PHP 5では、高度なOOPモデルを使用します。詳細は、php.netを参照してください。

このチュートリアルでは、データベース・コール機能を個々のPHPファイルからWishDBクラスに移動します。また、MySQLのユーザーは、手続き型のmysqliコールをオブジェクト指向のコールに置き換えます。これは、アプリケーションの新しいオブジェクト指向設計に沿っています。

現在のドキュメントは、PHP向けのNetBeans IDEでのCRUDアプリケーションの作成というPHPチュートリアルの一部です。


前のレッスンからのアプリケーション・ソース・コード

MySQLユーザー: 前のレッスンが完了した後のプロジェクトの状態を反映したソース・コードをダウンロードするには、ここをクリックします。

Oracleデータベース・ユーザー: 前のレッスンが完了した後のプロジェクトの状態を反映したソース・コードをダウンロードするには、ここをクリックします。

db.phpファイルの作成

「ソース・ファイル」フォルダに新しいサブフォルダを作成します。フォルダに「Includes」という名前を付けます。db.phpという名前の新しいファイルを作成し、このファイルを「Includes」に配置します。後で、他のPHPファイルでインクルードされる別のファイルをこのフォルダに追加できます。

db.phpを新しいフォルダに作成するには:

  1. 「ソース・ファイル」ノードでマウスの右ボタンをクリックし、コンテキスト・メニューから「新規」>「フォルダ」を選択します。「新規フォルダ」ダイアログが開きます。
  2. 「フォルダ名」フィールドに「Includes」と入力します。次に、「終了」をクリックします。
  3. 「Includes」ノードでマウスの右ボタンをクリックし、コンテキスト・メニューから「新規」>「PHPファイル」を選択します。「新規PHPファイル」ダイアログが開きます。
  4. 「ファイル名」フィールドに「db」と入力します。次に、「終了」をクリックします。

WishDBクラスの作成

WishDBクラスを作成するには、クラスの変数を初期化し、クラスのコンストラクタを実装する必要があります。MySQLユーザーは、WishDBクラスがmysqli拡張することに注意してください。これは、WishDBがPHP mysqliクラスの関数およびその他の特性を継承することを意味します。mysqli関数をクラスに追加するときに、この重要性が理解できます。

db.phpファイルを開き、WishDBクラスを作成します。クラス内では、データベースの所有者(ユーザー)の名前とパスワード、データベースの名前、およびデータベース・ホストを格納するためのデータベース構成変数を宣言します。これらの変数の宣言はすべて「private」で、これは、WishDBクラスの外部から宣言の初期値にアクセスできないことを意味します(php.netを参照)。また、WishDBのインスタンスを格納する、private static $instance変数も宣言します。「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クラスのインスタンス化

WishDBクラスの関数を使用するその他のPHPファイルの場合、これらのPHPファイルは、WishDBクラスのオブジェクトを作成(インスタンス化)する関数をコールする必要があります。WishDBは単一クラスとして設計されており、これは、同時に存在するクラスのインスタンスが1つのみであることを意味します。そのため、重複するインスタンスが作成される可能性のある、外部からの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関数はクラスをインスタンス化するためにコールされるため、staticである必要があります。この関数は静的な$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;
    }
}

変数$con$dbHost$user、または$passのかわりに、疑似変数$thisを使用しています。メソッドがオブジェクトのコンテキスト内からコールされた場合、疑似変数$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=[ウィッシャの名前の変数]を実行します。問合せの結果は、問合せに一致するレコードのIDの配列です。配列が空でない場合は自動的に要素を1つ含むことを意味し、これは、表の作成 時にフィールド名がUNIQUEとして指定されたためです。この場合、関数は$result配列の最初の要素(番号が0の要素)を返します。配列が空の場合、関数はnullを返します。

セキュリティ上の注意: MySQLデータベースの場合、$name文字列は、SQLインジェクション攻撃を避けるためにエスケープされます。SQLインジェクションに関するWikipediaおよび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データベースでは、データベースのパフォーマンスとセキュリティ上の理由でバインド変数を使用します。)この選択は、wishes表の外部キーであるwisherIDによって実行されます。

注意: id値はレッスン7までは不要です。

関数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 ([新しいウィッシャの名前とパスワードを表す変数])を実行します。問合せによって、$name$passwordの値がそれぞれ入力された「name」フィールドと「password」フィールドを持つ「wishers」表に、新しいレコードが追加されます。

アプリケーション・コードのリファクタリング

データベースを操作するための別のクラスができたので、重複したブロックを、このクラスからの関連する関数へのコールに置き換えることができます。これは、今後のミススペルと不整合を回避するのに役立ちます。機能に影響しないコードの最適化は、リファクタリングと呼ばれます。

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