PHP Logo オブジェクト

更新日:2018年11月25日

作成日:2016年03月14日

オブジェクト

PHP でのオブジェクトとは、データである変数とそのデータを操作する関数をまとめたものと言えます。

  • 配列:複数のデータ(変数)をまとめたもの
  • 関数:1つの処理手続きをまとめたもの
  • オブジェクト:複数の変数と複数の関数をセットにしてまとめたもの

オブジェクトの持つ変数のことを「プロパティ」、オブジェクトの持つ関数のことを「メソッド」と呼びます。

  • プロパティ(またはメンバー変数):オブジェクトの持つ変数
  • メソッド(またはメンバー関数):オブジェクトの持つ関数

クラス

クラスは、特定のオブジェクトを表すための雛形または設計図になります。

オブジェクトを使うには、オブジェクトの雛形となるクラスを定義する必要があります。以下はクラスの定義の例です。

class クラス名
{
  //プロパティ
  private $プロパティ名
  
  //メソッド
  function メソッド名(引数) {
    処理の記述
  }
}
  • 「class」キーワードに続けてクラス名を記述します。
  • その後の { } の中にオブジェクトのプロパティ(メンバー変数)やメソッド(メンバー関数)を定義(宣言)します。
  • プロパティは private などの「アクセス修飾子」の後にドルマーク($)を付けて名前を記述します。
  • メソッドは通常のユーザー定義関数と同じように定義します。メソッドにも「アクセス修飾子」を指定することができます。

アクセス修飾子

アクセス修飾子は、プロパティやメソッドへのアクセス制限を指定するキーワードで、「public」「protected」「private」の3種類があります。(PHP5 から導入)

アクセス修飾子は、クラス外部からオブジェクトのプロパティやメソッドを操作されないようにするための仕組みです。

アクセス修飾子を省略した場合は、そのメソッド、プロパティは public 扱いと見なされ、デフォルトでどこからでもアクセス可能になります。

アクセス修飾子
キーワード 意味
public どこからでもアクセス可能(デフォルト)
protected 定義するクラス内と、そのクラスを継承したクラスからアクセス可能
private 定義するクラス内からのみアクセス可能

アクセス修飾子はプロパティやメソッドへのアクセスを制限する仕組みで、クラス内部の機能を外部から隠蔽することを「カプセル化」と言います。

PHP4 には、アクセス修飾子はなく「var」が使われていて、「var」は public と同じでどこからでもアクセス可能という意味になります。

クラスの作成

以下は、人のデータを処理する「Person」クラスの例です。この「Person」クラスには、「名前」「性別」「生年月日」のプロパティと、年齢を返すメソッドを用意します。

規則ではありませんが、クラスは1クラス1ファイルで管理し、そのファイル名は「クラス名.class.php」とするのが一般的です。

まず「class」キーワードに続けてクラス名「Person」を記述します。クラス名の頭文字は大文字とするのが通例です。

Person.class.php

<?php
class Person 
{
  //プロパティの定義
  public $name;
  private $_gender;
  private $_birthday;
  
  //コンストラクタ
  function __construct($name, $gender, $birthday) {
    $this -> name = $name;
    $this -> _gender = $gender;
    $this -> _birthday = $birthday;
  }
  
  //メソッドの定義
  function get_age() {
    $age = floor((intval(date('Ymd')) - intval($this -> _birthday))/10000);
    return $age; 
  }
}
?>
プロパティの宣言

プロパティとして以下を宣言(定義)しています。

「アクセス修飾子」の後にドルマーク($)を付けて名前を記述します。

//プロパティの定義
public $name;
private $_gender;
private $_birthday;
コンストラクタ

オブジェクトを使うには、雛形のクラスからオブジェクトを生成(インスタンス化)する必要があります。

コンストラクタは、クラスからオブジェクトが生成(インスタンス化)される時に自動的に実行される特別なメソッド(関数)のことで、オブジェクトの初期化処理を行います。

PHP5 からはコンストラクタのメソッド名は、「__construct() 」と決まっています(最初のアンダースコアは2つです)。

一般的にコンストラクタは、プロパティの初期化や、クラスで使用するリソースなどの初期化といった処理(オブジェクトの初期化処理)を記述します。

また、初期化処理が不要な場合、コンストラクタは省略可能です。

//コンストラクタ
function __construct($name, $gender, $birthday) {
  $this -> name = $name;
  $this -> _gender = $gender;
  $this -> _birthday = $birthday;
}

$this

$this はオブジェクト(インスタンス)自身を示す特別な変数で、擬似変数とも呼ばれます。

