Help us understand the problem. What is going on with this article?

【PHP超入門】クラス~例外処理~PDOの基礎

はじめに

変数と関数の基礎はわかり、クラスも何となく聞いたことがある超初心者向けです。
長いですが、変数と関数しかわからなくても、読めばクラス、例外処理、PDOについて何となくわかるようになると思います。
それ以上の方は、読む必要はないと思います。
時間の無駄ですwww

PHPでデータベースを利用するには、PDOを理解する必要があります。
PDOを理解するには、クラスと例外処理の基礎知識が必要になります。
プログラミングの経験もなく、PHPを勉強し始めた超初心者にとっては非常にハードルが高いです。

なので、PDOを使ってデータベースに接続するために

という流れでまとめてみました。

超初心者向けというか、自分で理解するために調べた内容をまとめました。
カオスなまとめになってるかと思いますが、おそらくフィクションではないはずですw
PHPの説明で誤りがあれば、スローしてしていただければ喜んでキャッチしますのでご指摘ください((_ _ (´ω` )ペコ

序章

今までのデータベース接続

データベースへの接続方法の一つにmysql関数を使った方法があります。
下記のように関数を使い引数を指定するだけで簡単に接続できました。

mysql_connect関数を使った接続方法
$link = mysql_connect('ホスト名', 'ユーザー名', 'パスワード');

すげー簡単!やったー\(^o^)/ って感じですが、mysql関数を使った接続方法は、PHP 5.5.0 で非推奨になり、PHP 7.0.0 で削除されました。
特別な理由がない限りこの接続方法は使わないようにしましょう。

これからのデータベース接続

mysql関数以外でデータベースに接続するには、PDOまたはMySQLiを使用します。
今回は、PHP5.1.0から使えるPDOを使ってデータベースに接続したいと思います。

早速、PDOを調べてみると

データベース抽象化レイヤの一つで、プリペアドステートメントを ...

えっ ... 抽象化レイヤ? プリペアドステートメント? なにそれ ... (・ω・`;)

とりあえず難しい用語は忘れましょう。
PDOって何?という疑問もあるかと思いますが、今はPDOというものを使えば、データベースに接続できるとだけ覚えておきましょう。

今までのデータベース接続は、関数の知識があれば使えました。
PDOを使ってデータベースに接続するには、PDOクラスを利用します。
PDOを理解するには、クラス例外処理の知識が必要になります。
まずは、クラスと例外処理の基礎をおさえましょう。

今回は、PDOを使ってデータベース(MySQL)に接続することが目的です。
クラスや例外処理、PDOについて必要最低限の基礎しか触れておりませんのでご了承ください。

前提条件

  • PHPは5.3.6以上を使う前提で説明しております。PHP5.3.5以前での留意点も記述しております。
  • データベースはMySQLを利用し、文字エンコードはUTF-8を使う前提で説明しております。

第1章 クラスの基礎

クラスとは

ざっくり言うと変数関数の集まりです。

クラス = 変数 + 関数

クラスを定義する構文を見ると変数と関数の集まりなのがわかるかと思います。

クラスを定義する構文

ざっくりと書いた構文です。

クラスを定義する構文
class クラス名 {
    $変数名

    function 関数名(引数) {
    }
}

変数と関数の集まりなので、簡単ですね。
それだけならクラスを使う意味がないのでは?と思いますよね。
次は変数と関数とクラスのそれぞれの違いを見ていきましょう。

変数と関数とクラスの違い

クラスは、変数と関数の集まりですので、データの保持と処理が可能です。

  データの保持 データの処理
変数 ×
関数 ×
クラス

変数と関数とクラスの違いがわかりました。
実はクラス内に記述した変数と関数は別の名称になります。
次は、どのような名称になるのか見ていきましょう。

クラス内における変数と関数の名称

先ほど記述したクラスの構文で、クラス内にあっても変数と関数と書きましたが、実は名称が変わります。
クラス内に記述した変数プロパティと呼び、クラス内に記述した関数メソッドと呼びます。
以降の説明では、基本的にクラス内に記述された変数プロパティ関数メソッドと呼びますのでしっかり覚えてください。
しつこいですが、クラス内に記述した変数と関数のことをプロパティとメソッドと呼びます。
後から新しい用語がでてきますので、プロパティメソッドはここで完璧に覚えてください。

  クラス内に記述したときの名称
変数 プロパティ
関数 メソッド

先ほど記述したクラスの構文を正しい名称にすると下記のようになります。

クラスを定義する構文
class クラス名 {
    $プロパティ名

    function メソッド名(引数) {
    }
}

クラス内に記述した変数プロパティで、関数メソッドという名称に変わりました。
しつこいですが、クラス内に記述した変数と関数のみ名称が変わります。

実は名称を変えただけでは、クラスを定義する構文は、まだ完成しておりません。
もう一度クラスを定義する構文を見てみましょう。

クラスを定義する構文

先ほど記載した構文と少し変わっています。

クラスを定義する構文
class クラス名 {
    アクセス修飾子 $プロパティ名;

    アクセス修飾子 function メソッド名引数
    {
        //処理を記述
    }
}

注目すべきはプロパティとメソッドの前にアクセス修飾子があることです。
先ほどは、わかりやすくするためにアクセス修飾子をあえて書きませんでした。
実際にはアクセス修飾子というのが必要ですので、覚えておいてください。
アクセス修飾子が何なのか詳しく見ていきましょう。

アクセス修飾子とは

アクセス修飾子とは、どこからアクセスできるのかを指定するものです。
例えば、クラスの中にあるプロパティは、クラスの外からも使用できるのかといったアクセス権限に関する指定のことです。
アクセス修飾子は、下記の3種類があります。

種類 説明
public どこからでもアクセス可能
private クラス内のみからアクセス可能
protected クラス内とそのクラスを継承した子クラスからアクセス可能

説明の中で「クラスを継承した子クラス」とありますが、後ほど説明しますので今はそんなのがあるということだけ覚えておいてください。

ちなみにアクセス修飾子を指定しないと、publicの扱いになります。
アクセス修飾子を省略することも可能ですが、省略せずに明示した方が見たときにわかりやすくなります。
また、publicにする必要がない場合には、privateやprotectedを適切に指定してください。
ちなみに、PHP4ではアクセス修飾子はありません。
PHP4の環境でアクセス修飾子を明示して実行すると構文エラーが表示されます。

次はクラスを使うメリットを見ていきましょう。

クラスのメリット

クラスのメリットを知るには、クラスの概念を理解する必要があります。
クラスの概念を実世界に例えて理解していきましょう。
クラスは、実世界で例えると設計書にあたります。
車の設計書があれば、車をいくつも作成することが可能です。
設計書から作成された車のことを、プログラムの世界ではインスタンスと呼びます。
設計書から車を作成することを、プログラムの世界ではインスタンス化と呼びます。

img_class.png

新しい用語がでてきましたが、図を見るとイメージできるかと思います。
車の設計書(クラス)があれば、いくつも車(インスタンス)を作成することが可能です。
言い換えれば、クラスがあれば、いくつもインスタンスを容易に作成できます。
これがクラスを使うメリットの一つです。

次はクラスからインスタンスを作成する構文を見ていきましょう。

クラスからインスタンスを作成する構文

先ほど、車の設計書(クラス)から車(インスタンス)を作成すると説明しました。
実際にインスタンスを作成する構文を見ていきましょう。

インスタンスを作成する構文
$変数名 = new クラス名引数

new演算子の後にクラス名を記述してインスタンスを作成しています。
作成したインスタンスを変数に代入しています。
インスタンスは、基本的に変数に代入して使うようになります。
現時点では、「代入して使う」ということがわからないと思いますが後ほど説明します。

実際にインスタンス化してみる

頭の中を整理するために、実際にクラスを作成してインスタンス化してみたいと思います。

// クラスを作成
class Car {
    public function drive() {
        echo '走ります';
    }
}

// インスタンスを作成(インスタンス化)
$prius = new Car();

Carクラスを作成した後に変数($prius)にインスタンスを作成して代入してます。
ここまでは、今までのおさらいなので記述してあるコードの意味はわかるかと思います。
ただ、実行結果がどのようになるのかわかりますか?
コードを見るとecho '走ります'と書いてあるので走りますと画面に表示されるかと思いますが、実際には何も表示されません。
上記の記述では、まだCarクラスのインスタンスを作成して$priusに代入しただけです。
走りますと表示させるには、代入したインスタンスのdriveメソッドにアクセスする必要があります。
先ほど「インスタンスは変数に代入して使う」と説明しました。
変数$priusにインスタンスが代入されています。
変数に代入されているインスタンスのメソッドにアクセスするようになります。
代入されたインスタンスプロパティまたはインスタンスメソッドにアクセスするには、アロー演算子を使用します。

アロー演算子とは

インスタンスのプロパティやメソッドにアクセスするには、アロー演算子と呼ばれれる -> を使います。

アロー演算子
$インスタンスを代入した変数名->呼び出したいインスタンスのプロパティまたはメソッド名

先ほどのコードを使い、インスタンスのdriveメソッドへアクセスしてみます。

// Carクラスを作成
class Car {

    public function drive() {
        echo '走ります';
    }
}

// インスタンスを作成(インスタンス化)
$prius = new Car();

// アロー演算子でインスタンスメソッドへアクセス
$prius->drive();

最後の一行を追記しました。
アロー演算子を使い、$priusに代入したインスタンスにあるdriveメソッドを呼び出しています。
これを実行すると走りますと表示されるようになります。
インスタンスのプロパティやメソッドを呼び出すには、アロー演算子を使うようになります。

先ほど、アクセス修飾子でアクセス権限を指定すると説明したのを覚えていますか。
アクセス修飾子のpublicを指定すれば、クラスの外からでもアクセスできるはずですよね?
このアロー演算子は、インスタンスプロパティメソッドを呼び出すのに使います。
そうなると、インスタンス化しないとプロパティメソッドにアクセスできなくなりますね。
単純にクラスのプロパティやメソッドにアクセスするには、スコープ定義演算子を使います。

スコープ定義演算子とは

クラスのプロパティやメソッドにアクセスするには、スコープ定義演算子と呼ばれる :: を使います。

スコープ定義演算子
クラス名::呼び出したいクラスのプロパティまたはメソッド名

早速、スコープ定義演算子を使ってアクセスできるか確認したいところですが、その前にクラスのプロパティとメソッドとインスタンスのプロパティとメソッドの違いについて見ていきましょう。

クラスのプロパティとメソッドについて

今まで普通にクラスのプロパティとメソッド、インスタンスのプロパティとメソッドと説明してきましたが、どれがクラスのプロパティで、どれがインスタンスのプロパティなのか区別できないですよね。

普通にクラス内に記述したプロパティやメソッドはインスタンスのプロパティやメソッドです。
では、クラスのプロパティとメソッドにするためにはどうすればよいのか。
クラスのプロパティまたはメソッドにするには、staticという修飾子を付与する必要があります。
プロパティであれば、プロパティ名の前にstaticを記述します。
メソッドであれば、functionの前にstaticを記述します。

staticのあり・なしでクラスなのか、インスタンスなのか区別できます。
下記のコードでどれがクラスのプロパティまたはメソッドかわかりますよね。

class クラス名 {
    // No.1
    アクセス修飾子 static $プロパティ名;

    // No.2
    アクセス修飾子 $プロパティ名;

    // No.3
    アクセス修飾子 static function メソッド名(引数) {
        //処理を記述
    }

    // No.4
    アクセス修飾子 function メソッド名(引数) {
        //処理を記述
    }
}

staticがあるNo.1は、クラスのプロパティです。
No.3にもstaticがあります。これはクラスのメソッドです。
No.2とNo.4にはstaticがないため、インスタンスのプロパティとメソッドになります。
No.1とNo.3は、クラスのプロパティとメソッドになるため、インスタンス化しなくてもアクセスできます。
No.2とNo.4はインスタンスのプロパティとメソッドになるため、インスタンス化しないとアクセスできないということになります。

ここで、staticについてPHPマニュアルの説明を見ていきましょう。

クラスプロパティもしくはメソッドを static として宣言することで、 クラスのインスタンス化の必要なしにアクセスすることができます。 static なプロパティは、インスタンス化されたクラスオブジェクトから アクセスすることはできません (static なメソッドにはアクセスできます)。

理解力のない私は最初この説明をみたときに、static を宣言することで public と同じような意味になるのかと勘違いしました。
static と public の違いを見ていきましょう。

staticの意味は?staticとpublicの違いは?

PHPマニュアルの説明をみて「publicを宣言すれば、インスタンス化しなくてもアクセスできるのにstaticを宣言する意味がないのでは?」と疑問に思った方もいるかと思います。
もしかしたら、私だけですかね(・ω・`;)

まず、「publicを宣言すれば、インスタンス化しなくてもアクセスできる」というのは誤りです。
publicだけ宣言しても、インスタンス化しなければアクセスできません。
staticを宣言することで、クラスに属したプロパティまたはメソッドになります。
クラスに属したプロパティまたはメソッドのためインスタンス化しなくてもアクセスできるようになります。
逆にstaticを宣言していないプロパティまたはメソッドはインスタンスに属します。
なので、publicだけ宣言しても、インスタンス化しなければアクセスできません。
staticを指定することで、クラスに属し、publicを指定することで、クラスの外からアクセスできるようになります。
publicはあくまでもアクセス修飾子であり、どこからアクセスできるかを指定するものです。
staticはクラスに属するかを指定するものであり、publicとは別物です。

staticがあるプロパティを静的プロパティと呼び、staticがあるメソッドを静的メソッドと呼びます。

今までクラスのプロパティ、クラスのメソッドと呼んでいましたが、静的プロパティと静的メソッドへ名称を統一します。
以降、下記の言葉で説明しますので覚えてください。

  プロパティ メソッド
staticあり 静的プロパティ※1 静的メソッド※2
staticなし インスタンスプロパティ インスタンスメソッド

※1 今までクラスのプロパティと表記していましたが静的プロパティへ統一
※2 今までクラスのメソッドと表記していましたが静的メソッドへ統一

アロー演算子とスコープ定義演算子の使い分け

前にアロー演算子とスコープ定義演算子の使い方を説明したのを覚えているでしょうか。
いろいろな言葉がでてきたので、忘れている方もいるかと思います。
頭の中を整理するため、もう一度アロー演算子とスコープ定義演算子の使い分けを見ていきましょう。

種類 アクセス方法
静的プロパティ、静的メソッド ::
インスタンスプロパティ、インスタンスメソッド ->

クラス静的プロパティまたは静的メソッドへアクセスするときは、スコープ定義演算子 (::を使用します。
インスタンスプロパティまたはメソッドへアクセスするときは、アロー演算子(->を使用します。

クラスに属している場合はスコープ定義演算子を使い、インスタンスに属している場合はアロー演算子を使うだけの2パターンしかないので簡単ですね。

次は静的プロパティと静的メソッドへアクセスしてみましょう。

実際に静的プロパティ、静的メソッドへアクセスしてみる

静的かどうかは、staticのあり・なしで判断します。
staticがあれば静的プロパティもしくは静的メソッドで、staticがなければインスタンスプロパティもしくはインスタンスメソッドになります。
下記のコードを基に外部からTestクラスの静的プロパティ、静的メソッドへアクセスしてみましょう。

class Test {

    // No.1 静的プロパティ
    public static $a = 8;

    // No.2 静的メソッド
    public static function sum($b,$c) {
        echo $b + $c;
    }

    // No.3 インスタンスプロパティ
    public $d = 9;

    // No.4 インスタンスメソッド
    public function count() {
        static $e = 0;
        echo $e;
        $e++;
    }

    // No.5 インスタンスメソッド
    public function division($f,$g) {
        echo $f / $g;
    }

}

先ほど説明しましたが、静的プロパティと静的メソッドへアクセスするにはスコープ定義演算子(::)を使います。
No.1の静的プロパティとNo.2の静的メソッドへアクセスしてみます。

class Test {

    // No.1 静的プロパティ
    public static $a = 8;

    // No.2 静的メソッド
    public static function sum($b,$c) {
        echo $b + $c;
    }

    // No.3 インスタンスプロパティ
    public $d = 9;

    // No.4 インスタンスメソッド
    public function count() {
        static $e = 0;
        echo $e;
        $e++;
    }

    // No.5 インスタンスメソッド
    public function division($f,$g) {
        echo $f / $g;
    }

}

// No.1 静的プロパティへアクセス
echo Test::$a;

// No.2 静的メソッドへアクセス
Test::sum(2,3);

echo Test::$a;Test::sum(2,3); の命令を追加しています。
PHPのバージョン別の実行結果をご覧ください。
実行結果を見ていただくとわかりますが、PHP5では実行結果が表示されています。
PHP4には public などのアクセス修飾子がありませんので、上記のコードは構文エラーになっています。
この記事を読んでいる方でPHP4の環境を使っている方は、いないと思いますのでスルーします。

スコープ定義演算子を使い、外部から静的プロパティと静的メソッドへアクセスできました。
インスタンスプロパティとインスタンスメソッドへアクセスするには、インスタンス化する必要があります。
あたりまえですが、インスタンス化せずにインスタンスメソッドへアクセスすることは通常できません。
しかし、下位互換性のためにスコープ定義演算子を使ってインスタンスメソッドへアクセスできるようになっています。
通常このようなアクセスはさけるべきですが、インスタンスメソッドへアクセスしてみます。

class Test {

    // No.4 インスタンスメソッド
    public function count() {
        static $e = 0;
        echo $e;
        $e++;
    }

    // No.5 インスタンスメソッド
    public function division($f,$g) {
        echo $f / $g;
    }

}

// No.4 インスタンスメソッドへアクセス
Test::count();

// No.5 インスタンスメソッドへアクセス
Test::division(4,2);

PHPのバージョン別の実行結果を見てください。
PHP5ではStrict Standards: Non-static method Test::count()Strict Standards: Non-static method Test::division()が表示されますが、実行結果の02も表示されます。
Strict Standards: Non-static methodの意味は、「厳しく言えばstaticのないメソッド(インスタンスメソッド)へアクセスするべきではない」ということです。
アクセス修飾子を記述しているため、PHP4では構文エラーになっていますが、アクセス修飾子を外せば問題なく実行されます。

しつこいですが、スコープ定義演算子を使ってインスタンスメソッドへアクセスできますが、このようなアクセスはしないようにしましょう。
次は、インスタンス化するときに実行される特別なメソッドを見ていきましょう。

インスタンス化するときに実行される特別なメソッド

new演算子でインスタンス化するときに実行される特別なメソッドのことをコンストラクタと呼びます。
メソッド名に__construct()と記述することで、定義することができます。

コンストラクタの構文
class クラス名 {
    function __construct(引数) {
        //処理
    }
}

最初に説明しましたが、コンストラクタはインスタンス化するタイミングで実行されます。
インスタンス化するタイミングで実行されるため、コンストラクタはプロパティを初期化するときなどに使われます。
PDOでデータベースに接続するときにも、PDOクラスのコンストラクタの引数にデータベースの情報を記述してデータベースに接続しますのでコンストラクタについて覚えておいてください。

ちなみにPHP4ではクラス名と同じメソッド名にすることでコンストラクタとして機能します。
PHP5でも下位互換のために動作しますが、PHP5以上では__construct()と記述しましょう。

インスタンス自身を指す特別な変数($this

インスタンス自身を指す$thisという特別な変数があります。
インスタンス自身を指しますので、インスタンスメソッドの中でしか使えません。
静的メソッドの中では$thisは使えませんので注意してください。

メソッドを作る上で、インスタンス自身を指したいときは多々あります。
例えば、下記のようなCarクラスを作成して、走行するdriveメソッドと走行した距離を返すgetKmメソッドを記述したとします。
$thisは使わずに記述してみます。

class Car {

    private $km = 0; 

    public function drive($distance) {
        $km += $distance;
    }

    public function getKm() {
        return $km;
    }
}

上記の記述で、インスタンス化してgetKmメソッドにアクセスすると、$kmという変数が定義されていないとエラーが表示されます。
下記のように$thisを使ってインスタンスのプロパティであることを記述しなければいけません。

class Car {

    private $km = 0; 

    public function drive($distance) {
        $this->km += $distance;
    }

    public function getKm() {
        return $this->km;
    }
}

ここで注意したいのが$this->kmの記述です。
プロパティ名は$kmなので$this->$kmかと思いますが、$の記述はなく、$this->kmとなりますので注意してください。

実際にクラスを作り、インスタンス化してみる

実際に車のクラスを作成して、走らせてみましょう。

// No1.車クラスを作成
class Car {

    private $km = 0; 

    public function drive($distance) {
        $this->km += $distance;
    }

    public function getKm() {
        return $this->km;
    }
}

// No2.インスタンス化して変数priusに代入
$prius = new Car();

// No3.インスタンスのdriveメソッドへアクセスして引数に80を指定
$prius->drive(80);

// No4.同じくインスタンスのdriveメソッドへアクセスして引数に20を指定
$prius->drive(20);

// No5.インスタンスのgetKmメソッドへアクセスして走行距離を出力
print $prius->getKm();

// No6.インスタンス化して変数corollaに代入
$corolla = new Car();

// No7.インスタンスのdriveメソッドへアクセスして引数に50を指定
$corolla->drive(50);

// No8.インスタンスのgetKmメソッドへアクセスして走行距離を出力
print $corolla->getKm();

No5のprint $prius->getKm();では、何の数字が出力されるかわかりますか?
No3の処理で$kmの変数に20が足されています。
No4の処理で$kmの変数に80が足されています。
No5では合計である100が表示されます。
今までの説明があれば、それぞれ何をしているかわかるかわかったと思います。

上記ではCarというクラスを新たに作成しました。
実は、作成したクラスを基に新しいクラスを作成することができます。
次は、クラスの継承について見ていきましょう。

クラスの継承

Aというクラスを基にしてBという新しいクラスを作成することができます。
基となるクラスの変数や関数を引き継いで新しいクラスを定義することを継承と呼びます。
基になるクラスを親クラスと呼び、継承したクラスを子クラスと呼びます。

クラスを継承する構文
class クラス名 extends 親クラス {
}

親クラスと子クラスのことをスーパークラスとサブクラスと呼ぶこともあります。
スーパークラスやサブクラスという名称もよく使われるので覚えておきましょう。
この記事では、親クラス・子クラスという名称で記載します。

名称 別の名称
親クラス スーパークラス
子クラス サブクラス

継承における親子関係

他のクラスを継承して新しいクラスを作れることがわかりました。
次に、親クラスと子クラスの関係を見ていきましょう。

まず、PHPでは親クラスを複数指定する継承(多重継承)は認められておりません。

img_class_inheritance_ng.png

親クラスは、ひとつだけ継承(単一継承)することができます。

img_class_inheritance_ok.png

そして親子の関係ですが、子クラスは親クラスの変数や関数を引き継いでいます。
逆に親クラスは子クラスの変数や関数を引き継ぐことはありません。

親クラス、子クラスという名前のとおり、無関係なクラスを親子関係にしない方が無難です。
is-a関係が成り立つときに継承するのが望ましいとされています。

is-a関係とは、子クラス is a 親クラスが成り立つ関係です。
日本語にすると、子クラスは親クラスですという意味になります。
例えば、「Windows is a OS」であれば、「WindowsはOSです」とis-a関係が成り立ちます。
継承は、このような関係が成り立つときに利用するのが望ましいとされています。

次で第1章のクラスは終わりです。
最後は「オブジェクト」について見ていきましょう。

最後に「オブジェクト」とは

クラスを理解するのに、オブジェクトという言葉は欠かせません。
クラスについてググったり、書籍を見るとオブジェクトという言葉が使われているかと思います。
しかし、今までオブジェクトという言葉をあえて使いませんでした。
なぜかと言うと、オブジェクトという言葉を使わなくても説明できるし、いろいろな言葉を使うと理解し難いと思ったからです。
ひと通り覚えてから、オブジェクトについて学べばいいかと思います。

正直、オブジェクトについて私もよくわかりません。
オブジェクトについてググってみましたが、オブジェクトの定義がよくわかりません。
人によっては、クラスやインスタンスなど含めた全体をオブジェクトと呼んだり、
クラスのことをオブジェクトと呼んだり、
インスタンスのことをオブジェクトと呼んだり、
何がオブジェクトかわかりません(・ω・`;)

ただ、オブジェクトの定義は、全体クラスインスタンスかのいずれかを指すことが多いです。
なのでオブジェクトと言われたときには、どこを指しているのか考え、一番適切な箇所に置き換えるようにしています。

img_object.png

個人的にオブジェクトというのはインスタンスを指すことが多いかなという印象です。
以降もオブジェクトという言葉は使いません。
オブジェクトについて知りたい方は、ググってください。
これで、クラスの基礎については、理解できたかと思います。
次は、例外処理を見ていきましょう。

第2章 例外処理の基礎

例外処理とは

スクリプトを実行したときにデータベースが存在しない、読み込もうとしたファイルが存在しないなどのエラー(例外)が発生しても正常にスクリプトが動作するようにすることを例外処理といいます。
例外処理の機能はPHP5から使えるようになりました。
if文を使って例外処理と似たようなことも可能ですが、例外処理の機能を使うことでプログラムの処理とエラー処理にわかれるため読みやすいコードになります。

例外処理の構文

例外処理の構文を見ていきましょう。

例外処理する構文
try {
    //例外が発生するおそれがあるコード
} catch(例外クラス名 例外を受け取る変数名){
    //例外発生時の処理
}

例外処理はtryブロック内に例外が発生するおそれがあるコードを記述します。
catchブロックでは、例外を受け取ったときの処理を記述します。
catchの箇所をもう少し詳しく見ていきましょう。

catch例外クラス名 例外を受け取る変数名

構文の中に例外クラス名とあります。
PHPには最初からExceptionという例外クラスがあります。
PHPマニュアルの「Exception」の説明を見ていきましょう。

Exception は、PHP 5 ではすべての例外の基底クラスです。 PHP 7 では、すべてのユーザー例外の基底クラスとなります。

Exceptionというのが、例外のクラスであることがわかりました。

説明文を読んで疑問に思うのが、基底のクラスということは、Exception以外にも例外クラスがあるのかということです。
PHPマニュアルの「例外」には下記の記述があります。

スローされるオブジェクトは、ExceptionクラスあるいはExceptionのサブクラスのインスタンスでなければなりません。それ以外のオブジェクトをスローしようとするとPHPの Fatal Error が発生します。

一部わからない言葉もあると思いますが、ここで注目すべきは「ExceptionクラスあるいはExceptionのサブクラスのインスタンスでなければなりません。」ということです。
上記の説明からExceptionクラス以外にもExceptionクラスを継承した子クラス(サブクラス)が存在するということがわかりました。
catchの例外クラス名に記述できるのは、ExceptionクラスかExceptionクラスを継承した子クラス(サブクラス)でなければならないということです。

次は「例外」の説明にもあったスローについて見ていきましょう。

例外を投げる(スローする)

例外処理の説明で、tryブロック内にコードを記述すると説明しましたが、処理をずらずらと記述しただけでは、どの処理で例外が発生するのかわかりません。
どこが例外なのかを知らせる必要があります。
例外を知らせることを、例外を投げる(スローする)といいます。
例外を投げる構文を見ていきましょう。

例外を投げる構文
throw new 例外クラス名(引数)

throw文を使って例外を投げることができます。
右側にあるnew 例外クラス名(引数)はどこかで見たことありますね。
忘れた方もいるかと思いますが、クラスをインスタンス化するときの構文と同じですね。

クラスをインスタンス化する構文
$変数名 = new クラス名(引数)

例外を投げるときにインスタンス化しているわけですね。
先ほど見たPHPマニュアルの説明にもスローされるオブジェクトはインスタンスでなければならないと書いてありました。

スローされるオブジェクトは、Exception クラスあるいは Exception のサブクラスのインスタンスでなければなりません。 それ以外のオブジェクトをスローしようとすると PHP の Fatal Error が発生します。

インスタンスしか投げることができませんので、throwするときはnew演算子を使ってインスタンスを作成します。

クラスをインスタンス化するときは、変数名にインスタンスを代入していましたが、throw文では、まだ変数に代入していません。
throw文では、例外クラスのインスタンスを作成して投げているだけです。
例外処理ではcatchしたときにインスタンスを変数に代入します。

catch例外クラス名 例外を受け取る変数名

先ほども記述していましたが、catchしたときにインスタンスを変数に代入しているのがわかります。
クラスをインスタンス化するときはインスタンスを作成して変数に代入する処理を一行でやっていましたが、
例外処理ではインスタンスを作成する処理とインスタンスを変数に代入する処理を2つにわけているようなイメージですね。

例外処理の全体的な流れとしては、tryブロック内で例外を投げて、catchブロックで例外を捕捉しています。

try {
    //例外が発生するおそれがあるコード

    throw new 例外クラス名(引数)

} catch(例外クラス名 例外を受け取る変数名){
    //例外発生時の処理
}

実際に割り算関数を作成して例外処理をしてみましょう。

割り算関数を例外処理してみる

単純に割り算する関数です。

function warizan($val1, $val2) {
    return $val1 / $val2;
}

$val2に0を代入されると割ることができません。
0で割るとPHPのエラー(Warning: Division by zero)が表示されます。
例外処理を使って$val2に0が代入されたときは0で割ることはできませんと表示されるようにしてみましょう。

function warizan($val1,$val2) {
    try {
        if($val2 === 0) {
            //No.1 例外を投げる
            throw new Exception('0で割ることはできません');
        }
        return $val1 / $val2;

        // No.2 catchで例外を捕捉
    } catch(Exception $e) {

        // No.3 $eに代入されたインスタンスのメソッドへアクセス
        return $e->getMessage();

    }
}

ポイントとなるNo.1〜3の処理を詳しく見ていきましょう。

No.1 例外を投げる

new Exception('0で割ることはできません')では、new演算子を使ってExceptionクラスのインスタンスを作成しています。
その際に第一引数に0で割ることはできませんを指定しています。
最後にthrow文で作成したインスタンスを投げています。

No.2 例外を捕捉

catch(Exception $e) では、$eの変数にExceptionクラスのインスタンスを代入しています。
No.1のthrow文で投げられた例外のインスタンスを代入しています。

No.3 インスタンスのgetMessageメソッドへアクセス

catchブロック内に例外が発生したときの処理を記述しています。
$e->getMessage()の意味は前に説明したのでわかるかと思いますが、詳しく見ていきましょう。
インスタンスにアクセスするアロー演算子->を使用しています。
getMessage()はインスタンスメソッドになります。
$eに代入されたインスタンスのgetMessageメソッドへアクセスしているのがわかります。
getMessage()は、例外メッセージを取得するメソッドです。
例外メッセージは、インスタンスを作成するときに第一引数に指定した文字です。
上記のコードでは0で割ることはできませんが例外メッセージになります。

$val20が代入されると、throw文でインスタンス(例外)が投げられます。
例外が投げられた時点でcatchへ処理が移動します。
例えば、throw文の下にコードが記述してあってもその処理は実行されませんので注意してください。
catchするときにインスタンスを変数に代入します。
そのあとにcatchブロック内の処理を実行します。
getMessageメソッドで例外メッセージ(0で割ることはできません)を返す処理をしています。

例外処理の記述はわかりました。
これで自分で作成した関数やクラスなら例外を投げることができます。
次はPHPの定義済みの関数やクラスは例外を投げてくれるのか見ていきましょう。

PHPの定義済みの関数やクラスは例外を投げてくれるのか

PHPマニュアルの「例外」の説明には下記の記述があります。

PHP の内部関数の多くは エラー報告 を使っており、例外を使っているのは新しい オブジェクト指向 の拡張モジュールのみです。 しかし、ErrorException を使えば簡単にエラーを例外に変換することができます。

難しい言葉もありますが、定義済みの関数の多くは、エラー報告だけで例外は投げてくれないけど、エラーを例外に変換することもできるよって書いてあります。
新しい定義済みのクラスの一部は、例外を投げてくれるとも書いてあります。
多くの定義済みの関数と一部のクラスで例外を投げるには、エラーを例外に変換する必要があるということですね。

ちなみに、どの関数・クラスが例外を投げてくれるかは、PHPマニュアルに記述してあります。
例えばPDOクラスのコンストラクタ「PDO::__construct」では、下記のようにPDOExceptioonを投げますと書いてあります。

エラー / 例外
PDO::__construct() は、 指定されたデータベースへの接続に失敗した場合、 PDOException を投げます。

使用する関数やクラスが例外を投げてくれるかは、PHPマニュアルで確認してください。

例外処理の基礎については、理解できたかと思います。
次は、いよいよPDOについて見ていきましょう。

第3章 PDOの基礎

PDOとは

「PHP Data Objects」を略してPDOと呼んでいます。
PDOを一言で言うと、データベース抽象化レイヤの一つです。
何を言っているのか、さっぱりわからないですよね。
わからないときは細分化して考えるのが定石です。
データベース抽象化レイヤの2つに分けて考えてみましょう。

データベースとは、単純にMySQLやPostgreSQL、Oracleなどのデータベース管理システム(DBMS)のことを指しています。
抽象化レイヤをざっくり言うと、PHPとデータベースの間に入って一つの命令で、それぞれのデータベースにいい感じにアクセスしてくれる層(レイヤ)のことです。

序章でmysql関数を記述したのを覚えているでしょうか?

$link = mysql_connect('サーバー名', 'ユーザー名', 'パスワード');

mysql_connectという関数を使っています。
MySQLにアクセスする場合は問題ないですが、PostgreSQLへ変更するとなった場合には、命令を書き換える必要があります。
データベース抽象化レイヤは、通常であればデータベースごとの命令を記述しないといけないところを、一つの命令でいい感じに解釈してくれます。
データベース抽象化レイヤを使うことで、上記のような問題も解決してくれます。

img_pdo.png

おおよそのイメージ図ですが、抽象化レイヤはこんな感じの仕事をしているかと思います。

冒頭で「PDO(PHP Data Objects)を一言で言うと、データベース抽象化レイヤの一つです。」と説明しました。
データベース抽象化レイヤは、PDO以外にもdbxなどいくつか種類があります。
使うデータベース抽象化レイヤによって、対応しているデータベースの種類や処理速度などが異なります。
PDO以外のデータベース抽象化レイヤに興味がある方はググってください。

次はPDOクラスを使ってデータベースに接続する方法を見ていきましょう。

PDOクラスを使ってデータベースに接続する方法

PDOクラスをインスタンス化して、データベースに接続します。
インスタンス化するときに、引数に必要な情報を記述することでデータベースに接続できます。
データベースに接続するときに例外処理する必要がありますが、現時点ではデータベースへ接続する箇所だけみていきます。

$dbh = new PDO('DSN','ユーザー名','パスワード',オプション);

今まで記事を読んでいた方なら、何をしているか理解できると思いますが、詳しく見ていきましょう。
new演算子を使用してPDOクラスのインスタンスを作成しています。
作成したインスタンスを$dbhという変数に代入しています。
変数名なので任意で決められますが、データベースハンドラの略でdbhという変数名を用いることが多々あります。
ここでもdbhという変数名で説明していきます。

PDOクラスのコンストラクタを利用してデータベースに接続しています。

PDOの引数にDSNとありますが、DSNとは「Data Source Name」の略で、ここにはデータベースのサーバー名、データベース名、文字エンコードを指定します。
MySQLに接続するときは、下記のように記述します。

$dbh = new PDO('mysql:host=サーバー名;dbname=データベース名;charset=文字エンコード','ユーザー名','パスワード',オプション);

上記の書き方は、PHP5.3.6以降でしか対応しておりません。
尚、Windows版のPHP5.3.6にはバグがあるらしいので、同環境をお使いの方はググってください。
ここでは、そのような環境を使っている方はいないという前提で以降説明していきます。
PHP5.3.5以前では、DSNに文字エンコードの指定ができません。
ではどのように指定するのかと気になるところですが、その前に文字エンコードの指定とセキュリティについてを簡単に見ていきましょう。

文字エンコードの指定とセキュリティについて

そもそも文字エンコードとは、何のことを指しているかわかりますか?
「UTF-8」や「Shift_JIS」「EUC-JP」などのことです。

文字エンコードとセキュリティが関係あるの?って疑問に思いますが、適切に設定しないとセキュリティの問題を引き起こす可能性があります。

PHPマニュアルの「文字セット」の説明には下記の警告が記述されています。

警告
文字セットと文字のエスケープ

文字セットはきちんと理解して設定しておかないといけません。 すべての操作に影響が及ぶし、セキュリティの問題を引き起こす可能性があるからです。 たとえば、・・・

文字エンコードの設定が重要であることが、ご理解いただけたかと思います。

実際どのようなセキュリティの問題があるかなどの説明はしません。
何となく理解しておりますが、初心者の私が説明すると誤解を招いたり、誤った方法を紹介するおそれがあるからです。

セキュリティについて詳しくしりたい方は、経産省所管の独立行政法人情報処理推進機構(IPA)のウェブサイトに掲載されている「安全なSQLの呼び出し方」をご覧ください。
無料の上、説明がわかりやすいので一読されることをお勧めします。

セキュリティについてググるのもいいですが、紹介している内容が古かったり、誤りがあるときもありますので注意してください。
私は「PHPマニュアル」や「IPA」の情報を参考したり、あとはセキュリティ界隈で有名な徳丸浩さんの「ブログ」や書籍「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」を参考にしています。

今回は、文字エンコードを適切に指定しないとセキュリティの問題を引き起こすということを覚えておいてください。
それと、データベースを扱うにはセキュリティに注意する必要があると覚えておいてください。

PHP5.3.5以前の環境で文字エンコードを指定する方法をみる前にオプションの設定方法を見ていきましょう。

インスタンス化するときにオプションを設定する方法

データベースに接続するコードを思い出してください。

$dbh = new PDO('DSN','ユーザー名','パスワード',オプション);

PDOクラスをインスタンス化するときにオプションを記述することが可能です。
PDOを使う上で、例外を投げるかどうかなど様々な機能があります。
その機能を使うか使わないかをオプションで設定します。

オプションは連想配列で指定します。
連想配列についてわからない方はググってください。
array()を使い変更したい属性 => 値と指定します。
前述したアロー演算子(->)ではなく、ダブルアロー演算子(=>)なので気をつけてください。
全く別物です。
下記のような記述になります。

$dbh = new PDO(
    'DSN',
    'ユーザー名',
    'パスワード',
    array(
        変更したい属性 => ,
        変更したい属性 => ,
    )
);

オプションは何個でも設定できます。
今回は、PDOクラスをインスタンス化するときにオプションを設定しましたが、インスタンス化した後でもオプションの設定は可能です。
もう一つのオプションの設定方法を見ていきましょう。

インスタンス化した後にオプションを設定する方法

オプションを設定するには、setAttributeメソッドを使います。
第1引数に変更したい属性を、第2引数に値を指定します。
インスタンスメソッドへアクセスするためアロー演算子を使用します。

$dbh = new PDO(
    'DSN',
    'ユーザー名',
    'パスワード',
);

$dbh->setAttribute(変更したい属性 , );
$dbh->setAttribute(変更したい属性 , );

次はオプションの設定方法による違いを見ていきましょう。

オプションの設定方法による違いは

オプションの設定する方法を2つ紹介しました。
設定できるオプションの中には、インスタンス化のときだけしか設定できないものがあります。
後からsetAttributeでオプションの設定ができません。
特に理由がなければ、インスタンス化するときに連想配列でオプションを設定するようにしましょう。
次は少し脱線して英語の勉強をしましょう。

Attributeとは?

先ほど、setAttributeというメソッドを使いました。
Attributeはアトリビュートと読みます。
日本語にすると属性という意味です。
先ほど使用したsetAttributeメソッドは、属性をセットするメソッドということですね。
AttributeはAttrと略されて使われることがあります。
Attrと記述があるときは属性と置き換えて考えると理解しやすくなるかと思います。
オプションを設定するときにATTRという言葉が使われますので覚えておいてください。

PHP5.3.5以前の環境で文字エンコードを指定する方法

文字エンコードを指定するには、オプションで文字エンコードを指定します。

文字エンコードを指定するには、PDO::MYSQL_ATTR_INIT_COMMANDという属性にSET NAMES 文字エンコードと値を指定します。

はい、でました。ATTR。
ATTRは先ほど説明しましたが、属性という意味です。
INITは、initialize(イニシャライズ)の略で、初期化という意味です。

PDO::MYSQL_属性_初期化_コマンドと日本語にすると何をしているのか何となく理解できるかと思います。
MySQLの属性を初期化するコマンドなのかなと予想できたのではないでしょうか。
オプションに記述する内容は英語ですので、よく見ると何のオプションかわかるかと思います。

PHPマニュアルの「MySQL 関数 (PDO_MYSQL)」に「PDO::MYSQL_ATTR_INIT_COMMAND」の説明があります。

PDO::MYSQL_ATTR_INIT_COMMAND (integer)
MySQL サーバーへの接続時に実行するコマンドを指定します。 再接続の際には自動的に再実行されます。

この定数を使うのは、新しいデータベースハンドルを作るときの driver_options 配列内だけであることに注意しましょう。

「MySQL サーバーへの接続時に実行するコマンドを指定します」とありますので、先ほどの予想と大体同じ意味ですね。
ここで注目すべきは、「新しいデータベースハンドルを作るときの driver_options 配列内だけであることに注意しましょう」という記述です。
難しい言葉が使われていますが、要はインスタンス化するときのオプションに設定しなさいということです。
インスタンス化した後にsetAttributeメソッドを使って設定してもダメということです。

実際にUTF-8の文字エンコードを指定するには、下記のように記述します。

$dbh = new PDO(
    'DSN',
    'ユーザー名',
    'パスワード',
    array(
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
    )
);

PHP5.3.5以前の環境では上記のように文字エンコードを指定しましょう。
この記述は、あくまでもPHP5.3.5以前での記述です。
PHP5.3.6以上の環境では最初に紹介したようにDSNに文字エンコードを指定してください。

この記述でデータベースに接続することも可能ですが、まだ接続してはいけません。
他のオプションを適宜設定し、最終的に例外処理する必要があります。

他のオプションを設定する前に、もう少しオプションの記述について理解を深めましょう。
先ほど記述したPDO::MYSQL_ATTR_INIT_COMMANDについて詳しく見ていきましょう。

PDO::MYSQL_ATTR_INIT_COMMANDはどのような記述なのか

PDO::MYSQL_ATTR_INIT_COMMANDの意味は、先ほど説明したので、どのような記述なのか詳しく見ていきましょう。
まず、MYSQL_ATTR_INIT_COMMANDは、定義済み定数です。
もう少し詳しくいうと、PDOクラスの定義済み定数になります。
定数についてわからない方はググってください。

PHPでは、定数は全て大文字で表記するのが一般的です。
なので、MYSQL_ATTR_INIT_COMMANDと大文字で表記されているから定数なのかなと推測することができます。
PDO::MYSQL_ATTR_INIT_COMMANDの記述は、スコープ定義演算子で定数にアクセスしているというのことがわかりました。
スコープ定義演算子は、クラスに属する静的プロパティや静的メソッドへアクセスするときに使用しました。
ということはクラスに属する(staticがある)定数にアクセスしているのではないかと推測できますよね。

オプションを設定するのに、PDOクラスの定数を指定しているということがわかりました。
定数の名前をよく見るとMYSQLという表記があります。
設定したオプションは、MYSQLのオプションになります。
データベースの種類によって、オプションの記述が異なります。
PHPマニュアルの「PHP Data Objects」にPDOドライバという記述があります。
その記述の下にデータベース一覧があります。
閲覧したいデータベースを選択して、指定できるオプションを確認してください。
MySQLの場合は「MySQL (PDO) — MySQL 関数 (PDO_MYSQL) 」のページに記載されています。

次は、オプションでエラーモードの設定をし、例外を投げるように変更してみましょう。

オプションで例外を投げる設定に変更する

PDO::ATTR_ERRMODEという属性にPDO::ERRMODE_EXCEPTIONの値を設定することでエラーが発生したときに、PDOExceptionの例外を投げてくれるようになります。

$dbh = new PDO(
    'DSN',
    'ユーザー名',
    'パスワード',
    array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    )
);

これでPDOが発するエラーを例外処理することができるようになりました。
次は、PDOを使うなら是非利用したいプリペアドステートメントについて見ていきましょう。

プリペアドステートメント

プリペアドステートメントと言われてもさっぱりわからないですよね。
まずは、プリペアドステートメントを日本語にしてみましょう。
プリペアとは準備するという意味です。
ステートメントとは命令文のことです。
プリペアドステートメントを日本語にすると「準備する命令文」となります。
何となく意味がわかってきましたね。

データベースを利用するときに、データを取得したり、削除したり、追加したりと同じような命令を何度も行うことがあります。
同じような記述になるため、変更する箇所だけ虫食い状態した命令文を用意します。
この命令文だけを先にデータベース側に送り、解析します。
その後に虫食いにした箇所に当てはめる値だけをデータベース側に送り、先ほどの命令文に値を割り当てて実行します。
命令文を虫食い状態にしたことで、繰り返し使うことができ、処理が効率的になります。
このような仕組みをプリペアドステートメントと呼びます。

データベースを操作するにはSQL文を使用します。
実際にSQL文を使ってプリペアドステートメントについて見ていきましょう。

SQL文の詳しい説明はしませんが、単純な命令文しか使いませんので理解できるかと思います。
SQL文について詳しく知りたい方は、ググってください。

データベースに下記のフルーツというテーブルがあると仮定して説明します。

テーブル名:fruit

name price
リンゴ 100
バナナ 200
オレンジ 300

SQL文のみ記述して説明します。
条件を指定して該当するフィールド名を表示するSQL文は下記のようになります。
フィールド名とは上記のテーブルだと「name」や「price」のことです。

SELECT フィールド名 FROM テーブル名 WHERE 条件

フルーツテーブルのpriceの値が100のフィールド名のみ表示するには下記のように記述します。

SELECT name FROM fruit WHERE price='100'

priceに100の値があるのはリンゴのみですので、実行結果にはリンゴが表示されます。
次はpriceの値に200があるフィールド名のみ表示してみましょう。

SELECT name FROM fruit WHERE price='200'

上記のコードを実行するとバナナが表示されます。
次はpriceの値に300があるフィールド名のみ表示してみましょう。
priceの値しか変わっていないので、SQL文がわからなくてもどのように書くかわかるのではないでしょうか。

SELECT name FROM fruit WHERE price='300';

priceの値に300があるフィールドは、オレンジのみですので実行結果にはオレンジが表示されます。
3回SQL文を書きましたが、priceの値以外は同じ命令文になっています。
プリペアドステートメントを利用して効率化しましょう。

変更したい部分だけ虫食いにします。
虫食いにする部分は?を指定します。

SELECT name FROM fruit WHERE price= ?

後から、?に入る値だけを指定することができます。
非常に効率的になりましたね。
? の部分はプレースホルダと呼びます。
? の部分に値を割り当てることをバインドすると言いますので覚えておいてください。

ちなみに、このプレースホルダは一つのSQL文で幾つも記述することが可能です。
複数使う場合でも下記のように?と記述します。

SELECT name FROM fruit WHERE price= ? AND price = ?

バインドする方法は後ほど説明しますが、バインドするときに何番目のプレースホルダか指定する必要があります。
最初の ? が1番目となり、次の ? が2番目となります。
この番号をパラメータIDと呼びますので覚えておいてください。
パラメータIDは1から数えますので注意してください。

次はプレースホルダについて詳しく見ていきましょう。

プレースホルダ

先ほども説明しましたが、?の部分をプレースホルダと呼びます。
正確には、?なので疑問符プレースホルダと呼びます。
プレースホルダを使うことで後から値を割り当てることができるメリットがありますが、他にもエスケープ処理をしてくれるというメリットがあります。

ユーザーの入力を伴うSQL文は、エスケープ処理を行う必要があります。
エスケープとはコードとして解釈されてしまう特殊な記号(シングルクォートなど)を、コードとして解釈されない文字に変換することです。

先ほどの fruit というデータベースを用いて、エスケープ処理をしないと、どうなるのか説明します。
ユーザーが値段を入力すると該当する商品を表示するページがあったとします。
ユーザーから入力された値をそのまま $price に代入しているとします。

$price = $_POST['price'];

プレースホルダを使わずにユーザーから入力された値をそのままSQL文で利用したとします。

SELECT name FROM fruit WHERE price='$price'

ユーザーが100という値段を入力した場合、下記のようなSQL文になります。

SELECT name FROM fruit WHERE price='100'

ユーザーが100' OR 'A' = 'Aと入力したら

SELECT name FROM fruit WHERE price='100' OR 'A' = 'A'

'A' = 'A'という命令は「全て」を意味するため、全ての情報が表示されてしまいます。
シングルクォートが一つの区切りとなっているため、'A' = 'A'のような別の命令を記述できてしまいます。
SQL文において、シングルクォートのような特別な意味をもつ記号文字を取り除くことをエスケープと呼びます。

本来、エスケープ処理を自分で行う必要がありますが、プレースホルダが自動でエスケープしてくれます。
プレースホルダ便利ですね。
ユーザーの入力が伴うSQL文はエスケープ処理が必要ですので、そのときはプレースホルダを使ってエスケープ処理しましょう。

先ほどSQL文を変更してデータベースを不正に操作しました。
このような手法を「SQLインジェクション」と呼びます。
データベースには個人情報やパスワードなどを保存している場合もあります。
SQLインジェクションなどの脆弱性があるとデータベースに保存している重要な情報が流失する恐れがあります。
データベースを扱う上で、SQLインジェクション対策は必須です。
自分でWebアプリケーションを作成してリリースしたいと考えている方は、先ほども紹介した「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」などを読んで脆弱性対策について理解するようにしてください。

今まで疑問符プレースホルダを使ってきました。
プレースホルダにはもう1種類あります。
名前付きプレースホルダです。
次は名前付きプレースホルダを見ていきましょう。

名前付きプレースホルダを使う方法

疑問符プレースホルダのときに?を記述したところに:名前と記述します。
名前は英字で任意に決められます。

SELECT name FROM fruit WHERE price = :price

ちなみに疑問符プレースホルダと名前付きプレースホルダは混在できませんので、注意してください。
どんどんプレースホルダを使っていきたいところですが、実はプレースホルダには2種類あります。
静的プレースホルダ動的プレースホルダです。
それぞれの違いを詳しく見ていきましょう。

静的プレースホルダと動的プレースホルダ

静的プレースホルダと動的プレースホルダの大きな違いは、バインドするタイミングです。
先ほども説明しましたが、バインドとは値を割り当てることです。
この値を割り当てるタイミングが静的プレースホルダと動的プレースホルダで違います。

静的プレースホルダは、データベース側でバインドします。

img_placeholder_static.png

動的プレースホルダは、バインドしてからデータベース側へ命令文を渡します。

img_placeholder_dynamic.png

大体の解説図ですが、バインドするタイミングが何となくわかるかと思います。
別にバインドするタイミングなんていつでもいいよって思いますよね?
このタイミングが非常に重要なんです。

次は静的プレースホルダと動的プレースホルダのメリット・デメリットを見ていきましょう。

静的プレースホルダと動的プレースホルダのメリット・デメリット

先ほどの解説図をみると、静的プレースホルダと動的プレースホルダではデータをやり取りする回数が違うのがわかるかと思います。
動的プレースホルダはバインドしてからデータベースへ命令するためやり取りする回数が少ないです。
そのため、静的プレースホルダより、動的プレースホルダの方が処理速度が早くなります。

じゃあ、動的プレースホルダを使えばいいのかと思いますが、動的プレースホルダにもデメリットがあります。

先ほど紹介した経産省所管の独立行政法人情報処理推進機構(IPA)のウェブサイトに掲載されている「安全なSQLの呼び出し方」の11頁目に下記のような記述があります。

動的プレースホルダは静的プレースホルダとは異なり、バインド処理を実現するライブラリによっては、SQL構文を変化させるようなSQLインジェクションを許してしまう、脆弱な実装のものが存在する可能性を否定できません。

一概には言えないが動的プレースホルダを使うと、SQLインジェクションを許してしまう可能性があるということです。
この記事を読んでる方で、処理速度を気にするほどデータベースを使っている方はいないと思いますので、特別な理由が無いときは、静的プレースホルダを使うようにしましょう。

静的プレースホルダを使用するには、オプションを設定する必要があります。
PDOには、プリペアドステートメントをエミュレートする機能があります。
エミュレートとはプリペアドステートメントの'ふり'をするということです。
もっと、端的にいうと動的プレースホルダを使う機能ということです。

本来、プリペアドステートメントとは静的プレースホルダのことを指します。
動的プレースホルダは、プリペアドステートメントの'ふり'をしているだけです。

PHP5.2以上では、プリペアドステートメントをエミュレートする機能がONになっています。
動的プレースホルダを使う機能がONになっているということです。
静的プレースホルダを使うために、オプションでエミュレートする機能をOFFにする必要があります。

プレースホルダに関しては、先ほど紹介した経産省所管の独立行政法人情報処理推進機構(IPA)のウェブサイトに掲載されている「安全なSQLの呼び出し方」にわかりやすく説明されています。

セキュリティに関しても記述がありますので、必ず読んでください。

静的プレースホルダを使うようにオプションを設定する

エミュレートをOFFにするには、オプションでPDO::ATTR_EMULATE_PREPARESの属性にfalseの値を指定します。

$dbh = new PDO(
    'DSN',
    'ユーザー名',
    'パスワード',
    array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false,
    )
);

