クラスとオブジェクト

クラスの定義

クラス名の基本はPascal記法(Upper CamelCase ともいう)
DateTime、ArrayAccess、など
アンダースコア(_)は文法上OKだが通常あまり使わない

インスタンスによって作成した変数=オブジェクト変数
$myobj = new myClass();
クラスのコンストラクタに引数を渡さない場合、 クラス名の後の括弧は省略しても構いません。
$myobj = new myClass;

クラスは、そのインスタンスを作成する前に定義すべきです (これが必須となる場合もあります)。
クラスのコンテキストにおいては、 new selfnew parent のようにして新しいオブジェクトを作成することができます。

__construct

インスタンスの初期化。戻り値を返してはいけない。
php8以降では、プロパティを__constructの仮引数で宣言することも可能。

class Person {
  public string $firstName;
  public string $lastName;

  public function __construct(string $firstName, string $lastName) {
    $this->firstName = $firstName;
    $this->lastName = $lastName;
  }
}

//php8以降では下記の記述も可能。アクセス修飾子はpublicであっても省略不可。
class Person {
  public function __construct(
    public string $firstName,
    public string $lastName,
  ) {}
}

サブクラスでコンストラクターを定義(オーバーライド)した場合、スーパークラスのコンストラクターが暗黙的に呼び出されることはない。サブクラスでコンストラクターをオーバーライドした場合は、スーパークラスの初期化処理を実施するため、parent::__construct を呼び出す必要がある。オーバーライドしない場合はそのまま継承される
なお、__construct() を子クラスでオーバーライドする際、シグネチャの互換性に関するルールは適応されない

__destruct

コンストラクタと同様、親クラスのデストラクタがエンジンにより暗黙のうちに コールされるということはありません。親クラスのデストラクタを実行するには、 デストラクタの中で明示的に parent::__destruct() をコールする必要があります。 また、コンストラクタと同様、子クラスでデストラクタを定義していない場合は 親クラスのデストラクタを継承します。

プロパティ宣言

アクセス修飾子 データ型 プロパティ名
     public            string    $firstName;

初期値を持たせる場合 public string $firstName = ‘太郎’;
初期値で関数/メソッドの呼び出しはエラーとなる。
クラスの外側からプロパティを追加することも可能
PHP8.2.xから動的なプロパティの生成は推奨されなくなりました。

require_once('Person.php');

$p1 = new Person();
$p1->age = 40;  //プロパティを追加  PHP8.2.xから非推奨
print $p1->age; //結果:40

プロパティを配列で宣言して、$this->名前で取得できる。

class MyClass {
	private float $base = 1;
	private float $height = 1;

	//連想配列にしても同じ 配列の名前は任意
	private $props = [ 'base' => 1, 'height' => 1 ];

	// $this->base 、$this->height で取得可能

	//ただし配列のキーと同じ名前のプロパティを宣言すると上書きされることに注意
	private $props = ['base' => 1 ];
	private $base = 2;  //上の記述の 'base' => 1 が上書きされる
	
}

静的メソッド/静的プロパティ

staticキーワードで定義する。static変数と混同しないように。
静的メソッド、静的プロパティはインスタンスを作らず(newしない)下記のようにアクセスできる。

require_once クラス

クラス名::メソッド名
クラス名::プロパティ名

//静的メソッドの宣言
class Area {
	public static function circle(float $radius): float {
		return pow($radius, 2) * 3.14;
	}
}

静的メソッドの中では、$thisキーワードは使えない
静的メソッドではインスタンスが存在しないので、$thisキーワードは利用できません。

静的プロパティ

class Area {
	public static float $pi = 3.14;

	//静的メソッドから静的プロパティを呼び出す self::$プロパティ名($を付けることに注意)
	public static function circle(float $radius): float {
		return pow($radius, 2) * self::$pi;
	}
}

//静的プロパティへのアクセス ::
print Area::$pi;  //結果:3.14 ($を付けることに注意)
print Area::circle(3);  //結果:28.26

クラス定数

constキーワードで定義する。$は不要。慣習的に大文字にする。

class Area {
  public const PI = 3.14; //「$」無し 大文字

  public static function circle(float $radius): float {
    return pow($radius, 2) * self::PI; //「$」無し
  }
}

print Area::PI; //「$」無し 結果:3.14
print Area::circle(3); //結果:28.26

メンバーの種類とアクセス方法