そのクラスのプロパティにアクセスするには、クラス内では「$this->プロパティ名」のように記述します(-> の後に「$」は付けません)。

上記のコンストラクタでは、引数で与えられた「名前」「性別」「生年月日」のデータをそれぞれのプロパティに代入しています。

メソッドの定義

「Person」クラスでは、年齢を返すメソッド「get_age()」が定義されています。

メソッド「get_age()」は、生年月日から年齢を計算して返す処理です。

//メソッドの定義
function get_age() {
  $age = floor((intval(date('Ymd')) - intval($this -> _birthday))/10000);
  return $age; 
}

年齢は、(今日の日付-誕生日の日付)/10000 で求めることができます。

date() 関数の引数に「Ymd」を指定して yyyymmdd の形式で今日の日付を取得して、intval() 関数で整数値(integer)に変換しています。

また、割り算の結果を floor() で端数を切り捨てます。最後に return 文で計算結果の年齢を返しています。

オブジェクトの生成

作成した「Person」クラスを使って、オブジェクトを生成するには以下のようにします。

<?php
//Person クラスの読み込み
require 'Person.class.php';

//Person オブジェクトを生成して変数に代入
$john_smith = new Person('John Smith', 'male', '19820312');

//Person オブジェクトのプロパティにアクセス
echo '名前: ' . $john_smith->name . '<br>';

//Person オブジェクトのプロパティ(private)にアクセス
//echo '性別: ' . $john_smith->_gender ;  //エラーになる

//Person オブジェクトの get_age() メソッドの呼び出し
echo '年齢: ' . $john_smith->get_age() . '才<br>';
?>    

実行結果

名前: John Smith
年齢: 36才
オブジェクトの初期化

まず雛形であるクラスから、「new」演算子を使って実体であるオブジェクトを生成します。

生成されたオブジェクトを「インスタンス(実体)」と呼ぶこともあります。またオブジェクトを生成することを「インスタンス化」とも言います。

1つのクラスからいくつでもオブジェクト(インスタンス)を生成することができます。

オブジェクトの生成(インスタンス化)時には、コンストラクタが実行され、与えられた引数でプロパティが初期化されます。

「Person」クラスからオブジェクトを生成するには、以下のようにします。

$john_smith = new Person('John Smith', 'male', '19820312');

与えられた3つの引数は、コンストラクタ __construct($name, $gender, $birthday) に渡され、コンストラクタが実行されてオブジェクトが生成されます。生成された「Person」オブジェクトを代入演算子で変数「$john_smith」に代入しています。

変数「$john_smith」には、生成された「Person」オブジェクト(インスタンス)が入っています。

インスタンス = new クラス名( 引数 );
プロパティへのアクセス

オブジェクトのプロパティにアクセスするには「インスタンス名->プロパティ名」とします。

インスタンス名->プロパティ名;

以下では、「Person」オブジェクトの「$name」プロパティにアクセスして、echo 文で出力しています。

echo '名前: ' . $john_smith->name . '<br>';

「Person」クラスの定義で、「$name」は public ですが、「$_gender」は private なので、クラス外部からはアクセスできないので、アクセスしようとするとエラー(Fatal Error)になります。

メソッドの呼び出し

オブジェクトのメソッドを呼び出すには「インスタンス名->メソッド名()」とします。

メソッドには最後に () が付くので、() の有無でメソッドがプロパティかの区別ができます。

インスタンス名->メソッド名();

以下では、「Person」オブジェクトの「get_age()」メソッドを呼び出して、echo 文で出力しています。

echo '年齢: ' . $john_smith->get_age() . '才<br>';

アクセサメソッド

アクセス修飾子はプロパティやメソッドへのアクセスを制御する仕組みです。クラス外部からプロパティやメソッドを直接操作した場合、問題が発生する可能性がある場合などはアクセス修飾子でプロパティを private を使って外部からアクセスできないようにします。但し、そのままではプロパティの値を取得したり、設定することができないのでそれらのプロパティにアクセスするための public メソッドを用意します。

以下は、三角形の面積を求めるクラス「MyTriangle」の例です。(通常クラスは1クラス1ファイルで管理しますが、便宜上この例ではクラス定義とそのクラスを使用するコードを同じファイルに記述しています。)

<?php
class MyTriangle {
  private $width;
  private $height;
  public function __construct($width, $height) {
    $this->width = $width;
    $this->height = $height;
    echo __CLASS__ . 'クラスがインスタンス化されました<br>';
  }
  public function triangle_area() {
    return $this->width * $this->height / 2;
  }
}

$obj = new MyTriangle(50, 20);
echo '三角形の面積は' . $obj->triangle_area() . 'です。<br>';
?>    