これで静的プレースホルダを使えます。
次は、例外処理しながらデータベースに接続してみましょう。

例外処理しながらデータベースに接続する

PHP5.3.6以降でUTF-8を使用する場合の記述になります。
今までの説明があれば、何をしているかわかるかと思います。

try {

    $dbh = new PDO(
        'mysql:host=サーバー名;dbname=データベース名;charset=utf8',
        'ユーザー名',
        'パスワード',
        array(
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
        )
    );

} catch (PDOException $e) {

    $error = $e->getMessage();

}

データベースに無事接続できました。
次はデータベースに登録されているデータを取得して表示してみましょう。

データベースのデータを取得して表示する

データベースのデータを取得して表示するには、いくつかのメソッドを使います。
一つのメソッドでSQL文を実行してデータを取得できるわけではありません。
データを取得するには主に下記の手順で行います。

No 手順 メソッド
01 SQL文を実行する準備を行う PDO::prepare
02 値をバインドする PDOStatement::bindValue または PDOStatement::bindParam
03 プリペアドステートメントを実行する PDOStatement::execute
04 データを配列などで取得する PDOStatement::fetch または PDOStatement::fetchAll など

手順3のプリペアドステートメントを実行することで、はじめてデータベース側にprepareメソッドに記述したSQL文を送り、データベース側で構文解析をします。
その後にbindValueに記述したバインドする値をデータベース側に送り、先ほどのSQL文に値を割り当てて実行します。
手順1と2の段階では、データベース側とやり取りはしておりません。
手順3ではじめてデータベース側とやり取りをします。
手順4で実行結果のデータを配列で取得して表示するという流れになります。
それぞれの手順ごとに詳しく見ていきましょう。