種類アクセス方法
インスタンスメソッド/インスタンスプロパティ->(アロー演算子)
静的メソッド::
静的プロパティ:: ($を付けることに注意)
クラス定数::

アクセス修飾子

アクセス修飾子概要
publicどこからでもアクセスできる(規定)
protected現在のクラスとサブクラスでのみアクセスできる
private現在のクラスの中でのみアクセスできる

継承

class サブクラス名 extends スーパークラス名 {
	....
}

親クラスと子クラスにis_aの関係が成り立つ。
子クラスは親クラスのインスタンスでもある。

class X {}
class Y extends X {}

$y = new Y;
if( $y instanceof X ) {
	echo '$yはXのインスタンスです', PHP_EOL;
}

if( $y instanceof Y ) {
	echo '$yはYのインスタンスです', PHP_EOL;
}

if( is_a($y, 'X') ) {
	echo '$yはXです', PHP_EOL;
}

if( is_a($y, 'Y') ) {
	echo '$yはYです', PHP_EOL;
}

オーバーライド

同じ名前、同じ引数のメソッドを子クラスに記述する。
オーバーライドする場合、戻り値の型は一致していなければならない
php7.4で条件が緩和された。(下表参照)
スーパークラスでprivateなメソッドはサブクラスで全く別のものを定義できてしまう。privateは継承されないので、オーバーライド判定されない。プロパティは親でprivateでなくても別のものを定義可能。ただし紛らわしいので使わない。
メソッドやプロパティ、そして定数の アクセス権 に関するルールは、子クラスで緩めることが可能です。親クラスで protected なメソッドは 子クラスで public としてマークできます。 しかし、厳しくすることはできません。 つまり、親クラスで public なプロパティを 子クラスで private にすることはできません。

オーバーライドの条件

項目概要
メソッド名一致していること
仮引数型は一致しているか、より広い型であること。個数は一致していること(名前、規定値は異なっていても構わない)。
戻り値型一致しているか、より狭い型であること。
アクセス修飾子一致しているか、より緩いこと(親がprotectedであれば子のpublicは可能)。

サブクラスの戻り値はより狭い型を許容する
サブクラスの引数はより広い型を許容する
このような引数の性質を反変性(contravariant)、戻り値の性質を共変性(covariant)という。
コンストラクターでは親子間で引数(型、個数)が異なっていても構わない。

シグネチャの互換性に関するルール

finalキーワード

キーワード final を前に付けて定義されたメソッドや定数は、子クラスから上書きできません。 クラス自体がfinalと定義された場合には、このクラスを拡張することはできません。
子クラスで同名の関数を定義するとFatal errorとなる。
PHP 8.0.0 以降は、private メソッドを final として宣言できるのはコンストラクタだけになりました。
プロパティを final として宣言することはできません。final として宣言できるのはクラスとメソッド、 および定数(PHP 8.1.0以降)だけです。

final class BaseClass {...}
final public function moreTesting() {...}
final public const X = "foo";

クラスに属する関数、変数=メンバー関数、メンバー変数 またはメソッド、プロパティという。

メソッド・プロパティへのアクセス

$myobj->メソッド
$myobj->プロパティ
オブジェクトの継承

委譲

再利用したい機能を持つオブジェクトを、現在のクラスのプロパティとして取り込む。
継承の is_a 関係に対し、has_a 関係と呼ぶ。オブジェクトをプロパティとして保持し、メソッドを利用する。

ポリモーフィズム(多態性)

同名のメソッドがクラスによって異なる動作をする
ポリモーフィズムを実現するのが、抽象クラス。抽象メソッドがサブクラスで実装されることが保証される。(抽象メソッドのオーバーライドを強制する)

抽象クラス/抽象メソッド

クラス名の前にabstract、メソッドの定義にもabstract修飾子を指定する。
抽象メソッドを持つクラスは、クラス名にabstract を指定しなければならない。(注:traitも抽象メソッドを持てる。その場合は trait 命令)
抽象メソッドはサブクラスでオーバーライドされるもの。
抽象メソッドを含んだクラスのことを抽象クラスという。
通常のクラス同様、プロパティやメソッド、定数を実装できる
インスタンスを生成することはできない。
抽象クラスを継承したサブクラスは全ての抽象メソッド(abstract としてマークされたメソッド)をオーバライドしなければならない。
シグネチャ(関数やメソッドの名前、引数の数やデータ型、返り値の型など)も親クラスと互換性のあるものにする
多重継承できない。