実行結果

MyTriangleクラスがインスタンス化されました
三角形の面積は500です。

上記のクラス「MyTriangle」では、プロパティ $width と $height は private にしているため、値を取得したり設定することができません。そこで、これらのプロパティにアクセスするための public メソッドとして以下を用意します。

ゲッターメソッド
プロパティの値を取得するためのメソッド
一般的に get_プロパティ名 という名前にします。
例:get_width(), get_height()
public function get_プロパティ名() {
  return $this->プロパティ名;
}
セッターメソッド
プロパティの値を設定するためのメソッド
一般的に set_プロパティ名 という名前にします。
例:set_width(), set_height()
public function set_プロパティ名(引数) {
  $this->プロパティ名 = 引数;  
}

ゲッターメソッドやセッターメソッドのように、クラス内の private 変数(プロパティ)にアクセスするためのメソッドをアクセサメソッドと呼びます。

アクセサメソッドを用意することで以下のようなメリットがあります。

  • 読み書きの制御が可能になる
  • プロパティの値を設定する際に値の検証が可能になる
  • プロパティの値を取得・参照する際に値の加工が可能になる

以下のクラス「MyTriangle2」では、ゲッターメソッドとセッターメソッドを追加しています。

また、セッターメソッドでは、与えられた引数が数値の場合のみ(is_numeric() が True の場合のみ)プロパティに値を設定するようにしています。

<?php
class MyTriangle2 {
  private $width;
  private $height;
  public function __construct($width, $height) {
    $this->width = $width;
    $this->height = $height;
    echo __CLASS__ . 'クラスがインスタンス化されました<br>';
  }
  public function triangle_area() {
    return $this->width * $this->height / 2;
  }
  public function get_width() { //ゲッターメソッド
    return $this->width;
  }
  public function get_height() { //ゲッターメソッド
    return $this->height;
  }
  public function set_width($width) { //セッターメソッド
    if(is_numeric($width)) {
      $this->width = $width;
    }  
  }
  public function set_height($height) { //セッターメソッド
    if(is_numeric($height)) {
      $this->height = $height;
    }  
  }
}

$obj = new MyTriangle2(50, 20);
echo '幅'.$obj->get_width().',高さ'.$obj->get_height().
'の三角形の面積は'.$obj->triangle_area().'です。<br>';
$obj->set_width(20);
$obj->set_height(30);
echo '幅'.$obj->get_width().',高さ'.$obj->get_height().
'の三角形の面積は'.$obj->triangle_area().'です。<br>';
?>

実行結果

MyTriangle2クラスがインスタンス化されました
幅50,高さ20の三角形の面積は500です。
幅20,高さ30の三角形の面積は300です。

静的メソッド

通常クラスを利用する場合、インスタンス化を行って「インスタンス名->メソッド名()」としてオブジェクトのメソッドを呼び出しますが、例外的にインスタンス化を行わなくても利用できるメソッドがあり、このようなメソッドを静的メソッドと呼びます。

静的メソッドを定義するには、メソッドの定義の際に static 修飾子を付けます。

<?php
class MyTriangle3 {
  public static function triangle_area($width, $height) {
    return $width * $height / 2 ;
  }
}
?>

クラス外部から静的メソッドを呼び出すには、スコープ定義演算子「::」を使用します。

スコープ定義演算子「::」を使用することで、クラス名から直接メソッドを呼び出すことが可能になります。

require 'MyTriangle3.class.php';
echo '三角形の面積は' . MyTriangle3::triangle_area(10, 20) . 'です。<br>';

実行結果

三角形の面積は100です。

インスタンス化が必要になるのは、メソッドを実行する際にオブジェクトに設定されたプロパティの値を使用しなければならない場合などで、そのようにオブジェクトの状態を利用・保持するメソッドは「静的」にクラスから直接呼び出すことはできません。

固有の値を必要としない、単に外部からの値を処理するようなメソッドの場合は「静的メソッド」として宣言しておくと便利です。

但し「静的メソッド」は、クラス内のプロパティを参照・設定することはできません($this キーワードを使用することはできません)。

クラス内定数

PHP5 から const キーワードを利用することで、クラス内で定数を定義することができます。

また、クラス内定数を参照するには、静的メソッドと同様に「::」演算子を使用します。

クラス内定数を利用することで、特定のクラスでしか使用しない定数をクラス内部で管理することが可能になります。

<?php
class MySampleClass {
  const URL = 'http://webdesingleaves.com';
}

echo 'サイト URL:' . MySampleClass::URL . '<br>';
?>

実行結果

サイト URL:http://webdesingleaves.com

継承