先ほども使った下記のテーブルがデータベースにあると仮定します。

テーブル名:fruit

name price
リンゴ 100
バナナ 200
オレンジ 300

下記の手順でデータベースに接続していると仮定して、データの取得をしてみましょう。
前に説明しましたが、下記の記述はPHP5.3.6以上でUTF-8を使う場合の接続方法になりますので注意してください。

try {

    $dbh = new PDO(
        'mysql:host=サーバー名;dbname=データベース名;charset=utf8',
        'ユーザー名',
        'パスワード',
        array(
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
        )
    );

} catch (PDOException $e) {

    $error = $e->getMessage();

}

以降、必要な箇所しか記述しません。
データベースを操作するときは、下記のように必ずtryブロック内に記述して例外処理をしてください。

try {

    $dbh = new PDO(
        'mysql:host=サーバー名;dbname=データベース名;charset=utf8',
        'ユーザー名',
        'パスワード',
        array(
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
        )
    );

    // ここに記述してください

} catch (PDOException $e) {

    $error = $e->getMessage();

}

それぞれの手順を詳しく見ていきましょう。

No.1 SQL文を実行する準備を行う

PDOクラスのprepareメソッドを使い、SQL文を実行する準備を行います。
prepareメソッドの返り値としてPDOStatementのインスタンスを受け取ります。
prepareメソッドを使うとPDOStatementをインスタンス化し、作成したインスタンスを返します。
そのため、作成されたインスタンスを変数(下記の記述では$prepare)に代入します。