abstract class Figure {

	protected abstract function getArea(): float; //サブクラスがオーバーライドする
	// {...}は記述してはいけない
	protected abstract function getArea(): float {}; //エラーとなる
}

ただし、多重継承ができないので、ひとつの抽象クラスにメソッドをまとめなければならず、不要なメソッドまでオーバーライドしなければならくなる。インターフェイスは多重継承可能なため、メソッドを細分化し、必要なメソッドを選択、実装できる。

//抽象クラスの継承
class MyClass extends Figure { ... }

インターフェイス

抽象クラスと違い多重継承可能。
カンマで区切る class Triangle implements IFigure,other, other2

中身のあるメソッドやプロパティは定義できない。
抽象メソッド定数だけを持つので、abstract修飾子は必要ない。
アクセス修飾子は指定できない。インターフェイス内で宣言される全てのメソッドは public である必要があります。public と記述しても良いが意味はない。
インスタンスを作ることはできない(new できない)。
クラス や トレイト と名前空間を共有するので、 それらと同じ名前を使ってはいけません。
クラスと同様、インターフェイスも extends キーワードで継承することができます
インターフェイスが他のインターフェイスを継承するとき、同名のメソッドを宣言することはできない

interface IFigure {
	function getArea(): float;
}

//インターフェイスの継承
interface Otherfigure extends IFigure {..}

実装クラス

インターフェイスを実装したクラスを実装クラスという。
インターフェイスを実装するクラスでは、インターフェイスに含まれる全ての抽象メソッドを実装する必要がある。
各インターフェイスをカンマで区切って指定することで、 クラスは複数のインターフェイスを実装することができます。

require_once 'IFigure.php';

class Triangle implements IFigure {
	//プロパティを定義
	private float $width;
	private float $height;

	//コンストラクターでプロパティを初期化
	public function __construct(float $width, float $height) {
		$tihs->width = $width;
		$this->height = $height;
	}

	//IFigure::getAreaメソッド実装
	public function getArea(): float {
		return $this->width * $this->height / 2;
	}
}

instanceof 演算子(型演算子)

多重継承可能なため、実装クラスの中で親のインターフェイスを確認する。

var_dump($a instanceof MyClass); //bool(true) true/false を返す

<参照> insteadof

クラス/インターフェイス関連の関数