あるクラスに含まれるプロパティやメソッドを引き継ぎながら、新たな機能を追加したり、元の機能を修正したりする機能を「継承」と言います。

別の言い方をすると、「継承」とはあるクラスを元に、そのプロパティやメソッドを引き継いで新しいクラスを作成することです。

例えば、ある既存のクラスとほとんど同一の機能を持ったクラスを定義する場合、そのクラスを最初から定義するのは無駄が多くなってしまいますが、継承を利用するば、元になるクラスの機能を引き継ぎつつ新たに必要な機能だけを追加すれば良いので効率的です。

継承に際して、元になるクラスのことを「スーパークラス(親クラス、基底クラス)」、継承によって作成されたクラスのことを「サブクラス(子クラス、派生クラス)」と呼びます。

継承を行うには、以下のようにクラスの宣言で、「extends」を使ってスーパークラスを指定します。

class サブクラス名 extends スーパークラス名 {
  クラスの記述
}

以下の例では、スーパークラス「MyValue」を継承してサブクラス「MySubValue」を定義しています。

<?php
class MyValue {
  protected $value;  //継承したクラスまで参照可能な protected を指定
  public function __construct($value) {
    $this->value = $value;
  }
  public function showValue() {
    echo '値は ' . $this->value . ' です。<br>';
  }
}

class MySubValue extends MyValue {  //MyValue を継承
  public function showValue() {  //showValue()を上書き(オーバーライド)
    echo 'The value is ' .$this->value . '. <br>';
  }
}

$obj1 = new MyValue('PHP');
$obj1->showValue();
$obj2 = new MySubValue('"Java"');
$obj2->showValue();
?>

実行結果

値は PHP です。
The value is "Java".

オーバーライド

「MyValue」クラスで定義した showValue() メソッドが、サブクラス「MySubValue」で上書きされています。このようにスーパークラスの機能をサブクラスで上書きすることを「オーバーライド」と言います。

オーバーライドされなかったスーパークラスのメソッドは、そのまま引き継がれ、自分自身で定義したメソッドのように利用することができます。

メソッドをオーバーライドする場合、引数は基本的にスーパークラスと同じでなければいけませんが、デフォルト値を持つ引数を追加することは可能です。

<?php
class Foo {
  public function hoge(){
    echo "Hello Universe!";
  }

}
class Bar extends Foo{
  //デフォルト値を指定すれば、引数を追加することができます
  public function hoge($string = "デフォルト値"){
    echo "$string";
  }
}
$foo = new Foo();
$foo -> hoge() ;
echo "<br>";
$bar = new Bar();
$bar -> hoge() ;
echo "<br>";
$bar -> hoge("fuga") ;
echo "<br>";
?> 

実行結果

Hello Universe!
デフォルト値
fuga

parent キーワード

スーパークラスの機能を書き換えるのではなく、スーパークラスの機能を引き継ぎながら、サブクラス側で機能を追加したい場合は、以下のように parent キーワードとスコープ定義演算子「::」を使ってスーパークラスのメソッドを呼び出すことが可能です。

<?php
class MyValue {
  protected $value;  //継承したクラスまで参照可能な protected を指定
  public function __construct($value) {
    $this->value = $value;
  }
  public function showValue() {
    echo '値は ' . $this->value . ' です。<br>';
  }
}

class MySubValue2 extends MyValue {  //MyValue を継承
  public function showValue() {
    parent::showValue();  //スーパークラスのメソッドを呼び出し
    echo 'The value is ' .$this->value . '. <br>';
  }
}
$obj3 = new MySubValue2('"jQuery"');
$obj3->showValue();
?>

実行結果

値は "jQuery" です。
The value is "jQuery".

parent キーワードを利用すれば、サブクラスでスーパークラスのメソッドを少しだけ変更したい場合など、メソッドを一から書き直す必要がなくなります。

以下の例では、parent::を使って、サブクラスのコンストラクタからスーパークラスのコンストラクタを呼び出しています。

また、以下の例のようにサブクラスでコンストラクタを定義した場合、スーパークラスのコンストラクタは自動では呼ばれなくなります。

その場合、親クラスのコンストラクタを実行するには、子クラスのコンストラクタの中で parent::__construct() を呼び出す必要があります。

サブクラスでコンストラクタを定義しなかった場合は、スーパークラスのコンストラクタが自動で呼ばれます。

<?php
class MyParent {
  protected $name;
  public function __construct($name){
    $this->name = $name;
    echo "名前:". $this->name. "<br>";
  }
}
class MyChild extends MyParent {
  private $age;
  public function __construct($name, $age){
    parent::__construct($name);
    $this->age = $age;
    echo "年齢:". $this->age ."<br>";
  }
}
$myChild = new MyChild('John', '33');
?>

