corner imagecorner image
IDEPlatformPluginsDocs & SupportCommunityPartners

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

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

このページの内容は NetBeans IDE 6.9-7.0 が対象です

このレッスンでは、コードを最適化し、将来のコードの保守を簡単にします。これは、ファイル 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 for PHP での CRUD アプリケーションの作成」の一部です。


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

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 を拡張 (extends mysqli) していることに注目してください。これは、WishDB が PHP mysqli クラスの関数やほかの特徴を継承することを意味します。mysqli の関数をこのクラスに追加するときに、その重要性がわかります。

db.php ファイルを開き、WishDB クラスを作成します。このクラスでは、データベースの所有者 (ユーザー) の名前とパスワード、データベースの名前、およびデータベースホストを格納するためのデータベース構成変数を宣言します。これらの変数の宣言はすべて「private」です。「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 である必要があります。この関数は 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 クラスの次の関数を実装します。

  • ウィッシャーの名前に基づいてウィッシャーの ID を取得する get_wisher_id_by_name
  • 特定の ID を持つウィッシャーのウィッシュのリストを取得する get_wishes_by_wisher_id
  • wishers 表に新しいウィッシャーのレコードを追加する create_wisher

関数 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);
                        または
     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 の学習に戻る