関数戻り値
get_class([$obj])オブジェクトの元となるクラス名
get_debug_type($V)オブジェクトであればクラス名、基本型であれば型名 (php8.0)
get_parent_class([$obj])オブジェクトの元となるクラスの親クラス名
get_class_methods($clazz)クラスに属するメソッドの一覧($clazzはクラス名)
get_class_vars(($clazz)クラスに属するプロパティの一覧($clazzはクラス名)

遅延静的束縛

self:: による参照は定義時に解決される(定義したクラス)
static:: は実行時に「直近にコールされたクラス」を参照する(コールしたクラス

class MyParent {
	public static function show() {
		print __CLASS__;
	}
	public static function staticTest() {
		self::show();
	}
}

class MyChild extends MyParent {
	public static function show() {
		print __CLASS__;
	}
}

MyChild::staticTest();
//結果:MyParent
//self::show() を static::show() に変更すると結果は MyChild になる

self:: あるいは CLASS による現在のクラスへの静的参照は、 そのメソッドが属するクラス (つまり、 そのメソッドが定義されているクラス) に解決されます。

転送コールと非転送コール

self:: , parent:: , static:: などの静的コールは転送コール
明示的にクラス名を指定したものが非転送コール
X::foo() , Z::test() など

オブジェクトの代入

オブジェクト変数は参照渡し(リファレンス渡し)
値渡ししたい場合は clone命令を利用する。

$p1 = new Person();

$p2 = $p1; //代入
var_dump($p1 == $p2);  // bool (true)
var_dump($p1 === $p2); // bool (true)

$p3 = clone $p1; //値渡し
var_dump($p1 == $p3);  // bool (true)
var_dump($p1 === $p3); // bool (false)

オブジェクトの比較

演算子等しいとみなす条件
==同じクラスのインスタンスであること、同じプロパティと値を持つこと
===同じクラスの同じインスタンスを参照すること

オブジェクトの反復処理

オブジェクトはforeach命令で反復処理可能。アクセス権限に注意。

$a = new MyClass();
foreach( $a as $key => $value ) {
	// ...
}

//クラス内   $thisキーワード
foreach( $this as $key => $value ) { ... }

オブジェクトは配列にキャストすることができる。

$a = new MyClass();
$arr = (array)$a;
print $arr['prop'];

//連想配列をオブジェクトにキャストすることも可能 プロパティの要領でアクセスする
$arr = ['prop1' => 1, 'prop2' => 2, 'prop3' => 3];
$obj = (object)$arr;
print $obj->prop1;

不変クラス

オブジェクトを生成したところから一切の値が変化しないクラス。

▽不変クラスのポイント
全てのプロパティはprivate宣言
プロパティの追加は禁止 __setメソッドで明示的に例外を発生させる
オブジェクトを返すゲッターには注意
オブジェクトは参照渡しになってしまうので、clone命令などで値渡しで返す(防衛的コピー)
コンストラクター/ オブジェクトを渡される場合、__cloneメソッドで防衛的コピー
プロパティの変更はwithXxxxxxメソッドで(Xxxxxxはプロパティ名)
専用のメソッドを用意して複製を返す
クラスのコンテキストにおいては、 new selfnew parent のようにして新しいオブジェクトを作成することができます。

無名クラス

その場限りの使い捨てのオブジェクトが必要になった場合に利用する。

//構文
new class { ...プロパティ/メソッドに定義... }
new class extends 親クラス名 { ...プロパティ/メソッドに定義... }
new class implements インターフェイス名 { ...プロパティ/メソッドに定義... }
//特定のインターフェイスを実装したクラスを引数に要求するケース
interface Runnable {
  function run();
}

class MyClass {
  public function execute(Runnable $rc) : void {
    print 'start...';
    $rc->run();
    print 'end...';
  }
}

$cls = new MyClass();
$cls->execute(new class implements Runnable {
  public function run() : void {
    print 'process...';
  } 
});

//実行結果:start...process...end...

トレイト

再利用可能なコード(メソッド/プロパティ)をまとめて切り出しておくための仕組み。
単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。継承しなくてもクラスのメンバーに追加できるようになります。
(クラス階層上で共通の先祖を持たないクラスどうしでも機能を共有できる)

trait トレイト名 {
	//...プロパティ/メソッドの定義
}

定数は持てない(プロパティ、抽象/静的/インスタンスメソッドのみ可能)
PHP8.2から、定数を定義できるようになりました。
クラスの継承、インターフェイスの実装はできない
use命令で他のトレイトを利用できる。
インスタンス化することはできない。
インターフェイスと合わせて利用する(ことが多い)。
型を継承するインターフェイス、実装を継承するトレイト、と言われる。
インターフェイスはそれ自体が型になるが、トレイトは型ではない。クラス階層(型階層)から独立した機能を実装できる。(親子関係を持たない)

interface IFax { ... }
//IFax型を期待する関数 OK
public function myFunc(IFax $fax) { ... }

//faxTrait型を期待する関数 NG
trait faxTrait { ... }
public function myFunc(faxTrait $fax) { ... }

トレイトを使用するには、クラス内部でuse命令を利用する。カンマ区切りで複数のトレイトを取り込むことも可能

class MyClass {
	use trait, trait2, trait3;
	...
}

優先順位は現在のクラスのメンバーが最高で、その次がトレイトのメソッド、 そしてその次にくるのが継承したメソッドとなります。同名のメソッドをそれぞれ持っている場合、現在のクラス、トレイト、継承したメソッドの順に探す。例えばトレイトのメソッドが先に呼ばれたら継承したメソッドは呼ばれない。

insteadof 演算子

複数のトレイトを読み込めるが、トレイト同士で同じメソッド名があるとエラーになる。 衝突を避けるために、有効化するメンバーを選択する。

class MyClass {
	use trait1, trait2 {
		//trait1とtrait2にメソッドhogeがある trait2のhogeを無効にする
		trait1::hoge insteadof trait2;
	}
}

as演算子で別名を付与する

insteadof ではひとつしか有効にできないが、双方とも使用したい場合に別名を付与することができる。別名付与する前に、insteadof演算子で競合を回避しておくことに注意!

class MyClass {
	use trait1, trait2 {
		trait1::hoge insteadof trait2;
		trait2::hoge as foo; //trait2のhogeをfooとして使用する

		//アクセス権限を変更することも可能
		trait::hoge as private foo;
	}
}