実行結果

名前:John
年齢:33

final 修飾子

final 修飾子は、特定のメソッドをオーバーライドさせないように制限するための修飾子です。

final 修飾子が付いたメソッドを、サブクラス側でオーバーライドしようとすると、「Fatal error: Cannot override final method MyValue::showValue() 」のようなエラーが発生します。

<?php
class MyValue {
  protected $value; 
  public function __construct($value) {
    $this->value = $value;
  }
  public final function showValue() {  //final 修飾子を指定
    echo '値は ' . $this->value . ' です。<br>';
  }
}

class MySubValue extends MyValue {  //MyValue を継承
  public function showValue() {  
    //showValue()をオーバーライドしようとするとfinal 修飾子のためエラーになる
    echo 'The value is ' .$this->value . '. <br>';
  }
}

?>

あるメソッドがサブクラスで不用意に書き換えられてしまうと、動作に不具合が発生する恐れがあるようなメソッドに対しては、予め final 修飾子を付けておくことで、継承によるトラブルを防ぐことができます。

基本的に、オーバーライドを想定しないメソッドに関しては、final 修飾子を付けるようにすると良いでしょう。

ポリモーフィズム

ポリモーフィズムとはクラスは異なっても同名のメソッドで色々な動きを実現させる事です。

別の言い方をすると、ポリモーフィズムとは同名のメソッドで異なる動きを実現することを言います。

以下の例では、「Shape」クラスの getArea() メソッドでポリモーフィズムを行っています。

<?php
class Shape {
  protected $width;
  protected $height;
  public function __construct($width, $height) {
    $this->width = $width;
    $this->height = $height;
  }
  protected function getArea() {}
}

class Square extends Shape {
  public final function getArea() {  //getArea() をオーバーライド
    return $this->width * $this->height;
  }
}
class Triangle extends Shape {
  public final function getArea() {  //getArea() をオーバーライド
    return $this->width * $this->height / 2;
  }
}

$square = new Square(10, 20);
$triangle = new Triangle(10, 20);
echo '四角形の面積は '. $square->getArea(). ' です。<br>';
echo '三角形の面積は '. $triangle->getArea(). 'です。<br>';
?>

実行結果

四角形の面積は 200 です。
三角形の面積は 100です。

ポリモーフィズムのメリットは、同じ目的の機能に同じ名前のメソッドを指定できる点で、関連するクラスを利用する場合に、異なるメソッド名を覚える必要がなくなります。

但し、この例のように単なる継承とオーバーライドだけでは、それぞれのサブクラスが同名のメソッドを持つことが保証されないので、「抽象メソッド」という仕組みを利用する必要があります。

抽象メソッド

抽象メソッドとは、動作記述のない空のメソッドで、必ずサブクラスでオーバーライドする必要があります。

クラス内の全ての抽象メソッドはオーバーライドされなければ、そのクラスはインスタンス化することができず、この仕組みにより同名のメソッドがサブクラスで定義されることを保証します。

また、抽象メソッドを含むクラスのことを「抽象クラス」と言い、class キーワードの前に abstract 修飾子を付ける必要があります。

抽象クラスのサブクラスが全ての抽象メソッドをオーバーライドしていない場合、PHP はエラー(Fatal Error)を発生します。

<?php
abstract class Shape2 {  //抽象クラス
  protected $width;
  protected $height;
  public function __construct($width, $height) {
    $this->width = $width;
    $this->height = $height;
  }
  //抽象メソッドの定義(中身を持つことはできない)
  protected abstract function getArea() ;
}

class Square2 extends Shape2 {
  public final function getArea() {
    return $this->width * $this->height;
  }
}
class Triangle2 extends Shape2 {
  public final function getArea() {
    return $this->width * $this->height / 2;
  }
}

$square2 = new Square2(10, 20);
$triangle2 = new Triangle2(10, 20);
echo '四角形の面積は '. $square2->getArea(). ' です。<br>';
echo '三角形の面積は '. $triangle2->getArea(). 'です。<br>'; 
?>

実行結果

四角形の面積は 200 です。
三角形の面積は 100です。

抽象メソッドはサブクラスで必ずオーバーライドされなければならないので、スーパークラスの側では中身を持つことができないため、getArea() メソッドの定義では空のブロック {} 自体も記述しません(10行目)。

また、抽象クラスからは、直接インスタンスを生成することはできません。この例のように抽象クラスの派生クラスを作成し、そのクラスからインスタンスを生成します。

インターフェース