$prepare = $dbh->prepare('SQL文の文字列')

No.2 値をバインドする

値をバインドするには、PDOStatement::bindValueまたはPDOStatement::bindParamを使います。
ここでは、bindValueを使って値をバインドします。

bindValueメソッドを使うときは、下記のように記述します。

bindValue(パラメータID,バインドする値,データ型

第1引数にはパラメータIDを指定します。
パラメータIDとは名前付きプレースホルダの場合は:名前となります。
疑問符プレースホルダの場合は1からはじまる値になります。
第2引数にはバインドする値を指定します。値は直接入力するか、変数を入れて指定します。
第3引数にはデータ型を指定します。
データ型は、PDO::PARAM_からはじまる定義済み定数を指定します。

主な定義済み定数は下記になります。

定数 意味
PDO::PARAM_STR(デフォルト) 文字列型
PDO::PARAM_INT 整数型
PDO::PARAM_BOOL 真偽型
PDO::PARAM_NULL NULL型

他にもありますので、詳細はPHPマニュアルの定義済み定数を確認してください。
デフォルトでは、PDO::PARAM_STRが指定されます。
省略するとデフォルト値になります。
ここで注意すべきは、第3引数に指定したデータ型に必ず変換されるわけではありません。

例えば下記の記述したとします。

$price = '100';
$prepare = $dbh->prepare('SELECT name FROM fruit WHERE price = ?');
$prepare->bindValue(1,$price,PDO::PARAM_INT);

$priceには'100'という文字列を代入しています。
bindValueの第3引数でPDO::PARAM_INTと整数型を指定しておりますが、文字列として処理されます。
整数型にするには型キャストを行う必要があります。
文字列以外の型を指定するときは、第2引数に型を明示するのが無難です。
bindValueの型指定については、ググってください。

$price = '100';
$prepare = $dbh->prepare('SELECT name FROM fruit WHERE price = ?');
$prepare->bindValue(1,(int)$price,PDO::PARAM_INT);

第2引数に(int)と記述し、型キャストをしております。
型キャストがわからない方は、ググってください。

実際に疑問符プレースホルダを用いた場合と名前付きプレースホルダを用いた場合を見ていきましょう。

疑問符プレースホルダを用いた場合
$val1 = 100;
$val2 = 200;
$prepare = $dbh->prepare('SELECT name FROM fruit WHERE price = ? AND price = ?');
$prepare->bindValue(1,(int)$val1,PDO::PARAM_INT);
$prepare->bindValue(2,(int)$val2,PDO::PARAM_INT);
名前付きプレースホルダを用いた場合
$val1 = 100;
$val2 = 200;
$prepare = $dbh->prepare('SELECT name FROM fruit WHERE price = :value1 AND price = :value2');
$prepare->bindValue(':value1',(int)$val1,PDO::PARAM_INT);
$prepare->bindValue(':value2',(int)$val2,PDO::PARAM_INT);

値をバインドするのにbindValueメソッド以外にbindParamメソッドがあります。
このメソッドは、第2引数には変数のみしか指定できず、変数を参照としてバインドするため少しややこしく、いろいろとあれなので使わなくていいかもしれません。
基本的にbindValueメソッドを使えば問題ないみたいですので、bindParamメソッドは紹介しません。
気になる方はググってください。

No.3 プリペアドステートメントを実行する

プリペアドステートメントを実行するには、PDOStatement::executeメソッドを使います。

$price = 100;
$prepare = $dbh->prepare('SELECT name FROM fruit WHERE price = ? AND price = ?');
$prepare->bindValue(1,(int)$price,PDO::PARAM_INT);
$prepare->execute();

プリペアドステートメントを実行することで、PDOStatementのインスタンスに結果セットが格納されます。
結果セットとはデータベースから取り出されたデータを一時的に保持する仮想的なテーブルのようなものです。
この後に結果セットにあるデータを配列で取得します。

実は、executeメソッドの引数にバインドする値を直接指定することができます。
その場合は、手順2の「値をバインドする」で説明したbindValueなどを省略することが可能です。
但し、PDO::PARAM_STR扱いになるなど注意が必要です。
ここでは説明しませんので、ググってください。

PDOStatementのインスタンスメソッドを使って、データを取得します。
次は、データの取得方法を見ていきましょう。

No.4 データを取得する

データを取得するには、PDOStatement::fetchまたはPDOStatement::fetchAllなどのメソッドを使います。
データを取得するメソッドは、他にもありますので、PHPマニュアルで確認してください。

今回は下記のテーブルがデータベースにあると仮定して説明します。

テーブル名:fruit

name price
apple 100
banana 200
orange 300
リンゴ 100
バナナ 200
オレンジ 300

fetchメソッドでデータを取得する

fetchメソッドは該当するデータを1件のみ配列として返します。
該当するデータがないときはfalseを返します。
そのため、ループ処理をすることで該当する全てのデータを取得することもできます。

fetchメソッドを使って取得したデータを変数$resultに代入します。

$price = 100;
$prepare = $dbh->prepare('SELECT name FROM fruit WHERE price = ?');
$prepare->bindValue(1,(int)$price,PDO::PARAM_INT);
$prepare->execute();

$result = $prepare->fetch();
print_r($result);

fetchメソッドとfetchAllメソッドの引数に定数を指定することで、結果セットから取り出したデータをどのような形式で格納するか指定することができます。
主な定義済み定数は下記になります。

定数 意味
PDO::FETCH_BOTH(デフォルト) フィールド名と 0 から始まる添字を付けた配列を返す
PDO::FETCH_ASSOC フィールド名で添字を付けた配列を返す
PDO::FETCH_NUM 0 から始まる添字を付けた配列を返す

定数は他にもありますので、PHPマニュアルを確認してください。
実際に定数を入れて実行した結果から、それぞれの違いを見ていきましょう。

fetchメソッドの引数に「PDO::FETCH_BOTH(デフォルト)」を指定する

引数にPDO::FETCH_BOTHを指定します。
デフォルトなので省略できます。

$result = $prepare->fetch(PDO::FETCH_BOTH);
print_r($result);

print_rで出力した結果が下記になります。

Array
(
    [name] => apple
    [0] => apple
    [price] => 100
    [1] => 100
)

フィールド名と0から始める添字を付けた配列を返しているのがわかります。

fetchメソッドの引数に「PDO::FETCH_ASSOC」を指定する

引数にPDO::FETCH_ASSOCを指定します。

$result = $prepare->fetch(PDO::FETCH_ASSOC);
print_r($result);

print_rで出力した結果が下記になります。

Array
(
    [name] => apple
    [price] => 100
)

フィールド名で添字を付けた配列を返しているのがわかります。

fetchメソッドの引数に「PDO::FETCH_NUM」を指定する

引数にPDO::FETCH_NUMを指定します。

$result = $prepare->fetch(PDO::FETCH_NUM);
print_r($result);

print_rで出力した結果が下記になります。

Array
(
    [0] => apple
    [1] => 100
)

0から始まる添字を付けた配列を返しているのがわかります。
次はfetchAllメソッドを使ってデータを取得してみましょう。

fetchAllメソッドでデータを取得する

fetchAllメソッドは該当する全てのデータを配列として返します。
fetchメソッドを使って取得したデータを変数$resultに代入します。

$price = 100;
$prepare = $dbh->prepare('SELECT name FROM fruit WHERE price = ?');
$prepare->bindValue(1,(int)$price,PDO::PARAM_INT);
$prepare->execute();

$result = $prepare->fetchAll();
print_r($result);

先ほども説明しましたが、fetchAllメソッドにも引数に定数を指定することで、結果セットから取り出したデータをどのような形式で格納するか指定することができます。

先ほどと同じく、実際に定数を入れて実行した結果から、それぞれの違いを見ていきましょう。

fetchAllメソッドの引数に「PDO::FETCH_BOTH(デフォルト)」を指定する

引数にPDO::FETCH_BOTHを指定します。
デフォルトなので省略できます。

$result = $prepare->fetchAll(PDO::FETCH_BOTH);
print_r($result);

print_rで出力した結果が下記になります。

Array
(
    [0] => Array
        (
            [name] => apple
            [0] => apple
            [price] => 100
            [1] => 100
        )

    [1] => Array
        (
            [name] => リンゴ
            [0] => リンゴ
            [price] => 100
            [1] => 100
        )

)

フィールド名と0から始める添字を付けた配列を返しているのがわかります。
fetchメソッドとは違い、該当する全てのデータを配列として取得します。

fetchAllメソッドの引数に「PDO::FETCH_ASSOC」を指定する

引数にPDO::FETCH_ASSOCを指定します。

$result = $prepare->fetchAll(PDO::FETCH_ASSOC);
print_r($result);

print_rで出力した結果が下記になります。

Array
(
    [0] => Array
        (
            [name] => apple
            [price] => 100
        )

    [1] => Array
        (
            [name] => リンゴ
            [price] => 100
        )

)

フィールド名で添字を付けた配列を返しているのがわかります。
fetchメソッドとは違い、該当する全てのデータを配列として取得します。

fetchAllメソッドの引数に「PDO::FETCH_NUM」を指定する

引数にPDO::FETCH_NUMを指定します。

$result = $prepare->fetchAll(PDO::FETCH_NUM);
print_r($result);

print_rで出力した結果が下記になります。

Array
(
    [0] => Array
        (
            [0] => apple
            [1] => 100
        )

    [1] => Array
        (
            [0] => リンゴ
            [1] => 100
        )

)

0から始まる添字を付けた配列を返しているのがわかります。
fetchメソッドとは違い、該当する全てのデータを配列として取得します。

データを取得することができました。
実はプリペアドステートメントを使わずにSQL文を実行する方法もあります。
次はその方法を見ていきましょう。

結果を必要とするSQL文を実行するqueryメソッド

先ほどはprepareメソッドでSQL文を準備した後にexecuteメソッドでプリペアドステートメントを実行しました。
プリペアドステートメントは使わずに、そのままSQL文を実行できるqueryメソッドというのがあります。
プリペアドステートメントを使わないので、自分でエスケープ処理をする必要があります。
ユーザーからの入力を伴わず、SQL文が固定で、SELECTなどの結果を必要とするときだけqueryメソッドを使うようにしましょう。
queryメソッドを使ってデータを取得するには下記の手順になります。

No 手順 メソッド
01 SQL文を実行する PDO::query
02 データを行または配列で取得する PDOStatement::fetchPDOStatement::fetchAll など

下記のように記述します。

$result = $dbh->query('SELECT name FROM fruit WHERE price = 100');
$result = $result->fetch();

先ほど紹介したprepareメソッドと同じく、PDOStatementのインスタンスに結果セットが格納されます。
プリペアドステートメントを使わずにデータを取得することができました。
最初に説明しましたが、この方法は自分でエスケープ処理する必要がありますので、使うときは注意してください。

結果を必要としないSQLを実行するexecメソッド

こちらもプリペアドステートメントを使わないので、自分でエスケープ処理をする必要がありますので、使うときは注意してください。
ユーザーからの入力を伴わず、SQL文が固定で、INSERTやUPDATEなど結果を必要としないときだけexecメソッドを使いましょう。
正確には、何も返らないわけではなく、作用した行数が返ってきます。
例えば、execメソッドの引数にDELETE文を記述して実行し、データベースから1行削除されたら1と返します。
execメソッドのみで完結します。

No 手順 メソッド
01 SQL文を実行する PDO::exec

下記のように記述します。

$result = $dbh->exec('DELETE FROM fruit WHERE price = 100');

しつこいですが、ユーザーからの入力を伴うSQL文は、エスケープ処理する必要があります。
ユーザーからの入力を伴うSQL文のときは、プレースホルダを使いましょう。

これでPDOを使ってデータベースの接続からデータの取得までひと通りできるようになりました。
今回は、これで終わりです。
お疲れ様でした。

最後に

クラスと例外処理、そしてPDOについて基礎の基礎は理解できたかと思います。
今回はPDOでデータベースに接続するのが目的なので、クラスや例外処理、PDOについて必要最低限の基礎しか触れていません。
より深く、より正確に理解するために下記の記事も読んでください。
詳しく説明していない箇所についてはググってくださいと書きましたが、下記の記事に大体書いてあります。

また、POSTやGETなどでユーザーから値を受け取るときは、入力された値を必ずチェックする必要があります。
理解していない方は、下記の記事も必ず読んでください。

そもそも、GETやPOSTを理解していない方は、下記を読んでください。

PHP関連の記事をまとめていますので、気になるものがあればご覧ください。
超入門というのは徹底入門ではなく、初学者向けに一つ一つ冗長に説明している超入門記事という意味です。

今回の記事で超初心者の理解が少しでも深まれば幸いです。
また、今回の内容では説明が不十分な箇所も多々あるかと思います。
興味をもった箇所、疑問に思った箇所は、各自で調べて学んでください。
特にデータベースを利用したいと考えている方は、セキュリティについての理解が欠かせません。
セキュリティについても学ぶようにしてください。
最後まで読んでいただき、ありがとうございました。

note

note でも記事を公開してるので、興味がある方はご覧ください。

【初学者向けコードリーディング】 PHP の TODO アプリのコードを一緒に読み解こう

7968
学んだことを投稿していきます。誤りがあればご指摘ください。 note でも記事を投稿しています。
https://note.com/7968
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした