【PHP】 クラスとインターフェースの実装

PHPのクラスでは、他の言語と同様に継承やinterface、プライベートなメンバなど、一通りの機能を使用できます。

クラスの宣言

class句を使用して宣言します。

各メンバはクラス直下に、publicprivateを明示して宣言します。constな値は自動的にpublicになります。

メンバを利用する際は、クラス内からは$this->メンバで、constな値はクラス名::定数名でアクセスします。メンバにアクセスする際に$は不要です。

class Hoge {

    public $m1 = 'hoge';
    private $p1 = 'fuga';
    private $v = 0;

    const c = 'const value';

    // コンストラクタ。インスタンス生成時に実行される
    function __construct($arg1, $arg2) {
        print("constructor\n");
        $this->v = $arg1 + $arg2;
    }

    private function mf() {
        $s = $this->m1 . $this->p1;
        print("privateなメソッド {$s}\n");
    }

    public function pf() {
        // privateなメソッド呼び出し
        $this->mf();

        print("publicなメソッド {$this->v}\n");
    }
}

$hoge = new Hoge(10, 20);
$hoge->pf();
print($hoge->m1);
print(Hoge::c);
result
constructor
privateなメソッド hogefuga
publicなメソッド 30
hogeconst value

メンバ変数 (フィールド)

フィールドでは、そのクラス内からのみアクセスできるprivateか、クラス外から自由にアクセスできるpublic、自クラスと子孫クラスからのみ参照できるprotectedの3種類を指定できます。

フィールドは、原則privateにするべきです。 (カプセル化のため)

class Hoge {
    private $p = "";
    public $m = "";

    function f() {
        // インスタンス内では、$this->変数名 で呼ぶ。変数名に$は不要
        print($this->p);
    }
}

$hoge = new Hoge();
$hoge->m = "abc"; // OK。mはpublic
$hoge->p = 100; // Fatal error: Uncaught Error: Cannot access private property

メンバ関数 (メソッド)

メソッドの宣言は、従来の関数と同様にfunction 関数名(args) {...}で宣言できますが、アクセス識別子を明示することもできます。デフォルトはpublicです。

クラス内からメソッドを呼ぶ際は、$this->関数名()のようにします。

class Hoge {
    private $p = "aa";

    // デフォルトはpublic
    function f() {
        print($this->p);
    }

    private function pf() {
        print("クラス内からのみ呼び出せる\n");
    }

    public function mf() {
        print("どこからでも呼び出せる\n");

        // 自クラス内の他の関数を呼ぶ
        $this->pf();
    }
}

$hoge = new Hoge();
// メソッド呼び出し
$hoge->mf();
$hoge->pf(); // Fatal error: Uncaught Error: Call to private method Hoge::pf()

コンストラクタとデストラクタ

クラスのインスタンス生成時に自動で実行されるコンストラクタは、function __construct($args) {...}で宣言します。

インスタンスへの参照がなくなった際に呼ばれるデストラクタは、function __destruct() {...}で宣言します。デストラクタには引数はありません。

コンストラクタとデストラクタは不要なら書く必要はありません。

class Hoge {
    private $s = "";
    // コンストラクタ。インスタンス生成時に実行される
    function __construct($arg) {
        $this->s = $arg;
        print("constructor {$this->s}\n");
    }

    // デストラクタ。インスタンスの参照がなくなった際に呼ばれる。
    function __destruct() {
        print("destructor {$this->s}\n");
    }
}

$hoge = new Hoge("hoge1");
$hoge3 = null;
{
    $hoge2 = new Hoge("hoge2");
    
    $hoge4 = new Hoge("hoge4");
    $hoge3 = $hoge4;
} // $hoge2が破棄される。hoge4も終わるが、hoge3が参照しているのでまだデストラクタは呼ばれない

// 全てのコードが終わると同時に全ての変数が破棄される
result
constructor hoge1
constructor hoge2
constructor hoge4
destructor hoge2
destructor hoge1
destructor hoge4

staticなメンバ

staticを付与することで、そのクラスのインスタンス全てで共有するメンバを作成できます。

インスタンスを作成してなくても利用でき、クラス名::変数名 ($が必要) でアクセスできます。自クラス内からはself::変数名 ($が必要) でもアクセス可能です。

staticなメソッド内では、$thisを使用することができません。

constなフィールドは、実質staticです。ただ、staticと違ってアクセス修飾子を指定できず、強制的にpublicとなります。

class Hoge {
    private $p = "aa";
    public static $sp = "hoge";
    const c = 100;

    // static public function ...でも良い
    public static function mf() {
        print(self::$sp . "\n");
        print(Hoge::$sp . "\n");
        print(self::c . "\n");
        print(Hoge::c . "\n");

        // $thisは使えない
        // print($this->p); // Fatal error: Uncaught Error: Using $this when not in object context
    }

    public function f() {
        // staticなメソッド呼び出し
        self::mf();
    }
}

print(Hoge::$sp . "\n"); // hoge
Hoge::mf(); // hoge

継承

クラスの継承は、クラス名の後にextends クラス名とすると継承できます。

ここで、アクセス修飾子にprotectedを指定すると、アクセスをそのクラスと子クラスからのみに制限できます。

親のメソッドを呼び出す際は、parent::メソッド名のようにして呼び出します。親のコンストラクタやメソッドは、子がparent::__construct(...);などで呼び出しをしない限り実行されません。

多重継承はありません。

class MyParent {
    // このクラスのみ参照できる
    private $fuga = "aaa";

    // このクラスと子クラスのみ参照できる
    protected $hoge = "100";

    // どこからでも参照できる
    public $hige = 500;
    

    function __construct($arg) {
        print("親クラスのコンストラクタ\n");
        $this->hoge = $arg;
    }

    public function outputHoge() {
        print($this->hoge . "\n");
    }

    public function outputFuga() {
        print($this->fuga . "\n");
    }
}

class MyDerived extends MyParent {
    function __construct() {
        // 親のコンストラクタ呼び出し
        parent::__construct("abcde");
    }

    // メソッドのオーバーライド
    public function outputFuga() {
        print("子クラスから親メソッド呼び出し\n");
        parent::outputFuga();
    }
}

$d = new MyDerived();
$d->outputHoge(); // 子クラスには無いので親のが呼ばれる
$d->outputFuga(); // 子クラスのほうが呼ばれる
result
親クラスのコンストラクタ
abcde
子クラスから親メソッド呼び出し
aaa

interface

JavaやTypescriptなどにあるようなinterfaceをPHPでも使用できます。

interfaceには、関数のインターフェース (メソッド名と仮引数) を並べます。

クラスにinterfaceを実装する際は、implements インターフェース, インターフェース, ...と並べます。クラスと違い、複数のインターフェースを実装できます。このとき、interfaceにあるメソッドはクラスに全て実装しなければなりません。

インターフェースのメソッドは、全てpublicである必要があります。インターフェースは、クラス外に向けて、どのようなインターフェースを持っているかを公開するためのものであるためです。

1つのインターフェースを継承
interface iList {
    public function insert($pos, $elem);
    public function remove($pos);
    public function next();
    // ...
}

class MyList implements iList {
    private $list = [];
    private $ptr = null;
    
    public function insert($pos, $elem) {
        // 実装
    }

    public function remove($pos) {
        // 実装
    }

    public function next() {
        // 実装
    }
}
複数のインターフェースを継承
interface A {
    // publicは省略できる
    public function a();
}

interface B {
    function b();
}

interface C {
    function c();
}

class MyClass implements A, B, C {
    function a() {
        // ...
    }

    function b() {
        // ...
    }

    function c() {
        // ...
    }
}

また、interfaceには定数を置くことができます。

interface A {
    const a = "hoge";
}

class MyClass implements A {
    function f() {
        print(self::a);
        print(A::a);
        print(MyClass::a); // これもOK
    }
}

$c = new MyClass();
$c->f();
result
hogehogehoge

抽象クラス

クラスは、abstructを付与することで抽象クラスにすることができます。抽象クラスにしたクラスはそれだけではインスタンスを作成できず、必ず子クラスを作ってインスタンスを作成する必要があります。

抽象クラスでは、interfaceを実装する必要はありません。(実装することはできます)

また、抽象クラスではメソッドにabstractを指定できます。abstractが指定されたメソッドは、子孫クラスで必ず実装を書く必要があります。

interface MyInterface {
    function f();
}

abstract class AbstractMyClass implements MyInterface {
    private $hoge = 100;

    // abstractなため、f()は実装しなくて良い

    // abstractな関数は関数の存在だけ定義し、実装を子クラスに委譲する
    // protectedにもできるが、子クラスでpublicに上書きできるためあまり意味はない
    abstract function g();

    // ここで実装しているため、子クラスで実装する必要はない
    function h() {
        // ...
    }
}

class MyClass extends AbstractMyClass {
    // abstractなクラスではないため、残り (fとg) を実装しなければならない

    function f() {
        print("f\n");
    }

    function g() {
        print("g\n");
    }
}

// abstractなクラスはnewできない
// $a = new AbstractMyClass();

// 子クラスはnewできる
$c = new MyClass();
$c->f();

interfaceと抽象クラスの違い

どちらも継承して利用するものですが、どのように違うのでしょうか。

内容interfaceabstract
中身メソッドの名前と引数、定数クラスと同様の実装が可能、abstractなメソッドを定義可能
継承や実装複数実装可能1つだけ継承可能

つまり、

  • interfaceは、どのようなメソッド (インターフェース) を持つかを定義するもので、何ができるかを外に伝えるためのもの
  • abstractなクラスは、一部の実装を子クラスに委譲したクラス

ということです。

まとめ

PHPのクラスでも、他言語と同じような事が可能です。abstractを使用することで、必ず継承が必要なクラスを作成することができます。

interfaceとabstractの違いを把握して、意味に応じて使い分けられるようになりましょう。

php class thumb

役に立ったらシェアしよう!