PHP では1つのサブクラスが同時に複数のスーパークラスを継承すること(多重継承)ができません。

これはポリモーフィズムを実現したい場合、すべての機能(メソッド)をひとつの抽象クラスに含めなければいけないことを意味し、サブクラスがスーパークラスの特定の機能(メソッド)を必要としない場合でも、それらの機能をオーバーライドしなければならなくなるためコードが冗長になってしまいます。

PHP ではこのような場合、インターフェースを利用します。

インターフェースはクラスとは異なり、実装のない関数(抽象メソッド)のみを記述したものです。

インターフェースを定義する場合は、class キーワードの代わりに、interface キーワードを使用します。そしてインターフェースのメソッドは、抽象メソッドであることは明らかなので、abstract 修飾子は必要ありません。

また、インターフェース内で記述するメソッドは public になり、protected や private は使用できません。

以下は、前述の抽象クラス「Shape2」をインターフェースを使って書き換える例です。

<?php
interface Shape3 {
  public function getArea();
}
?>

インターフェースとクラスの違いは、多重継承が可能であると言うことになります。

メソッドを目的に合わせて、異なるインターフェースの中で宣言して、サブクラス側で必要なメソッドを含むインターフェースを実装するようにします。

インターフェースを実装(implements)する場合は、extends キーワードの代わりに implements キーワードを使用します。

また、複数のインターフェースを実装する場合は、インターフェース名をカンマ区切りで記述します。

<?php
interface Shape3 {  //インターフェース
  public function getArea();
}
interface Color {  //インターフェース
  public function get_color();
}

class Square3 implements Shape3 {  //Shape3 を実装
  private $width;
  private $height;
  public function __construct($width, $height) {
    $this->width = $width;
    $this->height = $height;
  }
  public final function getArea() {
    return $this->width * $this->height;
  }
}

class Triangle3 implements Shape3, Color {  //Shape3 と Color を実装
  private $width;
  private $height;
  private $color;
  public function __construct($width, $height, $color) {
    $this->width = $width;
    $this->height = $height;
    $this->color = $color;
  }
  public final function getArea() {
    return $this->width * $this->height / 2;
  }
  public final function get_color() {
    return $this->color;
  }
}


$square3 = new Square3(10, 20);
$triangle3 = new Triangle3(10, 20, 'green');
echo '四角形の面積は '. $square3->getArea(). ' です。<br>';
echo '三角形の面積は '. $triangle3->getArea(). 'です。<br>';
echo '三角形の色は '. $triangle3->get_color(). 'です。<br>';
?>

実行結果

四角形の面積は 200 です。
三角形の面積は 100です。
三角形の色は greenです。

例外処理

プログラムを実行すると、予想外のエラーが発生することがあります。例えば、存在しないファイルを指定した、数値計算でゼロで除算した、ユーザーの入力値が不正だった等、このようなエラーを例外(Exception)と言います。

PHP5 から例外処理として、try ~ catch 構文が使えるようになりました。以下が try ~ catch 構文です。

try {
  例外が発生する可能性のあるコード/try ブロック
}catch(例外の型 例外を受け取る変数) {
  例外が発生した場合の処理/catch ブロック
}

例外が発生する可能性のある処理を try ブロックに記述すると、例外が発生した場合に処理を強制に catch ブロックに移すことができます。

また、異なる型の例外を捕捉するために複数の catch フロックを使用することができます。

但し、PHP の内部関数の多くは例外を発生させるのではなく、エラーを報告(通知)します。これは、PHP5 まで例外がサポートされていなかったためです。

そのため例外を使っているのは新しいオブジェクト指向の拡張モジュールのみで、多くの場合、例外を自分で発生させる必要があります。

また、明示的に例外を発生させることを「例外を投げる(スローする)」と言います。

以下は除算をする独自定義関数 div() を try ~ catch 構文を使って記述した例です。

function div($val1, $val2){
  try{
    if($val2 === 0){
      throw new Exception('0 で割る事は出来ません!');
    }else if(!is_numeric($val1) || !is_numeric($val2)){
      throw new Exception('不正な値です。');
    }
    return $val1 / $val2;
  }catch(Exception $e){
    return $e->getMessage();
  }
}
echo div(10, 25). "<br>";
echo div(0, 0.1). "<br>";
echo div(10, "x"). "<br>";
echo div(30, 0). "<br>";    

実行結果

0.4
0
不正な値です。
0 で割る事は出来ません!

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

例外を発生させるには new 演算子で「Exception」クラスまたは「Exception」クラスの派生クラスのインスタンスを生成し、throw 命令を使って例外を投げます。引数にはメッセージ(文字列)や例外コード(数値)を指定できます。

throw new Exception('0 で割る事は出来ません。');

この例では、try ブロックの中で、割る数が 0 の場合か数値でない場合に、例外を投げています。もし条件に一致して例外が投げられると、try ブロック内の処理は中断され、例外が「catch(Exception $e)」によって捕捉され、catch ブロック内の処理が実行されます。

function div($val1, $val2){
  try{
    if($val2 === 0){
      throw new Exception('0 で割る事は出来ません!');
    }else if(!is_numeric($val1) || !is_numeric($val2)){
      throw new Exception('不正な値です。');
    }
    return $val1 / $val2;
  }catch(Exception $e){
    return $e->getMessage();
  }
}

例外を捕捉する(キャッチする)

throw 文によって投げられた Exception クラス(またはその派生クラス)のオブジェクト(インスタンス)は catch(Exception $e) によって捕捉され、変数「$e」に代入されます。キャッチブロック内で使用することができます。

$e には Exception のインスタンスが入っていて、プロパティやメソッドを内部に持っているので、getMessage() で new Exceptionをしたときにセットしたメッセージを取り出すことができます。

この例では、Exception クラスの getMessage() というメソッドを利用してメッセージを返しています。

Exception クラスのメソッド
メソッド名 概要
getMessage 例外メッセージを返す
getCode 例外コードを返す
getFile 例外を発生したファイル名を返す
getLine 例外を発生した行数を返す
getTrace スタックトレースの配列を返す
getTraceAsString スタックトレースの文字列を返す
__toString 例外オブジェクトの文字列表現を返す

Exception クラス

Exception クラスは、すべての例外の基底クラス(スーパークラス)です。

Exception クラスに定義されたメソッドを利用することができます。以下が Exception クラスの概要です。

Exception {
  /* プロパティ */
  protected string $message ;  //例外メッセージ
  protected int $code ;  //例外コード
  protected string $file ;  //例外が作られたファイル名
  protected int $line ;  //例外が作られた行
  
  /* メソッド */
  public __construct ([ string $message = "" [, int $code = 0 [, Exception $previous = NULL ]]] )
  final public string getMessage ( void )
  final public Exception getPrevious ( void )
  final public mixed getCode ( void )
  final public string getFile ( void )
  final public int getLine ( void )
  final public array getTrace ( void )
  final public string getTraceAsString ( void )
  public string __toString ( void )
  final private void __clone ( void )
} 

Exception クラスの継承(拡張)

throw 命令では、Exception クラスのインスタンスだけではなく、Exception クラスの派生クラスのインスタンスを「例外」として投げる事ができます。

組み込みの Exception クラスを継承して、独自の例外を定義することで、発生した例外に応じて処理を分岐することが可能になります。

簡単な方法は、以下のように単にException クラスを継承して独自の例外を定義するだけです。メソッドを実装する必要はありません。

class MyException extends Exception {}

または、以下のように親クラスのコンストラクタを呼び出して、例外メッセージや例外コードなどを設定することもできます。例外コードの「777」に特に意味はありません。

class MyException extends Exception {
  public function __construct(){
    parent::__construct('MyException が発生しました。', 777);
  }
}

参考:

例外パターンを分けて処理

例外パターンごとに例外時の処理を変えたい場合は、パターンごとの例外クラスを作成して、そのクラスのインスタンスを throw するようにします。

この場合、「catch」の引数に指定するクラス名を、throw される「例外オブジェクト」のクラス名にする必要があります。

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

そして、catchブロックは複数用意することができるので、発生した例外に応じて処理を分岐します。

この場合、注意することは、親(基底)クラスの Exception クラスはすべての例外処理を受け取るので、Exception クラスを捕捉する catch ブロックは最後に記述します。

最初に Exception クラスを捕捉する catch ブロックを記述してしまうと、全ての例外がそのブロックに捕捉されてしまいます。

ある例外クラスのサブクラスは、継承関係にある親クラスが指定された catch ブロックにも捕捉されます。

すべての例外クラスは Exception クラスを継承しているので、catch ブロックに Exception クラスを指定すれば、発生してきた例外はすべて捕捉されてしまいます。そのため、Exceptionクラスを指定したcatch ブロックは、一番最後に配置します。

以下は2つの例外クラスを作成して、処理を分岐する例です。

<?php
class ZeroException extends Exception {}
class NotNumberException extends Exception {}

function div2($val1, $val2){
  try{
    if($val2 === 0){
      throw new ZeroException('0 で割る事は出来ません!');
    }else if(!is_numeric($val1) || !is_numeric($val2)){
      throw new NotNumberException('不正な値です。');
    }
    echo $val1 / $val2 . "<br>";
  }catch(ZeroException $e){
    echo $e->getMessage(). "<br>";
    echo get_class($e). "<br><br>";
  }catch(NotNumberException $e){
    echo $e->getMessage(). "<br>";
    echo get_class($e). "<br><br>";
  }catch(Exception $e){
    echo $e->getMessage(). "<br>";
    echo get_class($e). "<br><br>";
  }
}
 
div2(10, "x");
div2(30, 0);
?>

実行結果

不正な値です。
NotNumberException

0 で割る事は出来ません!
ZeroException

クラスファイルの自動読み込み

PHP でインスタンスを生成する場合、その対象となるクラスが記述されたファイルがあらかじめ読み込まれている必要があります。

そのため、クラスを使用するにはクラスファイルを include() や require() で読み込む必要がありますが、読み込むファイルが多いと全てを記述するのが大変です。

クラス名とファイルの場所に一定のルールがあれば、spl_autoload_register() を使って簡単に自動的に読み込む仕組みを作ることができます。

以下は、spl_autoload_register() の構文です。

bool spl_autoload_register ([ $autoload_function [, $throw  [, $prepend ]]] )

パラメータ

  • autoload_function(callable):登録したい autoload 関数。 パラメータが指定されなかった場合は、デフォルト実装である spl_autoload() が登録されます。
  • throw(bool):このパラメータは、 spl_autoload_register() が autoload_function を登録できなかったときに例外をスローするかどうかを指定します。(デフォルト:true)
  • prepend(bool):true の場合、spl_autoload_register() はキューの最後に追加するのではなく先頭に追加します。(デフォルト:false)

戻り値

成功した場合に TRUE を、失敗した場合に FALSE を返します。

クラスファイルが以下のように「classes」フォルダに保存してある場合の例です。

autoload フォルダ構成

autoLoad_test.php

<?php
function my_autoloader($class) {
  include 'classes/' . $class . '.class.php';
}

spl_autoload_register('my_autoloader');
 
$test = new Test();
$person = new Person('Bill Evans', 'male', '19290816');
?>    

my_autoloader() が autoload 関数です。パラメータ「$class」には、使おうとしているクラスの名前が渡されます。

フォルダ名「classes」とクラス名($class + class.php)でクラスファイルのパスを作り、それを読み込む(include)ようにしています。

そして autoload 関数を spl_autoload_register() に渡します。

こうすることで、あるクラスのインスタンスを生成する際に、フォルダ「classes」にあるクラスファイルを自動的に読み込んでくれます。

PHP 5.3.0 以降なら無名関数を使えるので、以下のように記述することができます。

<?php
spl_autoload_register(function($class){
  include 'classes/' . $class . '.class.php';
});
 
$test = new Test();
$person = new Person('Bill Evans', 'male', '19290816');
?>

また、ファイルのパスは PHP の定数を使って以下のように記述することも可能です。

include __DIR__ . DIRECTORY_SEPARATOR . 'classes' .DIRECTORY_SEPARATOR . $class . '.class.php';

前述の例ではクラスファイル名が「クラス名.class.php」に統一されていましたが、以下は「クラス名.php」というクラスファイルがある例です。

autoload フォルダ構成

「クラス名.php」というクラスファイルに対応する autoload 関数を追加します。

<?php
function my_autoloader($class) {
  $file = 'classes/' . $class . '.class.php';
  if (is_file($file))  {
    include $file;
  }
}

function my_autoloader2($class) {
  $file = 'classes/' . $class . '.php';
  if (is_file($file))  {
    include $file;
  }
}

spl_autoload_register('my_autoloader');
spl_autoload_register('my_autoloader2');

$test = new Test();
$test = new Test2();
$person = new Person('Bill Evans', 'male', '19290816');
?>

この例では「クラス名.php」というクラスファイルに対応する my_autoloader2() と言う autoload 関数を追加しています。

そして、2つの autoload 関数を spl_autoload_register() に登録しています。

前述の例では、autoload 関数では単にパスを include していましたが、それぞれの autoload 関数で作成するファイルのパスには、該当しないファイルが存在するため、実際にそのファイルが存在するかを確認して include する必要があります。

以下は、無名関数を使って書き換えた場合です。

<?php
spl_autoload_register(function($class){
  $file = 'classes/' . $class . '.class.php';
  if (is_file($file))  {
    include $file;
  }
  $file = 'classes/' . $class . '.php';
  if (is_file($file))  {
    include $file;
  }
});
 
$test = new Test();
$test = new Test2();
$person = new Person('Bill Evans', 'male', '19290816');
?>

参考: